实验报告
计算学部
目 录
3.3 为什么是这个值?通过gdb调试验证并分析说明(10分)
第1章 实验基本信息
1.1 实验目的
熟练掌握计算机系统的ISA指令系统与寻址方式
熟练掌握Linux下调试器的反汇编调试跟踪分析机器语言的方法
增强对程序机器级表示、汇编语言、调试器和逆向工程等的理解
1.2 实验环境与工具
1.2.1 硬件环境
X64 CPU;2GHz;2G RAM;256GHD Disk 以上
1.2.2 软件环境
Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位 以上;
1.2.3 开发工具
Visual Studio 2010 64位以上;CodeBlocks 64位;vi/vim/gedit+gcc
1.3 实验预习
上实验课前,必须认真预习实验指导书(PPT或PDF)
了解实验的目的、实验环境与软硬件工具、实验操作步骤,复习与实验有关的理论知识。
请写出C语言下包含字符串比较、循环、分支(含switch)、函数调用、递归、指针、结构、链表等的例子程序sample.c。
生成执行程序sample.out。
用gcc –S或CodeBlocks或GDB或OBJDUMP等,反汇编,比较。
列出每一部分的C语言对应的汇编语言。
修改编译选项-O (缺省2)、O0、O1、O3、Og、-m32/m64。再次查看生成的汇编语言与原来的区别。
注意O1之后缺省无栈帧,RBP为普通寄存器。用 -fno-omit-frame-pointer加上栈指针。
GDB命令详解 –tui模式 ^XA切换 layout改变等等
有目的地学习: 看VS的功能,GDB命令用什么?
第2章 实验环境建立
2.1 Ubuntu下CodeBlocks反汇编(10分,选做)
CodeBlocks运行hello.c。反汇编查看printf函数的实现。
要求:C、ASM、内存(显示hello等内容)、堆栈(call printf前)、寄存器同时在一个窗口。
图2-1 Ubuntu下CodeBlocks反汇编截图
2.2 Ubuntu下EDB运行环境建立(10分,选做)
用EDB调试hello.c的执行文件,截图,要求同2.1
图2-2 Ubuntu下EDB截图
第3章 sum剖析
3.1 导致异常的临界n值(最小n值)是多少(5分)
262003
3.2 论述找到临界n值的整个过程 (5分)
首先手动依次输入n,根据二分法判断得到n在261800到262500。
因为尝试的过程中发现每次运行sum可执行文件,n是不固定的,但是在gdb中运行的时候每次n是固定值,是因为在gdb中发现rsp寄存器每次初始值和结束值是固定的,所以n不变。那么考虑会不会是sum可执行文件由于系统的栈随机化导致每次结果不一样,输入echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
来暂时取消栈随机化的功能,再次验证,发现这次运行sum可执行文件,每次结果都一样了。
得到sum1和sum2的临界值n为262003。
3.3 为什么是这个值?通过gdb调试验证并分析说明(10分)
在gdb过程中观察rsp寄存器的值从0x7fffffffddf0到0x7fffff7feff0,就得到以下内容并退出。0x7fffff7feff0是rsp寄存器的栈顶,再超出就会爆栈。
接下来分析sum的汇编代码。
经过单步调试,可知在进入sum函数时候,rsp寄存器的值会减8来存储返回地址,随后又push %rbp来保存调用者的栈帧基址指针,此时rsp寄存器的值又减8,随后通过sub来使rsp寄存器的值减16,来为当前函数局部变量分配空间,故调用一次sum函数需要减去32个字节。
而0x7fffffffddf0减去0x7fffff7feff0得到十进制数8384000,8384000/32=262000,这与结果几乎一致,故合理。
第4章 炸弹破解与分析
每阶段20分,密码10分,分析10分,总分不超过60分
(如果第二章没做,本章最高总分为80分)
4.1 阶段1的破解与分析
密码如下:I am just a renegade hockey mom.
破解过程:
先在gdb bomb中观察main函数如下图,可知在调用phase_1之前先要调用read_line读入字符串返回到%rax,并mov至%rdi。
随后为了测试,先输入字符串xby666,并用disas来反汇编phase_1函数,发现在phase_1函数中,string_not_equal是通过判断输入的字符串%rdi和%esi中地址为0x403150的字符串是否相等来决定是否爆炸,如果相等那么%eax为0,不会跳转到explode_bomb,即不会爆炸。
读取%rdi的值,发现就是刚刚输入的xby666,而地址为0x403150的字符串就是第一阶段的答案。
4.2 阶段2的破解与分析
密码如下:1 2 4 8 16 32
破解过程:
先得到phase_2的反汇编代码,发现调用了read_six_numbers函数,可能是输入六个数来判断。分析之后得到循环流程,为了直观,通过下图C代码来展示,即答案应该是以1为首项,2为公比的等比数列,为1、2、4、8、16、32。
4.3 阶段3的破解与分析
密码如下:5 y 841、4 w 498...(不止一组解)
破解过程:
得到上图phase_3的反汇编代码,发现调用了sscanf,而sscanf的返回值%eax为匹配成功的个数,%eax在大于2的情况下才不会bomb,所以猜测至少要输入三个值。接下来查看输入类型,查看地址为0x40319e的值为%d %c %d,所以先按照这个格式输入5 x 7来测试。
由8-16行可知传入了三个地址,在后面查看地址的值,发现这三个地址存放的正是输入的三个值。
在40行可知,把5传给了%eax,并与7作比较,如果比7大就会bomb,这里我们可以得知第一个数应该要小于等于7。
随后跳转到229行,判断第三个值是否和0x349一致,不一致则bomb,可得十进制表示为841。
接着继续测试,到了第320行,要比较第二个值和与%eax中后八位的0x79是否一致,不一致则bomb,可得字符为y。
所以结果为 5 y 841。经过观察发现不止一组解,因为是一个switch语句,例如第一个数为4时,后面对应的就是w 498,经过测试4 w 498也是一组解。
4.4 阶段4的破解与分析
密码如下:14 45
破解过程:
在phase_4的反汇编代码中,第26行要读入两个值,否则bomb。在第39行判断第一个值是否为负数,如果是负数则bomb。接下来在第43行判断第一个值是否小于等于14,否则bomb。在第66行调用func4,如果返回值不是45,则bomb,在第76行判断第二个值是否等于45,否则bomb。所以第一个值的确定关键就在于func4内部。
分析之后为了更加直观,用C代码来写func4函数,并在main函数中调用phase_4函数,因为第一个值只能在1到14之间,所以遍历这十四种情况,发现只有第一个值为14的时候,func4的返回值为45,所以结果为14 45。
4.5 阶段5的破解与分析
密码如下:CGILON(不止一组解)
破解过程:
由第17行可知要输入一个六位的字符串,之后进入循环,其中计数器%eax从0到5每循环一次加1,%ecx为累加器,当六次循环之后,%ecx的值如果为50,那么拆弹成功,问题在于%ecx怎么累加。%ecx每次会加上(0x403200+4*%rdx)地址的值,查看发现0x403200是一个数组的首地址。
而%edx为(%eax+%rbx)中地址的值与0xf做按位与,即只取低四位值,取值范围为0-15,正好可以取到上图中的16个数组元素,因为%eax是从0到5的,而%rbx存储的正是输入的六位字符串首地址,这里以123456为例。
因为char是一字节,所以在六次循环中,(%eax+%rbx)中地址的值分别就是字符串的六位。那么就是每次只取一个字符对应的ASCII二进制的后四位去找数组对应元素。在数组中随便取一组1 3 7 11 13 15,对应的位置分别是3 7 9 12 15 14,再找一组对应的字符为CGILON,拆弹成功!分析过程可知这道题的解非常多。
4.6 阶段6的破解与分析
密码如下:6 2 5 3 4 1
破解过程:
第17行可知要读入六个数,这里先输入6 7 8 9 2 4来测试。
观察第13行发现把%rbp-0x40的地址传给了寄存器,猜想就是刚刚输入的六个数组元素,每四字节打印一个值得到下图,发现正是输入的6 7 8 9 2 4。
经过分析,发现第一个循环是个嵌套循环,第一部分为了控制元素的范围小于等于6,注意第90行是ja,所以是无符号比较,同时保证大于等于1,嵌套中的第二部分保证了不能有元素相等。为了直观,编写了如下C语言代码。
根据上述分析,输入1 2 4 3 6 5来继续测试,发现0x4052d0是一个链表头节点的地址,如下图所示。
验证一下,发现node1的4215550的十六进制表示正是node2的地址,所以node1 -> next = node2。
之后进入下一个循环,发现又有一个新地址为%rbp-0x70作为数组b,经过分析,这个循环是为了根据输入数组的值,按照下标把链表中节点的地址赋给数组b,如下为C语言代码。
下一个循环是为了把b数组中的节点地址依次连接,得到新的一条链表。
而最后一个循环是为了保证链表中的元素是递增的,否则bomb。
分析之后,便可逆向思维,按照原链表中的节点数值进行排序,即node6->node2->node5->node3->node4->node1。显然answer就是6 2 5 3 4 1。
拆弹成功!
4.7 阶段7的破解与分析(隐藏阶段)
密码如下:47
破解过程:
发现main函数中并没有直接的隐藏关卡入口,但每个phase结束之后都有一个phase_defused,决定考察一下这个函数。
经过测试,发现0x3c2c(%rip)的值每通过一个phase,都会加1,从1一直到6,即这两行的目的是为了检查是否前六关全部通过。
随后注意到这几行在传参,关注一下0x403399和0x405870。
可知需要输入3个参数,其中两个是之前第四题答案,而最后一个是字符串。如果匹配到不是三个参数,那么会正常显示第六关通关,随后结束。
如果匹配到三个参数,那就要确认第三个参数是不是answer。
发现0x4033a2的值就是DrEvil,这就得到了第三个参数。
在第四题答案后补上DrEvil,成功进入隐藏关卡。
第17行的atoi函数把输入字符串变成整数,并且这个值小于等于0x3e9即1001,否则bomb,接下来把0x4050f0赋值给%edi,可知这是一个节点的地址。
随后进入fun7函数,并将返回值与5比较,如果不相等则bomb,如果相等那么得到下图,拆弹结束。
所以问题关键在于fun7函数内部,下面分析fun7。
下面先探讨一下%rdi是什么,发现和0x4050f0相关,看一下发现有一堆节点,而每个节点都是32个字节,有四个值,推测第一个值是value,第二个和第三个都是地址,而第四个均为0,可能是结构体为了对齐。这样的数据结构,很容易猜到是二叉树,数一下有15个节点,推测为4层满二叉树。
为了直观,编写C语言代码如下。
接下来考虑5怎么通过递归凑出来,5=2*(2*(2*0+1))+1,所以按照fun7的递归来说,就是要先走右儿子,再走左儿子,最后走右儿子。也就是在n46节点处递归返回0,然后在n33节点返回2*0+1,在n22节点返回2*(2*0+1),最后在n1节点返回2*(2*(2*0+1))+1=5。所以input为n46节点的值,即47。
5.1 请总结本次实验的收获
通过本次实验学会了如何使用gdb,能够自主的分析出汇编代码的意思,读懂汇编代码。更了解程序运行的内部逻辑,对linux的使用更加熟练自如。
5.2 请给出对本次实验内容的建议
暂无,感觉这次的实验很有趣。
注:本章为酌情加分项。
参考文献
为完成本次实验你翻阅的书籍与网站等
[1] 林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
[2] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.
[3] 赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[4] 谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
[5] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[6] CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.