计算机系统基础实验:二进制拆弹实验

一、实验目的:

  1. 学习并熟练使用 gdb 调试器和 objdump
  2. 理解汇编语言代码的行为或作用
  3. 提高阅读和理解汇编代码的能力

二、实验要求

实验共包括七个阶段,每个阶段考察机器级语言程序的不同方面,难度递增。

  • 阶段一:字符串比较
  • 阶段二:循环
  • 阶段三:条件/分支,含switch语句
  • 阶段四:递归调用和栈
  • 阶段五:指针
  • 阶段六:链表/指针/结构
  • 隐藏阶段:第四阶段之后附加特定字符串后出现

三、实验内容

1.phase_1

以下是phase_1的汇编代码:
在这里插入图片描述

phase_1的375行调用了string_not_equal函数,而string_not_equal函数中调用了string_length函数,所以先分析一下这两个函数。可以用Ctrl+F输入函数名称快速定位到这个函数在文件中的位置。
string_length函数汇编代码如下:
在这里插入图片描述

789行传入字符串参数,790行和0x0比较,如果相等则跳转到797行,eax存放字符串长度为0。若不等于0则执行792行循环累加操作,统计字符串长度。795行若最后一个字符为0,则跳出循环,返回的结果为eax中存放的字符串长度。
string_not_equal函数编代码如下:
在这里插入图片描述

分析完以上两个函数基本了解了它们的用途,回到phase_1。分析发现,在call调用strings_not_equal比较字符串之前,有两条用于压栈的指令,其中373行mov指令将函数入口参数送入栈中,推测371行movl指令将正确字符串的首地址送入栈中。启动gdb调试,用b命令在phase_1处设置一个断点,然后使用r命令运行,随意输入一个字符触发断点。在这里插入图片描述

再用x命令查看373行地址0x804a28c处的字符串,复制该字符串。在这里插入图片描述

运行./bomb,输入字符串,验证答案,第一个炸弹拆除成功。
在这里插入图片描述

把已经解出的答案放到一个文本文件ans.txt中,以后只需输入./bomb ans.txt执行程序直接从文件中读取,避免重复输入。

2.phase_2

以下是phase_2的汇编代码:
在这里插入图片描述

392行call指令调用函数read_six_numbers,其汇编代码如下:
在这里插入图片描述

946行movl指令将立即数0x804a42f放入esp+4所指的内存单元中,保存数据区地址。

950行call调用sscanf函数,下面是eax和0x5比较大小,如果输入的数的个数小于等于5则炸弹爆炸。使用gdb的x/s命令查看内存单元内容,根据6个%d可知read_six_numbers函数用来读取键盘输入的6个整数。
在这里插入图片描述

再看phase_2的汇编代码,388行开辟栈空间存放读取的6个数,设它们存放在数组a[6]中。从ebp+8开始读取,a[5]=ebp-0xc,a[4]=ebp-0x10,a[3]=ebp-0x14,a[2]=ebp-0x18,a[1]=ebp-0x1c,a[0]=ebp-0x20。

393行比较带符号数a[0]和0x0的大小,不相等则跳转到炸弹爆炸,所以a[0]=0。

395行比较带符号数a[1]和0x1的大小,相等则跳转到409行继续执行,否则炸弹爆炸,所以a[1]=1。

409行将ebp-18地址放到ebx寄存器中,即ebx中存放a[2]的地址,将ebp-8地址放到esi寄存器中,即esi中存放a[5]的地址,然后跳转到400行继续执行。

400行ebx-8等于*(&a[2]-2)=a[0],ebx-4等于 *(&a[2]-1)=a[1],把a[0]+a[1]的值放到eax中,然后和ebx中的值a[2]比较,相等则继续执行405行,否则炸弹爆炸,所以a[2]=a[1]+a[0]。

405行ebx=ebx+4,即ebx=&a[3],判断a[3]和a[5]是否相等,若不相等则继续执行400行,若相等则跳出循环。

400行和上面一样,此时比较a[3]和a[2]+a[1]是否相等,若相等则继续执行405行,否则炸弹爆炸,所以猜测答案是斐波那契数列,将答案0 1 1 2 3 5输入ans.txt,执行./bomb ans.txt验证猜测,第二个炸弹拆弹成功。
在这里插入图片描述

3.phase_3

以下是phase_3的汇编代码:
在这里插入图片描述

422-427行压入了3个参数,根据432行调用sscanf知道此处需要输入3个参数。
在这里插入图片描述

从ebp+8开始读取,第一个参数地址放在ebp-0x10,第二个参数地址放在epb-0x11,第三个参数地址放在epb-0xc。
根据428行函数入口参数地址,使用gdb的x/s命令查看输入的参数类型,sscanf函数输入为整数,字符,整数。

433行将eax和0x2比较大小,如果输入的参数个数大于2则继续执行,否则炸弹爆炸。

436行将第一个参数和7比较,大于则跳转,否则炸弹爆炸,所以第一个参数应该为小于等于7的数。

观察后面汇编代码,很多都跳转到phase_3+0x13b处,于是猜测可能是switch语句,如果是则需要查找跳转表,跳转表部分汇编代码如图中红色方框中所示。

根据439行jmp跳转指令转0x804a2fc+4*eax处地址,用gdb的x/20wx查看跳转表地址。
在这里插入图片描述

如果输入的第一个数为0,则通过跳转表跳转到第一个条件分支地址0x8048c55即440行,mov指令将0x75放到eax寄存器中,cmpl判断第三个参数和0x386即十进制902是否相等,若不相等炸弹爆炸,所以推断出第三个数是902,若相等继续跳转到0x8048d44即490行。

cmp指令将第二个参数和eax寄存器中的低8位进行比较,若相等则继续跳转到0x8048d4e即493行leave指令处离开,若不相等则炸弹爆炸,所以推断第二个参数对应为0x75代表的字符,查ASCII表可知为“u”。

所以最终答案为0 u 902,将答案输入ans.txt,执行./bomb ans.txt验证猜测,第三个炸弹拆弹成功。
在这里插入图片描述

第一个数的范围为0到7之间的任何一个整数,根据第一个数的不同,根据跳转表得到后面的字符和数字也不同,例如1 m 585也是已经验证正确的答案之一。

4.phase_4

以下是phase_4的汇编代码:
在这里插入图片描述

540-543行压入了2两个参数,根据548行调用scanf知道此处需要输入2个参数。从ebp+8开始读取,第一个参数地址放在ebp-0x10,第二个参数地址放在epb-0xc。

根据544行函数入口参数地址,使用gdb的x/s命令查看输入的参数类型,sscanf输入为整数,整数。
在这里插入图片描述

552行将第一个参数和0xe即十进制14比较,若小于等于则跳转,若大于则炸弹爆炸,所以推出第一个参数应该小于14。

561-566行将eax中结果和0x25即十进制37比较,而eax中存放的是函数调用后的返回值。若不相等则炸弹爆炸,若相等则离开,所以推出第二个参数为37。

560行call调用func4,传入3个参数,第一个参数为0,第二个参数为14,第三个参数为ebp-10处的数字。

以下是func4的汇编代码:
在这里插入图片描述

502-504行传入三个参数,第一个设为a1放在edx中,第二个设为a2放在eax中,第三个设为a3放在esi中。

507行开始减法和右移指令计算x=(a3-a2)/2放在ebx中,509行加法指令计算y=x+a2放在ecx中,比较y和a1大小,若小于等于则跳转。

若大于执行514行,把y-1作为第三个参数,a2作为第二个参数,a1作为第一个参数继续递归调用func4,返回的结果和y相加放在eax中。

若相等执行530行返回递归过程,返回y。

若小于执行523行,把a1作为第一个参数,y+1作为第二个参数,a3作为第三个参数继续递归调用func4,返回的结果和y相加放在eax中。

所以最终func4函数的递归形式如下C语言代码所示:
在这里插入图片描述

经过分析推算,最终答案为10 37,将答案输入ans.txt,执行./bomb ans.txt验证猜测,第四个炸弹拆弹成功。
在这里插入图片描述

5.phase_5

以下是phase_5的汇编代码:
在这里插入图片描述

574行传入第一个参数ebp+8,类似于phase_1,这里要求传入参数为字符串。575-576行原来看不懂,查了资料和书以后知道这是一个防止数组越界的栈保护者机制,用于检测缓冲区是否越界,对应教材P157。577行异或操作将eax置0,此时ZF=1。

579行调用string_length函数得到输入字符串的长度,若字符串长度等于6则跳转到601行将eax置0,否则炸弹爆炸,所以输入字符串长度应该等于6。

584行ebx中存放的是输入字符串设为input的首地址,当eax=i时,把input[i]的值放入edx中。585行把edx和0xf相与,得到input[i]的低四位放到edx中。586行把0x804a31c+input[i]低四位放到edx中,587行把edx的低八位放到ebp+i*1-0x13中。588行开始循环比较输入字符串和正确字符串,重复上述过程6次。

用gdb查看0x804a31c所在字符串,为maduiersnfotvbyl
在这里插入图片描述

592行传入参数0x804a2f3,后面调用string_not_equal,猜测这是正确字符串地址。597行test指令用于判断两个字符串是否相等,若相等则ZF=1,否则ZF=0,炸弹爆炸。

用gdb查看0x804a2f3所在字符串,为devils

603-604行和575-576行对应,用于判断数组是否越界,esp-0xc->eax和%gs:0x14异或相等为0说明数组没有越界,否则esp-0xc上内容改变说明数组越界。这几行汇编代码起到“哨兵”的作用,防止数组的访问越界。

所以最后phase_5大致的C语言代码如下:
在这里插入图片描述

根据分析,如果需要使new_str等于devils,那么输入的input[i]的低4位对应的十进制数分别是array[]数组中字符’d’,‘e’,‘v’,‘i’,‘l’,‘s’的下标。array[] = {'m', 'a', 'd', 'u', 'i', 'e', 'r', 's', 'n', 'f', 'o', 't', 'v', 'b', 'y', 'l '};
‘d’:对应与array[2],也即input[0]的低4位应该为0x2,符合条件的可显示字符有:’‘’‘,‘B’,‘2’,‘R’,‘b’,‘r’。
‘e’:对应于array[5],也即input[1]的低4位应该为0x5,符合条件的可显示字符有:’%‘,‘5’,‘E’,‘U’,‘e’,‘u’。
‘v’:对应于array[12],也即input[2]的低4位应该为0xC,符合条件的可显示字符有:’,‘,’<‘,‘L’,’‘,‘l’, ‘|’。
‘i’:对应于array[4],也即input[3]的低4位应该为0x4,符合条件的可显示字符有:’$‘,‘4 ‘,‘D’,‘T’,‘d’,‘t’。
‘e’:对应于array[15],也即input[4]的低4位应该为0xF,符合条件的可显示字符有:’/’,’?‘,‘O’,’_‘,‘o’。
‘s’:对应于array[7],也即input[5]的低4位应该为0x7,符合条件的可显示字符有:’‘’,'7 ',‘G’,‘W’,‘g’,‘w’。
ASCII码如下所示:
在这里插入图片描述

所以本题答案不唯一为6中字符的排列组合,只需每个位置字符后4位和“devils”相同即可,如beldog25<4?7都是其中一个正确答案。将答案输入ans.txt,执行./bomb ans.txt验证猜测,第五个炸弹拆弹成功。
在这里插入图片描述

6.phase_6

以下是phase_6的汇编代码:
在这里插入图片描述

620行传入第一个参数ebp+8,类似于phase_2,这里要求传入参数为数组首地址,622行调用read_six_numbers函数读取键盘输入的6个整数,设它们存放在数组a[6]中。

623行开始把数组中的每一个数都减1放在eax中,若小于0减出来是带符号的负数,解释为无符号数则为一个很大的数。然后把eax中的数和5进行无符号数比较,如果小于等于则跳转,否则炸弹爆炸,所以每一个数应该为大于等于1小于等于6的数。

632行开始判断a[esi]和a[ebx]是否相等,若不相等则跳转,否则炸弹爆炸,所以a[i]不等于其后面的每一个数,所以输入的数为1,2,3,4,5,6的任意一种排列组合。

648行开始将第一个节点node地址存入edx中,接着进行6次循环,从esi=0开始把6个node地址放到ebp+esi*4-0x20中,所以是进行压栈操作。

观察压入栈的内容,每个内容地址实际上是指向12字节的一段数据,该数据的末尾又是指向一个地址,因此,可以判断0x804c13c开始的地方指向的是一个链表,但这些链表的存空间是连续分配的,每个节点包括12个字节,其中最后一个是指向下一个的指针。通过gdb验证以上猜测,x/4x查看内存地址,每一个node最后一个地址都跟下一个node第一个地址相同。
在这里插入图片描述

671行开始把ebx中为node6地址,ebx+8为node6->next即node5地址放到eax中。将node6和node5所指内容进行比较,小于等于则跳转,否则炸弹爆炸。

所以根据以上分析,输入6个数的顺序为3 4 1 2 5 6。将答案输入ans.txt,执行./bomb ans.txt验证猜测,第六个炸弹拆弹成功。
在这里插入图片描述

7.secret_phase

分析完以上6个函数,应该还有一个secret_phase彩蛋,在汇编代码中还有phase_defused函数和func7函数没有调用。以下为phase_defused函数的汇编代码:
在这里插入图片描述

1035行cmpl指令将0x6和0x804c3c8中的值比较,推测应该是完成前六个phase以后才能开启secret_phase。

使用gdb的x/x命令,查看内存内容,进行验证,如图可知开始时这个值为0。
在这里插入图片描述

使用gdb的b命令在第6阶段设下断点,通过前面5个阶段后,0x804c3c8的值变为了6,说明猜想是正确的。
在这里插入图片描述

1043行先传入参数,后面调用sscanf函数,由下面的比较指令可知,此处应该从键盘输入三个参数。

使用gdb的x/s命令查看输入参数的类型,第一个参数和第二个参数均为正整数,第三个参数为字符串。
在这里插入图片描述

使用gdb的x/s命令查看0x804c4d0内存单元内容,发现是要在第四关答案后面加上字符串“DrEvil”。
在这里插入图片描述

所以我在phase_4答案后面加上字符串“DrEvil”后开启了secret_phase
在这里插入图片描述

以下为secret_phase函数的汇编代码:
在这里插入图片描述

725行调用read_line函数获取用户输入,731行调用strtol函数将用户输入的字符串转化为十进制数字。733行将返回结果减1,然后和0x3e8即十进制数1000进行比较大小,若小于等于则跳转至737行,否则炸弹爆炸。因为和phase_6类似进行无符号数比较大小,所以此处输入的数应该为1到1001之间的一个整数。739行调用fun7函数,由前面可知其需要传递两个参数,737行传入用户输入的数作为一个参数,738行传入另一个参数的地址。

以下为fun7的汇编代码:
在这里插入图片描述

观察fun7,发现其和fun4类似,也是一个递归调用。691行将第一个参数a1首地址放入edx中,第二个参数a2放入ecx中,693行test指令对edx进行按位与操作,验证其是否为0,若edx=0,ZF=1,跳转至714行返回0xffffffff即十进制-1。

若edx不等于0,比较ecx和edx中数的大小,若小于等于则跳转至705行把0x0放到eax中,若等于则返回0,若小于则返回2fun7(a1[2],a2)+1,若大于则返回2fun7(a1[1],a2)。

所以最后fun7大致的C语言代码如下:
在这里插入图片描述

根据分析,secret_phase要求用户输入一个1 ~ 1001的数,调用fun7函数搜索二叉树,根据secret_phase函数740行将0x1和eax比较知,其要求fun7返回值为1。fun7对二叉树的每个节点进行了编号,左子树为0,右子树为1,搜索1,fun7 = 000b = 0;搜索7,fun7 = 100b = 4;搜索20,fun7 = 010b = 2;搜索35,fun7= 110b = 6;搜索40,fun7 = 001b = 1;搜索47,fun7 = 101b = 5;搜索99,fun7= 011b = 3;搜索1001,fun7= 111b = 7。

所以最后答案为40,输入后验证成功。
在这里插入图片描述

四、实验截图

做完前6个的实验截图:
在这里插入图片描述

做完彩蛋的实验截图:
在这里插入图片描述

  • 36
    点赞
  • 198
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值