实验题目: LAB2-炸弹实验(bomb8) | ||||||||||||||||||||||||
实验目的: 一共有七个关卡(一个隐藏关卡),我们需要通过汇编代码来找到七个对应的正确的密码(可能是数字,可能是字符串),并顺利通关。 | ||||||||||||||||||||||||
实验环境: Ubuntu12.04 | ||||||||||||||||||||||||
实验内容及操作步骤: 一.第一关 08048b90 <phase_1>: 8048b90: 83 ec 1c sub $0x1c,%esp 8048b93: c7 44 24 04 6c a1 04 movl $0x804a16c,0x4(%esp) 8048b9a: 08 8048b9b: 8b 44 24 20 mov 0x20(%esp),%eax 8048b9f: 89 04 24 mov %eax,(%esp) 8048ba2: e8 a3 04 00 00 call 804904a <strings_not_equal> 8048ba7: 85 c0 test %eax,%eax 8048ba9: 74 05 je 8048bb0 <phase_1+0x20> 8048bab: e8 a5 05 00 00 call 8049155 <explode_bomb> 8048bb0: 83 c4 1c add $0x1c,%esp 8048bb3: c3 ret 分析:在观察汇编代码时,我们会看见有一串<explode_bomb>,代表发现炸弹。下面一行有$0x804a16c,是一个绝对地址,而在后面又有调用<strings_not_equal>,我们可以猜测正确答案就是$0x804a16c这个地址的内容。 实际操作时,我们可以在67处设置断点,之后运行进入第一关,通过x/s 0x804a16c来展示对应地址内容,最终可得到Wow!Brazil is big.
test命令可以将两个操作数进行逻辑与运算,并根据运算结果设置相关的标志位。在这里,test负责测试我们输入的内容是否为正确密码,如果是正确密码,下一行的 je可以跳转到8048bb0,即成功拆除炸弹;如果密码错误,那么炸弹就会爆炸。
二、第二关 08048bb4 <phase_2>: 8048bb4: 56 push %esi 8048bb5: 53 push %ebx 8048bb6: 83 ec 34 sub $0x34,%esp 8048bb9: 8d 44 24 18 lea 0x18(%esp),%eax 8048bbd: 89 44 24 04 mov %eax,0x4(%esp) 8048bc1: 8b 44 24 40 mov 0x40(%esp),%eax 8048bc5: 89 04 24 mov %eax,(%esp) 8048bc8: e8 af 05 00 00 call 804917c <read_six_numbers> 8048bcd: 83 7c 24 18 01 cmpl $0x1,0x18(%esp) 8048bd2: 74 1e je 8048bf2 <phase_2+0x3e> 8048bd4: e8 7c 05 00 00 call 8049155 <explode_bomb> 8048bd9: eb 17 jmp 8048bf2 <phase_2+0x3e> 8048bdb: 8b 43 fc mov -0x4(%ebx),%eax 8048bde: 01 c0 add %eax,%eax 8048be0: 39 03 cmp %eax,(%ebx) 8048be2: 74 05 je 8048be9 <phase_2+0x35> 8048be4: e8 6c 05 00 00 call 8049155 <explode_bomb> 8048be9: 83 c3 04 add $0x4,%ebx 8048bec: 39 f3 cmp %esi,%ebx 8048bee: 75 eb jne 8048bdb <phase_2+0x27> 8048bf0: eb 0a jmp 8048bfc <phase_2+0x48> 8048bf2: 8d 5c 24 1c lea 0x1c(%esp),%ebx 8048bf6: 8d 74 24 30 lea 0x30(%esp),%esi 8048bfa: eb df jmp 8048bdb <phase_2+0x27> 8048bfc: 83 c4 34 add $0x34,%esp 8048bff: 5b pop %ebx 8048c00: 5e pop %esi 8048c01: c3 ret 分析:首先我们可以看到<read_six_numbers>,猜测密码是6个数。(这里有一个易错点,很容易会以为只需要6个数字,但是实际上却是6个数)。 紧接着有两条指令是cmpl $0x1,0x18(%esp)和je 8048bf2 <phase_2+0x3e>,通常%esp或其前面带数字,都是上一个函数调用所造成的,指上一个函数处理后输出的结果。而$0x1指的是它第一个数必须是1,那么我们可以推断出第一个数是1。 接下来的lea 0x1c(%esp),%ebx和mov -0x4(%ebx),%eax两条指令先把第二个数的地址存放进ebx,然后又把第一个数放进eax。 接着add %eax,%eax指令相当于把,eax中的数据乘2。cmp %eax,(%ebx),将eax和ebx的地址中的数据比较,也就是和输入的第2个数比较,如果不相等的话就会调用函数<explode_bomb>引爆炸弹;如果相等,那么ebx的地址加4,也就是指向下一个数,也就是第3个数。 cmp %esi,%ebx这条指令比较当前的地址是不是已经超过了我们输入的6个数的存储地址,如果超过了那就停止循环,如果没有,就跳转到mov -0x4(%ebx),%eax指令继续进行循环。这个循环内进行的操作就是将前一个数乘2和当前的数比较。 因此如果不想引爆炸弹,我们输入的6个数中,后一个数必须是前一个数的2倍,并且第一个数是1,那么很容易就能得到了6个数分别是:1 2 4 8 16 32
三、第三关 8048c02: 83 ec 2c sub $0x2c,%esp 8048c05: 8d 44 24 1c lea 0x1c(%esp),%eax 8048c09: 89 44 24 0c mov %eax,0xc(%esp) 8048c0d: 8d 44 24 18 lea 0x18(%esp),%eax 8048c11: 89 44 24 08 mov %eax,0x8(%esp) 8048c15: c7 44 24 04 ef a2 04 movl $0x804a2ef,0x4(%esp) 8048c1c: 08 8048c1d: 8b 44 24 30 mov 0x30(%esp),%eax 8048c21: 89 04 24 mov %eax,(%esp) 8048c24: e8 37 fc ff ff call 8048860 <__isoc99_sscanf@plt> 输入 8048c29: 83 f8 01 cmp $0x1,%eax 至少两个数 8048c2c: 7f 05 jg 8048c33 <phase_3+0x31> 否则会爆炸 8048c2e: e8 22 05 00 00 call 8049155 <explode_bomb> 8048c33: 83 7c 24 18 07 cmpl $0x7,0x18(%esp) 8048c38: 77 66 ja 8048ca0 <phase_3+0x9e> 8048c3a: 8b 44 24 18 mov 0x18(%esp),%eax 8048c3e: ff 24 85 88 a1 04 08 jmp *0x804a188(,%eax,4) 8048c45: b8 00 00 00 00 mov $0x0,%eax 8048c4a: eb 05 jmp 8048c51 <phase_3+0x4f> 8048c4c: b8 4a 02 00 00 mov $0x24a,%eax 8048c51: 2d f1 02 00 00 sub $0x2f1,%eax 8048c56: eb 05 jmp 8048c5d <phase_3+0x5b> 8048c58: b8 00 00 00 00 mov $0x0,%eax 8048c5d: 05 e9 02 00 00 add $0x2e9,%eax 8048c62: eb 05 jmp 8048c69 <phase_3+0x67> 8048c64: b8 00 00 00 00 mov $0x0,%eax 8048c69: 2d 2c 03 00 00 sub $0x32c,%eax 8048c6e: eb 05 jmp 8048c75 <phase_3+0x73> 8048c70: b8 00 00 00 00 mov $0x0,%eax 8048c75: 05 2c 03 00 00 add $0x32c,%eax 8048c7a: eb 05 jmp 8048c81 <phase_3+0x7f> 8048c7c: b8 00 00 00 00 mov $0x0,%eax 8048c81: 2d 2c 03 00 00 sub $0x32c,%eax 8048c86: eb 05 jmp 8048c8d <phase_3+0x8b> 8048c88: b8 00 00 00 00 mov $0x0,%eax 8048c8d: 05 2c 03 00 00 add $0x32c,%eax 8048c92: eb 05 jmp 8048c99 <phase_3+0x97> 8048c94: b8 00 00 00 00 mov $0x0,%eax 8048c99: 2d 2c 03 00 00 sub $0x32c,%eax 8048c9e: eb 0a jmp 8048caa <phase_3+0xa8> 8048ca0: e8 b0 04 00 00 call 8049155 <explode_bomb> 8048ca5: b8 00 00 00 00 mov $0x0,%eax 8048caa: 83 7c 24 18 05 cmpl $0x5,0x18(%esp) 8048caf: 7f 06 jg 8048cb7 <phase_3+0xb5> 8048cb1: 3b 44 24 1c cmp 0x1c(%esp),%eax 8048cb5: 74 05 je 8048cbc <phase_3+0xba> 8048cb7: e8 99 04 00 00 call 8049155 <explode_bomb> 8048cbc: 83 c4 2c add $0x2c,%esp 8048cbf: 90 nop 8048cc0: c3 ret 在最开始的绝对地址中,我们可以看到movl $0x804a2ef,0x4(%esp),这里有着绝对地址0x804a2ef,经查询后发现内容为“%d %d”我们可以猜测密码是两个数字。 开头的地址传送部分都是给函数<__isoc99_sscanf@plt>传送参数,把我们输入的数据写入栈帧,函数在调用<__isoc99_sscanf@plt>函数后,接着函数调用了而cmp $0x1,%eax表明输入参数多于1个。这里也验证了我们的想法。
在之后的cmpl $0x7,0x18(%esp),我们可以得知输入的第一个数字不能大于7,否则会爆炸。 继续往下看,mov 0x18(%esp),%eax这句话我们输入的第一个数传给eax。然后看到jmp *0x804a188(,%eax,4),这是switch跳转语句,即跳转到以地址*0x804a188为基址的跳转表中。我们可以查看这个跳转表中的地址元素。
我们可以看到一共有8个地址,对应我们第一个数字输入0-7。 比如第一个数字输入0,我们跳转到0x08048c4c,对应mov $0x24a,%eax、sub $0x2f1,%eax和jmp 8048c5d <phase_3+0x5b>。以及最后跳转到的代码指令cmp 0x1c(%esp),%eax也就是将我们运算得到的eax和我们输入的第2个数作比较,相等时才算成功。 因此第一个数字为0时,输入的第二个数也就是对应的运算后的-234。
剩下7个也是ok的,这里不做赘述。 四、第四关 08048cc1 <func4>: 8048cc1: 56 push %esi 8048cc2: 53 push %ebx 8048cc3: 83 ec 14 sub $0x14,%esp 8048cc6: 8b 54 24 20 mov 0x20(%esp),%edx 8048cca: 8b 44 24 24 mov 0x24(%esp),%eax 8048cce: 8b 5c 24 28 mov 0x28(%esp),%ebx 8048cd2: 89 d9 mov %ebx,%ecx 8048cd4: 29 c1 sub %eax,%ecx 8048cd6: 89 ce mov %ecx,%esi 8048cd8: c1 ee 1f shr $0x1f,%esi 逻辑右移(不考虑符号位,全补0) 8048cdb: 01 f1 add %esi,%ecx 8048cdd: d1 f9 sar %ecx 算术右移(考虑符号位置) 8048cdf: 01 c1 add %eax,%ecx 8048ce1: 39 d1 cmp %edx,%ecx 8048ce3: 7e 17 jle 8048cfc <func4+0x3b> 分界点,小于等于则跳 8048ce5: 83 e9 01 sub $0x1,%ecx 8048ce8: 89 4c 24 08 mov %ecx,0x8(%esp) 8048cec: 89 44 24 04 mov %eax,0x4(%esp) 8048cf0: 89 14 24 mov %edx,(%esp) 8048cf3: e8 c9 ff ff ff call 8048cc1 <func4> 8048cf8: 01 c0 add %eax,%eax 8048cfa: eb 20 jmp 8048d1c <func4+0x5b> 8048cfc: b8 00 00 00 00 mov $0x0,%eax 8048d01: 39 d1 cmp %edx,%ecx 8048d03: 7d 17 jge 8048d1c <func4+0x5b> 8048d05: 89 5c 24 08 mov %ebx,0x8(%esp) 8048d09: 83 c1 01 add $0x1,%ecx 8048d0c: 89 4c 24 04 mov %ecx,0x4(%esp) 8048d10: 89 14 24 mov %edx,(%esp) 8048d13: e8 a9 ff ff ff call 8048cc1 <func4> 自己调用自己,推测出递归 8048d18: 8d 44 00 01 lea 0x1(%eax,%eax,1),%eax 8048d1c: 83 c4 14 add $0x14,%esp 8048d1f: 5b pop %ebx 8048d20: 5e pop %esi 8048d21: c3 ret 08048d22 <phase_4>: 8048d22: 83 ec 2c sub $0x2c,%esp 8048d25: 8d 44 24 1c lea 0x1c(%esp),%eax 8048d29: 89 44 24 0c mov %eax,0xc(%esp) 8048d2d: 8d 44 24 18 lea 0x18(%esp),%eax 8048d31: 89 44 24 08 mov %eax,0x8(%esp) 8048d35: c7 44 24 04 ef a2 04 movl $0x804a2ef,0x4(%esp) 8048d3c: 08 8048d3d: 8b 44 24 30 mov 0x30(%esp),%eax 8048d41: 89 04 24 mov %eax,(%esp) 8048d44: e8 17 fb ff ff call 8048860 <__isoc99_sscanf@plt> 调用函数输入 8048d49: 83 f8 02 cmp $0x2,%eax 由此可知返回值要为2 8048d4c: 75 07 jne 8048d55 <phase_4+0x33> 8048d4e: 83 7c 24 18 0e cmpl $0xe,0x18(%esp) 比较是否小于等于14 8048d53: 76 05 jbe 8048d5a <phase_4+0x38> 8048d55: e8 fb 03 00 00 call 8049155 <explode_bomb> 8048d5a: c7 44 24 08 0e 00 00 movl $0xe,0x8(%esp) 8048d61: 00 8048d62: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp) 8048d69: 00 8048d6a: 8b 44 24 18 mov 0x18(%esp),%eax 8048d6e: 89 04 24 mov %eax,(%esp) 到这里为止,设置参数14,0 8048d71: e8 4b ff ff ff call 8048cc1 <func4> 按顺序输入 (:,0,14) 8048d76: 83 f8 06 cmp $0x6,%eax 判断返回值是否为6 8048d79: 75 07 jne 8048d82 <phase_4+0x60> 8048d7b: 83 7c 24 1c 06 cmpl $0x6,0x1c(%esp) 判断输入的第二个参数是否为6,不是则爆炸,所以可以确定第二个参数值为6 8048d80: 74 05 je 8048d87 <phase_4+0x65> 8048d82: e8 ce 03 00 00 call 8049155 <explode_bomb> 8048d87: 83 c4 2c add $0x2c,%esp 8048d8a: c3 ret 首先我们分析fun4,通过call 8048cc1 <func4> 自己调用自己,推测出递归。那么我们需要推测出递归函数。
在分析过程中,我们能发现对于func4(a1,a2,a3),a1在(%esp),a2在0x4(%esp), a3在0x8(%esp)。 回到主函数,我们发现$0x804a2ef赋值给了0x4(%esp),查询后发现显示“%d %d”,我们猜测答案是两个数字。
再通过cmpl $0xe,0x18(%esp),比较是否小于等于14,我们知道了输入的第一个数小于等于14。 movl $0xe,0x8(%esp)和movl $0x0,0x4(%esp)两句话,让我们知道了a2=0,a3=14。再根据主函数,写得:
再继续往下分析,cmp $0x6,%eax 判断返回值是否为6,cmpl $0x6,0x1c(%esp)判断输入的第二个参数是否为6,不是则爆炸,所以可以确定第二个参数值需要为6。通过程序一个一个实验:
我们得到结果,第一个数为6时,第二个数也为6。
如上图,验证正确。 五、第五关 08048d8b <phase_5>: 8048d8b: 53 push %ebx 8048d8c: 83 ec 28 sub $0x28,%esp 8048d8f: 8b 5c 24 30 mov 0x30(%esp),%ebx 8048d93: 65 a1 14 00 00 00 mov %gs:0x14,%eax 8048d99: 89 44 24 1c mov %eax,0x1c(%esp) 8048d9d: 31 c0 xor %eax,%eax 相当于把eax置0,这个比mov $0x0 %eax效率高。 8048d9f: 89 1c 24 mov %ebx,(%esp) 8048da2: e8 84 02 00 00 call 804902b <string_length> 8048da7: 83 f8 06 cmp $0x6,%eax 8048daa: 74 46 je 8048df2 <phase_5+0x67> 8048dac: e8 a4 03 00 00 call 8049155 <explode_bomb> 8048db1: eb 3f jmp 8048df2 <phase_5+0x67> 8048db3: 0f b6 14 03 movzbl (%ebx,%eax,1),%edx 8048db7: 83 e2 0f and $0xf,%edx 8048dba: 0f b6 92 a8 a1 04 08 movzbl 0x804a1a8(%edx),%edx 8048dc1: 88 54 04 15 mov %dl,0x15(%esp,%eax,1) 8048dc5: 83 c0 01 add $0x1,%eax 8048dc8: 83 f8 06 cmp $0x6,%eax 8048dcb: 75 e6 jne 8048db3 <phase_5+0x28> 8048dcd: c6 44 24 1b 00 movb $0x0,0x1b(%esp) 8048dd2: c7 44 24 04 80 a1 04 movl $0x804a180,0x4(%esp) 8048dd9: 08 8048dda: 8d 44 24 15 lea 0x15(%esp),%eax 8048dde: 89 04 24 mov %eax,(%esp) 8048de1: e8 64 02 00 00 call 804904a <strings_not_equal> 8048de6: 85 c0 test %eax,%eax 8048de8: 74 0f je 8048df9 <phase_5+0x6e> 8048dea: e8 66 03 00 00 call 8049155 <explode_bomb> 8048def: 90 nop 8048df0: eb 07 jmp 8048df9 <phase_5+0x6e> 8048df2: b8 00 00 00 00 mov $0x0,%eax 8048df7: eb ba jmp 8048db3 <phase_5+0x28> 8048df9: 8b 44 24 1c mov 0x1c(%esp),%eax 8048dfd: 65 33 05 14 00 00 00 xor %gs:0x14,%eax 这里的xor把eax置0了,因为最开始还有一句mov %gs:0x14,%eax 8048e04: 74 05 je 8048e0b <phase_5+0x80> 8048e06: e8 b5 f9 ff ff call 80487c0 <__stack_chk_fail@plt> 8048e0b: 83 c4 28 add $0x28,%esp 8048e0e: 5b pop %ebx 8048e0f: 90 nop 8048e10: c3 ret 首先,从call 804902b <string_length>和cmp $0x6,%eax可以看出我们需要输入一个长度为6的字符串。一直到第二个cmp $0x6,%eax,如果字符串长度不为6,会一直循环直到为6才继续。 接下来我们看到movzbl (%ebx,%eax,1),%edx和and $0xf,%edx,就是把输入的字符串中的每个字符依次拿出来,保留低四位,高四位任意。然后用基址变址寻址从0x804a1a8(%edx),%edx中找到相应的字符传到edx,在取低8位也就是正好一个char类型的字符传到0x15(%esp,%eax,1)这样的一个地址中去。然后循环对6个字符都进行这个操作。至此我们可能还看不出什么头绪,但是得到了一个字符串。往下继续看。
再接下来,我们看到了movl $0x804a180,0x4(%esp)有确定地址,习惯性看看里面写了什么,发现是一个长度为6的字符串。再后面有call 804904a <strings_not_equal>,我们可以得知这里就需要判断答案是否正确了。
结合这两个地址,说明前面那一大串字符串经过变换后会得到最后的“sabres”,因此我们需要倒推,看看我们最开始输入的是什么。 因为sabres是通过基址变址寻址得到的,因此通过这个字符串我们就能得到他们对应的偏移分别是7 1 13 6 5 7,而这些偏移正是通过我们输入的字符的ASCII码的低四位得到的,因此他们对应的二进制分别是0111 0001 1101 0110 0101 0111,然后从ASCII码表中找出对应的低位能对应上的字母即可。同样这一题的答案也是不唯一的。
对应得其中一个答案是gamfeg,验证正确。(不止一个答案,wqmvuw验证有多个答案)
六、第六关 08048e11 <phase_6>: 8048e11: 56 push %esi 8048e12: 53 push %ebx 8048e13: 83 ec 44 sub $0x44,%esp 8048e16: 8d 44 24 10 lea 0x10(%esp),%eax 8048e1a: 89 44 24 04 mov %eax,0x4(%esp) 8048e1e: 8b 44 24 50 mov 0x50(%esp),%eax 8048e22: 89 04 24 mov %eax,(%esp) 8048e25: e8 52 03 00 00 call 804917c <read_six_numbers> 8048e2a: be 00 00 00 00 mov $0x0,%esi
8048e2f: 8b 44 b4 10 mov 0x10(%esp,%esi,4),%eax 8048e33: 83 e8 01 sub $0x1,%eax 8048e36: 83 f8 05 cmp $0x5,%eax eax-1要<=5,否则爆炸 8048e39: 76 05 jbe 8048e40 <phase_6+0x2f> 8048e3b: e8 15 03 00 00 call 8049155 <explode_bomb> 8048e40: 83 c6 01 add $0x1,%esi 8048e43: 83 fe 06 cmp $0x6,%esi esi是计数器 8048e46: 75 07 jne 8048e4f <phase_6+0x3e> 8048e48: bb 00 00 00 00 mov $0x0,%ebx 8048e4d: eb 38 jmp 8048e87 <phase_6+0x76> 第一个大循环的出口 8048e4f: 89 f3 mov %esi,%ebx 8048e51: 8b 44 9c 10 mov 0x10(%esp,%ebx,4),%eax 数组中的下一个值,开始小循环 8048e55: 39 44 b4 0c cmp %eax,0xc(%esp,%esi,4) 8048e59: 75 05 jne 8048e60 <phase_6+0x4f> 8048e5b: e8 f5 02 00 00 call 8049155 <explode_bomb> 8048e60: 83 c3 01 add $0x1,%ebx 8048e63: 83 fb 05 cmp $0x5,%ebx小于等于就跳出循环 8048e66: 7e e9 jle 8048e51 <phase_6+0x40> 小循环出口 8048e68: eb c5 jmp 8048e2f <phase_6+0x1e> 跳回去了,是个循环 //第二个循环 8048e6a: 8b 52 08 mov 0x8(%edx),%edx 8048e6d: 83 c0 01 add $0x1,%eax 8048e70: 39 c8 cmp %ecx,%eax 8048e72: 75 f6 jne 8048e6a <phase_6+0x59> 8048e74: eb 05 jmp 8048e7b <phase_6+0x6a> 8048e76: ba 3c c1 04 08 mov $0x804c13c,%edx 这里存了一个地址值 8048e7b: 89 54 b4 28 mov %edx,0x28(%esp,%esi,4) 把地址传给了一个点 8048e7f: 83 c3 01 add $0x1,%ebx 8048e82: 83 fb 06 cmp $0x6,%ebx 8048e85: 74 17 je 8048e9e <phase_6+0x8d> 8048e87: 89 de mov %ebx,%esi 8048e89: 8b 4c 9c 10 mov 0x10(%esp,%ebx,4),%ecx 8048e8d: 83 f9 01 cmp $0x1,%ecx 8048e90: 7e e4 jle 8048e76 <phase_6+0x65> 小于等于1则跳转 8048e92: b8 01 00 00 00 mov $0x1,%eax 8048e97: ba 3c c1 04 08 mov $0x804c13c,%edx 8048e9c: eb cc jmp 8048e6a <phase_6+0x59> 8048e9e: 8b 5c 24 28 mov 0x28(%esp),%ebx 8048ea2: 8d 44 24 2c lea 0x2c(%esp),%eax 8048ea6: 8d 74 24 40 lea 0x40(%esp),%esi 8048eaa: 89 d9 mov %ebx,%ecx 8048eac: 8b 10 mov (%eax),%edx 8048eae: 89 51 08 mov %edx,0x8(%ecx) 8048eb1: 83 c0 04 add $0x4,%eax 8048eb4: 39 f0 cmp %esi,%eax 8048eb6: 74 04 je 8048ebc <phase_6+0xab> 8048eb8: 89 d1 mov %edx,%ecx 8048eba: eb f0 jmp 8048eac <phase_6+0x9b> 8048ebc: c7 42 08 00 00 00 00 movl $0x0,0x8(%edx) 8048ec3: be 05 00 00 00 mov $0x5,%esi 8048ec8: 8b 43 08 mov 0x8(%ebx),%eax 8048ecb: 8b 00 mov (%eax),%eax 8048ecd: 39 03 cmp %eax,(%ebx) ebx<=eax就不爆炸,从大到小排 8048ecf: 7e 05 jle 8048ed6 <phase_6+0xc5> 8048ed1: e8 7f 02 00 00 call 8049155 <explode_bomb> 8048ed6: 8b 5b 08 mov 0x8(%ebx),%ebx 8048ed9: 83 ee 01 sub $0x1,%esi 8048edc: 75 ea jne 8048ec8 <phase_6+0xb7> 8048ede: 83 c4 44 add $0x44,%esp 8048ee1: 5b pop %ebx 8048ee2: 5e pop %esi 8048ee3: c3 ret 通过call 804917c <read_six_numbers>这句话,猜测我们的答案是6个数字 接下来看到sub $0x1,%eax和cmp $0x5,%eax,得知我们输入的数字必须要小于等于6。 在开始小循环时,我们能发现,mov 0x10(%esp,%ebx,4),%eax和cmp %eax,0xc(%esp,%esi,4)把后面的数和x进行比较,不相等才不会引发爆炸。这一层循环比较x和它后面的所有数。 这两层循环就能确定输入的所有数都不相等。 接着我们能够看到mov $0x804c13c,%edx,遇到固定地址,习惯性查看一下,发现这里存储的是一个链表的地址数据。
由这些内容还可以看出应该输入的数是1到6这六个数。
再按升序排列:
那么得到答案:1 4 2 6 3 5
验证正确。 七、如何进入隐藏关卡 我们发现汇编代码中存在
080492c6 <phase_defused>: 80492c6: 81 ec 8c 00 00 00 sub $0x8c,%esp 80492cc: 65 a1 14 00 00 00 mov %gs:0x14,%eax 80492d2: 89 44 24 7c mov %eax,0x7c(%esp) 80492d6: 31 c0 xor %eax,%eax 80492d8: 83 3d c8 c3 04 08 06 cmpl $0x6,0x804c3c8 80492df: 75 72 jne 8049353 <phase_defused+0x8d> phase_defused在每一关成功后都被调用,我们发现cmpl $0x6,0x804c3c8这句话可能与通过前六关有关,没通过的话就不能进入隐藏关卡。 80492e1: 8d 44 24 2c lea 0x2c(%esp),%eax 80492e5: 89 44 24 10 mov %eax,0x10(%esp) 80492e9: 8d 44 24 28 lea 0x28(%esp),%eax 80492ed: 89 44 24 0c mov %eax,0xc(%esp) 80492f1: 8d 44 24 24 lea 0x24(%esp),%eax 80492f5: 89 44 24 08 mov %eax,0x8(%esp) 80492f9: c7 44 24 04 49 a3 04 movl $0x804a349,0x4(%esp) 8049300: 08 8049301: c7 04 24 d0 c4 04 08 movl $0x804c4d0,(%esp) 8049308: e8 53 f5 ff ff call 8048860 <__isoc99_sscanf@plt> 传入了$0x804a349和$0x804c4d0两个立即数地址进去,我们看看传了什么。
此时,我们推测后一个地址可能是输入时的地址。查看每一个关卡输入时的地址,发现第四关输入时的地址正好是0x804c4d0。 804930d: 83 f8 03 cmp $0x3,%eax 8049310: 75 35 jne 8049347 <phase_defused+0x81> 8049312: c7 44 24 04 52 a3 04 movl $0x804a352,0x4(%esp) 8049319: 08 804931a: 8d 44 24 2c lea 0x2c(%esp),%eax 804931e: 89 04 24 mov %eax,(%esp) 8049321: e8 24 fd ff ff call 804904a <strings_not_equal> 8049326: 85 c0 test %eax,%eax 8049328: 75 1d jne 8049347 <phase_defused+0x81> 804932a: c7 04 24 18 a2 04 08 movl $0x804a218,(%esp) 8049331: e8 ba f4 ff ff call 80487f0 <puts@plt> 8049336: c7 04 24 40 a2 04 08 movl $0x804a240,(%esp) 804933d: e8 ae f4 ff ff call 80487f0 <puts@plt> 8049342: e8 ee fb ff ff call 8048f35 <secret_phase> 8049347: c7 04 24 78 a2 04 08 movl $0x804a278,(%esp) 804934e: e8 9d f4 ff ff call 80487f0 <puts@plt> 8049353: 8b 44 24 7c mov 0x7c(%esp),%eax 8049357: 65 33 05 14 00 00 00 xor %gs:0x14,%eax 804935e: 74 05 je 8049365 <phase_defused+0x9f> 8049360: e8 5b f4 ff ff call 80487c0 <__stack_chk_fail@plt> 8049365: 81 c4 8c 00 00 00 add $0x8c,%esp 804936b: c3 ret 804936c: 66 90 xchg %ax,%ax 804936e: 66 90 xchg %ax,%ax 将<__isoc99_sscanf@plt>的返回值与3比较,猜测该返回值应该是参数的数量,等于3才可能解锁隐藏关。然后又传递了一个地址0x804a352,将该地址处的字符串与输入的字符串比较,相等才能解锁隐藏关。,我们看看0x804a352处是什么:得到该处的字符串是DrEvil。
所以在进行第四关时,输入两个数后再输入DrEvil,通过第六关之后即可进入secret_phase。
(第四关里面获取输入元素数量也使用了<__isoc99_sscanf@plt>,在那里给的参数是"%d %d",应该是只能接受两个输入,即使输入了三个,返回值也会是2,第四关的通关不受影响。) 八、隐藏关卡 08048f35 <secret_phase>: 8048f35: 53 push %ebx 8048f36: 83 ec 18 sub $0x18,%esp 8048f39: e8 8e 02 00 00 call 80491cc <read_line> 8048f3e: c7 44 24 08 0a 00 00 movl $0xa,0x8(%esp) 8048f45: 00 8048f46: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp) 8048f4d: 00 8048f4e: 89 04 24 mov %eax,(%esp) 8048f51: e8 7a f9 ff ff call 80488d0 <strtol@plt> 从call 80491cc <read_line>我们可以推测,答案是一行输入。然后输入的内容和$0xa、$0x0一起作为<strtol@plt>的参数。 <strtol@plt>的作用是把参数按照其base(即进制)转换成长整型数,由此可知应该输入的是一个数。 8048f56: 89 c3 mov %eax,%ebx 8048f58: 8d 40 ff lea -0x1(%eax),%eax 8048f5b: 3d e8 03 00 00 cmp $0x3e8,%eax 8048f60: 76 05 jbe 8048f67 <secret_phase+0x32> 8048f62: e8 ee 01 00 00 call 8049155 <explode_bomb> $0x3e8是一个确定的数字,换成十进制是1000。但是前面还有lea -0x1(%eax),%eax,即eax-1。则我们知道了输入的数字需要小于等于1001。 8048f67: 89 5c 24 04 mov %ebx,0x4(%esp) 8048f6b: c7 04 24 88 c0 04 08 movl $0x804c088,(%esp) 8048f72: e8 6d ff ff ff call 8048ee4 <fun7> 8048f77: 85 c0 test %eax,%eax fun7返回值要为0 8048f79: 74 05 je 8048f80 <secret_phase+0x4b> 8048f7b: e8 d5 01 00 00 call 8049155 <explode_bomb> 8048f80: c7 04 24 b8 a1 04 08 movl $0x804a1b8,(%esp) (剩下的汇编代码略) 在这里, $0x804c088(x)与输入的数%ebx(y)作为参数调用<fun7>。test %eax,%eax相当于cmp $0x0,%eax。我们知道了<fun7>的返回值一定得是0。接下来我们来看<fun7>。 08048ee4 <fun7>: 8048ee4: 53 push %ebx 8048ee5: 83 ec 18 sub $0x18,%esp 8048ee8: 8b 54 24 20 mov 0x20(%esp),%edx 8048eec: 8b 4c 24 24 mov 0x24(%esp),%ecx (1)判断x是否为0 8048ef0: 85 d2 test %edx,%edx 8048ef2: 74 37 je 8048f2b <fun7+0x47> (2)x不为0时的三种情况: 8048ef4: 8b 1a mov (%edx),%ebx 8048ef6: 39 cb cmp %ecx,%ebx 8048ef8: 7e 13 jle 8048f0d <fun7+0x29> 8048efa: 89 4c 24 04 mov %ecx,0x4(%esp) 8048efe: 8b 42 04 mov 0x4(%edx),%eax 8048f01: 89 04 24 mov %eax,(%esp) 8048f04: e8 db ff ff ff call 8048ee4 <fun7> 8048f09: 01 c0 add %eax,%eax 8048f0b: eb 23 jmp 8048f30 <fun7+0x4c> 8048f0d: b8 00 00 00 00 mov $0x0,%eax 8048f12: 39 cb cmp %ecx,%ebx 8048f14: 74 1a je 8048f30 <fun7+0x4c> 8048f16: 89 4c 24 04 mov %ecx,0x4(%esp) 8048f1a: 8b 42 08 mov 0x8(%edx),%eax 8048f1d: 89 04 24 mov %eax,(%esp) 8048f20: e8 bf ff ff ff call 8048ee4 <fun7> 8048f25: 8d 44 00 01 lea 0x1(%eax,%eax,1),%eax (3)结尾部分 8048f29: eb 05 jmp 8048f30 <fun7+0x4c> 8048f2b: b8 ff ff ff ff mov $0xffffffff,%eax 8048f30: 83 c4 18 add $0x18,%esp 8048f33: 5b pop %ebx 8048f34: c3 ret 通过分析fun7,我们可以得到: (1)若x为0,函数返回$0xffffffff (2)若x不为0: 1、当x指向的值大于y,将地址x+4,和y一起作为参数调用fun7,返回其返回值的2倍。 2、当x指向的值等于y,返回0。 3、当x指向的值小于y,将地址x+8,和y一起作为参数调用fun7,返回其返回值的2倍加1。 而我们需要的返回值是0,即x指向的值等于y。我们来看看x($0x804c088)指向的值:
0x24转换为十进制,为36。
验证一下:正确。 九、至此,所有炸弹都拆除了。
总结一下各个关卡: phase_1:字符串比较 phase_2:循环 phase_3:switch phase_4:递归 phase_5:指针 phase_6:链表/指针/结构 secret_phase:隐藏关、条件判断(如果要求的是其他非0数字的话就需要二叉树遍历) 十、收获 一些自认为有必要记录的tips: 1、ja和jg同是大于时跳转,它们的差别:
2、mov和lea区别:mov传地址,lea传值。mov也能传值。
3、在第五关中有这样一句话:mov %gs:0x14,%eax。 gs是段寄存器,这句话意味着从地址 gs:0x14 的内存中将 4 个字节读入 eax。 4、关于movzbl指令
在函数中“unsigned int a = c”语句完成的是一个从unsigned char到unsigned int的赋值操作,由于int的类型长度大于char类型长度,所以实际是将一个字节的内容拷贝到一个可以容纳4个字节的地方,这样的话需要对源数据进行一下扩展,即填充高位的3个字节。 如何填充呢?由于变量a和c都为无符号整型,所以只需要填充0即可。而movzbl就是干这个活的。movzbl指令负责拷贝一个字节,并用0填充其目的操作数中的其余各位,这种扩展方式叫“零扩展”。 5、关于movsbl指令 与movzbl类似,只是它用符号位填充。 6、test %eax %eax,也相当于 cmp $0x0,%eax test指令操作是目的操作数和源操作数按位逻辑“与”操作。 十一、心得体会: 最开始做第一关的时候,就像无头苍蝇一样,不知道哪些语句通常是比较关键的,弯弯绕绕了不少时间。后来在看思路分享的PDF文件时逐渐明白了这个实验的正确打开方式。 第二关是循环类型的,比较简单。 第三关其实也不难,只要跟着汇编代码一步一步走就好。但是我前面算错了好几次,所以也卡了不少时间。后来换了一天去算就一次过了。(第一个数不同,第二个数也随之不同,多个答案) 第四关比较特殊,它有<fun_4>这个递归函数还需要分析。因为手算太难了,所以采用转换成类C代码,用数字一个一个尝试的方式把答案测出来了。 第五关的难点在于对ASCII码不熟悉,没想到还有高四位低四位这种。后来通过转换表可以得到答案。(答案不唯一) 第六关也卡了好一会儿,后来半想半猜写出来的。 隐藏关卡是听别人说有,所以多留意了一下,就发现了<secret_phase>。这里实际上相当于做了两道题,一是要找出进入隐藏关卡的入口以及条件,二是破解隐藏关卡的密码。 总而言之,这次炸弹实验让我对汇编语言的阅读更加熟练了,也对于汇编语言中哪一些是传参、哪一些是调用函数有了一个习惯上的认知。 |
CSAPP LAB2 bomb8
最新推荐文章于 2025-02-12 18:02:55 发布