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 指令……
学了 Switch 跳转表的汇编展示的时候,做个对比(这是64位系统):
switch_demo:
cmpl $3, %edi # x > 3?
ja .default # 超出范围 → default(无符号比较,负数也走这里)
movslq %edi, %rdi # x 符号扩展到 64 位(用作表下标)
jmp *.Ltable(,%rdi,8) # 间接跳转:跳到 table[x] 存的地址
.Ltable: # 跳转表,每项 8 字节(64 位地址)
.quad .default # x=0 → default
.quad .case1 # x=1
.quad .case2 # x=2
.quad .case3 # x=3
.case1:
movl $10, %eax
ret
.case2:
movl $20, %eax
ret
.case3:
movl $30, %eax
ret
.default:
movl $0, %eax
ret
其实看到这个就知道这是跳转表了:
804896b: cmpl $0x7, 0x28(%esp) ; 比较 key(第1个整数) 和 7
8048970: ja explode_bomb ; key > 7 → 无符号比较,爆炸
8048976: movl 0x28(%esp), %eax ; eax = key
804897a: jmpl *0x8049360(,%eax,4); 跳到 地址表[key]
这里的意思是就像当时超出范围 default,然后读取 0x28 (%esp),之后间接跳转,去内存地址 0x8049360 + %eax * 4 处读取一个 4 字节的地址,然后跳转到那个地址。
那我们要输入什么呢?
去一开头看看:
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)
读起来会感到疑惑是,0x804933a 究竟有什么?应该是输入的参数?这里我实在是不太看明白,所以问了一下 AI:
关键:数
leal的次数回头看 sscanf 调用前的准备:
8048935: leal 0x2c(%esp), %eax ; 取地址① 8048939: movl %eax, 0x10(%esp) ; 放到第4个参数位置 804893d: leal 0x27(%esp), %eax ; 取地址② 8048941: movl %eax, 0x0c(%esp) ; 放到第3个参数位置 8048945: leal 0x28(%esp), %eax ; 取地址③ 8048949: movl %eax, 0x08(%esp) ; 放到第2个参数位置 804894d: movl $0x804933a, 0x4(%esp) ; 格式字符串(第1个参数) 8048955: movl ..., (%esp) ; sscanf的第0个参数=输入字符串 804895c: calll sscanf
leal= Load Effective Address,就是取一个变量的地址,然后把这个地址作为参数传给 sscanf。sscanf 的签名是:
sscanf(输入字符串, 格式字符串, &变量1, &变量2, &变量3, ...)所以:
参数槽 栈位置 含义 (%esp)第0位 输入字符串(来自 read_line) 0x4(%esp)第1位 格式字符串 $0x804933a0x8(%esp)第2位 &变量1← leal③0xc(%esp)第3位 &变量2← leal②0x10(%esp)第4位 &变量3← leal①3 次
leal= 3 个输出参数 = 格式字符串里有 3 个%...
那格式字符串的具体内容怎么看?我们 GDB 就出来了……
0x804933a 是一个内存地址,在 .rodata(只读数据段)里,光看 .s 文件看不到内容。可以用 gdb 直接查:
(gdb) x/s 0x804933a
0x804933a: "%d %c %d"
(gdb)
那后面也很简单了,就是一个简单的跳转表,gdb也告诉我们输入的是两个数字,一个 char。
直接看第一段接着逻辑的switch表,输入key为0的情况:
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>
比较第一个参数key为0后,下一个参数应该就是char,我们只能用ASCII表查一下0x73是哪个字符,是s。
然后cmp比较第三个参数,就是16进制的0x319,也就是10进制的793。
那么一个答案很快就出来了:
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!
0 s 793
Halfway there!
| key (第1个数) | 期望字符 (ASCII) | 期望第3个数 (十进制) | 完整答案示例 |
|---|---|---|---|
| 0 | 0x73 = 's' | 0x319 = 793 | 0 s 793 |
| 1 | 0x62 = 'b' | 0x3a3 = 931 | 1 b 931 |
| 2 | 0x66 = 'f' | 0x3c4 = 964 | 2 f 964 |
| 3 | 0x6f = 'o' | 0x161 = 353 | 3 o 353 |
| 4 | 0x73 = 's' | 0x395 = 917 | 4 s 917 |
| 5 | 0x68 = 'h' | 0x2da = 730 | 5 h 730 |
| 6 | 0x6c = 'l' | 0x2a8 = 680 | 6 l 680 |
| 7 | 0x62 = 'b' | 0x3c9 = 969 | 7 b 969 |
任选一行都可以过 phase_3 ~。
第四关 递归与二分
先看代码:
08048aec <phase_4>:
8048aec: 83 ec 2c subl $0x2c, %esp
8048aef: 8d 44 24 1c leal 0x1c(%esp), %eax //输入第二个
8048af3: 89 44 24 0c movl %eax, 0xc(%esp)
8048af7: 8d 44 24 18 leal 0x18(%esp), %eax //第一个参数
8048afb: 89 44 24 08 movl %eax, 0x8(%esp)
8048aff: c7 44 24 04 d1 94 04 08 movl $0x80494d1, 0x4(%esp) # imm = 0x80494D1
8048b07: 8b 44 24 30 movl 0x30(%esp), %eax
8048b0b: 89 04 24 movl %eax, (%esp)
8048b0e: e8 ed fa ff ff calll 0x8048600 <__isoc99_sscanf@plt>
8048b13: 83 f8 02 cmpl $0x2, %eax
8048b16: 75 07 jne 0x8048b1f <phase_4+0x33>
8048b18: 83 7c 24 18 0e cmpl $0xe, 0x18(%esp)
8048b1d: 76 05 jbe 0x8048b24 <phase_4+0x38>
8048b1f: e8 c1 03 00 00 calll 0x8048ee5 <explode_bomb>
8048b24: c7 44 24 08 0e 00 00 00 movl $0xe, 0x8(%esp)
8048b2c: c7 44 24 04 00 00 00 00 movl $0x0, 0x4(%esp)
8048b34: 8b 44 24 18 movl 0x18(%esp), %eax
8048b38: 89 04 24 movl %eax, (%esp)
8048b3b: e8 4b ff ff ff calll 0x8048a8b <func4>
8048b40: 83 f8 01 cmpl $0x1, %eax
8048b43: 75 07 jne 0x8048b4c <phase_4+0x60>
8048b45: 83 7c 24 1c 01 cmpl $0x1, 0x1c(%esp)
8048b4a: 74 05 je 0x8048b51 <phase_4+0x65>
8048b4c: e8 94 03 00 00 calll 0x8048ee5 <explode_bomb>
8048b51: 83 c4 2c addl $0x2c, %esp
8048b54: c3 retl
精简了好多!结合上一关的经验,先看我们的imm里面的地址存了个啥……
(gdb) x/s 0x80494d1
0x80494d1: "%d %d"
那看来是输入两个数字了……只有两个触发bomb的条件,而且有很多cmp,甚至还有add,不知道在做什么呢……
同样的看着sscanf,也可以验证是输入两个数字
8048b0e: e8 ed fa ff ff calll 0x8048600 <__isoc99_sscanf@plt>
8048b13: 83 f8 02 cmpl $0x2, %eax
8048b16: 75 07 jne 0x8048b1f <phase_4+0x33> ; Jump if Equal
这里还是同样的比较 2,不过这里用的是 jne, Jump if Equal ,这里还要复习一下标志位的知识。
再看看后面的explode bomb的条件:
8048b18: 83 7c 24 18 0e cmpl $0xe, 0x18(%esp)
8048b1d: 76 05 jbe 0x8048b24 <phase_4+0x38>
8048b1f: e8 c1 03 00 00 calll 0x8048ee5 <explode_bomb>
这里又是一个cmpl,用的是jbe,也就是无符号小于等于才跳转到下一个,0xe就是14,我们有一个输入要小于等于14才行?
然后看第二个explode bomb的条件:
8048b24: c7 44 24 08 0e 00 00 00 movl $0xe, 0x8(%esp) ;传入第一个参数 14,放在esp+8
8048b2c: c7 44 24 04 00 00 00 00 movl $0x0, 0x4(%esp) ; 第二个参数 0,放在esp+4
8048b34: 8b 44 24 18 movl 0x18(%esp), %eax ;传入我们的输入,就是esp,用eax中转一下
8048b38: 89 04 24 movl %eax, (%esp)
8048b3b: e8 4b ff ff ff calll 0x8048a8b <func4>
8048b40: 83 f8 01 cmpl $0x1, %eax ; 检查返回值是否为1,否则bomb
8048b43: 75 07 jne 0x8048b4c <phase_4+0x60>
8048b45: 83 7c 24 1c 01 cmpl $0x1, 0x1c(%esp)
8048b4a: 74 05 je 0x8048b51 <phase_4+0x65>
8048b4c: e8 94 03 00 00 calll 0x8048ee5 <explode_bomb>
做了栈帧调用func4函数,那么func4是做什么的?就在phase_4上面:
func4里还调用了func4,看样子很像是我们常用的递归函数。但是要返回什么呢,什么是结束递归的条件呢?这应该就是解题的重点……还是面向汇编做注释……
08048a8b <func4>:
8048a8b: 56 pushl %esi
8048a8c: 53 pushl %ebx
8048a8d: 83 ec 14 subl $0x14, %esp
8048a90: 8b 54 24 20 movl 0x20(%esp), %edx ;传入了三个参数,应该就是用输入、0、14了,由于是压栈,所以先推入的是输入
8048a94: 8b 44 24 24 movl 0x24(%esp), %eax ; 0
8048a98: 8b 5c 24 28 movl 0x28(%esp), %ebx ; 14
8048a9c: 89 d9 movl %ebx, %ecx ; 设置 ebx
8048a9e: 29 c1 subl %eax, %ecx ;相减法,ecx=ebx-eax,是14-0?
8048aa0: 89 ce movl %ecx, %esi ;设置esi为ecx的值
8048aa2: c1 ee 1f shrl $0x1f, %esi ;逻辑右移,保留符号位
8048aa5: 01 f1 addl %esi, %ecx ; ecx=ebx-eax+esi
8048aa7: d1 f9 sarl %ecx ; 算数右移,ecx/2
8048aa9: 01 c1 addl %eax, %ecx ; ecx= (ebx-eax)/2+eax ,那就是中间值了,为7
8048aab: 39 d1 cmpl %edx, %ecx ;比较输入,这个是否为7?
8048aad: 7e 17 jle 0x8048ac6 <func4+0x3b> ; jump if lower or equal,小于等于7就是下一步?
8048aaf: 83 e9 01 subl $0x1, %ecx ; 传入,ecx=7-1=6
8048ab2: 89 4c 24 08 movl %ecx, 0x8(%esp) ; 传参数为7,也就是原来14的位置
8048ab6: 89 44 24 04 movl %eax, 0x4(%esp) ; 传参数为0
8048aba: 89 14 24 movl %edx, (%esp) ; 传入输入
8048abd: e8 c9 ff ff ff calll 0x8048a8b <func4> ;然后就是递归!看这里好像懂了一点了! 从[0,14]到[0,7]
8048ac2: 01 c0 addl %eax, %eax ;返回结果eax*2
8048ac4: eb 20 jmp 0x8048ae6 <func4+0x5b> ;如果大于等于7就到这一步
8048ac6: b8 00 00 00 00 movl $0x0, %eax ; eax=0?
8048acb: 39 d1 cmpl %edx, %ecx ;看输入是否为7
8048acd: 7d 17 jge 0x8048ae6 <func4+0x5b> ; jump if greater or equal ,看是否依然大于等于,那就直接返回
8048acf: 89 5c 24 08 movl %ebx, 0x8(%esp) ;又来凑输入了,ebx依然为14
8048ad3: 83 c1 01 addl $0x1, %ecx ; ecx=1+ecx=7+1
8048ad6: 89 4c 24 04 movl %ecx, 0x4(%esp) ;[8,14]的范围比较
8048ada: 89 14 24 movl %edx, (%esp)
8048add: e8 a9 ff ff ff calll 0x8048a8b <func4> ; 继续比较
8048ae2: 8d 44 00 01 leal 0x1(%eax,%eax), %eax ; 返回2*eax+1
8048ae6: 83 c4 14 addl $0x14, %esp ; 重制
8048ae9: 5b popl %ebx
8048aea: 5e popl %esi
8048aeb: c3 retl
其实分析这一段函数的逻辑,就可以写一个非常清晰易懂的c代码了,就是一个检测输入的代码:
int func4(int input,int min,int max){
int mid=abs((min-max))/2+min;
if(input<mid) return 2*func4(input,min,mid-1);
if(input>mid) return 2*func4(input,mid+1,max)+1;
return 0;
}
我们要求的是返回值为1 ,那么要我们输出1的话,在0-14的区域内怎么得到1的输出?
递归是一个不断累加的过程,那很简单的从0开始到1,无非就是input在的时候,经过1次运算得到0+1=1.(毕竟我们的结果是abs的,没有负数)。
- 如果 input 在左边,那么上一步得到就是
2*0=0了,那早就return 0了,故input一定在mid的右侧。 - input在右侧,上一步得到的就是
2*0+1=1,刚好结束递归。那么要得到刚好input=mid+1,那input的值只能是8了。
那答案很明显了,输入为8.
但是一开头说了,是两个数字,看看下一个 explode_bomb
8048b45: 83 7c 24 1c 01 cmpl $0x1, 0x1c(%esp)
8048b4a: 74 05 je 0x8048b51 <phase_4+0x65>
8048b4c: e8 94 03 00 00 calll 0x8048ee5 <explode_bomb>
这个就是一个很简单的比较了,jmp if equal,第二个输入为1。
那答案很简单了,就是 8 1。
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!
0 s 793
Halfway there!
8 1
So you got that one. Try this one.
嘿嘿又过了。
附加:看汇编代码的时候,我没看明白的是这一段要做什么:
8048aa0: 89 ce movl %ecx, %esi ;设置esi为ecx的值
8048aa2: c1 ee 1f shrl $0x1f, %esi ;逻辑右移,保留符号位
8048aa5: 01 f1 addl %esi, %ecx ; ecx=ebx-eax+esi?
问了问为什么要做这些,得到了回复,感觉现代编译器也是一个非常神奇的东西……:
这是编译器处理有符号整数除以2时的一个经典技巧,专门用来处理负数的舍入问题。
先看整个计算序列:
8048a9c: movl %ebx, %ecx ; ecx = high 8048a9e: subl %eax, %ecx ; ecx = high - low 8048aa0: movl %ecx, %esi ; esi = high - low (备份) 8048aa2: shrl $0x1f, %esi ; esi = 最高位(符号位) 8048aa5: addl %esi, %ecx ; ecx = (high-low) + 符号位 8048aa7: sarl %ecx ; ecx = ecx >> 1 (算术右移,即 /2) 8048aa9: addl %eax, %ecx ; ecx = low + (high-low)/2 = mid
问题:为什么不能直接
sarl?
sarl(算术右移1位)相当于向下取整的除以2,对负数会出问题:
值 sarl结果真正的 /2(截断向零)633✅-6-3-3✅-1-10❌-3-2-1❌问题出在负奇数:
sarl会向负无穷舍入,而 C 语言的/2应该向零舍入。
修正方法:加上符号位
shrl $0x1f, %esi ; 逻辑右移31位 → 正数得0,负数得1 addl %esi, %ecx ; 负奇数时 +1,让 sarl 结果向零靠 sarl %ecx举例
high-low = -3:
- 不加修正:
-3 >> 1 = -2(错)- 加修正:
(-3 + 1) >> 1 = -2 >> 1 = -1(对)✅
对题目有影响吗?
在 phase_4 里,调用是func4(input, 0, 14),所以high - low的值在0~14之间,永远是正数,esi 始终为0,这一行等于没做任何事。
这就是编译器的保守做法——它不知道运行时数值是不是正数,所以统一生成了这段修正代码。
第五关 位运算操作字符
08048b55 <phase_5>:
8048b55: 53 pushl %ebx
8048b56: 83 ec 18 subl $0x18, %esp
8048b59: 8b 5c 24 20 movl 0x20(%esp), %ebx
8048b5d: 89 1c 24 movl %ebx, (%esp)
8048b60: e8 56 02 00 00 calll 0x8048dbb <string_length>
8048b65: 83 f8 06 cmpl $0x6, %eax
8048b68: 74 05 je 0x8048b6f <phase_5+0x1a>
8048b6a: e8 76 03 00 00 calll 0x8048ee5 <explode_bomb>
8048b6f: ba 00 00 00 00 movl $0x0, %edx
8048b74: b8 00 00 00 00 movl $0x0, %eax
8048b79: 0f b6 0c 03 movzbl (%ebx,%eax), %ecx
8048b7d: 83 e1 0f andl $0xf, %ecx
8048b80: 03 14 8d 80 93 04 08 addl 0x8049380(,%ecx,4), %edx
8048b87: 83 c0 01 addl $0x1, %eax
8048b8a: 83 f8 06 cmpl $0x6, %eax
8048b8d: 75 ea jne 0x8048b79 <phase_5+0x24>
8048b8f: 83 fa 39 cmpl $0x39, %edx
8048b92: 74 05 je 0x8048b99 <phase_5+0x44>
8048b94: e8 4c 03 00 00 calll 0x8048ee5 <explode_bomb>
8048b99: 83 c4 18 addl $0x18, %esp
8048b9c: 5b popl %ebx
8048b9d: c3
一眼看过去看到了一个 string_length 函数,看来是要输入字符串了,长度还要等于6.
然后看后面的代码:
8048b6f: ba 00 00 00 00 movl $0x0, %edx
8048b74: b8 00 00 00 00 movl $0x0, %eax
8048b79: 0f b6 0c 03 movzbl (%ebx,%eax), %ecx
这里把edx和eax置零,感觉像是做什么函数的准备,这个movzbl不太熟悉。
第二眼是看到了一个movzbl,8048b79: 0f b6 0c 03 movzbl (%ebx,%eax), %ecx,感觉好陌生……然后查了一下:
movzbl = Move Zero-extended Byte to Long
拆开来看:
mov = 移动数据
z = zero-extend(零扩展)
b = 源是 byte(1字节)
l = 目标是 long(4字节,即 32位寄存器)
那这里为什么要零扩展,看看这里做了什么:
地址计算:%ebx + %eax(字符串基址 + 当前下标),从那里读 1个字节(一个字符),零扩展后存入 %ecx。
等价于 C:
ecx = (unsigned char) str[eax];
// ↑ 就是取字符串的第 eax 个字符
结合 phase_5 的上下文,这行是在逐个读取输入字符串的每个字符,
那我们看第二个explode_bomb前面:
8048b7d: 83 e1 0f andl $0xf, %ecx ;ecx = ecx & 0xf,每一个位做和0x0f的AND运算?做 00001111,只保留后四位,那其实就是读取第i个字符
8048b80: 03 14 8d 80 93 04 08 addl 0x8049380(,%ecx,4), %edx ;查表的同时给edx做累加,edx=ecx+array[i],edx一开始为0
8048b87: 83 c0 01 addl $0x1, %eax ;i++
8048b8a: 83 f8 06 cmpl $0x6, %eax ;看看i是否为6?
8048b8d: 75 ea jne 0x8048b79 <phase_5+0x24> ;jump if not equal
8048b8f: 83 fa 39 cmpl $0x39, %edx ;比较edx是否是0x39?也就是48+9=57
8048b92: 74 05 je 0x8048b99 <phase_5+0x44>
8048b94: e8 4c 03 00 00 calll 0x8048ee5 <explode_bomb>
我一开始没看明白这个addl是干什么的,只好求助了一下:
movl src, %edx → edx = 表[ecx](替换,每次覆盖)
addl src, %edx → edx = edx + 表[ecx](累加,保留历史)
使用addl就是查表了,wow……那用gdb看看这个0x8049380(,%ecx,4)里面有什么吧:
(gdb) x/s 0x8049380
0x8049380 <array.3144>: "\002"
# 感觉没读出来什么东西,x/s把内存当字符串读取,但这张表存的是digit(每个数据占位是4bit,可能是int),得用x/16wd
(gdb) x/16wd 0x8049380
0x8049380 <array.3144>: 2 10 6 1
0x8049390 <array.3144+16>: 12 16 9 3
0x80493a0 <array.3144+32>: 4 7 14 5
0x80493b0 <array.3144+48>: 11 8 15 13
(gdb)
刚好有16个整数……
然后这个逻辑做类C代码是:
int sum=0;
for(int i=0;i<6;i++){
int index=input[i]&0xf;
sum=sum+table[index];
}
if (sum==57) return true;
啊,所以就是,我们输入的字符串,每个字符的ascii码的最后一个数,转换成二进制然后进入映射表里看?
比如我们输入'a'(0x61),查表查的就是第2个数也就是table[1],输入's'(0x73)查表查的就是第4个数table[3],输入0x?f结尾的就是第16个数?table[15]?
我们要找到6个字符,然后它们这样的映射关系得到的数为57?感觉答案显而易见了。
关于字符,有ASCII码,去找一下ASCII码的表,一般都是这样显示的:
| 字符 | HEX | 低4位 | 字符 | HEX | 低4位 |
|---|---|---|---|---|---|
| a | 0x61 | 1 | n | 0x6e | e |
| b | 0x62 | 2 | o | 0x6f | f |
| c | 0x63 | 3 | p | 0x70 | 0 |
| d | 0x64 | 4 | q | 0x71 | 1 |
| e | 0x65 | 5 | r | 0x72 | 2 |
| f | 0x66 | 6 | s | 0x73 | 3 |
| g | 0x67 | 7 | t | 0x74 | 4 |
| h | 0x68 | 8 | u | 0x75 | 5 |
| i | 0x69 | 9 | v | 0x76 | 6 |
| j | 0x6a | a | w | 0x77 | 7 |
| k | 0x6b | b | x | 0x78 | 8 |
| l | 0x6c | c | y | 0x79 | 9 |
| m | 0x6d | d | z | 0x7a | a |
注意:
p(0x70) 和a(0x61)、q和b……低4位会循环重复。所以每个下标对应多个可用字符。
我们随便找个57=10*5+7,对应的表里就是,5个table[1],一个table[9]
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!
0 s 793
Halfway there!
8 1
So you got that one. Try this one.
aaaaai
Good work! On to the next...
第六关 链表排序
题目:
08048b9e <phase_6>:
8048b9e: 56 pushl %esi
8048b9f: 53 pushl %ebx
8048ba0: 83 ec 44 subl $0x44, %esp
8048ba3: 8d 44 24 10 leal 0x10(%esp), %eax
8048ba7: 89 44 24 04 movl %eax, 0x4(%esp)
8048bab: 8b 44 24 50 movl 0x50(%esp), %eax
8048baf: 89 04 24 movl %eax, (%esp)
8048bb2: e8 55 03 00 00 calll 0x8048f0c <read_six_numbers>
8048bb7: be 00 00 00 00 movl $0x0, %esi ;esi置0,
8048bbc: 8b 44 b4 10 movl 0x10(%esp,%esi,4), %eax
8048bc0: 83 e8 01 subl $0x1, %eax
8048bc3: 83 f8 05 cmpl $0x5, %eax
8048bc6: 76 05 jbe 0x8048bcd <phase_6+0x2f>
8048bc8: e8 18 03 00 00 calll 0x8048ee5 <explode_bomb>
8048bcd: 83 c6 01 addl $0x1, %esi
8048bd0: 83 fe 06 cmpl $0x6, %esi
8048bd3: 75 07 jne 0x8048bdc <phase_6+0x3e>
8048bd5: bb 00 00 00 00 movl $0x0, %ebx
8048bda: eb 38 jmp 0x8048c14 <phase_6+0x76>
8048bdc: 89 f3 movl %esi, %ebx
8048bde: 8b 44 9c 10 movl 0x10(%esp,%ebx,4), %eax
8048be2: 39 44 b4 0c cmpl %eax, 0xc(%esp,%esi,4)
8048be6: 75 05 jne 0x8048bed <phase_6+0x4f>
8048be8: e8 f8 02 00 00 calll 0x8048ee5 <explode_bomb>
8048bed: 83 c3 01 addl $0x1, %ebx
8048bf0: 83 fb 05 cmpl $0x5, %ebx
8048bf3: 7e e9 jle 0x8048bde <phase_6+0x40>
8048bf5: eb c5 jmp 0x8048bbc <phase_6+0x1e>
8048bf7: 8b 52 08 movl 0x8(%edx), %edx
8048bfa: 83 c0 01 addl $0x1, %eax
8048bfd: 39 c8 cmpl %ecx, %eax
8048bff: 75 f6 jne 0x8048bf7 <phase_6+0x59>
8048c01: eb 05 jmp 0x8048c08 <phase_6+0x6a>
8048c03: ba 1c b1 04 08 movl $0x804b11c, %edx # imm = 0x804B11C
8048c08: 89 54 b4 28 movl %edx, 0x28(%esp,%esi,4)
8048c0c: 83 c3 01 addl $0x1, %ebx
8048c0f: 83 fb 06 cmpl $0x6, %ebx
8048c12: 74 17 je 0x8048c2b <phase_6+0x8d>
8048c14: 89 de movl %ebx, %esi
8048c16: 8b 4c 9c 10 movl 0x10(%esp,%ebx,4), %ecx
8048c1a: 83 f9 01 cmpl $0x1, %ecx
8048c1d: 7e e4 jle 0x8048c03 <phase_6+0x65>
8048c1f: b8 01 00 00 00 movl $0x1, %eax
8048c24: ba 1c b1 04 08 movl $0x804b11c, %edx # imm = 0x804B11C
8048c29: eb cc jmp 0x8048bf7 <phase_6+0x59>
8048c2b: 8b 5c 24 28 movl 0x28(%esp), %ebx
8048c2f: 8d 44 24 2c leal 0x2c(%esp), %eax
8048c33: 8d 74 24 40 leal 0x40(%esp), %esi
8048c37: 89 d9 movl %ebx, %ecx
8048c39: 8b 10 movl (%eax), %edx
8048c3b: 89 51 08 movl %edx, 0x8(%ecx)
8048c3e: 83 c0 04 addl $0x4, %eax
8048c41: 39 f0 cmpl %esi, %eax
8048c43: 74 04 je 0x8048c49 <phase_6+0xab>
8048c45: 89 d1 movl %edx, %ecx
8048c47: eb f0 jmp 0x8048c39 <phase_6+0x9b>
8048c49: c7 42 08 00 00 00 00 movl $0x0, 0x8(%edx)
8048c50: be 05 00 00 00 movl $0x5, %esi
8048c55: 8b 43 08 movl 0x8(%ebx), %eax
8048c58: 8b 00 movl (%eax), %eax
8048c5a: 39 03 cmpl %eax, (%ebx)
8048c5c: 7e 05 jle 0x8048c63 <phase_6+0xc5>
8048c5e: e8 82 02 00 00 calll 0x8048ee5 <explode_bomb>
8048c63: 8b 5b 08 movl 0x8(%ebx), %ebx
8048c66: 83 ee 01 subl $0x1, %esi
8048c69: 75 ea jne 0x8048c55 <phase_6+0xb7>
8048c6b: 83 c4 44 addl $0x44, %esp
8048c6e: 5b popl %ebx
8048c6f: 5e popl %esi
8048c70: c3 retl
看这题目的复杂程度和代码量感觉惨绝人寰,先害怕一下……
首先看到的还是<read_six_numbers>函数的调用,看样子我们要输入的是6个数字。
粗读汇编,实在是好累了……靠IDA pro看了看逻辑:
① 输入 6 个数字(是 1~6 的一个排列,不能重复)
↓
② 内存里有一个链表,有 6 个节点,每个节点有一个值
↓
③ 用你输入的顺序,把链表节点重新排列
↓
④ 验证:重排后的链表,节点值必须从大到小排列
那就是链表排序?我们直接看注释出来的链表 0x804B11C:
(gdb) x/24wd 0x804b11c
0x804b11c <node1>: 896 1 134525224 64
0x804b12c <node2+4>: 2 134525236 926 3
0x804b13c <node3+8>: 134525248 884 4 134525260
0x804b14c <node5>: 359 5 134525272 743
0x804b15c <node6+4>: 6 0 1032 0
0x804b16c: 0 0 0 0
(gdb)
好,解析这个 gdb 输出。每个节点是 12字节(3个word),但 gdb 每行显示 4个word(16字节),所以边界是错位的。对齐一下:
| 节点 | 地址 | 值 | field2 | next指针 |
|---|---|---|---|---|
| node1 | 0x804b11c | 896 | 1 | → 0x804b128 |
| node2 | 0x804b128 | 64 | 2 | → 0x804b134 |
| node3 | 0x804b134 | 926 | 3 | → 0x804b140 |
| node4 | 0x804b140 | 884 | 4 | → 0x804b14c |
| node5 | 0x804b14c | 359 | 5 | → 0x804b158 |
| node6 | 0x804b158 | 743 | 6 | → NULL |
升序还是降序?去看验证代码
现在去看 phase_6 的最后几行:
8048c5a: cmpl %eax, (%ebx) ; 当前节点值 和 下一节点值 比较
8048c5c: jle 0x8048c63 ; 如果 当前 <= 下一个 → 安全,继续
8048c5e: calll explode_bomb ; 如果 当前 > 下一个 → 爆炸
jle = 如果当前 ≤ 下一个才安全。
💡 也就是说链表重排后必须是从小到大排列。
现在有了6个值:64, 359, 743, 884, 896, 926
把这6个值从小到大排,对应的是哪几号节点?输入的就是这个节点编号的顺序……
那很简单了,就是: 2 5 6 4 1 3
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!
0 s 793
Halfway there!
8 1
So you got that one. Try this one.
aaaaai
Good work! On to the next...
2 5 6 4 1 3
Congratulations! You've defused the bomb!
终于做完啦!!
Secret Phase 二叉搜索树遍历
在看 main 函数的时候,发现了一个很有趣的事情:
8048807: e8 b4 00 00 00 calll 0x80488c0 <phase_1>
804880c: e8 45 08 00 00 calll 0x8049056 <phase_defused>
8048811: c7 04 24 94 92 04 08 movl $0x8049294, (%esp) # imm = 0x8049294
8048818: e8 a3 fd ff ff calll 0x80485c0 <puts@plt>
804881d: e8 3a 07 00 00 calll 0x8048f5c <read_line>
8048822: 89 04 24 movl %eax, (%esp)
8048825: e8 ba 00 00 00 calll 0x80488e4 <phase_2>
804882a: e8 27 08 00 00 calll 0x8049056 <phase_defused>
804882f: c7 04 24 e1 91 04 08 movl $0x80491e1, (%esp) # imm = 0x80491E1
8048836: e8 85 fd ff ff calll 0x80485c0 <puts@plt>
804883b: e8 1c 07 00 00 calll 0x8048f5c <read_line>
8048840: 89 04 24 movl %eax, (%esp)
8048843: e8 ea 00 00 00 calll 0x8048932 <phase_3>
8048848: e8 09 08 00 00 calll 0x8049056 <phase_defused>
804884d: c7 04 24 ff 91 04 08 movl $0x80491ff, (%esp) # imm = 0x80491FF
8048854: e8 67 fd ff ff calll 0x80485c0 <puts@plt>
8048859: e8 fe 06 00 00 calll 0x8048f5c <read_line>
804885e: 89 04 24 movl %eax, (%esp)
8048861: e8 86 02 00 00 calll 0x8048aec <phase_4>
8048866: e8 eb 07 00 00 calll 0x8049056 <phase_defused>
804886b: c7 04 24 c0 92 04 08 movl $0x80492c0, (%esp) # imm = 0x80492C0
8048872: e8 49 fd ff ff calll 0x80485c0 <puts@plt>
8048877: e8 e0 06 00 00 calll 0x8048f5c <read_line>
804887c: 89 04 24 movl %eax, (%esp)
804887f: e8 d1 02 00 00 calll 0x8048b55 <phase_5>
8048884: e8 cd 07 00 00 calll 0x8049056 <phase_defused>
8048889: c7 04 24 0e 92 04 08 movl $0x804920e, (%esp) # imm = 0x804920E
8048890: e8 2b fd ff ff calll 0x80485c0 <puts@plt>
8048895: e8 c2 06 00 00 calll 0x8048f5c <read_line>
804889a: 89 04 24 movl %eax, (%esp)
804889d: e8 fc 02 00 00 calll 0x8048b9e <phase_6>
80488a2: e8 af 07 00 00 calll 0x8049056 <phase_defused>
为什么每个函数调用的后面接着一个0x8049056 <phase_defused>?
08049056 <phase_defused>:
8049056: 81 ec 8c 00 00 00 subl $0x8c, %esp
804905c: 65 a1 14 00 00 00 movl %gs:0x14, %eax
8049062: 89 44 24 7c movl %eax, 0x7c(%esp)
8049066: 31 c0 xorl %eax, %eax
8049068: 83 3d a8 b3 04 08 06 cmpl $0x6, 0x804b3a8
804906f: 75 72 jne 0x80490e3 <phase_defused+0x8d>
8049071: 8d 44 24 2c leal 0x2c(%esp), %eax
8049075: 89 44 24 10 movl %eax, 0x10(%esp)
8049079: 8d 44 24 28 leal 0x28(%esp), %eax
804907d: 89 44 24 0c movl %eax, 0xc(%esp)
8049081: 8d 44 24 24 leal 0x24(%esp), %eax
8049085: 89 44 24 08 movl %eax, 0x8(%esp)
8049089: c7 44 24 04 2b 95 04 08 movl $0x804952b, 0x4(%esp) # imm = 0x804952B 进行输入
8049091: c7 04 24 b0 b4 04 08 movl $0x804b4b0, (%esp) # imm = 0x804B4B0
8049098: e8 63 f5 ff ff calll 0x8048600 <__isoc99_sscanf@plt>
804909d: 83 f8 03 cmpl $0x3, %eax ; sscanf了,要输入三个数
80490a0: 75 35 jne 0x80490d7 <phase_defused+0x81>
80490a2: c7 44 24 04 34 95 04 08 movl $0x8049534, 0x4(%esp) # imm = 0x8049534 比较字符串
80490aa: 8d 44 24 2c leal 0x2c(%esp), %eax
80490ae: 89 04 24 movl %eax, (%esp)
80490b1: e8 24 fd ff ff calll 0x8048dda <strings_not_equal>
80490b6: 85 c0 testl %eax, %eax
80490b8: 75 1d jne 0x80490d7 <phase_defused+0x81>
80490ba: c7 04 24 f8 93 04 08 movl $0x80493f8, (%esp) # imm = 0x80493F8
80490c1: e8 fa f4 ff ff calll 0x80485c0 <puts@plt>
80490c6: c7 04 24 20 94 04 08 movl $0x8049420, (%esp) # imm = 0x8049420
80490cd: e8 ee f4 ff ff calll 0x80485c0 <puts@plt>
80490d2: e8 eb fb ff ff calll 0x8048cc2 <secret_phase>
80490d7: c7 04 24 58 94 04 08 movl $0x8049458, (%esp) # imm = 0x8049458
80490de: e8 dd f4 ff ff calll 0x80485c0 <puts@plt>
80490e3: 8b 44 24 7c movl 0x7c(%esp), %eax
80490e7: 65 33 05 14 00 00 00 xorl %gs:0x14, %eax
80490ee: 74 05 je 0x80490f5 <phase_defused+0x9f>
80490f0: e8 ab f4 ff ff calll 0x80485a0 <__stack_chk_fail@plt>
80490f5: 81 c4 8c 00 00 00 addl $0x8c, %esp
80490fb: c3 retl
80490fc: 66 90 nop
80490fe: 66 90 nop
直接看imm的地址吧:
(gdb) x/s 0x8049056
0x8049056 <phase_defused>: "\201\354\214"
(gdb) x/s 0x804952b
0x804952b: "%d %d %s"
(gdb) x/s 0x8049534
0x8049534: "DrEvil"
(gdb)
哈,在那个输入2个数字的 phase_4 输入 DrEvil 解锁神秘小关卡……
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!
0 s 793
Halfway there!
8 1 DrEvil
So you got that one. Try this one.
aaaaai
Good work! On to the next...
2 5 6 4 1 3
Curses, you've found the secret phase!
But finding it and solving it are quite different...
PS:其实用了IDA pro……(是的我实在是懒得看了)
unsigned int phase_defused()
{
char v1; // [esp+24h] [ebp-68h] BYREF
char v2; // [esp+28h] [ebp-64h] BYREF
_BYTE v3[80]; // [esp+2Ch] [ebp-60h] BYREF
unsigned int v4; // [esp+7Ch] [ebp-10h]
v4 = __readgsdword(0x14u);
if ( num_input_strings == 6 )
{
if ( __isoc99_sscanf(&unk_804B4B0, "%d %d %s", &v1, &v2, v3) == 3 && !strings_not_equal(v3, "DrEvil") )
{
puts("Curses, you've found the secret phase!");
puts("But finding it and solving it are quite different...");
secret_phase();
}
puts("Congratulations! You've defused the bomb!");
}
return __readgsdword(0x14u) ^ v4;
}
那secret_phase是什么呢?
08048cc2 <secret_phase>:
8048cc2: 53 pushl %ebx
8048cc3: 83 ec 18 subl $0x18, %esp
8048cc6: e8 91 02 00 00 calll 0x8048f5c <read_line>
8048ccb: c7 44 24 08 0a 00 00 00 movl $0xa, 0x8(%esp)
8048cd3: c7 44 24 04 00 00 00 00 movl $0x0, 0x4(%esp)
8048cdb: 89 04 24 movl %eax, (%esp)
8048cde: e8 4d f9 ff ff calll 0x8048630 <strtol@plt>
8048ce3: 89 c3 movl %eax, %ebx
8048ce5: 8d 40 ff leal -0x1(%eax), %eax
8048ce8: 3d e8 03 00 00 cmpl $0x3e8, %eax # imm = 0x3E8
8048ced: 76 05 jbe 0x8048cf4 <secret_phase+0x32>
8048cef: e8 f1 01 00 00 calll 0x8048ee5 <explode_bomb>
8048cf4: 89 5c 24 04 movl %ebx, 0x4(%esp)
8048cf8: c7 04 24 68 b0 04 08 movl $0x804b068, (%esp) # imm = 0x804B068
8048cff: e8 6d ff ff ff calll 0x8048c71 <fun7>
8048d04: 83 f8 05 cmpl $0x5, %eax
8048d07: 74 05 je 0x8048d0e <secret_phase+0x4c>
8048d09: e8 d7 01 00 00 calll 0x8048ee5 <explode_bomb>
8048d0e: c7 04 24 14 93 04 08 movl $0x8049314, (%esp) # imm = 0x8049314
8048d15: e8 a6 f8 ff ff calll 0x80485c0 <puts@plt>
8048d1a: e8 37 03 00 00 calll 0x8049056 <phase_defused>
8048d1f: 83 c4 18 addl $0x18, %esp
8048d22: 5b popl %ebx
8048d23: c3 retl
8048d24: 66 90 nop
8048d26: 66 90 nop
8048d28: 66 90 nop
8048d2a: 66 90 nop
8048d2c: 66 90 nop
8048d2e: 66 90 nop
还调用了func7呢,而且readline,看来是输入一句话。
看看进入func7,是啥玩意:
int fun7(_DWORD *a1, int a2)
{
int result; // eax
if ( !a1 )
return -1;
if ( *a1 > a2 )
return 2 * fun7(a1[1], a2);
result = 0;
if ( *a1 != a2 )
return 2 * fun7(a1[2], a2) + 1;
return result;
}
这不就是那个phase_4的递归吗?看最后要 8048d04: 83 f8 05 cmpl $0x5, %eax 递归的结果为5,转换为二进制就是101。
(gdb) x/3wd 0x804b068
0x804b068 <n1>: 36 134525044 134525056 #这里是个二叉树的表示,地址,不是digit,分别是根结点,左节点,右节点,我们要走 1 0 1,也就是 右 左 右,找到对应的数就行
(gdb) x/3wd 134525056 # 根结点往右
0x804b080 <n22>: 50 134525080 134525104
(gdb) x/3wd 134525080 # 节点往左
0x804b098 <n33>: 45 134525116 134525188
(gdb) x/3wd 134525188 # 节点往右
0x804b104 <n46>: 47 0 0
(gdb)
那刚好就是36 50 45 47 吗?我们要输入的是target,也就是递归的起点位置,那就是:
- target 要 > 36(才走右到 n22)
- target 要 < 50(才走左到 n33)
- target 要 > 45(才走右到 n46)
- n46 的值 = 47,两子节点都是 NULL
所以答案为 47.
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!
0 s 793
Halfway there!
8 1 DrEvil
So you got that one. Try this one.
aaaaai
Good work! On to the next...
2 5 6 4 1 3
Curses, you've found the secret phase!
But finding it and solving it are quite different...
47
Wow! You've defused the secret stage!
Congratulations! You've defused the bomb!
答案
有secret_phase的:
I was trying to give Tina Fey more material.
1 2 4 8 16 32
0 s 793
8 1 DrEvil
aaaaai
2 5 6 4 1 3
47
没有的:
I was trying to give Tina Fey more material.
1 2 4 8 16 32
0 s 793
8 1
aaaaai
2 5 6 4 1 3
感想
感谢GDB,我可以不要IDA PRO,可以不要AI,但是不能没有GDB。不然我的大脑会炸掉的。