[lab]csapp-bomb
bomb
读取六行输入, 内部的checker是隐藏的, 根据可执行文件找出正确的六行输入.
准备工作:
objdump, gdb
首先用 objdump -s 打出所有内容
然后 objdump -d 打出汇编代码
注意不同机器打印出来的地址和指令可能不一样
https://godbolt.org/ : 翻译汇编
1
0000000000400ee0 <phase_1>:
400ee0: 48 83 ec 08 sub $0x8,%rsp
400ee4: be 00 24 40 00 mov $0x402400,%esi
400ee9: e8 4a 04 00 00 callq 401338 <strings_not_equal>
400eee: 85 c0 test %eax,%eax
400ef0: 74 05 je 400ef7 <phase_1+0x17>
400ef2: e8 43 05 00 00 callq 40143a <explode_bomb>
400ef7: 48 83 c4 08 add $0x8,%rsp
400efb: c3 retq
内存 0x402400 所在的字符串 和 输入字符串做比较, 查看 .rodata 对应数据即可
2
0000000000400efc <phase_2>:
400efc: 55 push %rbp
400efd: 53 push %rbx
400efe: 48 83 ec 28 sub $0x28,%rsp # 分配栈空间
400f02: 48 89 e6 mov %rsp,%rsi
400f05: e8 52 05 00 00 callq 40145c <read_six_numbers> # 调用函数, 读取到6个整数
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) # 第一个整数应该为1
400f0e: 74 20 je 400f30 <phase_2+0x34>
400f10: e8 25 05 00 00 callq 40143a <explode_bomb>
400f15: eb 19 jmp 400f30 <phase_2+0x34>
400f17: 8b 43 fc mov -0x4(%rbx),%eax
400f1a: 01 c0 add %eax,%eax
400f1c: 39 03 cmp %eax,(%rbx)
400f1e: 74 05 je 400f25 <phase_2+0x29>
400f20: e8 15 05 00 00 callq 40143a <explode_bomb>
400f25: 48 83 c3 04 add $0x4,%rbx
400f29: 48 39 eb cmp %rbp,%rbx
400f2c: 75 e9 jne 400f17 <phase_2+0x1b>
400f2e: eb 0c jmp 400f3c <phase_2+0x40>
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp
400f3a: eb db jmp 400f17 <phase_2+0x1b>
400f3c: 48 83 c4 28 add $0x28,%rsp
400f40: 5b pop %rbx
400f41: 5d pop %rbp
400f42: c3 retq
先在分配空间读取6个整数, 判断第一个整数为1, 后续是一个循环, 后面的整数应该是前面整数的两倍, 因此得到答案
3
0000000000400f43 <phase_3>:
400f43: 48 83 ec 18 sub $0x18,%rsp
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
400f51: be cf 25 40 00 mov $0x4025cf,%esi
400f56: b8 00 00 00 00 mov $0x0,%eax
400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt>
400f60: 83 f8 01 cmp $0x1,%eax
400f63: 7f 05 jg 400f6a <phase_3+0x27>
400f65: e8 d0 04 00 00 callq 40143a <explode_bomb>
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp)
400f6f: 77 3c ja 400fad <phase_3+0x6a>
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)
400f7c: b8 cf 00 00 00 mov $0xcf,%eax
400f81: eb 3b jmp 400fbe <phase_3+0x7b>
400f83: b8 c3 02 00 00 mov $0x2c3,%eax
400f88: eb 34 jmp 400fbe <phase_3+0x7b>
400f8a: b8 00 01 00 00 mov $0x100,%eax
400f8f: eb 2d jmp 400fbe <phase_3+0x7b>
400f91: b8 85 01 00 00 mov $0x185,%eax
400f96: eb 26 jmp 400fbe <phase_3+0x7b>
400f98: b8 ce 00 00 00 mov $0xce,%eax
400f9d: eb 1f jmp 400fbe <phase_3+0x7b>
400f9f: b8 aa 02 00 00 mov $0x2aa,%eax
400fa4: eb 18 jmp 400fbe <phase_3+0x7b>
400fa6: b8 47 01 00 00 mov $0x147,%eax
400fab: eb 11 jmp 400fbe <phase_3+0x7b>
400fad: e8 88 04 00 00 callq 40143a <explode_bomb>
400fb2: b8 00 00 00 00 mov $0x0,%eax
400fb7: eb 05 jmp 400fbe <phase_3+0x7b>
400fb9: b8 37 01 00 00 mov $0x137,%eax
400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax
400fc2: 74 05 je 400fc9 <phase_3+0x86>
400fc4: e8 71 04 00 00 callq 40143a <explode_bomb>
400fc9: 48 83 c4 18 add $0x18,%rsp
400fcd: c3 retq
首先 sscanf, 要求输入超过1个数(有用的数字就两个)
判断第一个数小于8,
关键是 jmpq *0x402470(,%rax,8) 在静态区中放置了一个跳转表, 解析内存可以得到
# number 0 : : dist : dist_eax(number1)
# 0 : 402470: 400f7c : 0xcf
# 1 : 402478: 400fb9 : 0x137
# 2 : 402480: 400f83 : 0x2c3
# 3 : 402488: 400f8a : 0x100
# 4 : 402490: 400f91 : 0x185
# 5 : 402498: 400f98 : 0xce
# 6 : 4024A0: 400f9f : 0x2aa
# 7 : 4024A8: 400fa6 : 0x147
按照对应关系输入任意一组即可.
4
000000000040100c <phase_4>:
40100c: 48 83 ec 18 sub $0x18,%rsp
401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
40101a: be cf 25 40 00 mov $0x4025cf,%esi
40101f: b8 00 00 00 00 mov $0x0,%eax
401024: e8 c7 fb ff ff callq 400bf0 <__isoc99_sscanf@plt> # 输入两个数
401029: 83 f8 02 cmp $0x2,%eax
40102c: 75 07 jne 401035 <phase_4+0x29>
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp) #第一个数要小于 e
401033: 76 05 jbe 40103a <phase_4+0x2e>
401035: e8 00 04 00 00 callq 40143a <explode_bomb>
40103a: ba 0e 00 00 00 mov $0xe,%edx
40103f: be 00 00 00 00 mov $0x0,%esi
401044: 8b 7c 24 08 mov 0x8(%rsp),%edi
401048: e8 81 ff ff ff callq 400fce <func4> # 调用 func4
40104d: 85 c0 test %eax,%eax
40104f: 75 07 jne 401058 <phase_4+0x4c> # 应该返回0
401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp) # 第二个数是0
401056: 74 05 je 40105d <phase_4+0x51>
401058: e8 dd 03 00 00 callq 40143a <explode_bomb>
40105d: 48 83 c4 18 add $0x18,%rsp
401061: c3 retq
首先输入两个数, 且第一个数小于等于e, 第二个数为0
以第一个数为参数, 调用 func4, 且应该返回0, 继续观察func4
这里使用到的寄存器比较多, 进行一下简化注释
0000000000400fce <func4>:
# eax: ret
# ecx: local_1
# edx: local_3 = e
# esi: local_2 = 0
# edi: param_1
400fce: 48 83 ec 08 sub $0x8,%rsp
400fd2: 89 d0 mov %edx,%eax
400fd4: 29 f0 sub %esi,%eax # ret = local3 - local2
400fd6: 89 c1 mov %eax,%ecx
400fd8: c1 e9 1f shr $0x1f,%ecx # local_1 = ret >> 0x31
400fdb: 01 c8 add %ecx,%eax # ret += local_1
400fdd: d1 f8 sar %eax # ret >>=1
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx # local_1 = local2 + ret
400fe2: 39 f9 cmp %edi,%ecx
400fe4: 7e 0c jle 400ff2 <func4+0x24> # param_1 >= local_1 then jump
400fe6: 8d 51 ff lea -0x1(%rcx),%edx # local_3 = local_1 - 1
400fe9: e8 e0 ff ff ff callq 400fce <func4>
400fee: 01 c0 add %eax,%eax # ret = ret + ret
400ff0: eb 15 jmp 401007 <func4+0x39>
400ff2: b8 00 00 00 00 mov $0x0,%eax
400ff7: 39 f9 cmp %edi,%ecx # param_1 <= local_1
400ff9: 7d 0c jge 401007 <func4+0x39>
400ffb: 8d 71 01 lea 0x1(%rcx),%esi # local_2 = local_1+1
400ffe: e8 cb ff ff ff callq 400fce <func4>
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax # ret = ret + ret + 1
401007: 48 83 c4 08 add $0x8,%rsp
40100b: c3 retq
标记一波还是没梳理出开始一顿操作的逻辑, 但ret总会被赋上某些值, 但我们想要ret为0, 可以在gdb里走一遍逻辑, 发现第一次进入时
local_1 在与 param_1 比较前总等于7, 且在 400ff2 将 ret 设置成了0, local_1 与 param_1 进行了两次比较, 如果直接相等情况下,就可以在这里直接把返回ret 返回出去, 因此 7 就是一个正确答案.
那么其他数还有没有可能是答案呢 , 观察 local_1 和 param_1 比较失败后 400ffe 递归调用一次, 然后 ret = ret * 2 + 1, 因此走这条路径无法返回0, 即 param_1 > 7 没有答案. param_1 < 7 时, 会在 400fe9 递归, 此时local_3 = local_1 - 1 = 6. local_2 = 0, 即初始状态从 e,0 变成了 6,0. 类比下得到答案是3, 或者继续向下递归, 可以得到 1, 0 都是正确的.
5
这里直接给出简化好的汇编代码
phase_5:
mov %rdi,%rbx
mov %fs:0x28,%rax
mov %rax,0x18(%rsp)
xor %eax,%eax
callq string_length
cmp $0x6,%eax
je jump_1
callq explode_bomb
jump_1:
mov $0x0,%eax
loop_start:
movzbl (%rbx,%rax,1),%ecx
mov %cl,(%rsp)
mov (%rsp),%rdx
and $0xf,%edx
movzbl 0x4024b0(%rdx),%edx
mov %dl,0x10(%rsp,%rax,1)
add $0x1,%rax
cmp $0x6,%rax
jne loop_start
movb $0x0,0x16(%rsp)
mov $0x40245e,%esi
lea 0x10(%rsp),%rdi
callq strings_not_equal
很明显是做一个循环, 对输入字符做了一个处理, 然后与特定字符串比较, 查内存 0x40245e 对应的字符串是 flyers, 0x4024b0 是字符串对照表 maduiersnfotvbyl , 我们输入对应的ascii字符求余0xf后,经过对照表转换应该得到 flyers, 这里输入一个合法的字符串即可
6
阶段6的汇编代码有些长, 我们尝试将其翻译成c语音去理解.
void phase_6() {
int num[20];
int i;
int ret;
read_six_number(num);
for (int i = 0; i < 6; ++i) {
if (num[i] - 1 > 5) {
explode_bomb();
}
for (int j = i+1; j<= 5; ++j) {
if (num[i] == num[j]) {
explode_bomb();
}
}
}
for (int i=0;i<6;++i) {
num[i] = 7 - num[i];
}
// 2 1 6 5 4 3
int table[] = {0x14c, 0xa8, 0x39c, 0x2b3, 0x1dd, 0x1bb};
for (int i= 0;i<6;++i) {
int offset = 0;
int tmp = num[i];
if (num[i] > 1) {
offset += (num[i] - 1);
}
num[2*i + 8] = table[offset];
}
for (int i=0;i<6;++i) {
// non-op
num[i*2+8] = num[i*2+8];
}
for (int i=0;i<5;++i) {
if (num[i*2+8] <= num[(i+1)*2+8]) {
explode_bomb();
}
}
}
以上代码是我根据汇编手动翻译的, 具体细节有可能差别, 真正的代码可能是链表, 内存中的数据就是以链表形式存储的, 但大体逻辑是一致的.
分为3个部分, 1. 输入处理, 2. 映射内存, 3. 比较
我们想要一个下标数组, 他可以通过内存映射得到一个递减的序列, 根据查出的内存数据构建即可.
secret_phase
看到大佬的文章发现还有一层秘密阶段, secret_phase
它通过 phase_defused, 进入, 经过断点和看内存可以发现 在第四阶段后加上 DrEvil 即可进入
汇编代码如下
0000000000401242 <secret_phase>:
401242: 53 push %rbx
401243: e8 56 02 00 00 callq 40149e <read_line>
401248: ba 0a 00 00 00 mov $0xa,%edx
40124d: be 00 00 00 00 mov $0x0,%esi
401252: 48 89 c7 mov %rax,%rdi
401255: e8 76 f9 ff ff callq 400bd0 <strtol@plt>
40125a: 48 89 c3 mov %rax,%rbx
40125d: 8d 40 ff lea -0x1(%rax),%eax
401260: 3d e8 03 00 00 cmp $0x3e8,%eax
401265: 76 05 jbe 40126c <secret_phase+0x2a>
401267: e8 ce 01 00 00 callq 40143a <explode_bomb>
40126c: 89 de mov %ebx,%esi
40126e: bf f0 30 60 00 mov $0x6030f0,%edi
401273: e8 8c ff ff ff callq 401204 <fun7>
401278: 83 f8 02 cmp $0x2,%eax
40127b: 74 05 je 401282 <secret_phase+0x40>
40127d: e8 b8 01 00 00 callq 40143a <explode_bomb>
401282: bf 38 24 40 00 mov $0x402438,%edi
401287: e8 84 f8 ff ff callq 400b10 <puts@plt>
40128c: e8 33 03 00 00 callq 4015c4 <phase_defused>
401291: 5b pop %rbx
它将你输入转换为一个整数, 然后调用fun7, 并为它传入 0x6030f0. 并期望 fun7 返回 2. 我们先来看 fun7
0000000000401204 <fun7>:
401204: 48 83 ec 08 sub $0x8,%rsp
401208: 48 85 ff test %rdi,%rdi
40120b: 74 2b je 401238 <fun7+0x34>
40120d: 8b 17 mov (%rdi),%edx
40120f: 39 f2 cmp %esi,%edx
401211: 7e 0d jle 401220 <fun7+0x1c>
401213: 48 8b 7f 08 mov 0x8(%rdi),%rdi
401217: e8 e8 ff ff ff callq 401204 <fun7>
40121c: 01 c0 add %eax,%eax
40121e: eb 1d jmp 40123d <fun7+0x39>
401220: b8 00 00 00 00 mov $0x0,%eax
401225: 39 f2 cmp %esi,%edx
401227: 74 14 je 40123d <fun7+0x39>
401229: 48 8b 7f 10 mov 0x10(%rdi),%rdi
40122d: e8 d2 ff ff ff callq 401204 <fun7>
401232: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
401236: eb 05 jmp 40123d <fun7+0x39>
401238: b8 ff ff ff ff mov $0xffffffff,%eax
40123d: 48 83 c4 08 add $0x8,%rsp
401241: c3 retq
反汇编可以发现这是一段经典的二叉树搜索树查找程序, 内存中对应的二叉树如下
24
/ \
8 32
/ \ / \
6 16 2d 6b
/ \ / \ / \ / \
1 7 14 23 28 2f 63 3e9
我们让他先向左再向右查找一次即16, 就可以得到2
总结与答案
这个lab锻炼了查看汇编代码的能力, 首先你需要知道每条指令大概的作用, (但有些指令看起来确实没什么意义), 各种寻址, 然后了解函数调用的机制,
即前六个参数为寄存器传参, 后面的参数为堆栈传参, 本地变量通过 sp 分配, 返回时复原 sp ,同样也需要了解寄存器的名称和它对应的作用, 这里我觉得没必要强行记忆, 查找本身就是一种学习的过程吧.
个人觉得最难分析的地方就是多个跳转分支的部分, 这种情况可能是循环嵌套, 循环+条件语句导致的, 需要慢慢的分析出上下文, 依赖条件, 再把每个跳转点分解成单独片段.
最后就是gdb的使用, 这里主要是操作汇编/查看内存和寄存器.
Border relations with Canada have never been better.
1 2 4 8 16 32
0 207
7 0
IONEFG
4 3 2 1 6 5
22

浙公网安备 33010602011771号