3.bomb lab

参考:

  1. 深入理解计算机系统-bomblab详解_独小雪的博客-CSDN博客
  2. Lab2 CSAPP: BombLab环境+思路+全注释 - 知乎 (zhihu.com)
  3. 手把手教你拆解 CSAPP 的 炸弹实验室 BombLab - 知乎 (zhihu.com)

3.1基本指令

  • 必须反汇编对炸弹进行拆除
  • objdump -d bomb > bomb.s生成bomb的汇编文件
  • objdump -d bomb > bomb.t生成bomb的符号表
  • gdb bombgdb调试bomb程序
  • (gdb) b 0x0000设置断点
  • (gdb) x/wx 0x0000 指令x:查看内存指令、w:四字节 x:16进制显示
  • 所有的字符串都是通过%rdi寄存器传入的,在 bomb.s中搜索 phase_1,可以得到如下的代码:
1
2
3
4
5
400e32:	e8 67 06 00 00       	callq  40149e <read_line> # 读取命令行的字符串,%rax是 readline 的返回值也就是输入的string
400e37: 48 89 c7 mov %rax,%rdi #将输入的string存入 %rdi 寄存器,传入phase函数
400e3a: e8 a1 00 00 00 callq 400ee0 <phase_1>
400e3f: e8 80 07 00 00 callq 4015c4 <phase_defused>
400e44: bf a8 23 40 00 mov $0x4023a8,%edi

3.2 phase1

bomb.s搜素 phase_1 ,可以得到如下的反汇编代码:

1
2
3
4
5
6
7
8
9
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

题目分析:

  • 函数调用了strings_not_equal这个函数,主要是判断输入的字符串与对比的字符串是否不相等:相等返回0,不相等则会返回非0。即 如果%rdi 与 地址(0x402400)存的字符串相等就不会bomb

汇编代码分析:

  • sub $0x8,%rsp %rsp是栈指针寄存器,存储栈的顶部位置, 这一行将栈顶减去8位,为函数的局部变量分配空间。
  • mov $0x402400,%esi将 地址(0x402400)的字符串,放入到 %esi 中。
  • callq 401338 <strings_not_equal> 调用地址(401338)的函数 用于判断两个字符串是否不相等
  • test %eax,%eax 对%eax寄存器中的内容 进行与运算。test指令执行按位与的操作,类似于and指令,但是test 不会保存结果,只会更新标志寄存器的值
    • 因此test指令通常用作检查某个寄存器或者内存位置的位模式,而不需要保存结果。
    • test操作,如果结果为0,则将零标志位(ZF),设置为1 通常与(je、jne、j1、jg)配合使用
  • je 400ef7 <phase_1+0x17> 与运算设置了0 标志位 ,那么就跳转400ef7 add $0x8,%rsp 释放之前的空间。
    • je指令 对应于标志寄存器中的零标志(ZF),如果上一条指令产生了零标志,那么程序会跳转到指定地址。
  • callq 40143a <explode_bomb> 否则执行地址(40143a)的函数 爆炸

题解:

  • gdb bomb
  • x/s 0x402400
    • image-20231025214536447
  • 在中断中输入:Border relations with Canada have never been better.
    • image-20231025214702401

3.2 phase_2

phase_2汇编代码:

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
0000000000400efc <phase_2>:
400efc: 55 push %rbp
400efd: 53 push %rbx
400efe: 48 83 ec 28 sub $0x28,%rsp #开了10个4字节的空间,多开了4个???
400f02: 48 89 e6 mov %rsp,%rsi#将开辟的地址传入%rsp存放的就是数组第一个元素
400f05: e8 52 05 00 00 callq 40145c <read_six_numbers> #读取六个数字
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) #第一个参数是否是1
400f0e: 74 20 je 400f30 <phase_2+0x34> #是1则跳转0x400f30
400f10: e8 25 05 00 00 callq 40143a <explode_bomb> #否则就bomb
400f15: eb 19 jmp 400f30 <phase_2+0x34>
400f17: 8b 43 fc mov -0x4(%rbx),%eax #取得上一个输入的数,放入%eax
400f1a: 01 c0 add %eax,%eax #%加倍
400f1c: 39 03 cmp %eax,(%rbx) #与当前的数比较
400f1e: 74 05 je 400f25 <phase_2+0x29> #相等则跳过下一行到0x400f25
400f20: e8 15 05 00 00 callq 40143a <explode_bomb>
400f25: 48 83 c3 04 add $0x4,%rbx #指向下一个参数的位置 %rbx加上立即数
400f29: 48 39 eb cmp %rbp,%rbx #判断是否是第六个参数 0x18 == 24 == 4*6
400f2c: 75 e9 jne 400f17 <phase_2+0x1b> #不是则继续进行循环
400f2e: eb 0c jmp 400f3c <phase_2+0x40> # 是则跳转0x400f3c
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>#跳转0x400f17继续循环
400f3c: 48 83 c4 28 add $0x28,%rsp
400f40: 5b pop %rbx
400f41: 5d pop %rbp
400f42: c3 retq

read_six_numbers汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
000000000040145c <read_six_numbers>:#从我们输入的字符串中读取6个数字   第一个参数是%rdi
40145c: 48 83 ec 18 sub $0x18,%rsp #0x18 = 4 * 6 6个4字节的地址
401460: 48 89 f2 mov %rsi,%rdx #%rdx第三个参数寄存器
401463: 48 8d 4e 04 lea 0x4(%rsi),%rcx #%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> #sscanf
40148f: 83 f8 05 cmp $0x5,%eax
401492: 7f 05 jg 401499 <read_six_numbers+0x3d>
401494: e8 a1 ff ff ff callq 40143a <explode_bomb>
401499: 48 83 c4 18 add $0x18,%rsp
40149d: c3 retq

思路:

  • callq 40145c <read_six_numbers>调用函数“先读入六个数”,同 phase1一样,%rdi原封不动的传入了这个函数,那么%rsi存入的是什么呢?
  • sub $0x28,%rspmov %rsp,%rsi 这两行代码可以看出,先将%rsp中的栈指针栈顶 减去 0x28,然后存入%rsi中,所以%rsi中存入的是地址。结合函数 可以得%rsi 在源码是一个指针,指向一个数组的开头
  • 在read six numbers中 0x4(%rsi) 。。有几个地址还因为寄存器放不下 放入到了栈中,sscanf的第一个参数是read six numbers的第一个参数,输入的字符串 第二个参数是存储在0x4025c3中的字符串格式
    • x/s 0x4025c3查看 字符串存储的格式
    • image-20231026153037844
    • image-20231026151232389
  • 回到phase_2函数
    • 判断第一个元素是否是1,是则跳转0x400f30
    • image-20231026151727168
    • 0x400f30 是%rsp增加四字节(数组的第二个元素),放入%rbx中,然后把最后一个数字的下一个字节放入到%rbp——–0x18 = 24 = 4 * 6 用于判断循环终止,然后回到0x400f17
    • image-20231026151852711
    • 0x400f17 mov -0x4(%rbx),%eax 取得%rbx上一个数 放入%eax ,然后相加使其翻倍,最后将翻倍的数与当前的数进行比较 ,循环比较剩下五个数。
    • image-20231026152238146
    • 其实是以1为首项,2为公比的等比数列。
  • 所以答案输入1 2 4 8 16 32就好
  • phrase_2

3.3 phase_3

phase_3 coding

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
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 #“%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 #和1比较
400f63: 7f 05 jg 400f6a <phase_3+0x27> #greater大于则跳转 0x400f6a
400f65: e8 d0 04 00 00 callq 40143a <explode_bomb> #否则bomb
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp) #比较第一个数是否小于7
400f6f: 77 3c ja 400fad <phase_3+0x6a> #above无符号大于则跳转 bomb不满足 case
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8) #无条件跳转 0x400f7c case0
400f7c: b8 cf 00 00 00 mov $0xcf,%eax #case 0 0xcf = 207
400f81: eb 3b jmp 400fbe <phase_3+0x7b>
400f83: b8 c3 02 00 00 mov $0x2c3,%eax #case 1 0x2c3 = 707
400f88: eb 34 jmp 400fbe <phase_3+0x7b>
400f8a: b8 00 01 00 00 mov $0x100,%eax #case 2 0x100 = 256
400f8f: eb 2d jmp 400fbe <phase_3+0x7b>
400f91: b8 85 01 00 00 mov $0x185,%eax #case 3 0x185 = 389
400f96: eb 26 jmp 400fbe <phase_3+0x7b>
400f98: b8 ce 00 00 00 mov $0xce,%eax #case 4 0xce = 206
400f9d: eb 1f jmp 400fbe <phase_3+0x7b>
400f9f: b8 aa 02 00 00 mov $0x2aa,%eax #case 5 0x2aa = 682
400fa4: eb 18 jmp 400fbe <phase_3+0x7b>
400fa6: b8 47 01 00 00 mov $0x147,%eax #case 6 0x147 = 327
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 #else case
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 #处理第二个数 与case中存放的数进行比较
400fc2: 74 05 je 400fc9 <phase_3+0x86> #equal相等则跳转
400fc4: e8 71 04 00 00 callq 40143a <explode_bomb>
400fc9: 48 83 c4 18 add $0x18,%rsp
400fcd: c3 retq

思路:

  • 考察的是switch语句的汇编表示,也调用了sscanf
    • x/s 0x4025cf结果是两个数,第一个放在0xc(%rsp)存入rcx 和 第二个数放在0x8(rsp)存入 rdx
    • 0x4025cf
  • 对于0x8(rsp)只在下面出现过
    • image-20231026162400954
    • *解引用 也就是0x402470这个地址在存储的 值与8乘rax的值加在一起组成的地址 rax既是前一个输入的参数
    • x/wx 0x402470得到 指令x:查看内存指令、w:四字节 x:16进制显示,跳转到0x400f7c,也就是下一行,对应switch的第一个case
      • image-20231026162723425
  • 对于0xc:
    • image-20231026163415020
  • 这七个case 处理不同的逻辑,即我们传入两个数,第一个数用来判断要去哪个case, 第二个数用来和这个case存放的书进行比较,如果相等则通过,不相等则bomb
  • 答案不唯一 对应的case 输入对应的值就好 0 207
    • phase_3

3.4 phase_4

phase_4 coding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 # %d %d
40101f: b8 00 00 00 00 mov $0x0,%eax #eax存入0
401024: e8 c7 fb ff ff callq 400bf0 <__isoc99_sscanf@plt> #sscanf
401029: 83 f8 02 cmp $0x2,%eax #
40102c: 75 07 jne 401035 <phase_4+0x29> #jump not equal 不相等则跳转 bomb
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp) #32位比较0xe = 14
401033: 76 05 jbe 40103a <phase_4+0x2e> #jbe小于等于则跳转 0x40103a
401035: e8 00 04 00 00 callq 40143a <explode_bomb>
40103a: ba 0e 00 00 00 mov $0xe,%edx # 14
40103f: be 00 00 00 00 mov $0x0,%esi # 0 传入func函数
401044: 8b 7c 24 08 mov 0x8(%rsp),%edi #第一个参数传入 edi
401048: e8 81 ff ff ff callq 400fce <func4> # func 调用
40104d: 85 c0 test %eax,%eax #test 与运算
40104f: 75 07 jne 401058 <phase_4+0x4c> #不相等 不等于0则bomb
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

func4 coding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
0000000000400fce <func4>:
400fce: 48 83 ec 08 sub $0x8,%rsp
400fd2: 89 d0 mov %edx,%eax #edx = 14 eax = 0xe
400fd4: 29 f0 sub %esi,%eax #esi = 0 eax = 0xe - 0x0
400fd6: 89 c1 mov %eax,%ecx # ecx = 0xe
400fd8: c1 e9 1f shr $0x1f,%ecx #逻辑右移 0x1f = 31位 ecx = 0
400fdb: 01 c8 add %ecx,%eax # eax = 0xe
400fdd: d1 f8 sar %eax #算数右移1位 符号位不变 1110(14) ->0111(7)
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx #rsi(0) + rax(7) = 0x7
400fe2: 39 f9 cmp %edi,%ecx #与传入的参数比较
400fe4: 7e 0c jle 400ff2 <func4+0x24> #小于或等于跳0x400ff2
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 #eax = 0
400ff7: 39 f9 cmp %edi,%ecx # 比较ecx = 7 edi 传入的参数
400ff9: 7d 0c jge 401007 <func4+0x39> #>= 跳401007
400ffb: 8d 71 01 lea 0x1(%rcx),%esi #否则(小于的情况) esi + 1
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

思路

  • 同上sscanf 输入标准化是“%d %d” 第一个数放在0x8(%rsp),第二个数放在0xc(%rsp)
  • 要求第一个数不大于14
    • image-20231026194939554
  • 要求第二个数等于0
    • image-20231026195056021
  • 使用 0x8,调用了func4 函数,func函数的第一个参数是我们输入的第一个数 0x8(%rsp),第二个 第三个参数是常数,然后判断func的返回值 是否等于0,不能等0则bomb
    • image-20231026171015752
  • 然后就分析 func函数传入0和 14实现了什么 那么 ecx = 7 只需要让rdi也等于7 即第一参数等于7即可
    • sar %eax 等价于SAR EAX 1
  • 输入 7 0
    • phase_4

3.5 phase_5

phase_5 coding

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
0000000000401062 <phase_5>:
401062: 53 push %rbx
401063: 48 83 ec 20 sub $0x20,%rsp #开辟空间 32字节
401067: 48 89 fb mov %rdi,%rbx #rdi = rbx
40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax #fs偏移0x28(40)存入%rax中 保护堆栈
401071: 00 00
401073: 48 89 44 24 18 mov %rax,0x18(%rsp)
401078: 31 c0 xor %eax,%eax #异或
40107a: e8 9c 02 00 00 callq 40131b <string_length> #调用函数
40107f: 83 f8 06 cmp $0x6,%eax #6 和eax比较 传入6个字符
401082: 74 4e je 4010d2 <phase_5+0x70> #相等则跳转4010d2
401084: e8 b1 03 00 00 callq 40143a <explode_bomb> #否则bomb
401089: eb 47 jmp 4010d2 <phase_5+0x70>
40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx # 0扩展 ecx = *rax + *rbx = *rdi + *rax 第rax个字符
40108f: 88 0c 24 mov %cl,(%rsp) #栈顶rsp = cl(rax的低4位)
401092: 48 8b 14 24 mov (%rsp),%rdx #rdx = rsp = cl (rax的低4位)
401096: 83 e2 0f and $0xf,%edx #edx与运算 1111 即取cl的低四位 存入edx
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx #第一条字符串 0x4024b0[rdx]处的字符加载到edx上
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1) #dl->rax + rsp + 0x10处
4010a4: 48 83 c0 01 add $0x1,%rax #rax++
4010a8: 48 83 f8 06 cmp $0x6,%rax #循环6次
4010ac: 75 dd jne 40108b <phase_5+0x29> #不相等最跳转0x40108b循环
4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp)
4010b3: be 5e 24 40 00 mov $0x40245e,%esi #第二条字符串 “flyers”
4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi #0x10~0x15(rsp) 和flyers是否相等
4010bd: e8 76 02 00 00 callq 401338 <strings_not_equal> #看字符串是否不相等 不相等则爆炸
4010c2: 85 c0 test %eax,%eax #与运算
4010c4: 74 13 je 4010d9 <phase_5+0x77> #相等跳d9
4010c6: e8 6f 03 00 00 callq 40143a <explode_bomb> #否则报错
4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
4010d0: eb 07 jmp 4010d9 <phase_5+0x77>
4010d2: b8 00 00 00 00 mov $0x0,%eax # 最初 eax = 0
4010d7: eb b2 jmp 40108b <phase_5+0x29> #跳转40108b
4010d9: 48 8b 44 24 18 mov 0x18(%rsp),%rax
4010de: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
4010e5: 00 00
4010e7: 74 05 je 4010ee <phase_5+0x8c>
4010e9: e8 42 fa ff ff callq 400b30 <__stack_chk_fail@plt>
4010ee: 48 83 c4 20 add $0x20,%rsp
4010f2: 5b pop %rbx
4010f3: c3 retq

思路:

  • gdb x/s 0x4024b0得到如下字符串
    • image-20231026204707552
  • gdb x/s 0x40245e 得到如下字符串:
    • image-20231026205058798
  • cmp $0x6,%eaxje 4010d2 <phase_5+0x70> 规定了要传入6个字符 否则会爆炸
  • 即我们输入的6个字符 低四位rdx 在0x4024b0[rdx] 得到一个字符串要与 flyers相等就行
  • 所以在字符串1中找出rdx即可 分别是9(1001)、15(1111)、14(1110)、5(0101)、6(0110)、7(0111)
  • 查找ascii码数的低四位即可是上述即可 9?NUVW
    • image-20231026214015659

3.6 phase_6

phase_6 coding

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
00000000004010f4 <phase_6>:
4010f4: 41 56 push %r14
4010f6: 41 55 push %r13
4010f8: 41 54 push %r12
4010fa: 55 push %rbp
4010fb: 53 push %rbx
4010fc: 48 83 ec 50 sub $0x50,%rsp #开辟0x50 空间
401100: 49 89 e5 mov %rsp,%r13 # r13 = rsp
401103: 48 89 e6 mov %rsp,%rsi #rsi = rsp
401106: e8 51 03 00 00 callq 40145c <read_six_numbers> #调用函数
40110b: 49 89 e6 mov %rsp,%r14 #r14 = rsp
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d #r12d = 0x0
--------------------------------------------------------------------#大循环1 r12d是下标
401114: 4c 89 ed mov %r13,%rbp #rbp = r13 =rsp
401117: 41 8b 45 00 mov 0x0(%r13),%eax #
40111b: 83 e8 01 sub $0x1,%eax #eax--
40111e: 83 f8 05 cmp $0x5,%eax
401121: 76 05 jbe 401128 <phase_6+0x34> #<=5 则跳401128说明输入在[1,6]之间 !!!!--------判断这个数是否处于[1,6]之间
401123: e8 12 03 00 00 callq 40143a <explode_bomb> #>5 bomb
401128: 41 83 c4 01 add $0x1,%r12d # r12d++ r12d = 1 当前值的索引值加1
40112c: 41 83 fc 06 cmp $0x6,%r12d # #循环6次
401130: 74 21 je 401153 <phase_6+0x5f> #相等则跳转401153
401132: 44 89 e3 mov %r12d,%ebx #ebx = r12d 索引下一个数
------------------------#嵌套循环 !!!判断这个数是否与数组中其余数相等
401135: 48 63 c3 movslq %ebx,%rax #sign 符号拓展 索引放入rax
401138: 8b 04 84 mov (%rsp,%rax,4),%eax #eax = 4*rax + rsp 算出上面查过数的地址,通过地址找到这个数,放入eax rsp[4*ebx]
40113b: 39 45 00 cmp %eax,0x0(%rbp) #把eax找的数与rbp的比较 即六个数字不能相等
40113e: 75 05 jne 401145 <phase_6+0x51> #不相等则跳转401145
401140: e8 f5 02 00 00 callq 40143a <explode_bomb> #相等则bomb
401145: 83 c3 01 add $0x1,%ebx #ebx++
401148: 83 fb 05 cmp $0x5,%ebx #
40114b: 7e e8 jle 401135 <phase_6+0x41> #小于跳401135 循环
---------------------- #嵌套循环结束
40114d: 49 83 c5 04 add $0x4,%r13 #r13继续指向下一个数
401151: eb c1 jmp 401114 <phase_6+0x20>
-------------------------------------------------------------#大循环结束
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi
401158: 4c 89 f0 mov %r14,%rax #r14 = rsp = 输入的6个数
40115b: b9 07 00 00 00 mov $0x7,%ecx #exc = 7
401160: 89 ca mov %ecx,%edx #edx=ecx = 7
401162: 2b 10 sub (%rax),%edx #rax 指向输入的数然后用7减 edx -= rax
401164: 89 10 mov %edx,(%rax) #将结果放入rax
401166: 48 83 c0 04 add $0x4,%rax #下一个数
40116a: 48 39 f0 cmp %rsi,%rax #比较rsi与rax
40116d: 75 f1 jne 401160 <phase_6+0x6c> #不相等则跳转 441160循环直到把所有参数都改变了
40116f: be 00 00 00 00 mov $0x0,%esi
401174: eb 21 jmp 401197 <phase_6+0xa3> #全都不相等跳401197
--------------------------# edx中的7-int的元素放入 链表中
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx #rdx = *rdx + 8 字节对齐 p = p->next
40117a: 83 c0 01 add $0x1,%eax #eax++
40117d: 39 c8 cmp %ecx,%eax #
40117f: 75 f5 jne 401176 <phase_6+0x82> #不等跳401176
401181: eb 05 jmp 401188 <phase_6+0x94> #否则跳401188
401183: ba d0 32 60 00 mov $0x6032d0,%edx #链表头指针
401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2) #rdx -》 rsp[2*rsi+0x20]
40118d: 48 83 c6 04 add $0x4,%rsi #指向下一个
401191: 48 83 fe 18 cmp $0x18,%rsi
401195: 74 14 je 4011ab <phase_6+0xb7> #相等跳4011ab
----------------------------------------------------------------
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx #ecx = rsp[rsi]
40119a: 83 f9 01 cmp $0x1,%ecx #和1 比较
40119d: 7e e4 jle 401183 <phase_6+0x8f> #<=1 跳转401183
40119f: b8 01 00 00 00 mov $0x1,%eax # eax = 1
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx #edx = 0x6032d0 gdb一下 node变量
4011a9: eb cb jmp 401176 <phase_6+0x82> #然会跳转401176 上面那段
-------------------------------------------------------------------------
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx #rbx最初节点
4011b0: 48 8d 44 24 28 lea 0x28(%rsp),%rax # 下一个节点
4011b5: 48 8d 74 24 50 lea 0x50(%rsp),%rsi
4011ba: 48 89 d9 mov %rbx,%rcx
-------------------------------------------------------------
4011bd: 48 8b 10 mov (%rax),%rdx #rax指向位置拿出node变量的地址
4011c0: 48 89 51 08 mov %rdx,0x8(%rcx) #node->next
4011c4: 48 83 c0 08 add $0x8,%rax
4011c8: 48 39 f0 cmp %rsi,%rax
4011cb: 74 05 je 4011d2 <phase_6+0xde> #相等跳 4011d2
4011cd: 48 89 d1 mov %rdx,%rcx
4011d0: eb eb jmp 4011bd <phase_6+0xc9>
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) #指向最后一个节点
4011d9: 00
---------------------------------------------------------------------
4011da: bd 05 00 00 00 mov $0x5,%ebp #循环变量 索引
4011df: 48 8b 43 08 mov 0x8(%rbx),%rax #rbx指向第一个索引对应的节点 node.next放入 rax
4011e3: 8b 00 mov (%rax),%eax #rax解引用指向节点的val值 放入eax
4011e5: 39 03 cmp %eax,(%rbx) #比较eax和 rbx(上一个元素)
4011e7: 7d 05 jge 4011ee <phase_6+0xfa> #rbx大于eax后者 则跳转4011ee 通过
4011e9: e8 4c 02 00 00 callq 40143a <explode_bomb> #否则就bomb
4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx
4011f2: 83 ed 01 sub $0x1,%ebp #ebp--
4011f5: 75 e8 jne 4011df <phase_6+0xeb> #不循环
4011f7: 48 83 c4 50 add $0x50,%rsp #恢复栈顶
4011fb: 5b pop %rbx
4011fc: 5d pop %rbp
4011fd: 41 5c pop %r12
4011ff: 41 5d pop %r13
401201: 41 5e pop %r14
401203: c3 retq

思路

  • 实际上是链表的应用
  • 将第一个大循环转换成c语言代码可以得
1
2
3
4
5
6
7
int sixNum[6] = {输入的6个数} 
for (int i = 0; i < 6; i++) {
if (sixNum[i] > 6) explode_bomb();
for (int j = i + 1; j < 6; j++) {
if (sixNum[i] == sixNum[j]) explode_bomb;
}
}
  • x/wx 0x6032d0

    • image-20231028113244985
    • node 变量 链表!!!
  • x/24wx 0x6032d0 24字节

    • image-20231028114724993

    • 索引
      1 332[0x14c]
      2 168[0xa8]
      3 924[0x39c]
      4 691[0x2b3]
      5 477[0x1dd]
      6 443[0x1bb]
  • 单向链表:

1
2
3
4
5
6
//单链表
struct ListNode {
int val;//节点上存储的元素
ListNode *next;//指向下一个节点的指针
ListNode (int x) : val(x), next(NULL){}//节点的构造函数
};
  • 那么前面的嵌套循环作用:根据输入的索引,找到对应的node值,并且把这个值放入到一个数组中
1
2
3
4
5
6
7
8
Node* addresses[6] = {};
for (int i = 0; i< 6; i++) {
int index = sixNum[i];
addresses[i] = &node1;
while (addresses[i]->index != index){
addresses[i] = addresses[i]->next;
}
}
  • 所以题目要我们输入的是链表的索引顺序,这个顺序是降序排列的,并且每个值都要用 7- 。所以降序排列顺序为3 4 5 6 1 2答案为 4 3 2 1 6 5
    • image-20231028152223411

3.7 secret_phase

secret_phase coding:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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> #strtol 将字符串转换成long
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> #小于跳0x40126c
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 # %edi存放地址
401273: e8 8c ff ff ff callq 401204 <fun7>
401278: 83 f8 02 cmp $0x2,%eax #2== eax
40127b: 74 05 je 401282 <secret_phase+0x40> #2等于2 跳结束
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
401292: c3 retq

func_7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0000000000401204 <fun7>:
401204: 48 83 ec 08 sub $0x8,%rsp
401208: 48 85 ff test %rdi,%rdi #test 如果两个数位与 则会将0标志位置1
40120b: 74 2b je 401238 <fun7+0x34> #判断是否是null 是返回1
40120d: 8b 17 mov (%rdi),%edx #node->val
40120f: 39 f2 cmp %esi,%edx #与传入的值进行比较
401211: 7e 0d jle 401220 <fun7+0x1c> #小于等于则 401220
401213: 48 8b 7f 08 mov 0x8(%rdi),%rdi #左子树 递归 8位
401217: e8 e8 ff ff ff callq 401204 <fun7> #递归
40121c: 01 c0 add %eax,%eax #eax += eax 翻倍 调用左子树
40121e: eb 1d jmp 40123d <fun7+0x39>
401220: b8 00 00 00 00 mov $0x0,%eax #eax = 0 归零
401225: 39 f2 cmp %esi,%edx # 所以这一段需要额外判断等
401227: 74 14 je 40123d <fun7+0x39> #等于 返回
401229: 48 8b 7f 10 mov 0x10(%rdi),%rdi #否则取其右子树 0x10 = 16 位
40122d: e8 d2 ff ff ff callq 401204 <fun7> #递归
401232: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax #翻倍+1 调用右子树
401236: eb 05 jmp 40123d <fun7+0x39>
401238: b8 ff ff ff ff mov $0xffffffff,%eax #为null返回0xffffffff
40123d: 48 83 c4 08 add $0x8,%rsp
401241: c3 retq

满足6个数后phase_defused代码即0x4015e1之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
4015e1:	4c 8d 44 24 10       	lea    0x`10(%rsp),%r8
4015e6: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
4015eb: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
4015f0: be 19 26 40 00 mov $0x402619,%esi
4015f5: bf 70 38 60 00 mov $0x603870,%edi
4015fa: e8 f1 f5 ff ff callq 400bf0 <__isoc99_sscanf@plt>
4015ff: 83 f8 03 cmp $0x3,%eax #查看是否三个参数 不相等跳转0x401635
401602: 75 31 jne 401635 <phase_defused+0x71>
401604: be 22 26 40 00 mov $0x402622,%esi #答案
401609: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi #需要对比的字符串
40160e: e8 25 fd ff ff callq 401338 <strings_not_equal>
401613: 85 c0 test %eax,%eax
401615: 75 1e jne 401635 <phase_defused+0x71>
401617: bf f8 24 40 00 mov $0x4024f8,%edi
40161c: e8 ef f4 ff ff callq 400b10 <puts@plt>
401621: bf 20 25 40 00 mov $0x402520,%edi
401626: e8 e5 f4 ff ff callq 400b10 <puts@plt>
40162b: b8 00 00 00 00 mov $0x0,%eax
401630: e8 0d fc ff ff callq 401242 <secret_phase>
  1. 搜索secret_phase,发现在phase_defused调用,然后搜索phase_defused,发现每个phase结束后都会调用。但不确定是哪个phase

    image-20231029160310128

  2. phase_defused看什么情况下才能触发secret_phase,当操作数不等于6时,直接跳0x40163f,直接返回,跳过了secret_phase

    image-20231029160612778

  3. 当输入的字符串数量等于6时才判断是否调用secret函数。——-即必须要解答6个bomb后才能调用secret_phase

  4. 查看0x4015e1后的代码,调用了sscanf函数 对输入的字符串进行模板对比。

    1. 在b 0x4015e1 设置断点,查看 地址0x402619 即sscanf的输入格式 %d %d %s
    2. 然后查看0x603870传入sscanf是什么数x/s 0x6038707 0 ,恰好是phase_4输入的答案,因此可以推断,phase_4应该输入特定的答案从而触发secret_phase
    3. image-20231029162326774
    4. 再往下看,最后那个s%的字符串是是什么呢?可以看到将``0x402622地址的字符串传入sstrings_not_equal 判断与输入的是否相等 ,所以查看0x402622存放了什么。---DrEvil`
    5. image-20231029163445370

secret_phase解析:

  1. phase_4答案后面加上DrEvil

  2. image-20231029164152074

  3. b secret_phase打上断点,然后运行r ans.txtlayout asm看到调用了 strtol 将输入的字符串转换成long,然后比较 eax0x3e8[1000]

  4. 调用 func 传入的参数是(%edi(0x6030f0),%esi(用户输入)),只有当func 7的返回值为2时才能避免爆炸。

  5. 0x6030f0这个数就推断是个地址,x/130xw 查看这个地址,每个节点占据32位 分别是 val值(前四字节)、填充(4字节)、左子节点地址(8字节)、右子节点(8字节)。因此可以得出这个是一个二叉树结构

  6. image-20231029192506719

  7. 写成二叉树的形式如下:每个节点占据32位 分别是 val值(前四字节)、填充(4字节)、左子节点地址(8字节)、右子节点(8字节)。下面是16进制的表达

  8. image-20231029192849626

  9. 分析func7的汇编代码,可以写成如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int fun7(TreeNode* tree, int num) {
    if (tree == NULL) return -1;
    if (tree->val < num) {
    return 2*fun7(tree->right, num) +1;
    }else if(tree->val == num) {
    return 0;
    }else{
    return 2*fun7(tree->left, num);
    }
    }
  10. 所以可以看出,想要返回2,输入的值必须在数中,否则会返回负数。并且左边递归会无法返回2,只会把原来的值加倍,因此需要原来右子树递归+1,获取1,然后利用2*fun7() 获得2.

  11. 从根节点出发0x24,那么若向右走,则执行 2*fun7(tree->right, num) + 1想要return 2,需要fun7() ==0.5,显然不可能,那么从左走 2*fun7(tree->left, num),到达节点0x8 第二层,此时,前面一次选择已经有2的倍数了,所以只需要能够获得1即可,那么怎么获得1呢?—->向右走2*fun7(tree->right, num) + 1 使得num 等于 右边子树的一个fun7()==0,所以就可以获得1,最终得到2*(fun7(tree->right,num) + 1).所以num = 0x16 或0x14

总结:

答案:

  1. Border relations with Canada have never been better.
  2. 1 2 4 8 16 32
  3. 0 207
  4. 7 0 DrEvil
  5. 9?NUVW
  6. 4 3 2 1 6 5
  7. 20 或 22