Last Updated: 2023-10-09 02:27:46 Monday
-- TOC --
《CSAPP》的Bomb Lab很经典,是学习x86-64汇编的很好的练手项目。这个Lab需要你完全通过分析汇编代码,输入6个字符串,让程序顺序执行到最后,此时算解除Bomb,否则Bomb爆炸,任务失败。本文将自己的解题过程完整的记录下来,供同学们参考。
前置技能:
首先通过gdb打开bomb程序,执行start指令,停在main处,设置你喜欢的汇编风格,开始查看汇编源码。
$ gdb -q bomb
Reading symbols from bomb...
(gdb) start
This GDB supports auto-downloading debuginfo from the following URLs:
https://debuginfod.fedoraproject.org/
Enable debuginfod for this session? (y or [n])
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
Temporary breakpoint 1 at 0x400da0: file bomb.c, line 37.
Starting program: /home/xinlin/test2/bomb/bomb
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Temporary breakpoint 1, main (argc=1, argv=0x7fffffffe3b8) at bomb.c:37
37 bomb.c: No such file or directory.
Missing separate debuginfos, use: dnf debuginfo-install glibc-2.34-49.fc35.x86_64
(gdb) set disassembly-flavor intel
(gdb) disass
Dump of assembler code for function main:
=> 0x0000000000400da0 <+0>: push rbx
0x0000000000400da1 <+1>: cmp edi,0x1
0x0000000000400da4 <+4>: jne 0x400db6 <main+22>
0x0000000000400da6 <+6>: mov rax,QWORD PTR [rip+0x20299b] # 0x603748 <stdin@@GLIBC_2.2.5>
0x0000000000400dad <+13>: mov QWORD PTR [rip+0x2029b4],rax # 0x603768 <infile>
0x0000000000400db4 <+20>: jmp 0x400e19 <main+121>
0x0000000000400db6 <+22>: mov rbx,rsi
0x0000000000400db9 <+25>: cmp edi,0x2
0x0000000000400dbc <+28>: jne 0x400df8 <main+88>
0x0000000000400dbe <+30>: mov rdi,QWORD PTR [rsi+0x8]
0x0000000000400dc2 <+34>: mov esi,0x4022b4
0x0000000000400dc7 <+39>: call 0x400c10 <fopen@plt>
0x0000000000400dcc <+44>: mov QWORD PTR [rip+0x202995],rax # 0x603768 <infile>
0x0000000000400dd3 <+51>: test rax,rax
0x0000000000400dd6 <+54>: jne 0x400e19 <main+121>
0x0000000000400dd8 <+56>: mov rcx,QWORD PTR [rbx+0x8]
0x0000000000400ddc <+60>: mov rdx,QWORD PTR [rbx]
0x0000000000400ddf <+63>: mov esi,0x4022b6
0x0000000000400de4 <+68>: mov edi,0x1
0x0000000000400de9 <+73>: call 0x400c00 <__printf_chk@plt>
0x0000000000400dee <+78>: mov edi,0x8
0x0000000000400df3 <+83>: call 0x400c20 <exit@plt>
0x0000000000400df8 <+88>: mov rdx,QWORD PTR [rsi]
0x0000000000400dfb <+91>: mov esi,0x4022d3
0x0000000000400e00 <+96>: mov edi,0x1
0x0000000000400e05 <+101>: mov eax,0x0
0x0000000000400e0a <+106>: call 0x400c00 <__printf_chk@plt>
0x0000000000400e0f <+111>: mov edi,0x8
0x0000000000400e14 <+116>: call 0x400c20 <exit@plt>
0x0000000000400e19 <+121>: call 0x4013a2 <initialize_bomb>
0x0000000000400e1e <+126>: mov edi,0x402338
0x0000000000400e23 <+131>: call 0x400b10 <puts@plt>
0x0000000000400e28 <+136>: mov edi,0x402378
0x0000000000400e2d <+141>: call 0x400b10 <puts@plt>
0x0000000000400e32 <+146>: call 0x40149e <read_line>
0x0000000000400e37 <+151>: mov rdi,rax
0x0000000000400e3a <+154>: call 0x400ee0 <phase_1>
0x0000000000400e3f <+159>: call 0x4015c4 <phase_defused>
0x0000000000400e44 <+164>: mov edi,0x4023a8
0x0000000000400e49 <+169>: call 0x400b10 <puts@plt>
0x0000000000400e4e <+174>: call 0x40149e <read_line>
0x0000000000400e53 <+179>: mov rdi,rax
0x0000000000400e56 <+182>: call 0x400efc <phase_2>
0x0000000000400e5b <+187>: call 0x4015c4 <phase_defused>
0x0000000000400e60 <+192>: mov edi,0x4022ed
0x0000000000400e65 <+197>: call 0x400b10 <puts@plt>
0x0000000000400e6a <+202>: call 0x40149e <read_line>
0x0000000000400e6f <+207>: mov rdi,rax
0x0000000000400e72 <+210>: call 0x400f43 <phase_3>
0x0000000000400e77 <+215>: call 0x4015c4 <phase_defused>
0x0000000000400e7c <+220>: mov edi,0x40230b
0x0000000000400e81 <+225>: call 0x400b10 <puts@plt>
0x0000000000400e86 <+230>: call 0x40149e <read_line>
0x0000000000400e8b <+235>: mov rdi,rax
0x0000000000400e8e <+238>: call 0x40100c <phase_4>
0x0000000000400e93 <+243>: call 0x4015c4 <phase_defused>
0x0000000000400e98 <+248>: mov edi,0x4023d8
0x0000000000400e9d <+253>: call 0x400b10 <puts@plt>
0x0000000000400ea2 <+258>: call 0x40149e <read_line>
0x0000000000400ea7 <+263>: mov rdi,rax
0x0000000000400eaa <+266>: call 0x401062 <phase_5>
0x0000000000400eaf <+271>: call 0x4015c4 <phase_defused>
0x0000000000400eb4 <+276>: mov edi,0x40231a
0x0000000000400eb9 <+281>: call 0x400b10 <puts@plt>
0x0000000000400ebe <+286>: call 0x40149e <read_line>
0x0000000000400ec3 <+291>: mov rdi,rax
0x0000000000400ec6 <+294>: call 0x4010f4 <phase_6>
0x0000000000400ecb <+299>: call 0x4015c4 <phase_defused>
0x0000000000400ed0 <+304>: mov eax,0x0
0x0000000000400ed5 <+309>: pop rbx
0x0000000000400ed6 <+310>: ret
End of assembler dump.
由于bomb程序没有strip,有符号表,因此反汇编出来的代码带有符号名称,这降低了解除bomb的难度。大致浏览一下代码结构,一共6个phase,每个phase的入口,都只有rdi一个参数,这个参数指向用户输入的字符串的开始地址。
call 0x40149e <read_line>
# rax是read_line的返回,付给rdi,传入phase接口
mov rdi,rax
call ...
先看看phase_1的代码:
(gdb) disass 0x400ee0
Dump of assembler code for function phase_1:
0x0000000000400ee0 <+0>: sub rsp,0x8
0x0000000000400ee4 <+4>: mov esi,0x402400
0x0000000000400ee9 <+9>: call 0x401338 <strings_not_equal>
0x0000000000400eee <+14>: test eax,eax
0x0000000000400ef0 <+16>: je 0x400ef7 <phase_1+23>
0x0000000000400ef2 <+18>: call 0x40143a <explode_bomb>
0x0000000000400ef7 <+23>: add rsp,0x8
0x0000000000400efb <+27>: ret
End of assembler dump.
esi得到了一个地址,然后与rdi一起,传给了strings_not_equal接口,显然,这个接口用来判断两个字符串是否相同,eax是返回值,如果不等于0,explode bomb。
解除phase_1,我们只需要看一下esi这个地址指向的字符串是啥,输入一个完全一样的,就可以了:
(gdb) x/s 0x402400
0x402400: "Border relations with Canada have never been better."
这就是phase_1要输入的字符串:
(gdb) b *0x400efc
Breakpoint 2 at 0x400efc
(gdb) c
Continuing.
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Border relations with Canada have never been better.
Phase 1 defused. How about the next one?
phase_2的代码如下:
(gdb) disass 0x400efc
Dump of assembler code for function phase_2:
0x0000000000400efc <+0>: push rbp
0x0000000000400efd <+1>: push rbx
0x0000000000400efe <+2>: sub rsp,0x28
0x0000000000400f02 <+6>: mov rsi,rsp
0x0000000000400f05 <+9>: call 0x40145c <read_six_numbers>
0x0000000000400f0a <+14>: cmp DWORD PTR [rsp],0x1
0x0000000000400f0e <+18>: je 0x400f30 <phase_2+52>
0x0000000000400f10 <+20>: call 0x40143a <explode_bomb>
0x0000000000400f15 <+25>: jmp 0x400f30 <phase_2+52>
0x0000000000400f17 <+27>: mov eax,DWORD PTR [rbx-0x4]
0x0000000000400f1a <+30>: add eax,eax
0x0000000000400f1c <+32>: cmp DWORD PTR [rbx],eax
0x0000000000400f1e <+34>: je 0x400f25 <phase_2+41>
0x0000000000400f20 <+36>: call 0x40143a <explode_bomb>
0x0000000000400f25 <+41>: add rbx,0x4
0x0000000000400f29 <+45>: cmp rbx,rbp
0x0000000000400f2c <+48>: jne 0x400f17 <phase_2+27>
0x0000000000400f2e <+50>: jmp 0x400f3c <phase_2+64>
0x0000000000400f30 <+52>: lea rbx,[rsp+0x4]
0x0000000000400f35 <+57>: lea rbp,[rsp+0x18]
0x0000000000400f3a <+62>: jmp 0x400f17 <phase_2+27>
0x0000000000400f3c <+64>: add rsp,0x28
0x0000000000400f40 <+68>: pop rbx
0x0000000000400f41 <+69>: pop rbp
0x0000000000400f42 <+70>: ret
End of assembler dump.
从符号名称上看,需要输入6个数字,rdi指向用户输入的字符串,rsi指向了栈顶。我们需要看一看read_six_numbers的代码:
(gdb) disass 0x40145c
Dump of assembler code for function read_six_numbers:
0x000000000040145c <+0>: sub rsp,0x18
0x0000000000401460 <+4>: mov rdx,rsi
0x0000000000401463 <+7>: lea rcx,[rsi+0x4]
0x0000000000401467 <+11>: lea rax,[rsi+0x14]
0x000000000040146b <+15>: mov QWORD PTR [rsp+0x8],rax
0x0000000000401470 <+20>: lea rax,[rsi+0x10]
0x0000000000401474 <+24>: mov QWORD PTR [rsp],rax
0x0000000000401478 <+28>: lea r9,[rsi+0xc]
0x000000000040147c <+32>: lea r8,[rsi+0x8]
0x0000000000401480 <+36>: mov esi,0x4025c3
0x0000000000401485 <+41>: mov eax,0x0
0x000000000040148a <+46>: call 0x400bf0 <__isoc99_sscanf@plt>
0x000000000040148f <+51>: cmp eax,0x5
0x0000000000401492 <+54>: jg 0x401499 <read_six_numbers+61>
0x0000000000401494 <+56>: call 0x40143a <explode_bomb>
0x0000000000401499 <+61>: add rsp,0x18
0x000000000040149d <+65>: ret
End of assembler dump.
这个接口内部调用了sscanf,第2个参数esi指向了一个地址,这个地址指向格式解析字符串:
(gdb) x/s 0x4025c3
0x4025c3: "%d %d %d %d %d %d"
这就很明显了,需要用户输入6个数字。这6个数字解析之后的存放位置如下:
最后两个参数,只能压栈了。这几行代码,将rsi+0x10和rsi+0x14这两个地址,进行了压栈:
0x0000000000401467 <+11>: lea rax,[rsi+0x14]
0x000000000040146b <+15>: mov QWORD PTR [rsp+0x8],rax
0x0000000000401470 <+20>: lea rax,[rsi+0x10]
0x0000000000401474 <+24>: mov QWORD PTR [rsp],rax
压栈的规则是:必须8字节对齐,从右到左。所以,rsi+0x14这个地址,在当前stack frame的rsp+0x8位置,栈顶是最左边的参数。轻轻地将这两个参数压栈之后,最后调用sscanf,得到6个int,全部都在上一层的stack frame中。
现在就要来研究一下,这6个数字到底是什么,才不会bomb!
0x0000000000400f05 <+9>: call 0x40145c <read_six_numbers>
0x0000000000400f0a <+14>: cmp DWORD PTR [rsp],0x1
0x0000000000400f0e <+18>: je 0x400f30 <phase_2+52>
0x0000000000400f10 <+20>: call 0x40143a <explode_bomb>
当read_six_numbers返回后,栈顶保存的是用户输入的第1个int,与0x1进行比较,不相等就bomb,显然第1个数字只能是1。
0x0000000000400f15 <+25>: jmp 0x400f30 <phase_2+52>
0x0000000000400f17 <+27>: mov eax,DWORD PTR [rbx-0x4]
0x0000000000400f1a <+30>: add eax,eax
0x0000000000400f1c <+32>: cmp DWORD PTR [rbx],eax
0x0000000000400f1e <+34>: je 0x400f25 <phase_2+41>
0x0000000000400f20 <+36>: call 0x40143a <explode_bomb>
0x0000000000400f25 <+41>: add rbx,0x4
0x0000000000400f29 <+45>: cmp rbx,rbp
0x0000000000400f2c <+48>: jne 0x400f17 <phase_2+27>
0x0000000000400f2e <+50>: jmp 0x400f3c <phase_2+64>
0x0000000000400f30 <+52>: lea rbx,[rsp+0x4]
0x0000000000400f35 <+57>: lea rbp,[rsp+0x18]
0x0000000000400f3a <+62>: jmp 0x400f17 <phase_2+27>
仔细推敲这段代码的逻辑,为了不bomb,后面的数字,都是前面数字的2倍,关键就是add eax eax
这一行指令。rbp指向的地址,刚好是6个int占用空间的上界,cmp指令进行的是地址的比较,到了这个地址,这段循环就结束。因此,这6个数字就是:1 2 4 8 16 32
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2. Keep going!
phase_3接口的代码如下:
(gdb) disass 0x400f43
Dump of assembler code for function phase_3:
0x0000000000400f43 <+0>: sub rsp,0x18
0x0000000000400f47 <+4>: lea rcx,[rsp+0xc]
0x0000000000400f4c <+9>: lea rdx,[rsp+0x8]
0x0000000000400f51 <+14>: mov esi,0x4025cf
0x0000000000400f56 <+19>: mov eax,0x0
0x0000000000400f5b <+24>: call 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000400f60 <+29>: cmp eax,0x1
0x0000000000400f63 <+32>: jg 0x400f6a <phase_3+39>
0x0000000000400f65 <+34>: call 0x40143a <explode_bomb>
0x0000000000400f6a <+39>: cmp DWORD PTR [rsp+0x8],0x7
0x0000000000400f6f <+44>: ja 0x400fad <phase_3+106>
0x0000000000400f71 <+46>: mov eax,DWORD PTR [rsp+0x8]
0x0000000000400f75 <+50>: jmp QWORD PTR [rax*8+0x402470]
0x0000000000400f7c <+57>: mov eax,0xcf
0x0000000000400f81 <+62>: jmp 0x400fbe <phase_3+123>
0x0000000000400f83 <+64>: mov eax,0x2c3
0x0000000000400f88 <+69>: jmp 0x400fbe <phase_3+123>
0x0000000000400f8a <+71>: mov eax,0x100
0x0000000000400f8f <+76>: jmp 0x400fbe <phase_3+123>
0x0000000000400f91 <+78>: mov eax,0x185
0x0000000000400f96 <+83>: jmp 0x400fbe <phase_3+123>
0x0000000000400f98 <+85>: mov eax,0xce
0x0000000000400f9d <+90>: jmp 0x400fbe <phase_3+123>
0x0000000000400f9f <+92>: mov eax,0x2aa
0x0000000000400fa4 <+97>: jmp 0x400fbe <phase_3+123>
0x0000000000400fa6 <+99>: mov eax,0x147
0x0000000000400fab <+104>: jmp 0x400fbe <phase_3+123>
0x0000000000400fad <+106>: call 0x40143a <explode_bomb>
0x0000000000400fb2 <+111>: mov eax,0x0
0x0000000000400fb7 <+116>: jmp 0x400fbe <phase_3+123>
0x0000000000400fb9 <+118>: mov eax,0x137
0x0000000000400fbe <+123>: cmp eax,DWORD PTR [rsp+0xc]
0x0000000000400fc2 <+127>: je 0x400fc9 <phase_3+134>
0x0000000000400fc4 <+129>: call 0x40143a <explode_bomb>
0x0000000000400fc9 <+134>: add rsp,0x18
0x0000000000400fcd <+138>: ret
End of assembler dump.
开始依然是调用sscanf接口,rdi保持没变,指向用户输入,rsi指向格式字符串,地址为0x4025cf,看看这里面是啥:
(gdb) x/s 0x4025cf
0x4025cf: "%d %d"
如此变明了了,phase 3需要我们输入两个数字。紧跟后面的cmp指令也说明了这一点。跳过了第一处引爆点,来到下面这段代码:
0x0000000000400f6a <+39>: cmp DWORD PTR [rsp+0x8],0x7
0x0000000000400f6f <+44>: ja 0x400fad <phase_3+106>
0x0000000000400f71 <+46>: mov eax,DWORD PTR [rsp+0x8]
0x0000000000400f75 <+50>: jmp QWORD PTR [rax*8+0x402470]
cmp结果如果above,即unsigned大于,就调到0x400fad地址,看看这个地址,又是个引爆点。那我们就不能让cmp满足ja条件。参与cmp的第1个operand是rsp+0x8这个地址的内容,这个地址在前面的代码中,被赋给了rdx,属于sscanf的第3个参数,因此,cmp是在用我们输入的第1个int数字,与立即数7进行比较。不能ja,那么这个数就只能小于等于7了。紧跟着的mov,将输入的第1个int传给rax,然后一个间接跳转,地址为rax*8+0x402470,就是在0x402470的位置偏移rax个8字节。此时,我们就要看看0x402470开始位置,都有些啥了:
(gdb) x/zg 0x402470
0x402470: 0x0000000000400f7c
(gdb) x/zg 0x402470+8
0x402478: 0x0000000000400fb9
(gdb) x/zg 0x402470+8*2
0x402480: 0x0000000000400f83
(gdb) x/zg 0x402470+8*3
0x402488: 0x0000000000400f8a
(gdb) x/zg 0x402470+8*4
0x402490: 0x0000000000400f91
(gdb) x/zg 0x402470+8*5
0x402498: 0x0000000000400f98
(gdb) x/zg 0x402470+8*6
0x4024a0: 0x0000000000400f9f
(gdb) x/zg 0x402470+8*7
0x4024a8: 0x0000000000400fa6
rax中取值不能大于7,以上就是可能计算出的全部地址。
仔细观察这些地址,它们就是后面指令的地址,而且它们基本都一样,给rax一个值,然后再调到一个固定的位置:
0x0000000000400fbe <+123>: cmp eax,DWORD PTR [rsp+0xc]
0x0000000000400fc2 <+127>: je 0x400fc9 <phase_3+134>
0x0000000000400fc4 <+129>: call 0x40143a <explode_bomb>
0x0000000000400fc9 <+134>: add rsp,0x18
0x0000000000400fcd <+138>: ret
这个位置通过rax与rsp+0xc地址的内容进行比较,必须相等,才能绕过后面的引爆点。
因此,phase 3的输入有多个可能,第1个int决定了跳转地址,这里有决定了第2个int的值。我的选择是1和311
。第1个int为1,给rax的值为311,然后就紧跟着cmp,没有跳转,代码执行最快。
1 311
Halfway there!
还是先要看看phase_4的代码,找找线索:
(gdb) disass phase_4
Dump of assembler code for function phase_4:
0x000000000040100c <+0>: sub rsp,0x18
0x0000000000401010 <+4>: lea rcx,[rsp+0xc]
0x0000000000401015 <+9>: lea rdx,[rsp+0x8]
0x000000000040101a <+14>: mov esi,0x4025cf
0x000000000040101f <+19>: mov eax,0x0
0x0000000000401024 <+24>: call 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000401029 <+29>: cmp eax,0x2
0x000000000040102c <+32>: jne 0x401035 <phase_4+41>
0x000000000040102e <+34>: cmp DWORD PTR [rsp+0x8],0xe
0x0000000000401033 <+39>: jbe 0x40103a <phase_4+46>
0x0000000000401035 <+41>: call 0x40143a <explode_bomb>
0x000000000040103a <+46>: mov edx,0xe
0x000000000040103f <+51>: mov esi,0x0
0x0000000000401044 <+56>: mov edi,DWORD PTR [rsp+0x8]
0x0000000000401048 <+60>: call 0x400fce <func4>
0x000000000040104d <+65>: test eax,eax
0x000000000040104f <+67>: jne 0x401058 <phase_4+76>
0x0000000000401051 <+69>: cmp DWORD PTR [rsp+0xc],0x0
0x0000000000401056 <+74>: je 0x40105d <phase_4+81>
0x0000000000401058 <+76>: call 0x40143a <explode_bomb>
0x000000000040105d <+81>: add rsp,0x18
0x0000000000401061 <+85>: ret
End of assembler dump.
前面几行代码,与phase 3完全一样,给esi的地址也一样,这说明,phase 4也要求我们输入两个int。调用sscanf后面的cmp也说明了,如果不是输入两个int,bomb。然后,第1个int与0xe比较,jbe是signed小于等于,必须要满足,否则bomb。下面开始准备调用func4:
后面的代码说明,必须要让func4返回0,否则bomb。下面是func4的代码:
(gdb) disass func4
Dump of assembler code for function func4:
0x0000000000400fce <+0>: sub rsp,0x8
0x0000000000400fd2 <+4>: mov eax,edx
0x0000000000400fd4 <+6>: sub eax,esi
0x0000000000400fd6 <+8>: mov ecx,eax
0x0000000000400fd8 <+10>: shr ecx,0x1f
0x0000000000400fdb <+13>: add eax,ecx
0x0000000000400fdd <+15>: sar eax,1
0x0000000000400fdf <+17>: lea ecx,[rax+rsi*1]
0x0000000000400fe2 <+20>: cmp ecx,edi
0x0000000000400fe4 <+22>: jle 0x400ff2 <func4+36>
0x0000000000400fe6 <+24>: lea edx,[rcx-0x1]
0x0000000000400fe9 <+27>: call 0x400fce <func4>
0x0000000000400fee <+32>: add eax,eax
0x0000000000400ff0 <+34>: jmp 0x401007 <func4+57>
0x0000000000400ff2 <+36>: mov eax,0x0
0x0000000000400ff7 <+41>: cmp ecx,edi
0x0000000000400ff9 <+43>: jge 0x401007 <func4+57>
0x0000000000400ffb <+45>: lea esi,[rcx+0x1]
0x0000000000400ffe <+48>: call 0x400fce <func4>
0x0000000000401003 <+53>: lea eax,[rax+rax*1+0x1]
0x0000000000401007 <+57>: add rsp,0x8
0x000000000040100b <+61>: ret
End of assembler dump.
func4里面没有引爆点,我们需要仔细跟踪代码逻辑,看看rdi在什么值的时候,能够让func4返回0。咋一看func4,还是个递归接口,好恐怖!只能从rdx=0xe开始,一行行代码跟踪计算。最后会发现,ecx的值与edi比较,edi是我们输入的第1个int,ecx=7,edi要先小于等于7,然后要大于等于7,才能顺利带着0离开func4。因此,我们输入的第1个int就是7
。
func4顺利返回0后,才出现使用到输入的第2个int的代码,第2个int等于0
。
7 0
So you got that one. Try this one.
(gdb) disass 0x401062
Dump of assembler code for function phase_5:
0x0000000000401062 <+0>: push rbx
0x0000000000401063 <+1>: sub rsp,0x20
0x0000000000401067 <+5>: mov rbx,rdi
0x000000000040106a <+8>: mov rax,QWORD PTR fs:0x28
0x0000000000401073 <+17>: mov QWORD PTR [rsp+0x18],rax
0x0000000000401078 <+22>: xor eax,eax
0x000000000040107a <+24>: call 0x40131b <string_length>
0x000000000040107f <+29>: cmp eax,0x6
0x0000000000401082 <+32>: je 0x4010d2 <phase_5+112>
0x0000000000401084 <+34>: call 0x40143a <explode_bomb>
0x0000000000401089 <+39>: jmp 0x4010d2 <phase_5+112>
0x000000000040108b <+41>: movzx ecx,BYTE PTR [rbx+rax*1]
0x000000000040108f <+45>: mov BYTE PTR [rsp],cl
0x0000000000401092 <+48>: mov rdx,QWORD PTR [rsp]
0x0000000000401096 <+52>: and edx,0xf
0x0000000000401099 <+55>: movzx edx,BYTE PTR [rdx+0x4024b0]
0x00000000004010a0 <+62>: mov BYTE PTR [rsp+rax*1+0x10],dl
0x00000000004010a4 <+66>: add rax,0x1
0x00000000004010a8 <+70>: cmp rax,0x6
0x00000000004010ac <+74>: jne 0x40108b <phase_5+41>
0x00000000004010ae <+76>: mov BYTE PTR [rsp+0x16],0x0
0x00000000004010b3 <+81>: mov esi,0x40245e
0x00000000004010b8 <+86>: lea rdi,[rsp+0x10]
0x00000000004010bd <+91>: call 0x401338 <strings_not_equal>
0x00000000004010c2 <+96>: test eax,eax
0x00000000004010c4 <+98>: je 0x4010d9 <phase_5+119>
0x00000000004010c6 <+100>: call 0x40143a <explode_bomb>
0x00000000004010cb <+105>: nop DWORD PTR [rax+rax*1+0x0]
0x00000000004010d0 <+110>: jmp 0x4010d9 <phase_5+119>
0x00000000004010d2 <+112>: mov eax,0x0
0x00000000004010d7 <+117>: jmp 0x40108b <phase_5+41>
0x00000000004010d9 <+119>: mov rax,QWORD PTR [rsp+0x18]
0x00000000004010de <+124>: xor rax,QWORD PTR fs:0x28
0x00000000004010e7 <+133>: je 0x4010ee <phase_5+140>
0x00000000004010e9 <+135>: call 0x400b30 <__stack_chk_fail@plt>
0x00000000004010ee <+140>: add rsp,0x20
0x00000000004010f2 <+144>: pop rbx
0x00000000004010f3 <+145>: ret
End of assembler dump.
开头有几行代码是编译器插入的对抗buffer overflow的代码:
0x000000000040106a <+8>: mov rax,QWORD PTR fs:0x28
0x0000000000401073 <+17>: mov QWORD PTR [rsp+0x18],rax
0x0000000000401078 <+22>: xor eax,eax
可以忽略这几行代码。
紧接着call,从符号名称上看,这个调用返回用户输入字符串的长度,而且长度必须要等于6。两次很随性的跳转之后,来到下面这段关键的代码:
0x000000000040108b <+41>: movzx ecx,BYTE PTR [rbx+rax*1]
0x000000000040108f <+45>: mov BYTE PTR [rsp],cl
0x0000000000401092 <+48>: mov rdx,QWORD PTR [rsp]
0x0000000000401096 <+52>: and edx,0xf
0x0000000000401099 <+55>: movzx edx,BYTE PTR [rdx+0x4024b0]
0x00000000004010a0 <+62>: mov BYTE PTR [rsp+rax*1+0x10],dl
0x00000000004010a4 <+66>: add rax,0x1
0x00000000004010a8 <+70>: cmp rax,0x6
0x00000000004010ac <+74>: jne 0x40108b <phase_5+41>
0x00000000004010ae <+76>: mov BYTE PTR [rsp+0x16],0x0
0x00000000004010b3 <+81>: mov esi,0x40245e
0x00000000004010b8 <+86>: lea rdi,[rsp+0x10]
0x00000000004010bd <+91>: call 0x401338 <strings_not_equal>
0x00000000004010c2 <+96>: test eax,eax
0x00000000004010c4 <+98>: je 0x4010d9 <phase_5+119>
0x00000000004010c6 <+100>: call 0x40143a <explode_bomb>
第1次进来这段代码时,rax=0,rbx一直指向用户输入。这段代码通过用户输入的每个字符计算了一个偏移,0-15,加到0x4024b0地址,从这个地址取字节,存入rsp+0x10开始的位置。这个过程重复6次后,对两个字符串进行比较,必须要相等。
(gdb) x/s 0x40245e
0x40245e: "flyers"
(gdb) x/15c 0x4024b0
0x4024b0 <array.3449>: 109 'm' 97 'a' 100 'd' 117 'u' 105 'i' 101 'e' 114 'r' 115 's'
0x4024b8 <array.3449+8>: 110 'n' 102 'f' 111 'o' 116 't' 118 'v' 98 'b' 121 'y'
所以,输入的每个字符的ASCII码的低4bit作为基于0x4024b0地址的偏移,6个偏移对应6个字符,这6个字符是flyers。我们需要用flyers和0x4024b0地址开始的16个字节,反推输入。由于只取了输入字符的低4bit,这一关的解也有多个,我使用ionefg
过关:
ionefg
Good work! On to the next...
面对眼花缭乱的汇编代码,我觉得语言好无力,完全不知道应该如何描述这一关的内容...代码就不贴了,大致说一下这段代码都在干什么吧。
首先是读取6个数字,然后对这6个数字进行一个合法性判断,这里是一个烧脑的双重循环,即判断输入数字的范围,也判断相互关系。这6个数字,只能在1-6之间,而且每个都不相同。然后,用一个循环,用7原地减去每个数字。然后,从神奇的0x6032d0这个地址(属于.data)开始的一段范围内,以被7减去后的值为index,读取6个指针到stack的前半段。然后还有一段指向指针的指针的操作....晕死了,指针的值一会儿作为值,一会儿作为地址....最后,按照某种顺序,对这一组指针指向的地址的值做比较,比较如果不成功,Bomb!
有一个感觉,虽然x86属于传统意义上的CISC,但实际上在直接阅读汇编的时候,x86的可读性可能更好。
$ ./bomb
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Border relations with Canada have never been better.
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2. Keep going!
1 311
Halfway there!
7 0
So you got that one. Try this one.
ionefg
Good work! On to the next...
4 3 2 1 6 5
Congratulations! You've defused the bomb!
debug的过程,看到代码中,有一个secret phase,在最后才可能开启。但仔细研究了代码后发现,网上下载的自学版本,无法进入secret phase!没有输入的调用,内部流程直接给了一个错误的输入,然后就走到了最后的Congratulations...
我将《CSAPP》Data Lab分成了两个部分:
本文链接:https://cs.pynote.net/hd/asm/202310061/
-- EOF --
-- MORE --