潜入 Bomb Lab 拆弹记
date
Mar 19, 2018
slug
bomb-lab
status
Published
tags
Programming
Computer Science
Assembly
summary
KA BOOOM~
type
Post
Dr. Evil's Insidious Bomb
Bomb Lab 是 《深入理解计算机系统》 (Computer Systems A Programmer's perspective) 中对应第三章内容:程序的机器级表示的lab。主要内容为提供一个二进制对象文件bomb,当运行时,它会要求用户输入六个字符串,如果其中的任何一个不正确,炸弹就会爆炸,输出一行错误信息。
学生必须通过反汇编和逆向工程来找到六个正确的字符串来解除自己的炸弹
理论上每个人的炸弹答案都不同, 本文章仅供参考
拆弹工具
objdump
- 用于反汇编二进制对象文件
gedit
- 用于查看反汇编后的结果与文本文件的编写
gdb
- 用于运行时单步调试与查看运行时内存与寄存器信息
开始拆弹
第一阶段
08048b90 <phase_1>:
8048b90: 83 ec 1c sub $0x1c,%esp
8048b93: c7 44 24 04 3c a1 04 movl $0x804a13c,0x4(%esp)
8048b9a: 08
8048b9b: 8b 44 24 20 mov 0x20(%esp),%eax
8048b9f: 89 04 24 mov %eax,(%esp)
8048ba2: e8 73 04 00 00 call 804901a <strings_not_equal>
8048ba7: 85 c0 test %eax,%eax
8048ba9: 74 05 je 8048bb0 <phase_1+0x20>
8048bab: e8 75 05 00 00 call 8049125 <explode_bomb>
8048bb0: 83 c4 1c add $0x1c,%esp
8048bb3: c3 ret
① 从call 804901a <strings_not_equal>这里在比较字符串应该可以猜到第一关要我们输入的是一串字符,如果输入的字符和它要求 的字符串一样的话,就ok,不然就会boom。
首先,在进入bomb函数前先设置断点,先找到call 8049125 <explode_bomb>,输入b *0x8049125
②然后输入r,运行
③找到movl $0x804a13c,0x4(%esp),输入p (char*)0x804a13c打印出存放的内容
④退出gdb,输入./bomb,输入刚刚输出的字符串,即可成功拆掉第一弹。
第二阶段
08048bb4 <phase_2>:
8048bb4: 53 push %ebx
8048bb5: 83 ec 38 sub $0x38,%esp
8048bb8: 8d 44 24 18 lea 0x18(%esp),%eax
8048bbc: 89 44 24 04 mov %eax,0x4(%esp)
8048bc0: 8b 44 24 40 mov 0x40(%esp),%eax
8048bc4: 89 04 24 mov %eax,(%esp)
8048bc7: e8 80 05 00 00 call 804914c <read_six_numbers>
8048bcc: 83 7c 24 18 00 cmpl $0x0,0x18(%esp)
8048bd1: 79 22 jns 8048bf5 <phase_2+0x41>
8048bd3: e8 4d 05 00 00 call 8049125 <explode_bomb>
8048bd8: eb 1b jmp 8048bf5 <phase_2+0x41>
8048bda: 89 d8 mov %ebx,%eax
8048bdc: 03 44 9c 14 add 0x14(%esp,%ebx,4),%eax
8048be0: 39 44 9c 18 cmp %eax,0x18(%esp,%ebx,4)
8048be4: 74 05 je 8048beb <phase_2+0x37>
8048be6: e8 3a 05 00 00 call 8049125 <explode_bomb>
8048beb: 83 c3 01 add $0x1,%ebx
8048bee: 83 fb 06 cmp $0x6,%ebx
8048bf1: 75 e7 jne 8048bda <phase_2+0x26>
8048bf3: eb 07 jmp 8048bfc <phase_2+0x48>
8048bf5: bb 01 00 00 00 mov $0x1,%ebx
8048bfa: eb de jmp 8048bda <phase_2+0x26>
8048bfc: 83 c4 38 add $0x38,%esp
8048bff: 5b pop %ebx
8048c00: c3 ret
- 首先我们观察8048bc7这一行,它调用了read_six_numbers这个方法读取六个数字。所以我们要输入六个数字
- 炸弹的触发条件很明确,一旦%eax和参数不相等,那么炸弹便会爆炸。否则继续拆弹循环。
- 在第一个循环时,%eax和parameter(i+1)做比较,以此类推。
- 而%eax=%ebx+parameter(i),同时我们可以很惊奇的发现8048beb 每次都对%ebx 自增1, 我们可以把 ebx 看作为 for 循环时的迭代数字。 所以整个问题便迎刃而解了。只要满足parameter(i+1)= i + parameter(i)就不会引爆炸弹
- 同时由开头的代码处(8048bd1) 可以看出,第一个数字必须大于等于0,否则立马引爆炸弹。我觉得使用1作为第一个数字。
- 而从最后的代码可以看出这个循环会进行5次,也就是对输入的前6个参数进行比较。所以答案可以是
所以阶段2的答案为1 2 4 7 11 16.
第三阶段
08048c01 <phase_3>:
8048c01: 83 ec 2c sub $0x2c,%esp
8048c04: 8d 44 24 1c lea 0x1c(%esp),%eax
8048c08: 89 44 24 0c mov %eax,0xc(%esp)
8048c0c: 8d 44 24 18 lea 0x18(%esp),%eax
8048c10: 89 44 24 08 mov %eax,0x8(%esp)
8048c14: c7 44 24 04 db a2 04 movl $0x804a2db,0x4(%esp)
8048c1b: 08
8048c1c: 8b 44 24 30 mov 0x30(%esp),%eax
8048c20: 89 04 24 mov %eax,(%esp)
8048c23: e8 38 fc ff ff call 8048860 <__isoc99_sscanf@plt>
8048c28: 83 f8 01 cmp $0x1,%eax
8048c2b: 7f 05 jg 8048c32 <phase_3+0x31>
8048c2d: e8 f3 04 00 00 call 8049125 <explode_bomb>
8048c32: 83 7c 24 18 07 cmpl $0x7,0x18(%esp)
8048c37: 77 3c ja 8048c75 <phase_3+0x74>
8048c39: 8b 44 24 18 mov 0x18(%esp),%eax
8048c3d: ff 24 85 9c a1 04 08 jmp *0x804a19c(,%eax,4)
8048c44: b8 5d 02 00 00 mov $0x25d,%eax
8048c49: eb 3b jmp 8048c86 <phase_3+0x85>
8048c4b: b8 af 00 00 00 mov $0xaf,%eax
8048c50: eb 34 jmp 8048c86 <phase_3+0x85>
8048c52: b8 14 01 00 00 mov $0x114,%eax
8048c57: eb 2d jmp 8048c86 <phase_3+0x85>
8048c59: b8 dd 01 00 00 mov $0x1dd,%eax
8048c5e: eb 26 jmp 8048c86 <phase_3+0x85>
8048c60: b8 59 01 00 00 mov $0x159,%eax
8048c65: eb 1f jmp 8048c86 <phase_3+0x85>
8048c67: b8 4f 00 00 00 mov $0x4f,%eax
8048c6c: eb 18 jmp 8048c86 <phase_3+0x85>
8048c6e: b8 95 03 00 00 mov $0x395,%eax
8048c73: eb 11 jmp 8048c86 <phase_3+0x85>
8048c75: e8 ab 04 00 00 call 8049125 <explode_bomb>
8048c7a: b8 00 00 00 00 mov $0x0,%eax
8048c7f: eb 05 jmp 8048c86 <phase_3+0x85>
8048c81: b8 19 01 00 00 mov $0x119,%eax
8048c86: 3b 44 24 1c cmp 0x1c(%esp),%eax
8048c8a: 74 05 je 8048c91 <phase_3+0x90>
8048c8c: e8 94 04 00 00 call 8049125 <explode_bomb>
8048c91: 83 c4 2c add $0x2c,%esp
8048c94: c3 ret
- 下一步调用了库函数sscanf,我们想到sscanf中的参数中需要一个格式化字符串,那么esp中的这个地址值就很有可能存放了这个字符串,我们同样使用gdb在运行时查看这个字符串:
- 下面第x行将eax与1进行比较,eax一般用于存放函数返回值,而sscanf 的返回值是成功读入的数值个数,也就是说这几行将成功读入的个数与1进行比较,如果大于1则跳过引爆的代码。
- 下面第x行将esp+0x18中存放的值与0x7进行比较,如果大于则跳到8048c75的位置,我们看这个地址的指令:
8048c75: e8 ab 04 00 00 call 8049125 <explode_bomb>
假设读入的第一个数为x,看到所有分支最后都跳转到了8048c86 这行判断 (cmp) 中,将eax中的值与esp+0x1c也就是我们读入的第二个数进行判断,如果相等的话跳过引爆代码。
- 结果详细分析,我们得出了等价的伪代码
function phase_3(input) {
var a, b :Int;
val qty :Int = sscanf(input, "%d %d", &a, &b); // Side effect with a b
if (qty <= 1) {explode_bomb();}
switch (a) {
case 0: a = 605; break;
case 1: a = 281; break;
case 2: a = 175; break;
case 3: a = 276; break;
case 4: a = 477; break;
case 5: a = 345; break;
case 6: a = 79; break;
case 7: a = 917; break;
default: {
explode_bomb(); // 0<a || a >7 is boom!!
a = 0;
break;
}
}
if (a != b) {explode_bomb();}
}
- 通过以上等价代码也就是说我们只要根据不同的第一个参数的值读入对应的第二个参数就可以了,所以我们可以随意选择一个x值,这里我选择x=1,对应的第二个参数为0x119换成十进制是281,所以第3阶段的答案为:
A的值
B的值
0
605
1
281
2
175
3
276
4
477
5
345
6
79
7
917