《深入理解计算机系统》Lab3 Bomblab
准备工作
在终端中输入objdump -d bomb > bomb.asm,将bomb反汇编并保存到文件bomb.asm中,相较于在gdb中输入disassemble命令更便于阅读。
080489e4 <main>:80489e4: 55 push %ebp80489e5: 89 e5 mov %esp,%ebp80489e7: 53 push %ebx80489e8: 83 e4 f0 and $0xfffffff0,%esp80489eb: 83 ec 10 sub $0x10,%esp80489ee: 8b 45 08 mov 0x8(%ebp),%eax80489f1: 8b 5d 0c mov 0xc(%ebp),%ebx80489f4: 83 f8 01 cmp $0x1,%eax80489f7: 75 0c jne 8048a05 <main+0x21>80489f9: a1 a4 c3 04 08 mov 0x804c3a4,%eax80489fe: a3 d0 c3 04 08 mov %eax,0x804c3d08048a03: eb 74 jmp 8048a79 <main+0x95>8048a05: 83 f8 02 cmp $0x2,%eax8048a08: 75 49 jne 8048a53 <main+0x6f>8048a0a: c7 44 24 04 68 a0 04 movl $0x804a068,0x4(%esp)8048a11: 08 8048a12: 8b 43 04 mov 0x4(%ebx),%eax8048a15: 89 04 24 mov %eax,(%esp)8048a18: e8 63 fe ff ff call 8048880 <fopen@plt>8048a1d: a3 d0 c3 04 08 mov %eax,0x804c3d08048a22: 85 c0 test %eax,%eax8048a24: 75 53 jne 8048a79 <main+0x95>8048a26: 8b 43 04 mov 0x4(%ebx),%eax8048a29: 89 44 24 0c mov %eax,0xc(%esp)8048a2d: 8b 03 mov (%ebx),%eax8048a2f: 89 44 24 08 mov %eax,0x8(%esp)8048a33: c7 44 24 04 6a a0 04 movl $0x804a06a,0x4(%esp)8048a3a: 08 8048a3b: c7 04 24 01 00 00 00 movl $0x1,(%esp)8048a42: e8 59 fe ff ff call 80488a0 <__printf_chk@plt>8048a47: c7 04 24 08 00 00 00 movl $0x8,(%esp)8048a4e: e8 ed fd ff ff call 8048840 <exit@plt>8048a53: 8b 03 mov (%ebx),%eax8048a55: 89 44 24 08 mov %eax,0x8(%esp)8048a59: c7 44 24 04 87 a0 04 movl $0x804a087,0x4(%esp)8048a60: 08 8048a61: c7 04 24 01 00 00 00 movl $0x1,(%esp)8048a68: e8 33 fe ff ff call 80488a0 <__printf_chk@plt>8048a6d: c7 04 24 08 00 00 00 movl $0x8,(%esp)8048a74: e8 c7 fd ff ff call 8048840 <exit@plt>8048a79: e8 bd 05 00 00 call 804903b <initialize_bomb>8048a7e: c7 04 24 ec a0 04 08 movl $0x804a0ec,(%esp)8048a85: e8 76 fd ff ff call 8048800 <puts@plt>8048a8a: c7 04 24 28 a1 04 08 movl $0x804a128,(%esp)8048a91: e8 6a fd ff ff call 8048800 <puts@plt>8048a96: e8 62 06 00 00 call 80490fd <read_line>8048a9b: 89 04 24 mov %eax,(%esp)8048a9e: e8 ad 00 00 00 call 8048b50 <phase_1>8048aa3: e8 b3 07 00 00 call 804925b <phase_defused>8048aa8: c7 04 24 54 a1 04 08 movl $0x804a154,(%esp)8048aaf: e8 4c fd ff ff call 8048800 <puts@plt>8048ab4: e8 44 06 00 00 call 80490fd <read_line>8048ab9: 89 04 24 mov %eax,(%esp)8048abc: e8 b3 00 00 00 call 8048b74 <phase_2>8048ac1: e8 95 07 00 00 call 804925b <phase_defused>8048ac6: c7 04 24 a1 a0 04 08 movl $0x804a0a1,(%esp)8048acd: e8 2e fd ff ff call 8048800 <puts@plt>8048ad2: e8 26 06 00 00 call 80490fd <read_line>8048ad7: 89 04 24 mov %eax,(%esp)8048ada: e8 e5 00 00 00 call 8048bc4 <phase_3>8048adf: e8 77 07 00 00 call 804925b <phase_defused>8048ae4: c7 04 24 bf a0 04 08 movl $0x804a0bf,(%esp)8048aeb: e8 10 fd ff ff call 8048800 <puts@plt>8048af0: e8 08 06 00 00 call 80490fd <read_line>8048af5: 89 04 24 mov %eax,(%esp)8048af8: e8 e2 01 00 00 call 8048cdf <phase_4>8048afd: e8 59 07 00 00 call 804925b <phase_defused>8048b02: c7 04 24 80 a1 04 08 movl $0x804a180,(%esp)8048b09: e8 f2 fc ff ff call 8048800 <puts@plt>8048b0e: e8 ea 05 00 00 call 80490fd <read_line>8048b13: 89 04 24 mov %eax,(%esp)8048b16: e8 26 02 00 00 call 8048d41 <phase_5>8048b1b: e8 3b 07 00 00 call 804925b <phase_defused>8048b20: c7 04 24 ce a0 04 08 movl $0x804a0ce,(%esp)8048b27: e8 d4 fc ff ff call 8048800 <puts@plt>8048b2c: e8 cc 05 00 00 call 80490fd <read_line>8048b31: 89 04 24 mov %eax,(%esp)8048b34: e8 51 02 00 00 call 8048d8a <phase_6>8048b39: e8 1d 07 00 00 call 804925b <phase_defused>8048b3e: b8 00 00 00 00 mov $0x0,%eax8048b43: 8b 5d fc mov -0x4(%ebp),%ebx8048b46: c9 leave 8048b47: c3 ret 8048b48: 90 nop8048b49: 90 nop8048b4a: 90 nop8048b4b: 90 nop8048b4c: 90 nop8048b4d: 90 nop8048b4e: 90 nop8048b4f: 90 nop
首先分析main函数,观察到main函数中,read_line函数的意义是读取一行字符串。phase_1~phase_6,read_line之后都会将返回值%eax传给(%esp),也就是read_line读取的字符串的首地址作为接下来执行phase_n的第一个参数。
Phase_1
密钥:I can see Russia from my house!
08048b50 <phase_1>:8048b50: 83 ec 1c sub $0x1c,%esp8048b53: c7 44 24 04 a4 a1 04 movl $0x804a1a4,0x4(%esp)8048b5a: 08 8048b5b: 8b 44 24 20 mov 0x20(%esp),%eax8048b5f: 89 04 24 mov %eax,(%esp)8048b62: e8 5d 04 00 00 call 8048fc4 <strings_not_equal>8048b67: 85 c0 test %eax,%eax8048b69: 74 05 je 8048b70 <phase_1+0x20>8048b6b: e8 66 05 00 00 call 80490d6 <explode_bomb>8048b70: 83 c4 1c add $0x1c,%esp8048b73: c3 ret
分析
阅读phase_1的前5行代码,观察到数据$0x804a1a4和0x20(%esp)对应的read_line读入的字符串首地址作为参数被传入栈帧中,结合第6行的strings_not_equal函数,可以推测,$0x804a1a4处存储的也是一个字符串的首地址,并且将要与此前读入的字符串进行比较。阅读strings_not_equal的汇编代码,得知该函数比较两个字符串是否相等,若相等则返回0,保存在%eax中。再看第7、8、9行,可知若%eax为0,则跳过第9行至<phase_1+0x20>处,否则执行第9行。第9行的指令进入explode_bomb函数,执行此行命令将会引爆炸弹,所以必须令程序在第8行时进行跳转。结合以上信息,得知,我们必须令输入的字符串与$0x804a1a4处的字符串相等,才能通过phase_1。
通过gdb中的print命令,打印出$0x804a1a4处的字符串,出现了如图所示的字符串“I can see Russia from my house!”,这便是phase_1的通关密钥。运行bomb,输入该字符串,成功通过phase_1。
Phase_2
密钥:0 1 1 2 3 5
08048b74 <phase_2>:8048b74: 56 push %esi8048b75: 53 push %ebx8048b76: 83 ec 34 sub $0x34,%esp8048b79: 8d 44 24 18 lea 0x18(%esp),%eax8048b7d: 89 44 24 04 mov %eax,0x4(%esp)8048b81: 8b 44 24 40 mov 0x40(%esp),%eax8048b85: 89 04 24 mov %eax,(%esp)8048b88: e8 7e 06 00 00 call 804920b <read_six_numbers>8048b8d: 83 7c 24 18 00 cmpl $0x0,0x18(%esp)8048b92: 75 07 jne 8048b9b <phase_2+0x27>8048b94: 83 7c 24 1c 01 cmpl $0x1,0x1c(%esp)8048b99: 74 05 je 8048ba0 <phase_2+0x2c>8048b9b: e8 36 05 00 00 call 80490d6 <explode_bomb>8048ba0: 8d 5c 24 20 lea 0x20(%esp),%ebx8048ba4: 8d 74 24 30 lea 0x30(%esp),%esi8048ba8: 8b 43 f8 mov -0x8(%ebx),%eax8048bab: 03 43 fc add -0x4(%ebx),%eax8048bae: 39 03 cmp %eax,(%ebx)8048bb0: 74 05 je 8048bb7 <phase_2+0x43>8048bb2: e8 1f 05 00 00 call 80490d6 <explode_bomb>8048bb7: 83 c3 04 add $0x4,%ebx8048bba: 39 f3 cmp %esi,%ebx8048bbc: 75 ea jne 8048ba8 <phase_2+0x34>8048bbe: 83 c4 34 add $0x34,%esp8048bc1: 5b pop %ebx8048bc2: 5e pop %esi8048bc3: c3 ret
分析
观察到phase_2调用了read_six_numbers这个函数,根据phase_1的经验,phase_n函数内调用的函数很可能就是突破口。第4~7行配置read_six_numbers的参数,其中根据main函数可知0x40(%esp)中的内容为输入的字符串的首地址,结合阅读read_six_numbers函数的代码,得知该函数读取6个整型数字,并将读取的数字从左到右依次保存至0x18(%esp)~0x2c(%esp)中。若读取的数字不足6个,将会引爆炸弹。然而,并非随意输入6个数字即可。通过第9~13行得知,0x18(%esp)中的内容,即第一个数字必须等于0,而0x1c(%esp)中的内容,即第二个数字必须等于1,否则都将进入explode_bomb函数导致炸弹引爆。而15~24行是一个循环结构,翻译成伪代码为
pos = %edx; end_pos = %esi;
a[] = 读取的数字
while(pos != end_pos)
{if(a[pos] + a[pos+1] != a[pos+2])explode_bomb();pos++;
}
于是,可以得知,要输入的6个数字,就是斐波那契数列的前六个数字:0 1 1 2 3 5。
Phase_3
密钥:0 392 或 1 -213 或 2 -47 或 3 -878 或 4 0 或5 -878
08048bc4 <phase_3>:8048bc4: 83 ec 2c sub $0x2c,%esp8048bc7: 8d 44 24 1c lea 0x1c(%esp),%eax8048bcb: 89 44 24 0c mov %eax,0xc(%esp)8048bcf: 8d 44 24 18 lea 0x18(%esp),%eax8048bd3: 89 44 24 08 mov %eax,0x8(%esp)8048bd7: c7 44 24 04 c3 a3 04 movl $0x804a3c3,0x4(%esp)8048bde: 08 8048bdf: 8b 44 24 30 mov 0x30(%esp),%eax8048be3: 89 04 24 mov %eax,(%esp)8048be6: e8 85 fc ff ff call 8048870 <__isoc99_sscanf@plt>8048beb: 83 f8 01 cmp $0x1,%eax8048bee: 7f 05 jg 8048bf5 <phase_3+0x31>8048bf0: e8 e1 04 00 00 call 80490d6 <explode_bomb>8048bf5: 83 7c 24 18 07 cmpl $0x7,0x18(%esp)8048bfa: 77 66 ja 8048c62 <phase_3+0x9e>8048bfc: 8b 44 24 18 mov 0x18(%esp),%eax8048c00: ff 24 85 00 a2 04 08 jmp *0x804a200(,%eax,4)8048c07: b8 00 00 00 00 mov $0x0,%eax8048c0c: eb 05 jmp 8048c13 <phase_3+0x4f>8048c0e: b8 5d 02 00 00 mov $0x25d,%eax8048c13: 2d a6 00 00 00 sub $0xa6,%eax8048c18: eb 05 jmp 8048c1f <phase_3+0x5b>8048c1a: b8 00 00 00 00 mov $0x0,%eax8048c1f: 05 3f 03 00 00 add $0x33f,%eax8048c24: eb 05 jmp 8048c2b <phase_3+0x67>8048c26: b8 00 00 00 00 mov $0x0,%eax8048c2b: 2d 6e 03 00 00 sub $0x36e,%eax8048c30: eb 05 jmp 8048c37 <phase_3+0x73>8048c32: b8 00 00 00 00 mov $0x0,%eax8048c37: 05 6e 03 00 00 add $0x36e,%eax8048c3c: eb 05 jmp 8048c43 <phase_3+0x7f>8048c3e: b8 00 00 00 00 mov $0x0,%eax8048c43: 2d 6e 03 00 00 sub $0x36e,%eax8048c48: eb 05 jmp 8048c4f <phase_3+0x8b>8048c4a: b8 00 00 00 00 mov $0x0,%eax8048c4f: 05 6e 03 00 00 add $0x36e,%eax8048c54: eb 05 jmp 8048c5b <phase_3+0x97>8048c56: b8 00 00 00 00 mov $0x0,%eax8048c5b: 2d 6e 03 00 00 sub $0x36e,%eax8048c60: eb 0a jmp 8048c6c <phase_3+0xa8>8048c62: e8 6f 04 00 00 call 80490d6 <explode_bomb>8048c67: b8 00 00 00 00 mov $0x0,%eax8048c6c: 83 7c 24 18 05 cmpl $0x5,0x18(%esp)8048c71: 7f 06 jg 8048c79 <phase_3+0xb5>8048c73: 3b 44 24 1c cmp 0x1c(%esp),%eax8048c77: 74 05 je 8048c7e <phase_3+0xba>8048c79: e8 58 04 00 00 call 80490d6 <explode_bomb>8048c7e: 83 c4 2c add $0x2c,%esp8048c81: c3 ret
分析
第2~9行是为scanf函数配置参数以及执行scanf函数的过程,用print查看$0x804a3c3中的内容,发现是“%d %d”,说明scanf读取两个整型数,即密钥由两个整型数构成。第13~14行,0x18(%esp)中是读取的第一个数,ja 指令意味着无符号数的大小比较,即第一个数的取值范围在[0, 7]中。第15~16行是变址寻址的地址跳转,%eax中是我们输入的第一个数,跳转的地址是*(0x804a200+(%eax)*4)。这里以输入的第一个数是0为例,跳转的地址为*0x804a200,查看该地址中的内容,发现是0x8048c0e,于是跳转至该处的指令上。指令不停执行,直到倒数第6~7行,我们发现,该处指令对第一个数的范围做了新的限制,即将第一个数的范围缩小到[0, 5]。倒数第5行,将%eax与输入的第二个数进行比较,若不相等则引爆炸弹。%eax是根据此前的操作得来的,这时利用print命令查看%eax的内容,发现为392,即密钥为0 392。但由于第一个数可取0,1,2,3,4,5,故依次测试其余5个数作为第一个数,可得到不同第一个数对应的第二个数。所有可能的结果为0 392 或 1 -213 或 2 -47 或 3 -878 或 4 0 或5 -878。
Phase_4
密钥:40 2 或 60 3 或 80 4
08048c82 <func4>:8048c82: 83 ec 1c sub $0x1c,%esp8048c85: 89 5c 24 10 mov %ebx,0x10(%esp)8048c89: 89 74 24 14 mov %esi,0x14(%esp)8048c8d: 89 7c 24 18 mov %edi,0x18(%esp)8048c91: 8b 74 24 20 mov 0x20(%esp),%esi8048c95: 8b 5c 24 24 mov 0x24(%esp),%ebx8048c99: 85 f6 test %esi,%esi8048c9b: 7e 2b jle 8048cc8 <func4+0x46>8048c9d: 83 fe 01 cmp $0x1,%esi8048ca0: 74 2b je 8048ccd <func4+0x4b>8048ca2: 89 5c 24 04 mov %ebx,0x4(%esp)8048ca6: 8d 46 ff lea -0x1(%esi),%eax8048ca9: 89 04 24 mov %eax,(%esp)8048cac: e8 d1 ff ff ff call 8048c82 <func4>8048cb1: 8d 3c 18 lea (%eax,%ebx,1),%edi8048cb4: 89 5c 24 04 mov %ebx,0x4(%esp)8048cb8: 83 ee 02 sub $0x2,%esi8048cbb: 89 34 24 mov %esi,(%esp)8048cbe: e8 bf ff ff ff call 8048c82 <func4>8048cc3: 8d 1c 07 lea (%edi,%eax,1),%ebx8048cc6: eb 05 jmp 8048ccd <func4+0x4b>8048cc8: bb 00 00 00 00 mov $0x0,%ebx8048ccd: 89 d8 mov %ebx,%eax8048ccf: 8b 5c 24 10 mov 0x10(%esp),%ebx8048cd3: 8b 74 24 14 mov 0x14(%esp),%esi8048cd7: 8b 7c 24 18 mov 0x18(%esp),%edi8048cdb: 83 c4 1c add $0x1c,%esp8048cde: c3 ret 08048cdf <phase_4>:8048cdf: 83 ec 2c sub $0x2c,%esp8048ce2: 8d 44 24 18 lea 0x18(%esp),%eax8048ce6: 89 44 24 0c mov %eax,0xc(%esp)8048cea: 8d 44 24 1c lea 0x1c(%esp),%eax8048cee: 89 44 24 08 mov %eax,0x8(%esp)8048cf2: c7 44 24 04 c3 a3 04 movl $0x804a3c3,0x4(%esp)8048cf9: 08 8048cfa: 8b 44 24 30 mov 0x30(%esp),%eax8048cfe: 89 04 24 mov %eax,(%esp)8048d01: e8 6a fb ff ff call 8048870 <__isoc99_sscanf@plt>8048d06: 83 f8 02 cmp $0x2,%eax8048d09: 75 0e jne 8048d19 <phase_4+0x3a>8048d0b: 8b 44 24 18 mov 0x18(%esp),%eax8048d0f: 83 f8 01 cmp $0x1,%eax8048d12: 7e 05 jle 8048d19 <phase_4+0x3a>8048d14: 83 f8 04 cmp $0x4,%eax8048d17: 7e 05 jle 8048d1e <phase_4+0x3f>8048d19: e8 b8 03 00 00 call 80490d6 <explode_bomb>8048d1e: 8b 44 24 18 mov 0x18(%esp),%eax8048d22: 89 44 24 04 mov %eax,0x4(%esp)8048d26: c7 04 24 06 00 00 00 movl $0x6,(%esp)8048d2d: e8 50 ff ff ff call 8048c82 <func4>8048d32: 3b 44 24 1c cmp 0x1c(%esp),%eax8048d36: 74 05 je 8048d3d <phase_4+0x5e>8048d38: e8 99 03 00 00 call 80490d6 <explode_bomb>8048d3d: 83 c4 2c add $0x2c,%esp8048d40: c3 ret
分析
第2~9行是为scanf函数配置参数以及执行scanf函数的过程,用print查看$0x804a3c3中的内容,发现是“%d %d”,说明scanf读取两个整型数,即密钥由两个整型数构成,这也可以通过第10~11行,scanf的返回值不为2时则引爆炸弹看出。第12~16行,0x18(%esp)中的内容是输入的第二个数(与phase_3不同是因为写入的位置调换了),可以看出第二个数的取值范围为(1, 4]。第18~20行,为func4函数配置参数。接下来我们进入func4函数,func4是一个递归函数,为了便于解释,在此将func4函数翻译成c语言。func4函数翻译成c语言为
int func4(int k, int n)
{if(k <= 0) return 0;if(k == 1) return n;return func4(k-1, n) + func4(k-2, n) + n;
}
由于第二个数的取值范围为(1, 4],故第二个数可取2,3,4,这里以2为例。代码中传入的参数为k = 6, n = 2(即以2为例的第二个数),经过计算得到func4(6, 2)的结果为40。接着观察到第21~22行,需要比较func4的结果是否与0x1c(%esp),即输入的第一个数相等,若不相等则引爆炸弹。于是,便可得知第一个数即是func(6, 第二个数)的值,当第二个数为2时,第一个数为40,故密钥为40 2。当第二个数取3/4时,还可以生成密钥60 3和80 4。
Phase_5
密钥:`adejn 或 abdejl 或 abejmn 或 adefin 或 adefjm 或 adekln 或 adijln 或 aefjkn 或 aeflmn 或 aegjln 或 bdefln 或 beijln 或 cdejln 或 defgjn 或 defijl 或 dehjmn 或 deijkn 或 deilmn 或 dfjlmn 或 efhjln 或 efijmn 或 ejklmn 或 更多
08048d41 <phase_5>:8048d41: 53 push %ebx8048d42: 83 ec 18 sub $0x18,%esp8048d45: 8b 5c 24 20 mov 0x20(%esp),%ebx8048d49: 89 1c 24 mov %ebx,(%esp)8048d4c: e8 5a 02 00 00 call 8048fab <string_length>8048d51: 83 f8 06 cmp $0x6,%eax8048d54: 74 05 je 8048d5b <phase_5+0x1a>8048d56: e8 7b 03 00 00 call 80490d6 <explode_bomb>8048d5b: ba 00 00 00 00 mov $0x0,%edx8048d60: b8 00 00 00 00 mov $0x0,%eax8048d65: 0f be 0c 03 movsbl (%ebx,%eax,1),%ecx8048d69: 83 e1 0f and $0xf,%ecx8048d6c: 03 14 8d 20 a2 04 08 add 0x804a220(,%ecx,4),%edx8048d73: 83 c0 01 add $0x1,%eax8048d76: 83 f8 06 cmp $0x6,%eax8048d79: 75 ea jne 8048d65 <phase_5+0x24>8048d7b: 83 fa 45 cmp $0x45,%edx8048d7e: 74 05 je 8048d85 <phase_5+0x44>8048d80: e8 51 03 00 00 call 80490d6 <explode_bomb>8048d85: 83 c4 18 add $0x18,%esp8048d88: 5b pop %ebx8048d89: c3 ret
分析
由第3~6行可知,程序将输入的字符串作为参数执行了string_length函数,若输入的字符串长度不为6,则引爆炸弹。接下来是一个循环结构,遍历输入的字符串,将当前遍历的字节与0xf相与得到一个数,这个数将作为首地址在0x804a220处的整型数组的下标,并将访问到的整型数组中的数进行累加,若和为0x45,则该字符串即是密钥。为了便于解释,将汇编代码翻译成c代码如下:
char s[6] = 输入的字符串
int a[16] = 首地址在0x804a220处的数组,由于和0xf相与,故下标最大值为15,数组长度为16
int sum = 0; //累加和
for(int i = 0; i < 6; ++i)
{int pos = (int)s[i] & 0xf;sum += a[pos];
}
if(sum != 0x45)explode_bomb();
于是,我们要找到密钥,就要先找到总和为0x45的6个数的下标。为了方便找到这样的6个数的集合,可以编写程序,得到这16个数中,共有22组这样的6个数的集合。这22组数在数组中的下标如下:
0 1 4 5 10 14
1 2 4 5 10 12
1 2 5 10 13 14
1 4 5 6 9 14
1 4 5 6 10 13
1 4 5 11 12 14
1 4 9 10 12 14
1 5 6 10 11 14
1 5 6 12 13 14
1 5 7 10 12 14
2 4 5 6 12 14
2 5 9 10 12 14
3 4 5 10 12 14
4 5 6 7 10 14
4 5 6 9 10 12
4 5 8 10 13 14
4 5 9 10 11 14
4 5 9 12 13 14
4 6 10 12 13 14
5 6 8 10 12 14
5 6 9 10 13 14
5 10 11 12 13 14
那么,现在的问题变成了如何构造满足条件的字符串了。因为下标的范围为[0, 15],ASCII码为0~15的字符都是控制字符,是不可打印的字符,所以不能够直接输入[0, 15]对应的字符组成的字符串。考虑到输出英文字母比较直观,且字母’a’和’A’的ASCII码分别为1100001和1000001,后4位是0001,故可以将下标加上’a’-1或’A’-1,将得到的字符组成字符串即为密钥。如下标为5时,其二进制为0101,加上’a’-1为1100101,和0xf(1111)相与后仍为0101。
通过这种方法,更换下标的加数或调换下标的顺序,可以产生大量的密钥。
Phase_6
密钥:5 3 2 4 6 1
08048d8a <phase_6>:8048d8a: 56 push %esi8048d8b: 53 push %ebx8048d8c: 83 ec 44 sub $0x44,%esp8048d8f: 8d 44 24 10 lea 0x10(%esp),%eax8048d93: 89 44 24 04 mov %eax,0x4(%esp)8048d97: 8b 44 24 50 mov 0x50(%esp),%eax8048d9b: 89 04 24 mov %eax,(%esp)8048d9e: e8 68 04 00 00 call 804920b <read_six_numbers>8048da3: be 00 00 00 00 mov $0x0,%esi8048da8: 8b 44 b4 10 mov 0x10(%esp,%esi,4),%eax8048dac: 83 e8 01 sub $0x1,%eax8048daf: 83 f8 05 cmp $0x5,%eax8048db2: 76 05 jbe 8048db9 <phase_6+0x2f>8048db4: e8 1d 03 00 00 call 80490d6 <explode_bomb>8048db9: 83 c6 01 add $0x1,%esi8048dbc: 83 fe 06 cmp $0x6,%esi8048dbf: 74 33 je 8048df4 <phase_6+0x6a>8048dc1: 89 f3 mov %esi,%ebx8048dc3: 8b 44 9c 10 mov 0x10(%esp,%ebx,4),%eax8048dc7: 39 44 b4 0c cmp %eax,0xc(%esp,%esi,4)8048dcb: 75 05 jne 8048dd2 <phase_6+0x48>8048dcd: e8 04 03 00 00 call 80490d6 <explode_bomb>8048dd2: 83 c3 01 add $0x1,%ebx8048dd5: 83 fb 05 cmp $0x5,%ebx8048dd8: 7e e9 jle 8048dc3 <phase_6+0x39>8048dda: eb cc jmp 8048da8 <phase_6+0x1e>8048ddc: 8b 52 08 mov 0x8(%edx),%edx8048ddf: 83 c0 01 add $0x1,%eax8048de2: 39 c8 cmp %ecx,%eax8048de4: 75 f6 jne 8048ddc <phase_6+0x52>8048de6: 89 54 b4 28 mov %edx,0x28(%esp,%esi,4)8048dea: 83 c3 01 add $0x1,%ebx8048ded: 83 fb 06 cmp $0x6,%ebx8048df0: 75 07 jne 8048df9 <phase_6+0x6f>8048df2: eb 1c jmp 8048e10 <phase_6+0x86>8048df4: bb 00 00 00 00 mov $0x0,%ebx8048df9: 89 de mov %ebx,%esi8048dfb: 8b 4c 9c 10 mov 0x10(%esp,%ebx,4),%ecx8048dff: b8 01 00 00 00 mov $0x1,%eax8048e04: ba 3c c1 04 08 mov $0x804c13c,%edx8048e09: 83 f9 01 cmp $0x1,%ecx8048e0c: 7f ce jg 8048ddc <phase_6+0x52>8048e0e: eb d6 jmp 8048de6 <phase_6+0x5c>8048e10: 8b 5c 24 28 mov 0x28(%esp),%ebx8048e14: 8b 44 24 2c mov 0x2c(%esp),%eax8048e18: 89 43 08 mov %eax,0x8(%ebx)8048e1b: 8b 54 24 30 mov 0x30(%esp),%edx8048e1f: 89 50 08 mov %edx,0x8(%eax)8048e22: 8b 44 24 34 mov 0x34(%esp),%eax8048e26: 89 42 08 mov %eax,0x8(%edx)8048e29: 8b 54 24 38 mov 0x38(%esp),%edx8048e2d: 89 50 08 mov %edx,0x8(%eax)8048e30: 8b 44 24 3c mov 0x3c(%esp),%eax8048e34: 89 42 08 mov %eax,0x8(%edx)8048e37: c7 40 08 00 00 00 00 movl $0x0,0x8(%eax)8048e3e: be 05 00 00 00 mov $0x5,%esi8048e43: 8b 43 08 mov 0x8(%ebx),%eax8048e46: 8b 10 mov (%eax),%edx8048e48: 39 13 cmp %edx,(%ebx)8048e4a: 7d 05 jge 8048e51 <phase_6+0xc7>8048e4c: e8 85 02 00 00 call 80490d6 <explode_bomb>8048e51: 8b 5b 08 mov 0x8(%ebx),%ebx8048e54: 83 ee 01 sub $0x1,%esi8048e57: 75 ea jne 8048e43 <phase_6+0xb9>8048e59: 83 c4 44 add $0x44,%esp8048e5c: 5b pop %ebx8048e5d: 5e pop %esi8048e5e: c3 ret
分析
第2~5行读取6个整型数,由6~11行可知,读入的6个数的范围都必须在区间[1, 6]中。第12~22行的作用是判断读入的6个数是否两两互不相同,若否则会引爆炸弹。接下来的部分是将存储在0x804c13c处的内容存储到栈帧中,其中0x804c13c处存储的数据类型是node,node是一个由两个int(一个记录数值,一个记录node编号)、一个node*构成的结构体(实际上,node是一个链表),共占12个字节。存储node的方式是:按照读入的6个数(假定为a[i]),将编号为a[i]的node存储到第i个位置。然后从栈帧中的存储的第一个node开始,每个node与其后一个node中的数值进行比较,若后大于前,则会引爆炸弹。
综合该函数的行为,可以发现,函数要求将6个node在栈帧中根据数值从大到小排列,而输入的6个数,即是从大到小排列的node的编号。
以下是6个node的信息:
(gdb) x 0x804c13c
0x804c13c <node1>: 0x00000036
0x804c140 <node1+4>: 0x00000001
0x804c144 <node1+8>: 0x0804c148
0x804c148 <node2>: 0x000000c1
0x804c14c <node2+4>: 0x00000002
0x804c150 <node2+8>: 0x0804c154
0x804c154 <node3>: 0x00000273
0x804c158 <node3+4>: 0x00000003
0x804c15c <node3+8>: 0x0804c160
0x804c160 <node4>: 0x00000089
0x804c164 <node4+4>: 0x00000004
0x804c168 <node4+8>: 0x0804c16c
0x804c16c <node5>: 0x000003d7
0x804c170 <node5+4>: 0x00000005
0x804c174 <node5+8>: 0x0804c178
0x804c178 <node6>: 0x0000006c
0x804c17c <node6+4>: 0x00000006
0x804c180 <node6+8>: 0x00000000
可见,6个node根据数值从大到小排列的编号为5 3 2 4 6 1,即为密钥。
Secret_phase
密钥:107
0804925b <phase_defused>:804925b: 81 ec 8c 00 00 00 sub $0x8c,%esp8049261: 65 a1 14 00 00 00 mov %gs:0x14,%eax8049267: 89 44 24 7c mov %eax,0x7c(%esp)804926b: 31 c0 xor %eax,%eax804926d: 83 3d cc c3 04 08 06 cmpl $0x6,0x804c3cc8049274: 75 72 jne 80492e8 <phase_defused+0x8d>8049276: 8d 44 24 2c lea 0x2c(%esp),%eax804927a: 89 44 24 10 mov %eax,0x10(%esp)804927e: 8d 44 24 28 lea 0x28(%esp),%eax8049282: 89 44 24 0c mov %eax,0xc(%esp)8049286: 8d 44 24 24 lea 0x24(%esp),%eax804928a: 89 44 24 08 mov %eax,0x8(%esp)804928e: c7 44 24 04 c9 a3 04 movl $0x804a3c9,0x4(%esp)8049295: 08 8049296: c7 04 24 d0 c4 04 08 movl $0x804c4d0,(%esp)804929d: e8 ce f5 ff ff call 8048870 <__isoc99_sscanf@plt>80492a2: 83 f8 03 cmp $0x3,%eax80492a5: 75 35 jne 80492dc <phase_defused+0x81>80492a7: c7 44 24 04 d2 a3 04 movl $0x804a3d2,0x4(%esp)80492ae: 08 80492af: 8d 44 24 2c lea 0x2c(%esp),%eax80492b3: 89 04 24 mov %eax,(%esp)80492b6: e8 09 fd ff ff call 8048fc4 <strings_not_equal>80492bb: 85 c0 test %eax,%eax80492bd: 75 1d jne 80492dc <phase_defused+0x81>80492bf: c7 04 24 98 a2 04 08 movl $0x804a298,(%esp)80492c6: e8 35 f5 ff ff call 8048800 <puts@plt>80492cb: c7 04 24 c0 a2 04 08 movl $0x804a2c0,(%esp)80492d2: e8 29 f5 ff ff call 8048800 <puts@plt>80492d7: e8 d4 fb ff ff call 8048eb0 <secret_phase>80492dc: c7 04 24 f8 a2 04 08 movl $0x804a2f8,(%esp)80492e3: e8 18 f5 ff ff call 8048800 <puts@plt>80492e8: 8b 44 24 7c mov 0x7c(%esp),%eax80492ec: 65 33 05 14 00 00 00 xor %gs:0x14,%eax80492f3: 74 05 je 80492fa <phase_defused+0x9f>80492f5: e8 d6 f4 ff ff call 80487d0 <__stack_chk_fail@plt>80492fa: 81 c4 8c 00 00 00 add $0x8c,%esp8049300: c3 ret 8049301: 90 nop8049302: 90 nop8049303: 90 nop8049304: 90 nop8049305: 90 nop8049306: 90 nop8049307: 90 nop8049308: 90 nop8049309: 90 nop804930a: 90 nop804930b: 90 nop804930c: 90 nop804930d: 90 nop804930e: 90 nop804930f: 90 nop08048e5f <fun7>:8048e5f: 53 push %ebx8048e60: 83 ec 18 sub $0x18,%esp8048e63: 8b 54 24 20 mov 0x20(%esp),%edx8048e67: 8b 4c 24 24 mov 0x24(%esp),%ecx8048e6b: 85 d2 test %edx,%edx8048e6d: 74 37 je 8048ea6 <fun7+0x47>8048e6f: 8b 1a mov (%edx),%ebx8048e71: 39 cb cmp %ecx,%ebx8048e73: 7e 13 jle 8048e88 <fun7+0x29>8048e75: 89 4c 24 04 mov %ecx,0x4(%esp)8048e79: 8b 42 04 mov 0x4(%edx),%eax8048e7c: 89 04 24 mov %eax,(%esp)8048e7f: e8 db ff ff ff call 8048e5f <fun7>8048e84: 01 c0 add %eax,%eax8048e86: eb 23 jmp 8048eab <fun7+0x4c>8048e88: b8 00 00 00 00 mov $0x0,%eax8048e8d: 39 cb cmp %ecx,%ebx8048e8f: 74 1a je 8048eab <fun7+0x4c>8048e91: 89 4c 24 04 mov %ecx,0x4(%esp)8048e95: 8b 42 08 mov 0x8(%edx),%eax8048e98: 89 04 24 mov %eax,(%esp)8048e9b: e8 bf ff ff ff call 8048e5f <fun7>8048ea0: 8d 44 00 01 lea 0x1(%eax,%eax,1),%eax8048ea4: eb 05 jmp 8048eab <fun7+0x4c>8048ea6: b8 ff ff ff ff mov $0xffffffff,%eax8048eab: 83 c4 18 add $0x18,%esp8048eae: 5b pop %ebx8048eaf: c3 ret 08048eb0 <secret_phase>:8048eb0: 53 push %ebx8048eb1: 83 ec 18 sub $0x18,%esp8048eb4: e8 44 02 00 00 call 80490fd <read_line>8048eb9: c7 44 24 08 0a 00 00 movl $0xa,0x8(%esp)8048ec0: 00 8048ec1: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)8048ec8: 00 8048ec9: 89 04 24 mov %eax,(%esp)8048ecc: e8 0f fa ff ff call 80488e0 <strtol@plt>8048ed1: 89 c3 mov %eax,%ebx8048ed3: 8d 40 ff lea -0x1(%eax),%eax8048ed6: 3d e8 03 00 00 cmp $0x3e8,%eax8048edb: 76 05 jbe 8048ee2 <secret_phase+0x32>8048edd: e8 f4 01 00 00 call 80490d6 <explode_bomb>8048ee2: 89 5c 24 04 mov %ebx,0x4(%esp)8048ee6: c7 04 24 88 c0 04 08 movl $0x804c088,(%esp)8048eed: e8 6d ff ff ff call 8048e5f <fun7>8048ef2: 83 f8 03 cmp $0x3,%eax8048ef5: 74 05 je 8048efc <secret_phase+0x4c>8048ef7: e8 da 01 00 00 call 80490d6 <explode_bomb>8048efc: c7 04 24 c4 a1 04 08 movl $0x804a1c4,(%esp)8048f03: e8 f8 f8 ff ff call 8048800 <puts@plt>8048f08: e8 4e 03 00 00 call 804925b <phase_defused>8048f0d: 83 c4 18 add $0x18,%esp8048f10: 5b pop %ebx8048f11: c3 ret 8048f12: 90 nop8048f13: 90 nop8048f14: 90 nop8048f15: 90 nop8048f16: 90 nop8048f17: 90 nop8048f18: 90 nop8048f19: 90 nop8048f1a: 90 nop8048f1b: 90 nop8048f1c: 90 nop8048f1d: 90 nop8048f1e: 90 nop8048f1f: 90 nop
分析
在阅读汇编代码时,发现phase_6之下还有一个secret_phase,直接输入6个密钥会发现直接退出了运行,而不会进入这个秘密关卡,于是我启用搜索,看看是什么函数调用了这个secret_phase。
在代码中搜索secret_phase,会发现phase_defused函数调用了secret_phase,而这个phase_defused函数是每个phase通过之后会执行的函数,于是我联想到是否需要在某个phase输入特定的信息才会进入这个secret_phase。阅读phase_defused的汇编代码,会发现出现了几个内存的地址,查看这些地址的内容,会发现这的确是secret_phase的入口。
其中,DrEvil出现在0x804a3d2中,而phase_defused调用了strings_not_equal函数来比较DrEvil和一个应该是我们输入的字符串的地方,这说明,我们开启secret_phase需要输入的信息是DrEvil!
80492a7: c7 44 24 04 d2 a3 04 movl $0x 804a3d2,0x4(%esp)80492ae: 0880492af: 8d 44 24 2c lea 0x2c(%esp),%eax80492b3: 89 04 24 mov %eax,(%esp)80492b6: e8 09 fd ff ff call 8048fc4 <strings_not_equal>80492bb: 85 c0 test %eax,%eax80492bd: 75 1d jne 80492dc <phase_defused+0x81>
那么这个DrEvil该什么时候输入呢?
804929d: e8 ce f5 ff ff call 8048870 <__isoc99_sscanf@plt>
80492a2: 83 f8 03 cmp $0x3,%eax
80492a5: 75 35 jne 80492dc <phase_defused+0x81>
通过以上这3行可以看到,包括DrEvil在内,输入的参数的数量需要是3,且分别为%d %d %s否则将会跳过secret_phase的调用。可以发现,只有phase_4的密钥加上DrEvil满足这个条件。经测试,在phase_4的密钥后加上DrEvil后,在phase_6通过后,果然出现了secret_phase。
进入secret_phase,函数先读取我们输入的密钥,然后调用了c语言的strtol函数,这个函数将字符串转为长整型数,其中参数0xa意味着将其转为十进制。接下来函数会比较经转换后的数自减1后与0x3e8(十进制为1000)的无符号数大小,这意味着我们输入的数必须在[1, 1001]中。接着,函数会调用一个递归函数func7,参数为0x804c088和我们输入的数,func7的伪代码如下
int func7(int* k, int n)
{if(k <= 0) return -1;int kn = *k;if(kn == n) return 0;else if(kn < n) return 2*func7(k+2, n) + 1;else return 2*func7(k+1, n);
}
又由以下两个指令知,func7(0x804c088, 我们输入的数)的返回值必须为3,否则将引爆炸弹。
8048ef2: 83 f8 03 cmp $0x3,%eax
8048ef5: 74 05 je 8048efc <secret_phase+0x4c>
观察内存中地址为0x804c088的内容,是一棵二叉树:第一个地址存储数据,第二、三个地址存储下子结点的地址。于是我们只要分析func7的行为,并根据0x804c088中的内容,即可确定要输入的数。
首先,函数要求func7最终返回3。要构造一个3,根据func7的特性,可以这样构造:2*(2*0+1)+1,这意味着,最后一次要返回0,倒数第二次(或第二次)要返回2*func7(k+2, n) + 1,倒数第三次(或第一次)要返回2*func7(k+2, n) + 1。或者说,要返回的值是二叉树查找时比较的次数。要满足这样的关系,假如我们把第1~3次比较的数设为k1、k2、k3,那么,我们输入的数n要满足的条件是n < k1且n < k2且n == k3。要找到k3,查找0x804c088处的内存即可,经查找,为0x6b,转换为十进制为107,即为密钥。
(gdb) x/x 0x804c088
0x804c088 <n1>: 0x00000024
0x804c08c <n1+4>: 0x0804c094
0x804c090 <n1+8>: 0x0804c0a0
0x804c094 <n21>: 0x00000008
0x804c098 <n21+4>: 0x0804c0c4
0x804c09c <n21+8>: 0x0804c0ac
0x804c0a0 <n22>: 0x00000032
0x804c0a4 <n22+4>: 0x0804c0b8
0x804c0a8 <n22+8>: 0x0804c0d0
0x804c0ac <n32>: 0x00000016
0x804c0b0 <n32+4>: 0x0804c118
0x804c0b4 <n32+8>: 0x0804c100
0x804c0b8 <n33>: 0x0000002d
0x804c0bc <n33+4>: 0x0804c0dc
0x804c0c0 <n33+8>: 0x0804c124
0x804c0c4 <n31>: 0x00000006
0x804c0c8 <n31+4>: 0x0804c0e8
0x804c0cc <n31+8>: 0x0804c10c
0x804c0d0 <n34>: 0x0000006b
0x804c0d4 <n34+4>: 0x0804c0f4
0x804c0d8 <n34+8>: 0x0804c130
0x804c0dc <n45>: 0x00000028
0x804c0e0 <n45+4>: 0x00000000
0x804c0e4 <n45+8>: 0x00000000
0x804c0e8 <n41>: 0x00000001
0x804c0ec <n41+4>: 0x00000000
0x804c0f0 <n41+8>: 0x00000000
0x804c0f4 <n47>: 0x00000063
0x804c0f8 <n47+4>: 0x00000000
0x804c0fc <n47+8>: 0x00000000
0x804c100 <n44>: 0x00000023
0x804c104 <n44+4>: 0x00000000
0x804c108 <n44+8>: 0x00000000
0x804c10c <n42>: 0x00000007
0x804c110 <n42+4>: 0x00000000
0x804c114 <n42+8>: 0x00000000
0x804c118 <n43>: 0x00000014
0x804c11c <n43+4>: 0x00000000
0x804c120 <n43+8>: 0x00000000
0x804c124 <n46>: 0x0000002f
0x804c128 <n46+4>: 0x00000000
0x804c12c <n46+8>: 0x00000000
0x804c130 <n48>: 0x000003e9
0x804c134 <n48+4>: 0x00000000
0x804c138 <n48+8>: 0x00000000
《深入理解计算机系统》Lab3 Bomblab相关推荐
- 《深入理解计算机系统》--BombLab学习笔记(含隐藏阶段)
前言 在皓哥的鼓励下,磕磕绊绊断断续续终于做完了BombLab,这个实验确实很有趣而且对我帮助很大,做完也非常的有成就感(HGNB)
- 《深入理解计算机系统》实验二Bomb Lab下载和官方文档机翻
前言 <深入理解计算机系统>官网:http://csapp.cs.cmu.edu/3e/labs.html 该篇文章是实验二Bomb Lab的Writeup机翻. 原文:http://cs ...
- 《深入理解计算机系统》读书随笔-位操作
最近开始读<深入理解计算机系统>这本书.对于书中提到的从程序员的角度解读计算机系统这一说法非常感兴趣,所以决定好好读一读.从开始接触计算机编程就是站在一个高级语言的层次,虽然对编译原理,操 ...
- 【组队学习】【32期】深入理解计算机系统
深入理解计算机系统 航路开辟者:李岳昆.易远哲 领航员:初晓宇 航海士:叶前坤.沈豪 基本信息 开源内容:https://github.com/datawhalechina/team-learning ...
- 深入理解计算机系统 -资料整理 高清中文版_在所不辞的博客-CSDN博客_深入理解计算机系统第四版pdf
深入理解计算机系统 -资料整理 高清中文版_在所不辞的博客-CSDN博客_深入理解计算机系统第四版pdf
- 深入理解计算机系统(3)
深入理解计算机系统(3) 本文我们主要讲关于数据的的表示方式:原码,反码和补码. 本文在写作过程中,参考了园中的这篇文章<原码,反码,补码详解>,特此声明. 一原码 计算机中是使用二进制来 ...
- 《深入理解计算机系统-程序结构》读书笔记
1.计算机系统漫游 计算机系统是由硬件和系统软件组成的,他们共同工作来运行应用程序.在<深入理解计算机系统>一书中将会学到很多实践的技巧.例如:了解编译器是如何实现过程调用的.避免缓冲区溢 ...
- csapp 、sicp 、深入理解计算机系统、 计算机程序的构造和解释
CSAPP 第一版的英文版 深入理解计算机系统第一版中文版 这个是csdn账号 这里上传文件大小在10M以内 这个pdf是19+M的 深入理解计算机系统第二版的中文版下载 第一版英文版的介绍原书 ...
- 《深入理解计算机系统》第七章读书笔记
<深入理解计算机系统>第七章读书笔记 第七章:连接 连接 1.连接:将各种代码和数据部分收集起来并组合成为一个单一文件的过程.这个文件可被加载或拷贝到存储器并执行. 2.连接可以执行于编译 ...
- 深入理解计算机系统:网络编程 上
一直以来对计算机网络比较感兴趣,但是无奈大学计算机网络的学习非常表面,已经忘得差不多了.毕业后读了一些网络方面的书,对网络知识的冰山一角有了一些感悟. 随着网络方面的书越读越多,不懂的地方也越来越多. ...
最新文章
- HTML5新特性总结
- 安卓手机也能跑YOLOv5了!
- 基于SSM实现保健院管理系统
- qemu 对虚机的地址空间管理
- FireDAC 中文字段过滤问题
- 电脑老是弹出vrvedp_m_出现三个可疑进程vrvedp_m.exe vrvrf_c.exe vrvsafec.exe
- 理解zookeeper选举机制
- Swift中文教程(一)基础数据类型
- 【two pointers 细节题】cf1041dD. Glider
- 关于Java String对象创建问题解惑
- 解决apache的the requested operation has failed
- CSS hack 初学小结
- STC学习:温度与光照传感器
- 报告:黑马11月就业薪资出炉!哪个学科更好就业?
- 北京大学王悦博士给学生的话
- 计算机ram特点,RAM有什么特点
- Python开发 之 Python3打包(windows/linux)详解
- 去《挪威的森林》之后
- ROS 问题(topic types do not match、topic datatype/md5sum not match、msg xxx have changed. rerun cmake)
- 【小技巧】PDF 转 图片 虚拟打印