CSAPP-lab2:Bomb-lab
本文最后更新于:2022年10月4日 晚上
先导知识
- AT&T格式的x86汇编
- gdb调试工具的使用
函数参数的压栈顺序:
- rdi
- rsi
- rdx
- rcx
- r8
- r9
命令速记:
- list,简写为l
- 输出源代码,默认为十行
- l n : 输出第n行前后的代码
- l function : 输出函数function前后的代码
- break,简写为b
- b 行号:在某一行设置断点
- b 函数:在函数入口设置断点
- run运行程序并在断点处停止
- next往后执行一行语句
- backtrace查看函数调用的栈帧
- p打印变量的值或者地址(print),类似的还有x命令(examine)
- info locals查看当前栈帧局部变量的值
- info registers可以查看当前寄存器的值
- 打印单个寄存器的值
- i registers eax
- p $eax
- display + 变量名 可以每次停下来的时候自动打印变量的值
- 直接敲回车键:重复上一条命令
- layout命令可以拆分窗口
- layout regs显示寄存器的值的独立窗口
- layout asm显示汇编的独立窗口
- ctrl+x 1 单窗口模式
- ctrl+x 2 双窗口模式
- ctrl+x a回到默认的gdb模式
准备工作
- 得到汇编代码
1
2
3objdump -d bomb >> bomb.s
less bomb.s - ssh到服务器
- tmux进行分屏,一边是汇编代码一边是gdb调试
phase_1
- strings_not_equal有两个参数,第一个是你所输入的字符串的首地址(rdi),第二个是正确的字符串的首地址(rsi)
- strings_not_equal(input_string,correct_string)
- 如果两个个字符串是相等的,那么eax作为函数的返回值将会是0
- 可以使用x/s 0x402400去观察这个地址的值是什么,可以看到是一个字符串
- 得到答案:
Border relations with Canada have never been better.
strings_not_equal函数的具体实现1
2
3
4
5
6
7
8
90000000000400ee0 <phase_1>:
400ee0: 48 83 ec 08 sub $0x8,%rsp # 给栈分配空间
400ee4: be 00 24 40 00 mov $0x402400,%esi # rdi保存第一个参数,rsi保存第二个参数,这里是为strings_not_equal函数准备参数
400ee9: e8 4a 04 00 00 callq 401338 <strings_not_equal>
400eee: 85 c0 test %eax,%eax # test a,b 是a与b做与运算的意思
400ef0: 74 05 je 400ef7 <phase_1+0x17> # je/jz 是当运算结果为0(ZF标志为1)时跳转。test和je一起使用是为了检测eax是不是0
400ef2: e8 43 05 00 00 callq 40143a <explode_bomb>
400ef7: 48 83 c4 08 add $0x8,%rsp
400efb: c3 retq1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
390000000000401338 <strings_not_equal>: # %rdi保存第一个参数 %rsi保存第二个参数 %rdx保存第三个参数
401338: 41 54 push %r12
40133a: 55 push %rbp
40133b: 53 push %rbx
40133c: 48 89 fb mov %rdi,%rbx #将我们输入的字符串的地址赋给rbx,这里也是下面string_length函数的参数
40133f: 48 89 f5 mov %rsi,%rbp #将答案的字符串的地址赋给rbp。这里赋值的目的是为了给下面的函数调用腾出寄存器
401342: e8 d4 ff ff ff callq 40131b <string_length>
401347: 41 89 c4 mov %eax,%r12d #将string_length函数的返回值赋给r12d
40134a: 48 89 ef mov %rbp,%rdi #桢指针赋给rdi,也即是答案的字符串的指针赋给rdi,这里是计算出答案的字符串的长度
40134d: e8 c9 ff ff ff callq 40131b <string_length>
401352: ba 01 00 00 00 mov $0x1,%edx
401357: 41 39 c4 cmp %eax,%r12d#将答案的字符串的长度与我们的输入的字符串的长度进行比较
40135a: 75 3f jne 40139b <strings_not_equal+0x63>
40135c: 0f b6 03 movzbl (%rbx),%eax #将我们输入的字符串的地址赋给eax
40135f: 84 c0 test %al,%al#检测al的值是不是0
401361: 74 25 je 401388 <strings_not_equal+0x50>#是0就跳到401388
401363: 3a 45 00 cmp 0x0(%rbp),%al#将答案字符串第一个字节与al进行比对
401366: 74 0a je 401372 <strings_not_equal+0x3a>#如果相等就跳到401372
401368: eb 25 jmp 40138f <strings_not_equal+0x57>
40136a: 3a 45 00 cmp 0x0(%rbp),%al
40136d: 0f 1f 00 nopl (%rax)
401370: 75 24 jne 401396 <strings_not_equal+0x5e>
401372: 48 83 c3 01 add $0x1,%rbx#rbx加到下一个字节
401376: 48 83 c5 01 add $0x1,%rbp#rbp加到下一个字节
40137a: 0f b6 03 movzbl (%rbx),%eax#把rbx对应内存的值赋给eax
40137d: 84 c0 test %al,%al#看al是不是0,0说明到了字符串末尾处
40137f: 75 e9 jne 40136a <strings_not_equal+0x32>
401381: ba 00 00 00 00 mov $0x0,%edx
401386: eb 13 jmp 40139b <strings_not_equal+0x63>
401388: ba 00 00 00 00 mov $0x0,%edx
40138d: eb 0c jmp 40139b <strings_not_equal+0x63>
40138f: ba 01 00 00 00 mov $0x1,%edx
401394: eb 05 jmp 40139b <strings_not_equal+0x63>
401396: ba 01 00 00 00 mov $0x1,%edx
40139b: 89 d0 mov %edx,%eax #保存返回值
40139d: 5b pop %rbx
40139e: 5d pop %rbp
40139f: 41 5c pop %r12
4013a1: c3 retq
phase_2
- read_six_numbers(input_string,rsp)
- 第一个参数由rdi保存,也即是输入的字符串的地址
- 第二个参数是rsi保存,也即是rsp的地址,本质上strings_not_equal函数是把字符串中的数字存到了rsp上
- 调用了sscanf函数
- 读取了六个数字之后需要和正确答案比对,这里使用了循环进行流程控制
- 比如第一个数字必须是1,然后后面的数字可以陆续推导得出
1 2 4 8 16 32
1 |
|
关于sscanf函数的具体实现:
关于sscanf函数的用法可以在C++ reference上找到,理解该函数的调用和传参可以帮助理解汇编代码
1 |
|
汇编代码:
1 |
|
phase_3
- 刚进入phase_3这个函数就调用了sscanf函数,因此需要准备参数
- sscanf(input_string,”%d %d”,rdx,rcx)
- sscanf函数返回读取的数字的数量,如果不是大于等于2会爆炸
- 输入的第一个数字和7进行比较,如果大于就爆炸,因此只能大于等于0小于等于7
- 重点是jmpq *0x402470(,%rax,8)语句,这里可以查看*0x402470的值从而确定要跳转的地方
- 如果输入的第一个数字是0,那么就会跳转到下面的第一个分支语句,从而确定第二个值
- 这里有很多个答案,其中一个是
0 207
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
380000000000400f43 <phase_3>:# 参数顺序:rdi(输入的数字字符串) rsi() rdx rcx r8 r9
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 #gdb查看发现是 “%d %d”,第二个参数
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#eax是输入的第一个数字
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)#8*rax+*0x402470,跳转到下面语句的其中之一
400f7c: b8 cf 00 00 00 mov $0xcf,%eax#如果输入的第一个数字是0那么就会跳到这里
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#得到第二个值0xc(%rsp)
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
phase_4
- 这个和上一个类似,都是输入两个值判断是不是满足要求
- 函数内又调用了func4函数,所以是一个递归。func4函数是一个有三个参数的递归函数,如果一直手动分析递归的过程会把自己绕晕,所以执行jle直接跳出递归,最后发现解题确实用不着递归,直接跳出去就好了
- 第一个参数需要满足<=7又要满足>=7因此就是7
- 第二个参数需要满足是0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47000000000040100c <phase_4>:# 参数顺序:rdi(输入的字符串) rsi() rdx rcx r8 r9
40100c: 48 83 ec 18 sub $0x18,%rsp
401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx #输入的第二个数字,也即是sscanf的第三个参数
401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx #输入的第一个数字
40101a: be cf 25 40 00 mov $0x4025cf,%esi #字符串“%d %d”的首地址
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 #eax是sscanf函数的返回值,这里必须是2
40102c: 75 07 jne 401035 <phase_4+0x29>
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp)#0xe=14 (rsp+8)<=14
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#edx=14
40103f: be 00 00 00 00 mov $0x0,%esi#esi=0
401044: 8b 7c 24 08 mov 0x8(%rsp),%edi#输入的第一个数字=edi,edi<=8
401048: e8 81 ff ff ff callq 400fce <func4>
40104d: 85 c0 test %eax,%eax
40104f: 75 07 jne 401058 <phase_4+0x4c>
401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp)#第二个参数
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
0000000000400fce <func4>:#edi:输入的第一个数字 esi:0
400fce: 48 83 ec 08 sub $0x8,%rsp
400fd2: 89 d0 mov %edx,%eax#eax=14
400fd4: 29 f0 sub %esi,%eax#eax=eax-esi=14-0=14
400fd6: 89 c1 mov %eax,%ecx#ecx=14
400fd8: c1 e9 1f shr $0x1f,%ecx#logic shift right 31 bits ecx=0
400fdb: 01 c8 add %ecx,%eax#eax=14
400fdd: d1 f8 sar %eax #mathematical shift right 1 bit,rax=14>>1=7
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx #ecx=rax+rsi=7+0=7
400fe2: 39 f9 cmp %edi,%ecx #edi<=ecx
400fe4: 7e 0c jle 400ff2 <func4+0x24>
400fe6: 8d 51 ff lea -0x1(%rcx),%edx
400fe9: e8 e0 ff ff ff callq 400fce <func4>
400fee: 01 c0 add %eax,%eax
400ff0: eb 15 jmp 401007 <func4+0x39>
400ff2: b8 00 00 00 00 mov $0x0,%eax
400ff7: 39 f9 cmp %edi,%ecx
400ff9: 7d 0c jge 401007 <func4+0x39>
400ffb: 8d 71 01 lea 0x1(%rcx),%esi
400ffe: e8 cb ff ff ff callq 400fce <func4>
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
401007: 48 83 c4 08 add $0x8,%rsp
40100b: c3 retq
phase_5
输入一个字符串,长度为6,对于每个字符,取低四位,然后加上0x4024b0进行偏移,这样操作得到的字符串与地址为0x40245e的字符串进行比较(x/s查看可以得到是“flyer”)
1 |
|
phase_6
拖进ida F5得到C语言代码,下面是我加了注释的版本:
1 |
|
观察node结构体的特点,发现node[0]表示存储的值,node[1]表示node编号,node[2] 存储下一个node的地址
1 |
|
我们输入的数字将会把结构体进行排序,排序结果最终需要满足的要求是,结构体存储的值必须要从大到小排列,因此我们找出所有的node存储的值即可
结构体编号 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
储存值 | 0xFD | 0x2D5 | 0x12D | 0x3E5 | 0xD4 | 0x1B0 |
排序后编号 | 5 | 2 | 4 | 1 | 6 | 3 |
4 2 6 3 1 5
CSAPP-lab2:Bomb-lab
http://gls.show/p/44962f68/