参考思路与用到的代码:链接:pan.baidu.com/s/1M2ZxKBK3RKD6ACZCTc931A
码:7ssw
实验题目:bomblab | |
实验目的:熟悉汇编程序,学习gdb调试工具,熟悉并掌握函数调用过程中的栈帧结构的变化,熟悉汇编程序及其调试方法; 程序运行中有七个关卡每个关卡需要输入特定的字符或数字才能通过否则失败。使用gdb工具反汇编出汇编代码,结合c语言文件找到每个关卡的入口函数,分析汇编代码,找到在每个phase程序段中,引导程序跳转到“explode_bomb”程序段的地方,并分析其成功跳转的条件,以此为突破口寻找应该在命令行输入何种字符通关。 | |
实验环境:个人PC,Linux发行版本 | |
实验内容及操作步骤:
main函数中的相关提示 告诉我们需要先输入一定的信息才能进入phase_1进行比较; 后面的phase多数都是这样(先输入); 第一关很简单,在进入phase_1之前输入了一段字符串,在phase_1中将输入的字符串与以0x804a1a4为首地址的字符串作比较,若相等则通关,否则炸弹爆炸; 0x804a1a4为首地址的字符串: 因此,第一关要输入的就是: For NASA, space is still a high priority. //每个人的这一行是不一定相同的,需要自己查看 答案正确——第一关成功破解;
较为简单,讲一下这个关卡最主要的地方; 首先,call <read_six_numbers>是表示输入了六个数字,这六个数是解题的答案,cmpl $0x1,0x18(%esp)表示第一个数是1,不是的话将会爆炸; 这里的%eax保存的是Xi-1,%ebx保存的是Xi的地址,将%eax加倍后与(%ebx)相比较,不相等则爆炸; 因此六个数的关系就确定了,Xi=2*Xi+1 (X0=1); %esi中保存了X5,当(%ebx)不等于X5时循环Xi=Xi-1*2的判断过程;每次另%ebx加4,保证了(%ebx)的值是从X1-X5; 因此输入的六个数应该是1、2、4、8、16、32; 答案正确,破解成功;
这是将0x804ac3c作为call <_isoc99_sscanf@plt>的参数,而0x804a3c3的内容是: 输入两个整数作为答案 输入后的前两个语句就是比较1和第一个输入的关系; 接着比较7和第一个输入的关系,即这两个语句限制了0<X0<8; 接着是根据第一个值X0来求第二个值X1: %eax中存储X0的值,根据jmp语句执行跳转(位置由%eax的值确定); X0=1时%eax=1,跳转位置: 0x804a204:0x08048bff 这里我先假设X0=1,那么跳转的位置就是jmp的下一行,这是%eax更新为0; 接下来有几个jump控制程序使程序对%eax的值执行如下操作(蓝色jmp是跳到下一行,只留下一个jump,还有一些忽略掉只留下对%eax的加减操作): 8048c0b: 2d d7 02 00 00 sub $0x2d7,%eax 8048c10: eb 05 jmp 8048c17 <phase_3+0x5b> 8048c17: 05 44 03 00 00 add $0x344,%eax 8048c23: 2d 28 01 00 00 sub $0x128,%eax 8048c2f: 05 28 01 00 00 add $0x128,%eax 8048c3b: 2d 28 01 00 00 sub $0x128,%eax 8048c47: 05 28 01 00 00 add $0x128,%eax 8048c53: 2d 28 01 00 00 sub $0x128,%eax 也就是%eax=0要经过这一系列的操作来更新%eax,最终%eax=-187(十进制); 这个语句又限制了X0的大小必须是小于5的,否则爆炸; 当然,取X0=2、3、4、5同样可行,只是跳转的位置不一样,%eax所要进行的加减操作数就不一样,比如X0=2时仅仅是不用执行第一个sub $2d7,其余都要执行; 最后比较X1和计算得到的%eax的值,不相等则爆炸; 因此,对于我选择的X0=1,X1应该等于-187; 即输入的两个数应该是1 、-187(随着X0的取值不同,答案也不同) 可以看出结果正确,破解成功;
phase_3中同样有一个,代表有两个整数输入 这个比较同样说明输入应该是两个参数 上面的两个比较是判断输入的X0的值,限定了0<=X0<=14 接下来将0、14、X0作为参数来调用func4函数 此时观察func4函数的功能: 对输入的三个参数进行运算 high为传入的第三个参数、low为第二个,经过一些运算的到aver的值如上图所示; 比较输入的X0(第一个参数)与aver的关系,如果X0>aver,则使aver-1,并作为参数三再次进入func调用(func(x,low,aver-1)),返回得到的结果再加到aver上; 而如果X0<aver则将aver+1并作为第二个参数调用func(x,aver+1,high),将返回结果加到aver上;即代表汇编语言中的:
只有当以上两个判断条件同时满足时才不需要继续调用,也就是说X0==aver; 最后将aver return; 将func写回c代码则如下: 那么如果我们知道了返回的aver的值就可以用c程序倒退回X0的值; 而在phase_4中: 我们得知返回值为43,通时通过判断发现输入的第二个数X1的值就等于aver=43; 因此编写程序将0-14都带入func中去推出X0的值,最后发现运行结果为12; 因此X0的值应该为12,X1的值应该是43; 即答案为 12 、43; 结果验证正确,破解成功!
这一部分因为确实很绕而且网上基本上没有这样形式的关卡的解析,因此花费了很长的一段时间才刚明白; 以下是整个的汇编代码: 在这里进行解析: 首先,这里将输入的字符串的首地址放入(%esp)中; 接下来的一个call string_length函数是求输入的字符串的字数 而判断语句则说明输入的字符串的长度为6; 这里首先将%edx赋0,%eax(用来计数)也赋为0; 接着用movsbl语句将字符串中的字母(从第一个开始)传入%ecx,接着做与运算,只留下了字母ASCII码的低四位(最大为1111),然后将这低四位(假设值为x)乘于4加到0x804a220上作为地址(也就是0x804a220偏移4*x个字节); 接着将这个地址中的内容加到%edx中,同时将%eax加1; 此时如果%eax!=6则说明字符串中的六个字符还未处理完,就要回到movsbl语句进行下一个字符的同样的处理; 到最后(加完六个字符生成的地址中的值),%edx的值应该是0x4b也就是75; 那么我们看0x804a220所在连续16个整型空间的值(对应了偏移量x*4(x最大为1111(15))的每种情况): 而ASCII码表中所有字母低四位的情况为:
还有大写的字母以及从p往后的小写字母因为低四位与上述对应,因此不再展示出来; 使字母和x*4+0x804a220里的内容相对应得到: (上下对应) 因此输入p相当与%edx加2、a相当于加10、b相当于加6...... 以上是解题的所有条件,此时我们要自己配出来一个6位字母组合使得最后%edx得结果为75; 我配出得一个答案是:nejaaa 答案测试正确,解题成功; 因为很多字母低四位是冲突的,并且配成75的组合非常非常多,因此答案也非常多,不唯一;
这个关卡比上一个关卡还要困难一些,因为涉及到了一些链表的知识; 看到这个就想到了输入的答案是6个数字; 首先便是一个大循环嵌套一个小循环,大循环是判断每一个输入的数Xi<=6(jb保证了Xi>0),而小循环是对当前的Xi要保证与剩余的五个数都不同,也就是说上述语句所实现的整个功能就是确保输入的六个数X0-X5互不相等,且它们都大于0小于等于6,说明1-6是我们输入的六个数,但是还不知道输入的顺序; 接下来判断它们的输入顺序: 循环结束后的操作用链表结点的方法来解析,例:,两个数与一个指针(地址); 从X0开始将Xi存入%ecx中,将第一个节点的地址(0x804c13c)放入%edx,接下来判断Xi是否大于1,大于的话跳转: %eax的初始值为1,因此这里先将结点指针指向下一个结点,之后再将%eax+1,判断%ecx(Xi)与%eax 的大小,不相等则说明结点位置不对,需要继续向下找,而相等则找到(说明此时该结点的第二个数与Xi相等),此时将结点中的第一个数放入到28-3c(%esp)的连续空间中; 链表所有的结点如下: 最后一个为指针,指向下一个结点; 此时,如果我们输入的就是1 2 3 4 5 6,那么28-3c(%esp)连续空间内就会是0x1aa 0x2a3 0x1ad 0x334 0x1ea 0x3ad,我们输入的是不同的顺序则空间中放入的循序也不同; 这是在28-3c(%esp)设置了一个链表,好进行接下来的循环比较; 这一段中cmp循环比较Yi和Yi+1的大小,如果出现了Yi<=Yi+1,则爆炸; 也就是说28-3c(%esp)连续空间内中的0x1aa、0x2a3、0x1ad、 0x334、 0x1ea、 0x3ad是降序排列的,也就是说,在将这些值通过xi的值遍历结点写入28-32c(%esp)空间时的顺序是固定的、降序的,因此呢Xi输入的顺序就可以推出来了: 6 4 2 5 3 1 这就是要输入的答案; 结果验证正确,破解成功!
在bomb.c中没有发现secrt_phase的相关信息,因此到bomb.txt中搜索ctrl +F ,发现phase_defused中含有secret_phase; 仔细看了与唯一提到进入秘密关卡的<phase_defused>,从上往下看首先发现: 也就是进入隐藏关卡的入口; 接下来是进入的条件,在call的前面: 表明6个关卡都通过后才能进行隐藏关卡; 首先是movl $0x804a3c9 ,0x4(%esp),做参数,也就是: sscanf可能是C语言的内部函数,查到其定义为:int sscanf(const char *str, const char *format,…), 一个使用实例为:sscanf(“s 1 2”, “%s %d %d”, str, &a,&b),函数返回3(因为接收了3个参数。因此这里要求0x804a3c9处存放的是一个数字和一个字符串。 即输入两个整数后输入一个字符串;如果参数不足三个则不会进入隐藏关卡; 这是一个字符串比较的函数,0x804a3d2是我们输入的字符串所要进行对比的字符串的首地址; 可以看出该字符串为: DrEvil 看着两个字符串发现这仅仅是输入完DrEvil后出现的提示句子,没有实际意义; 现在知道了进入隐藏关的字符串,但是并没有确定输入这个字符串“DrEvil”的的位置; phase_3、phase_4都是输入了两个整数,却不确定是哪一个后面输入字符串; 通过测试,发现在第四关的位置输入字符串可以进入隐藏关卡:
整个的汇编代码如下(做了充分注释): 可以看到我们要输入的答案是一个十进制值,并且该值小于等于1001(十进制); 接下来将输入的x和一个地址0x804c088作为参数调用func7(*p , x)函数; 接下来分析func7函数: 在func7函数中同样需要进行x和*p的大小比较, 当x<*p时,将p+4,继续调用func7(*(p+4),x)函数,令返回值*2(%eax*2); 而当x>*p时,将p+8,调用func7(*(p+8),x)函数,令返回值*2+1(%eax*2+1); 而当两个值相等时,则直接返回; 当开始从secret_phase中调用func7时,%eax的值为0,而最终返回到secret_phase中时: 返回值%eax应该为4; 因此%eax在func7函数中的变化应该是:0->1->2->4 逆递归过程就是4->2->1->0的,逆递归过程求解X的值: 此时%eax=4; p+4(此时x无限制可以满足x<0x24(*p+4)); %eax=%eax/2=2; p+4(x本来<0x24,因此满足x<8(*p+4)) %eax=%eax/2=1; p+8(x<8可以同时满足x>6(*p+8)) %eax=(%eax-1)/2=0; 这个时候回到顶层,%eax为0,*p=x,直接返回,因此7为输入的x,且满足递归过程中的所有不等式; 可以看到,7是隐藏关卡的答案,破解成功; 至此,所有的关卡都破解完成了,答案如上; 实验结果及分析: bomblab的六个关卡和一个隐藏关卡全部解析完成,结果如上图,完整的解决了整个实验; 收获与体会:通过这次实验,对于Linux系统的一些操作命令有了一些了解和掌握,学习了如何使用gdb 这个强大的工具进行调试,以及加深了对于汇编语言的熟悉。另外学会了看字符串的x/s、和看结点的p/x *(地址)@n (其中n是结点的大小)等几种指令; 前4个关卡还算是简单,但到了第五六个关卡就已经变得很难了,涉及到了链表等内容,做的时候真的非常烧脑,有时候一点点的内容不清楚甚至要想非常久才能解决,整个实验耗费了不少的时间,但是解决之后所获得的知识和成就感是值得花费大量的时间的,这次实验令我对反汇编破解有了一些深入的理解。 |