计算机系统基础实验——拆除二进制炸弹
实验简要介绍: 该实验是书籍深入理解计算机操作系统中最有趣的实验之一,主要使用 gdb来拆解二进制(或者说程序)的一个文件,了解其运行逻辑。我们对这个程序的了解有:有六个关卡,分别是 phase_1 - phase_6每一步都会要求你输入一个内容,就好像是密码,但是密码需要自己通过技术手段获取。程序是 C 写的。bomb.c 是程序的总逻辑
实验简要介绍:
该实验是书籍深入理解计算机操作系统中最有趣的实验之一,主要使用 gdb来拆解二进制(或者说程序)的一个文件,了解其运行逻辑。我们对这个程序的了解有:
- 有六个关卡,分别是 phase_1 - phase_6
- 每一步都会要求你输入一个内容,就好像是密码,但是密码需要自己通过技术手段获取。
- 程序是 C 写的。
- bomb.c 是程序的总逻辑,可以看到每一个关卡都是调用一个 phase 开头的函数。
首先使用命令objdump -d bomb > disassemble.txt反汇编bomb文件,将生成的汇编文件重定向到disassemble.txt文件中,然后结合gdb设置断点调试进行每一关炸弹的拆除。
phase_1
图1.1
图1.2
首先阅读汇编代码,发现图1.1中1处进行了有效地址传送,使用命令x/2s查看一下内容,发现是“I am just a renegade hockey mom.” 于是猜测这个字符串应该和答案有关,接下来看到2处调用了strings_not_equal函数,应该是用来比较两个字符串是否相等,于是查看该函数的汇编代码,图1.3所示的两行可知是入口参数,执行完这两句汇编语句之后可以看到图1.4所示EBX储存的是我输入的字符串“My name is Kerin.”,ESI存储的是“I am just a renegade hockey mom.”,通过分析后面的代码可知该函数的作用即为比较用户输入字符串和程序中这个固定的字符串是否相等,相等则strings_not_equal函数返回值为0,回到phase_1函数经过图1.1中14bf处的跳转语句不会跳转到14c6引爆炸弹,而是顺序执行离开函数phase_1,因此可知答案即为“I am just a renegade hockey mom.”。
图1.3
图1.4
phase_2
从汇编代码图2.1中可知调用函数read_six_numbers,进入到该函数里面通过命令p (char*)($ebx-0x1c5f)可以查看到要求输入数据的格式(图2.2),通过sscanf函数也可以看到格式(图2.3),其中“0 1 2 3 4 5”是我输入的6个数字。
读入6个数字后再看phase_2的汇编代码(图2.4),首先拿M[R[ebp]-0x30]的数据和0比较,于是先查看这个位置的数据,如图2.5所示,发现这一块连续的存储单元存储了我刚才输入的“0 1 2 3 4 5”(下面均以这个输入为例解释),那么很显然M[R[ebp]-0x30]处存储的是我输入第一个数字0,如果两者不相等就会跳转到14fc引爆炸弹,因此输入的第一个数字必为0,接下来比较M[R[ebp]-0x2c]和数字1,即比较我输入的第二个数字和数字1的大小,如果不相等则引爆炸弹,相等则跳转到1501处执行,因此这一部分代码的作用就是检查输入的前两个数字是否为“0 1”。
图2.6所示,接下来进行有效地址传送,将R[ebp]-0x30放入到esi中,将R[ebp]-0x20放入到edi中,可知esi即指向“0 1 2 3 4 5”中的0的地址,edi指向“4 5”中的4的地址,接下来看1515-151a部分,首先将M[R[esi]+0x4]放入eax,即eax等于1,又进行M [R[esi]]+R[eax]给R[eax],即进行0+1给eax,比较eax中的值和M[R[esi]+0x8]的值,M[R[esi]+0x8]值为2,相等则跳转到150e,不等则引爆炸弹,若跳转到150e,进行R[esi]+0x4放到R[esi],实现esi指向的存储单元向后移动了四个字节,接下来比较R[edi]和R[esi]是否相等,相等则终止循环,可以知道刚才让edi指向“4 5”中的4的地址,实际上就是终止条件,若不等则继续刚才的操作进行比较(将前两个数相加和后一个数进行比较),很容易看出这实际就在验证输入的6个数字是否是斐波那契数列,因此答案是“0 1 1 2 3 5”。
phase_3
在这个分支下,解释如图3.4所示,可知要求输入的第二个参数应该为686,相等的话继续跳转到166c处进行比较,解释如图3.5所示。al中存进去了字母‘e’,后面要将M[R[ebp]-0x11]的低8位与‘e’,进行比较,因此知道第二个参数应该为‘e’才能通关。综上,其中一个答案是“1 e 686”,同理可得其他答案,比如“2 c 559”也可以通关。故本关答案不唯一。
phase_4
惯例,先查看要求的输入格式,即“%d %d”, 汇编代码部分如图4.2所示,可知要求输入的第一个参数小于等于0xe,要求输入的第二个参数必须为6,(可用x/x命令查看-0x10(%ebp)储存的值),要求调用func4函数的返回值应该为6,传递给fun4函数的参数是0xe,0,以及输入的第一个参数,接下来看fun4函数。
图4.3 fun4函数的汇编代码解释
如图4.3所示为fun4函数的详细解释,代码可分为3块来解读,圈出来的是递归调用自己之后两种情况对应的返回值,根据此汇编代码的逻辑可以得到对应的C代码为图4.4中fun4所示:
phase_5
首先由图5.1所示汇编代码可知要求输入一个长度为6的字符串,否则将引爆炸弹。
再看图5.2所示汇编代码,首先将累加器ecx清零,然后进行了有效地址传送,将R[ebx]-0x1e40给了R[edi],在这里用命令x/70cb查看该地址,如图5.3所示,可以看到这应该是一个数组,相当于把数组的首地址给了R[edi],接下来汇编语句的解释详见图5.2,可知这一部分的作用就是用输入的字符串的每个字符的末四位作为变址去获取内存中固有数组对应的元素进行累加,要求累加6次并且累加结果等于0x24,该数组如表5.4所示,
手推可知0x24=0x10+0x10+0x1+0x1+0x1+0x1=a[5]+a[5]+a[3]+a[3]+a[3]+a[3],所以只需要6个字符的末四位分别是553333即可,最简单的当然就是字符串“553333”,当然对此重新排列成“333355”等也会是答案,故本题答案也不唯一。
phase_6
本部分代码很长,分部分来看。首先需要读入6个数字,先查看格式如图6.1所示,即“%d %d %d %d %d %d”。
进入下一块,保证输入的6个数字互不相同且都在1-6之内,汇编代码及解释如图6.2所示,图6.3为相应的c语言的逻辑,这里用数组来简化,真正的汇编代码使用的是指针。
图6.2
如果输入的数据符合规范,执行180多步就能跳转到1818处执行,可用命令n 180,节省时间。
最开始R[ebp]-0x30存的是我输入的数字的首地址,图6.4展示了查看到这几个数字。接下来进行了有效地址传送,将R[ebx]+0x578给edx,使用命令x/32wx查看一下,发现这些数字存储的很有规律,可认为3个一组,第一个数字是二进制数,第二个数字是序号,第三个数字是地址,如0x00405584指向的就是0x18e,加上开头由node单词,于是可以判断出这是一个链表,其结构如表6.6所示。
见图6.7汇编代码解释,接下来是按照我输入数字的顺序,将链表每个节点的地址存到一个新数组中,这里也是用一个大循环套一层小循环,大循环负责选定我某一个输入的数字n,小循环根据这个数字n将第n个节点地址存储到一个新数组中,以“6 5 4 3 2 1”为例,执行完这个大循环后用命令x/32wx $ebp-0x48查看这个新数组存的数据,如图6.8所示,可以看到新数组中依次存放的是0x00405100、0x004055a8、0x0040559c、0x00405590、0x00405584、0x00405578,是一开始链表地址的逆序(因为我输入的是“6 5 4 3 2 1”)。
如图6.9所示,接下来就是按照新的顺序将这节点衔接起来,组成一个新的链表,最后又是一个循环来检查这个链表的数据域是否按照从大到小的顺序排列,如图6.10所示,edi用来计数,循环中依次比较当前节点的数据域是否大于等于后一个节点的数据域,若大于等于继续循环,否则将爆炸,因此可以知道要输入的6个数字应该使链表重排后数据域从大到小排列,因为0x28b>0x18e>0x17b>0x172>0x14c>0xb2, 对应顺序为“3 2 6 5 4 1”,此即为答案。
secret_phase
1.隐藏关的进入
首先在phase_defused的反汇编代码中找到调用secret_phase函数(图7.1),所以应该是拆除完每个炸弹的时候才有机会进入隐藏关卡,当拆完6个炸弹之后,使用s命令进入phase_defused函数中逐步调试,在sccanf的时候(图7.2)查看一下输入的格式(图7.3),发现要求属入“%d %d %s”,而我输入的是“6 6”,为第四关的答案,可知要求在第四关的答案后面再附加一个字符串才能进入隐藏关。为了弄清这个字符串是什么,先输入“6 6 mds”进行实验,在phase_defused函数中进入strings_not_equal函数调试,在比较字符串长度之前将我输入的“mds”放入到了ebx,把“DrEvil”放入到esi中(图7.4所示),由第一关的经验可知想进入隐藏关卡的字符串即为“DrEvil”。
2.隐藏关炸弹的拆除
由汇编代码知,调用完fun7函数之后,test了返回值eax,如果不为0将引爆炸弹(图7.5),如果为0则通关,于是进入fun7函数(图7.6),发现很有意思的是18ab处比较了R[ecx]和R[ebx],若不满足大于,则不进行跳转而是执行18af处,eax清零,紧着判断两者是否相等,相等的话也不进行跳转,直接返回到secret_phase函数了,因此可以知道只需保证R[ecx]和R[ebx]相等即可通关。
图7.8所示,单步运行到比较R[ecx]和R[ebx]时,发现ebx存储的是0x24(十进制36),ecx存储的是0x23(十进制35,我实验中输入的),因此可以正确答案是36。
更多推荐
所有评论(0)