CSAPP - Bomb Lab
目录
目录
来挑战邪恶博士了!每一个 CS 学生的必经之路……
正在做,持续更新中……
这依然是 HNU 的计算机系统的实验作业,老师给每个同学生成了不同的 lab 文件,我做的结果只是我的文件答案。
不过解题方法都是大同小异啦。
这是题目,当然是翻译后的版本:
/***************************************************************************
* 邪恶博士的阴险炸弹,版本 1.1
* 版权所有 2011,邪恶博士股份有限公司。保留所有权利。
*
* 许可协议:
*
* 邪恶博士股份有限公司(下称“施害者”)特此授予你(下称“受害者”)
* 使用本炸弹(下称“炸弹”)的明确许可。这是一个时间受限的许可,
* 在受害者死亡时失效。
* “施害者”对“受害者”可能遭受的伤害、挫折感、精神失常、眼球突出、
* 腕管综合征、睡眠不足或其他损害概不负责——除非“施害者”想以此邀功。
* “受害者”不得将此炸弹源代码分发给“施害人”的任何敌人。
* 严禁任何“受害者”进行调试、逆向工程、运行 "strings"、反编译、
* 解密或使用任何其他技术来获取相关知识并拆除炸弹。
* 处理本程序时不得穿着防弹衣。
* “施害人”不会为其糟糕的幽默感道歉。
* 在法律禁止使用“炸弹”的地区,此许可无效。
***************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include "support.h"
#include "phases.h"
/*
* 自留笔记:记得把这个文件删了,这样我的受害者们就完全不知道发生了什么,
* 然后他们就会在一次华丽壮观、充满恶意的大爆炸中死翘翘。
* —— 邪恶博士
*/
FILE *infile;
int main(int argc, char *argv[])
{
char *input;
/* 自留笔记:记得把这个炸弹移植到 Windows 并加上炫酷的图形界面。 */
/* 当不带参数运行时,炸弹从标准输入(键盘)读取输入。 */
if (argc == 1) {
infile = stdin;
}
/* 当带有一个参数 <file> 时,炸弹会从该文件中持续读取直到文件结束(EOF),
* 然后再切换到键盘输入。因此,当你每解开一个阶段,你都可以把破解字符串
* 添加到文件中,从而避免重复输入。 */
else if (argc == 2) {
if (!(infile = fopen(argv[1], "r"))) {
printf("%s: 错误:无法打开文件 %s\n", argv[0], argv[1]);
exit(8);
}
}
/* 你不能带超过 1 个命令行参数来调用炸弹。 */
else {
printf("用法: %s [<输入文件>]\n", argv[0]);
exit(8);
}
/* 执行各种秘密操作,增加炸弹的拆除难度。 */
initialize_bomb();
printf("欢迎来到我邪恶的小炸弹。你一共有 6 个阶段\n");
printf("可以让自己被炸飞。祝你今天愉快!\n");
/* 唔…… 6 个阶段肯定比 1 个阶段更安全! */
input = read_line(); /* 获取输入 */
phase_1(input); /* 运行第一阶段 */
phase_defused(); /* 讨厌!他们竟然解开了!
* 让我知道他们是怎么做到的。 */
printf("第一阶段已拆除。下一关怎么样?\n");
/* 第二阶段更难。没人能搞清楚怎么拆除这一个…… */
input = read_line();
phase_2(input);
phase_defused();
printf("这是第 2 关。继续努力!\n");
/* 我猜目前为止还是太简单了。一些更复杂的代码会把大家搞糊涂。 */
input = read_line();
phase_3(input);
phase_defused();
printf("已经完成一半了!\n");
/* 哦,是吗?那你的数学怎么样?试试这个刁钻的问题! */
input = read_line();
phase_4(input);
phase_defused();
printf("看来你把那个也解开了。试试这个。\n");
/* 在内存里转啊转,转到哪儿,炸弹就炸到哪儿! */
input = read_line();
phase_5(input);
phase_defused();
printf("做得好!还剩最后一关!\n");
/* 这就是你要面对的最终挑战。在这里认输并不会丢人。 */
input = read_line();
phase_6(input);
phase_defused();
/* 呜呼!你居然真的拆除了炸弹! */
return 0;
}
第一关
我们使用 objdump -d bomb > bomb.s,找到 <phase_1> 这一段函数:
080488c0 <phase_1>:
80488c0: 83 ec 1c subl $0x1c, %esp
80488c3: c7 44 24 04 e4 92 04 08 movl $0x80492e4, 0x4(%esp) # imm = 0x80492E4
80488cb: 8b 44 24 20 movl 0x20(%esp), %eax
80488cf: 89 04 24 movl %eax, (%esp)
80488d2: e8 03 05 00 00 calll 0x8048dda <strings_not_equal>
80488d7: 85 c0 testl %eax, %eax
80488d9: 74 05 je 0x80488e0 <phase_1+0x20>
80488db: e8 05 06 00 00 calll 0x8048ee5 <explode_bomb>
80488e0: 83 c4 1c addl $0x1c, %esp
80488e3: c3 retl
注意到这一堆汇编里面有一个 <strings_not_equal> 的调用,看名字应该就是个比较字符串是否相等的函数。
还可以看到一个 # imm = 0x80492E4 的注释,imm 是立即数的 immediate value 的缩写。
那看看这一行的含义中,80488c3 这一行本来的意思就是:把内存地址 0x80492E4 放到栈上,作为参数传给接下来的函数。
说明内存地址 0x80492E4 肯定有东西!于是 gdb 做调试,看看里面写了什么:
gdb ./bomb
(gdb) x/s 0x80492e4
0x80492e4: "I was trying to give Tina Fey more material."
(gdb)
是一段字符串!那应该就是我们的题解了,我们的输入的字符串与它一致,即可解除这个 bomb 啦。
root@794cb0e6f7ce:/workspace/bomb# ./bomb
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
# 在这里输入这个字符串
I was trying to give Tina Fey more material.
Phase 1 defused. How about the next one?
当然也可以 IDA Pro 或者更加 Apple Native 的 Hopper Disassembler,直接去反汇编,看 <phase_1> 的函数,答案就这样浮出水面了……

第二关
先去翻一翻第二个函数的汇编:
080488e4 <phase_2>:
80488e4: 56 pushl %esi
80488e5: 53 pushl %ebx
80488e6: 83 ec 34 subl $0x34, %esp
80488e9: 8d 44 24 18 leal 0x18(%esp), %eax
80488ed: 89 44 24 04 movl %eax, 0x4(%esp)
80488f1: 8b 44 24 40 movl 0x40(%esp), %eax
80488f5: 89 04 24 movl %eax, (%esp)
80488f8: e8 0f 06 00 00 calll 0x8048f0c <read_six_numbers>
80488fd: 83 7c 24 18 01 cmpl $0x1, 0x18(%esp)
8048902: 74 1e je 0x8048922 <phase_2+0x3e>
8048904: e8 dc 05 00 00 calll 0x8048ee5 <explode_bomb>
8048909: eb 17 jmp 0x8048922 <phase_2+0x3e>
804890b: 8b 43 fc movl -0x4(%ebx), %eax
804890e: 01 c0 addl %eax, %eax
8048910: 39 03 cmpl %eax, (%ebx)
8048912: 74 05 je 0x8048919 <phase_2+0x35>
8048914: e8 cc 05 00 00 calll 0x8048ee5 <explode_bomb>
8048919: 83 c3 04 addl $0x4, %ebx
804891c: 39 f3 cmpl %esi, %ebx
804891e: 75 eb jne 0x804890b <phase_2+0x27>
8048920: eb 0a jmp 0x804892c <phase_2+0x48>
8048922: 8d 5c 24 1c leal 0x1c(%esp), %ebx
8048926: 8d 74 24 30 leal 0x30(%esp), %esi
804892a: eb df jmp 0x804890b <phase_2+0x27>
804892c: 83 c4 34 addl $0x34, %esp
804892f: 5b popl %ebx
8048930: 5e popl %esi
8048931: c3 retl
看上去就明显复杂了很多,一眼看过去就看明白了个 <read_six_numbers> 的调用,读取六个数字?还有两个 <explode_bomb> 的触发?
先分析汇编,注释一下:
80488e6: 83 ec 34 subl $0x34, %esp # 在栈上开辟 0x34 就是 52 的空间
80488e9: 8d 44 24 18 leal 0x18(%esp), %eax # 计算栈上偏移 24 (0x18) 的地址
80488ed: 89 44 24 04 movl %eax, 0x4(%esp) # 将该地址存为后面 read_six_numbers 的函数参数
80488f1: 8b 44 24 40 movl 0x40(%esp), %eax
80488f5: 89 04 24 movl %eax, (%esp)
80488f8: e8 d9 02 00 00 calll 0x8048f0c <read_six_numbers> # 读取 6 个数并存入 0x18(%esp) 开始的区域
80488fd: 83 7c 24 18 01 cmpl $0x1, 0x18(%esp) # 比较!
因为是读取六个数,且一个数会占4个字节,所以 0x18(%esp),0x1c(%esp),0x20(%esp)... 分别存6个数。
那么 cmpl $0x1, 0x18(%esp) 就是用存的第一个数做比较了。
注意到第一个 explode 前面的 cmp 比较指令:
80488fd: 83 7c 24 18 01 cmpl $0x1, 0x18(%esp)
8048902: 74 1e je 0x8048922 <phase_2+0x3e>
8048904: e8 dc 05 00 00 calll 0x8048ee5 <explode_bomb>
这一段汇编的含义大概是:比较第一个数字是否为1,如果是就跳过,否则就 boom。
- 结论 1:第一个数字必须是
1。
然后看第二个 bomb 之前的事情,这后面的看的好头疼……
804890b: 8b 43 fc movl -0x4(%ebx), %eax
804890e: 01 c0 addl %eax, %eax
8048910: 39 03 cmpl %eax, (%ebx)
8048912: 74 05 je 0x8048919 <phase_2+0x35>
8048914: e8 cc 05 00 00 calll 0x8048ee5 <explode_bomb>
这一段比较有意思,把往前偏移4位的值存起来,其实也就是上一位数字,然后再自己相加,再和原数比较。
如果翻译成 c,大概的意思就是:
int a = arr[x-1];
a+=a;
if (a==arr[x]){
continue;
}
else explode_bomb();
//...
那这个就很明了了,我们的下一位数字,必须是上一位数字的2倍!
后面的汇编实在是看的头疼,于是就直接写6个数字符合这个条件吧~也就是 1 2 4 8 16 32
root@794cb0e6f7ce:/workspace/bomb# ./bomb
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
I was trying to give Tina Fey more material.
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2. Keep going!
总算过了。
第三关
再看看题目:
08048932 <phase_3>:
8048932: 83 ec 3c subl $0x3c, %esp
8048935: 8d 44 24 2c leal 0x2c(%esp), %eax
8048939: 89 44 24 10 movl %eax, 0x10(%esp)
804893d: 8d 44 24 27 leal 0x27(%esp), %eax
8048941: 89 44 24 0c movl %eax, 0xc(%esp)
8048945: 8d 44 24 28 leal 0x28(%esp), %eax
8048949: 89 44 24 08 movl %eax, 0x8(%esp)
804894d: c7 44 24 04 3a 93 04 08 movl $0x804933a, 0x4(%esp) # imm = 0x804933A
8048955: 8b 44 24 40 movl 0x40(%esp), %eax
8048959: 89 04 24 movl %eax, (%esp)
804895c: e8 9f fc ff ff calll 0x8048600 <__isoc99_sscanf@plt>
8048961: 83 f8 02 cmpl $0x2, %eax
8048964: 7f 05 jg 0x804896b <phase_3+0x39>
8048966: e8 7a 05 00 00 calll 0x8048ee5 <explode_bomb>
804896b: 83 7c 24 28 07 cmpl $0x7, 0x28(%esp)
8048970: 0f 87 fc 00 00 00 ja 0x8048a72 <phase_3+0x140>
8048976: 8b 44 24 28 movl 0x28(%esp), %eax
804897a: ff 24 85 60 93 04 08 jmpl *0x8049360(,%eax,4)
8048981: b8 73 00 00 00 movl $0x73, %eax
8048986: 81 7c 24 2c 19 03 00 00 cmpl $0x319, 0x2c(%esp) # imm = 0x319
804898e: 0f 84 e8 00 00 00 je 0x8048a7c <phase_3+0x14a>
8048994: e8 4c 05 00 00 calll 0x8048ee5 <explode_bomb>
8048999: b8 73 00 00 00 movl $0x73, %eax
804899e: e9 d9 00 00 00 jmp 0x8048a7c <phase_3+0x14a>
80489a3: b8 62 00 00 00 movl $0x62, %eax
80489a8: 81 7c 24 2c a3 03 00 00 cmpl $0x3a3, 0x2c(%esp) # imm = 0x3A3
80489b0: 0f 84 c6 00 00 00 je 0x8048a7c <phase_3+0x14a>
80489b6: e8 2a 05 00 00 calll 0x8048ee5 <explode_bomb>
80489bb: b8 62 00 00 00 movl $0x62, %eax
80489c0: e9 b7 00 00 00 jmp 0x8048a7c <phase_3+0x14a>
80489c5: b8 66 00 00 00 movl $0x66, %eax
80489ca: 81 7c 24 2c c4 03 00 00 cmpl $0x3c4, 0x2c(%esp) # imm = 0x3C4
80489d2: 0f 84 a4 00 00 00 je 0x8048a7c <phase_3+0x14a>
80489d8: e8 08 05 00 00 calll 0x8048ee5 <explode_bomb>
80489dd: b8 66 00 00 00 movl $0x66, %eax
80489e2: e9 95 00 00 00 jmp 0x8048a7c <phase_3+0x14a>
80489e7: b8 6f 00 00 00 movl $0x6f, %eax
80489ec: 81 7c 24 2c 61 01 00 00 cmpl $0x161, 0x2c(%esp) # imm = 0x161
80489f4: 0f 84 82 00 00 00 je 0x8048a7c <phase_3+0x14a>
80489fa: e8 e6 04 00 00 calll 0x8048ee5 <explode_bomb>
80489ff: b8 6f 00 00 00 movl $0x6f, %eax
8048a04: eb 76 jmp 0x8048a7c <phase_3+0x14a>
8048a06: b8 73 00 00 00 movl $0x73, %eax
8048a0b: 81 7c 24 2c 95 03 00 00 cmpl $0x395, 0x2c(%esp) # imm = 0x395
8048a13: 74 67 je 0x8048a7c <phase_3+0x14a>
8048a15: e8 cb 04 00 00 calll 0x8048ee5 <explode_bomb>
8048a1a: b8 73 00 00 00 movl $0x73, %eax
8048a1f: eb 5b jmp 0x8048a7c <phase_3+0x14a>
8048a21: b8 68 00 00 00 movl $0x68, %eax
8048a26: 81 7c 24 2c da 02 00 00 cmpl $0x2da, 0x2c(%esp) # imm = 0x2DA
8048a2e: 74 4c je 0x8048a7c <phase_3+0x14a>
8048a30: e8 b0 04 00 00 calll 0x8048ee5 <explode_bomb>
8048a35: b8 68 00 00 00 movl $0x68, %eax
8048a3a: eb 40 jmp 0x8048a7c <phase_3+0x14a>
8048a3c: b8 6c 00 00 00 movl $0x6c, %eax
8048a41: 81 7c 24 2c a8 02 00 00 cmpl $0x2a8, 0x2c(%esp) # imm = 0x2A8
8048a49: 74 31 je 0x8048a7c <phase_3+0x14a>
8048a4b: e8 95 04 00 00 calll 0x8048ee5 <explode_bomb>
8048a50: b8 6c 00 00 00 movl $0x6c, %eax
8048a55: eb 25 jmp 0x8048a7c <phase_3+0x14a>
8048a57: b8 62 00 00 00 movl $0x62, %eax
8048a5c: 81 7c 24 2c c9 03 00 00 cmpl $0x3c9, 0x2c(%esp) # imm = 0x3C9
8048a64: 74 16 je 0x8048a7c <phase_3+0x14a>
8048a66: e8 7a 04 00 00 calll 0x8048ee5 <explode_bomb>
8048a6b: b8 62 00 00 00 movl $0x62, %eax
8048a70: eb 0a jmp 0x8048a7c <phase_3+0x14a>
8048a72: e8 6e 04 00 00 calll 0x8048ee5 <explode_bomb>
8048a77: b8 6a 00 00 00 movl $0x6a, %eax
8048a7c: 3a 44 24 27 cmpb 0x27(%esp), %al
8048a80: 74 05 je 0x8048a87 <phase_3+0x155>
8048a82: e8 5e 04 00 00 calll 0x8048ee5 <explode_bomb>
8048a87: 83 c4 3c addl $0x3c, %esp
8048a8a: c3 retl
立马变得超级复杂!有好多 explode 指令……