Get to know there're 6 phase from bomb.c and get to know the bomb will be trigged via explode_bomb
.
Phase 1
- run debugger : gdb bomb
- set breakpoints: b explode_bomb
- disassmble phase_1: disas phase_1
Dump of assembler code for function phase_1:
0x0000000000400ee0 <+0>: sub $0x8,%rsp
0x0000000000400ee4 <+4>: mov $0x402400,%esi
0x0000000000400ee9 <+9>: callq 0x401338
0x0000000000400eee <+14>: test %eax,%eax
0x0000000000400ef0 <+16>: je 0x400ef7
0x0000000000400ef2 <+18>: callq 0x40143a
0x0000000000400ef7 <+23>: add $0x8,%rsp
0x0000000000400efb <+27>: retq
End of assembler dump.
- we can see something stored in esi: mov $0x402400,%esi, since %rsi/%esi is the second argument.
- check the value in location 0x402400: x/s 0x402400
0x402400: "Border relations with Canada have never been better."
- run the program: run
- type the key: "Border relations with Canada have never been better."
实际上这里的 %rsp - 0x8 是生成 stack grow, 相当于 push. 后面的 %rsp + 0x8 是 pop from stack.
然后常见的register %esi 相当于 %rsi 第二个参数。
这里的 0x0000000000400ee9 <+9>: callq 0x401338
Phase 2
The key to solve phase 2 is understand the difference between mov and lea
lea是“load effective address”的缩写,简单的说,lea指令可以用来将一个内存地址直接赋给目的操作数,
例如:lea eax,[ebx+8]就是将ebx+8这个值直接赋给eax,而不是把ebx+8处的内存地址里的数据赋给eax。
而mov指令则恰恰相反,例如:mov eax,[ebx+8]则是把内存地址为ebx+8处的数据赋给eax。
- run debugger : gdb bomb
- set breakpoints: b explode_bomb
- disassmble phase_1: disas phase_2
0000000000400efc :
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
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp)
400f0e: 74 20 je 400f30
400f10: e8 25 05 00 00 callq 40143a
400f15: eb 19 jmp 400f30
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
400f20: e8 15 05 00 00 callq 40143a
400f25: 48 83 c3 04 add $0x4,%rbx
400f29: 48 39 eb cmp %rbp,%rbx
400f2c: 75 e9 jne 400f17
400f2e: eb 0c jmp 400f3c
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
400f3c: 48 83 c4 28 add $0x28,%rsp
400f40: 5b pop %rbx
400f41: 5d pop %rbp
400f42: c3 retq
we can tell by the name we have a function called read_six_numbers
.
000000000040145c :
40145c: 48 83 ec 18 sub $0x18,%rsp
401460: 48 89 f2 mov %rsi,%rdx
401463: 48 8d 4e 04 lea 0x4(%rsi),%rcx
401467: 48 8d 46 14 lea 0x14(%rsi),%rax
40146b: 48 89 44 24 08 mov %rax,0x8(%rsp)
401470: 48 8d 46 10 lea 0x10(%rsi),%rax
401474: 48 89 04 24 mov %rax,(%rsp)
401478: 4c 8d 4e 0c lea 0xc(%rsi),%r9
40147c: 4c 8d 46 08 lea 0x8(%rsi),%r8
401480: be c3 25 40 00 mov $0x4025c3,%esi
401485: b8 00 00 00 00 mov $0x0,%eax
40148a: e8 61 f7 ff ff callq 400bf0 <__isoc99_sscanf@plt>
40148f: 83 f8 05 cmp $0x5,%eax
401492: 7f 05 jg 401499
401494: e8 a1 ff ff ff callq 40143a
401499: 48 83 c4 18 add $0x18,%rsp
40149d: c3 retq
we called sscanf inside this function. we do a man sscanf
sscanf(const char *restrict s, const char *restrict format, ...);
Actually we can tell by its name we try to read six numbers in.
Try to decode this line:
401480: be c3 25 40 00 mov $0x4025c3,%esi
x/s 0x4025c3
0x4025c3 :"%d %d %d %d %d %d"
We know first it will try to read the input as string, maybe something like input2
.
then we do sscanf from the input2
.
Decode these line:
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp)
400f0e: 74 20 je 400f30
register | value |
---|---|
%rsp | some meory address at rsp |
and we know the value at some meory address at rsp
have to be 1, otherwise it will not je, it'll go to next line and explode_bomb.
memory adress | value |
---|---|
memory address at rsp | 1 |
Guess the first number is 1.
Then we jump, decode these lines:
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
register | value |
---|---|
%rbx | memory address at rsp + 0x4 |
%rbp | memory address at rsp + 0x18 |
if we have format like this:
int int int int int int
int take 2 byte, blank space take 1 byte: 这里纯粹都是错误的分析啊||||
6 * 4 + 5 + 1 = 18
try to guess rbp = '\0'
rbx is we try to read next number?
Then we jump again, decode these lines:
400f17: 8b 43 fc mov -0x4(%rbx),%eax
400f1a: 01 c0 add %eax,%eax
400f1c: 39 03 cmp %eax,(%rbx)
action | register | value |
---|---|---|
mov -0x4(%rbx),%eax | %eax | memory address at rbx - 0x4 = memory adress at rsp |
add %eax,%eax | %eax | 2 |
400f1c: 39 03 cmp %eax,(%rbx)
This line requires value at %rbx, that is the value at emory address at rsp + 0x4 equal to 2. double value of %eax.
Guess the second number 2.
Accroding to the format, int takes 2 bytes, char takes 1 byte. Guess the input is:
01 02 04 08 16 32
Type in. phase 2 solved.
然后阅读了这篇文章 : CSAPP: Bomb Lab 实验解析,发现
- 并不一定需要 type in
01 02 04 08 16 32
, 直接写入1 2 4 8 16 32
也一样,sscanf 会自动读入int 占4位。0x18 也不是 18啊, 是 1 *16 + 8 = 24 - 画出数据存储和写出对应的类C代码更帮助解题
- 这里还有一些推断,比如 %rsp 中存的内存中的值是第一个input number,这里是否也说明了 stack pointer作用?
位置 & 内存
% rsp | %rsp + 0x4 | %rsp + 0x8 | %rsp + 0xc | %rsp + 0x10 | %rsp + 0x14 | %rbp = %rsp + 0x18 |
---|---|---|---|---|---|---|
num[0] | num[1] | num[2] | num[3] | num[4] | num[5] | edge |
对应的类C代码:
void phase_2(){
if (%rsp) == 0x1 { // 保证第一个数是1,不jump则会
goto label_400f30;
}
label_400f17:
%rax = (%rbx - 0x4);
%eax = %eax + %rax;
if %rax != (%rbx){ // 保证后一个数为前一个数的两倍,否则会
explode_bomb();
}
%rbx = %rbx + 0x4;
if (%rbp != %rbx) {
goto label_400f17;
} else {
return ; //jump out of phase_2;
}
label_400f30:
%rbx = (%rsp + 0x4);
%rbp = (%rsp + 0x18);
goto label_400f17;
}
Phase 3
disas phase_3
Dump of assembler code for function phase_3:
0x0000000000400f43 <+0>: sub $0x18,%rsp
0x0000000000400f47 <+4>: lea 0xc(%rsp),%rcx
0x0000000000400f4c <+9>: lea 0x8(%rsp),%rdx
0x0000000000400f51 <+14>: mov $0x4025cf,%esi
0x0000000000400f56 <+19>: mov $0x0,%eax
0x0000000000400f5b <+24>: callq 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000400f60 <+29>: cmp $0x1,%eax
0x0000000000400f63 <+32>: jg 0x400f6a
0x0000000000400f65 <+34>: callq 0x40143a
0x0000000000400f6a <+39>: cmpl $0x7,0x8(%rsp)
0x0000000000400f6f <+44>: ja 0x400fad
0x0000000000400f71 <+46>: mov 0x8(%rsp),%eax
0x0000000000400f75 <+50>: jmpq *0x402470(,%rax,8)
0x0000000000400f7c <+57>: mov $0xcf,%eax
0x0000000000400f81 <+62>: jmp 0x400fbe
0x0000000000400f83 <+64>: mov $0x2c3,%eax
0x0000000000400f88 <+69>: jmp 0x400fbe
0x0000000000400f8a <+71>: mov $0x100,%eax
0x0000000000400f8f <+76>: jmp 0x400fbe
0x0000000000400f91 <+78>: mov $0x185,%eax
0x0000000000400f96 <+83>: jmp 0x400fbe
0x0000000000400f98 <+85>: mov $0xce,%eax
0x0000000000400f9d <+90>: jmp 0x400fbe
0x0000000000400f9f <+92>: mov $0x2aa,%eax
0x0000000000400fa4 <+97>: jmp 0x400fbe
0x0000000000400fa6 <+99>: mov $0x147,%eax
0x0000000000400fab <+104>: jmp 0x400fbe
0x0000000000400fad <+106>: callq 0x40143a
0x0000000000400fb2 <+111>: mov $0x0,%eax
0x0000000000400fb7 <+116>: jmp 0x400fbe
0x0000000000400fb9 <+118>: mov $0x137,%eax
0x0000000000400fbe <+123>: cmp 0xc(%rsp),%eax
0x0000000000400fc2 <+127>: je 0x400fc9
0x0000000000400fc4 <+129>: callq 0x40143a
0x0000000000400fc9 <+134>: add $0x18,%rsp
0x0000000000400fcd <+138>: retq
End of assembler dump.
Decode these two lines:
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
register | value |
---|---|
%rcx | memory address at rsp + 0xc |
%rdx | memory address at rsp + 0x8 |
400f51: be cf 25 40 00 mov $0x4025cf,%esi
x/w 0x4025cf
0x4025cf: "%d %d"
So we need two ints for this phase.
400f60: 83 f8 01 cmp $0x1,%eax
if return value is greater than 0x1, and it should, since we read two numbers in:
400f63: 7f 05 jg 400f6a
We jump to 400f6a
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp)
compare the value %rsp + 0x8 with $0x7, if above:
400f6f: 77 3c ja 400fad
jump to 400fad
400fad: e8 88 04 00 00 callq 40143a
So the value %rsp + 0x8 should be smaller than $0x7. also here we use ja, so the value should be >= 0 and <7.
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
mov the vluae of %rsp + 0x8 to %eax, sth smaller than $0x7。
for this line: 400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)
This operation jmpq *0x402390(,%rax,8) is for jumping directly to the absolute address stored at
8 * %rax + 0x402390
If you do x/16gx 0x402390 in gdb (inspect 16 "giant words" in hexadecimal starting at 0x402390) you will find an address table looks like the following:(i have a different lab so it's not the same as yours)
0x402880: 0x0000000000400fee 0x000000000040102b
0x402890: 0x0000000000400ff5 0x0000000000400ffc
0x4028a0: 0x0000000000401003 0x000000000040100a
0x4028b0: 0x0000000000401011 0x0000000000401018
Where all these addresses all point back to the a single mov operation immediately after jmpq *0x402390(,%rax,8)
https://stackoverflow.com/a/50978155/3608824
We have the instruction jump = 0x402470 + 8 *i (i <= 7)
If I do x/16gx 0x402470
:
(gdb) x/16gx 0x402470
0x402470: 0x0000000000400f7c 0x0000000000400fb9
0x402480: 0x0000000000400f83 0x0000000000400f8a
0x402490: 0x0000000000400f91 0x0000000000400f98
0x4024a0: 0x0000000000400f9f 0x0000000000400fa6
0x4024b0 : 0x737265697564616d 0x6c796276746f666e
0x4024c0: 0x7420756f79206f53 0x756f79206b6e6968
0x4024d0: 0x6f7473206e616320 0x6f62206568742070
0x4024e0: 0x206874697720626d 0x202c632d6c727463
For the first 0 - 7 locations, we have all those lines:
0x0000000000400f7c <+57>: mov $0xcf,%eax
0x0000000000400fb9 <+118>: mov $0x137,%eax
0x0000000000400f83 <+64>: mov $0x2c3,%eax
0x0000000000400f8a <+71>: mov $0x100,%eax
0x0000000000400f91 <+78>: mov $0x185,%eax
0x0000000000400f98 <+85>: mov $0xce,%eax
0x0000000000400f9f <+92>: mov $0x2aa,%eax
0x0000000000400fa6 <+99>: mov $0x147,%eax
try i = 0, we jump to 0x0000000000400f7c
.
0x0000000000400f7c <+57>: mov $0xcf,%eax
0x0000000000400f81 <+62>: jmp 0x400fbe
the %eax number is 0xcf, and we jump to 0x400fbe.
0x0000000000400fbe <+123>: cmp 0xc(%rsp),%eax
0x0000000000400fc2 <+127>: je 0x400fc9
0x0000000000400fc4 <+129>: callq 0x40143a
0x0000000000400fc9 <+134>: add $0x18,%rsp
The value at memory address at rsp + 0xc should equal to value in eax. and then we jump out of the function.
And we have rsp + 0x8 and rsp + 0xc, so we should count the 0s, tried type in
0000 0207
solved.
I can also try other values: 0001 0311
can also solve phase_3.
这一关的思路基本上和文章一致,值得注意的是 x 的用法:
x /[Length][Format] [Address expression]
以给定的参数查看内存中的内容。
x commnad
(Note: the format string for the ‘x’ command has the general form
x/[NUM][SIZE][FORMAT] where
NUM = number of objects to display
SIZE = size of each object (b=byte, h=half-word, w=word,
g=giant (quad-word))
FORMAT = how to display each object (d=decimal, x=hex, o=octal, etc.)
If you don’t specify SIZE or FORMAT, either a default value, or the last
value you specified in a previous ‘print’ or ‘x’ command is used.
)
如果我们直接
(gdb) x 0x402470
0x402470: 0x0000000000400f7c
但是我们知道 第一个数 < 7 ,所以尝试 x/8gx 0x402470
x/8gx 0x402470
0x402470: 0x0000000000400f7c 0x0000000000400fb9
0x402480: 0x0000000000400f83 0x0000000000400f8a
0x402490: 0x0000000000400f91 0x0000000000400f98
0x4024a0: 0x0000000000400f9f 0x0000000000400fa6
这一句还是这样理解:
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)
0x400f75
处的跳转指令依据第一个数的值做间接跳转,还是相当于去去存在此处的内存中的地址。这个 *
有点取(地址)内容的意思。
所以phase_3的答案是7组。