计算机系统基础实验三(解了但尽量理解)

一.准备阶段

1、下载好32位的实验代码后,将文件解压缩并且通过共享文件夹操作将文件添加到虚拟机中,双击查看bomb.c代码,将c代码完整看了一遍,发现看这里的c代码是无从下手的,代码中只含有主函数,触发炸弹的过程被隐藏了起来。阅读老师给出的readme文件后,得知本题需要在Linux终端中对汇编代码进行分析并且调试才能得出答案。
2、输入objdump -d bomb,将可执行文件反汇编成汇编文件,为方便查看,可以使用objdump -d bomb >1.txt将汇编代码输出到txt文档里
3、试探一下炸弹爆炸的效果,gdb调试该可执行文件,随便输入一个字符串,果然爆炸了

二.实验开始

 一.实验一phase_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  
对上面汇编语言进行分析:

这段汇编代码实现了一个函数phase_1,它检查一个字符串是否与某个预期值相匹配,如果不匹配,则会调用一个函数explode_bomb。下面是对每一行代码的解释:

  1. 400ee0: 48 83 ec 08 sub $0x8,%rsp

    • 函数开始时,调整栈指针%rsp,为局部变量分配8个字节的空间。这通常是为了保持栈的对齐或为局部变量腾出空间。
  2. 400ee4: be 00 24 40 00 mov $0x402400,%esi

    • 将常量地址0x402400加载到寄存器%esi中。这个地址通常指向一个字符串常量。
  3. 400ee9: e8 4a 04 00 00 callq 401338 <strings_not_equal>

    • 调用函数strings_not_equal,传递参数%esi(即字符串常量地址0x402400)。该函数将检查传递给phase_1的字符串与位于0x402400的字符串是否不同。
  4. 400eee: 85 c0 test %eax,%eax

    • 测试寄存器%eax的值。strings_not_equal函数的返回值存储在%eax中。如果返回值是0(表示字符串相同),则test %eax,%eax将设置ZF(零标志)。
  5. 400ef0: 74 05 je 400ef7 <phase_1+0x17>

    • 如果ZF(零标志)被设置(即%eax为0,表示字符串相同),跳转到地址400ef7,跳过explode_bomb调用。
  6. 400ef2: e8 43 05 00 00 callq 40143a <explode_bomb>

    • 如果字符串不相同,调用explode_bomb函数,这通常会导致程序失败或某种惩罚措施(比如在某些安全竞赛中模拟炸弹爆炸)。
  7. 400ef7: 48 83 c4 08 add $0x8,%rsp

    • 恢复栈指针%rsp,将之前减去的8个字节加回来。
  8. 400efb: c3 retq

    • 函数返回。

总结一下,这段汇编的逻辑是:

  • 从栈中分配一些空间。
  • 加载一个字符串常量地址。
  • 调用一个函数检查输入字符串是否与预期字符串相同。
  • 如果不相同,调用explode_bomb
  • 恢复栈指针并返回。

接下来我们看string_not_equal这个函数:
 0000000000401338 <strings_not_equal>:
  401338:    41 54                    push   %r12
  40133a:    55                       push   %rbp
  40133b:    53                       push   %rbx
  40133c:    48 89 fb                 mov    %rdi,%rbx
  40133f:    48 89 f5                 mov    %rsi,%rbp
  401342:    e8 d4 ff ff ff           callq  40131b <string_length>
  401347:    41 89 c4                 mov    %eax,%r12d
  40134a:    48 89 ef                 mov    %rbp,%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
  40135f:    84 c0                    test   %al,%al
  401361:    74 25                    je     401388 <strings_not_equal+0x50>
  401363:    3a 45 00                 cmp    0x0(%rbp),%al
  401366:    74 0a                    je     401372 <strings_not_equal+0x3a>
  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
  401376:    48 83 c5 01              add    $0x1,%rbp
  40137a:    0f b6 03                 movzbl (%rbx),%eax
  40137d:    84 c0                    test   %al,%al
  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 

下面是对每一行汇编的解释:
 

  1. 401338: 41 54 push %r12

    • 将寄存器 %r12 的值压入栈中,保存它的原始值。
  2. 40133a: 55 push %rbp

    • 将寄存器 %rbp 的值压入栈中,保存它的原始值。
  3. 40133b: 53 push %rbx

    • 将寄存器 %rbx 的值压入栈中,保存它的原始值。
  4. 40133c: 48 89 fb mov %rdi,%rbx

    • 将寄存器 %rdi 的值移动到 %rbx%rdi 是第一个字符串的地址。
  5. 40133f: 48 89 f5 mov %rsi,%rbp

    • 将寄存器 %rsi 的值移动到 %rbp%rsi 是第二个字符串的地址。
  6. 401342: e8 d4 ff ff ff callq 40131b <string_length>

    • 调用 string_length 函数来计算第一个字符串的长度。
  7. 401347: 41 89 c4 mov %eax,%r12d

    • string_length 返回的值(第一个字符串的长度)存储到 %r12d
  8. 40134a: 48 89 ef mov %rbp,%rdi

    • %rbp(第二个字符串的地址)移动到 %rdi,为下一次调用 string_length 准备参数。
  9. 40134d: e8 c9 ff ff ff callq 40131b <string_length>

    • 调用 string_length 函数来计算第二个字符串的长度。
  10. 401352: ba 01 00 00 00 mov $0x1,%edx

    • 将常量 1 移动到 %edx。如果字符串不相等,该值将用作返回值。
  11. 401357: 41 39 c4 cmp %eax,%r12d

    • 比较第二个字符串的长度(存储在 %eax 中)与第一个字符串的长度(存储在 %r12d 中)。
  12. 40135a: 75 3f jne 40139b <strings_not_equal+0x63>

    • 如果长度不同,跳转到 40139b,设置返回值为 1,表示字符串不相等。
  13. 40135c: 0f b6 03 movzbl (%rbx),%eax

    • %rbx 指向的第一个字符串的字符加载到 %eax 中,扩展为零填充。
  14. 40135f: 84 c0 test %al,%al

    • 测试 %al 是否为零(字符串是否结束)。
  15. 401361: 74 25 je 401388 <strings_not_equal+0x50>

    • 如果 %al 为零(字符串结束),跳转到 401388,设置返回值为 0,表示字符串相等。
  16. 401363: 3a 45 00 cmp 0x0(%rbp),%al

    • 比较 %al%rbp 指向的第二个字符串的字符。
  17. 401366: 74 0a je 401372 <strings_not_equal+0x3a>

    • 如果字符相同,跳转到 401372,继续比较下一个字符。
  18. 401368: eb 25 jmp 40138f <strings_not_equal+0x57>

    • 如果字符不同,跳转到 40138f,设置返回值为 1,表示字符串不相等。
  19. 40136a: 3a 45 00 cmp 0x0(%rbp),%al

    • 再次比较 %al%rbp 指向的第二个字符串的字符。
  20. 40136d: 0f 1f 00 nopl (%rax)

    • 无操作指令,可能用于对齐。
  21. 401370: 75 24 jne 401396 <strings_not_equal+0x5e>

    • 如果字符不同,跳转到 401396,设置返回值为 1,表示字符串不相等。
  22. 401372: 48 83 c3 01 add $0x1,%rbx

    • 增加 %rbx,指向下一个字符。
  23. 401376: 48 83 c5 01 add $0x1,%rbp

    • 增加 %rbp,指向下一个字符。
  24. 40137a: 0f b6 03 movzbl (%rbx),%eax

    • %rbx 指向的第一个字符串的下一个字符加载到 %eax 中,扩展为零填充。
  25. 40137d: 84 c0 test %al,%al

    • 测试 %al 是否为零(字符串是否结束)。
  26. 40137f: 75 e9 jne 40136a <strings_not_equal+0x32>

    • 如果 %al 不为零,跳转回去继续比较下一个字符。
  27. 401381: ba 00 00 00 00 mov $0x0,%edx

    • 将常量 0 移动到 %edx,表示字符串相等。
  28. 401386: eb 13 jmp 40139b <strings_not_equal+0x63>

    • 跳转到 40139b,将结果返回。
  29. 401388: ba 00 00 00 00 mov $0x0,%edx

    • 将常量 0 移动到 %edx,表示字符串相等。
  30. 40138d: eb 0c jmp 40139b <strings_not_equal+0x63>

    • 跳转到 40139b,将结果返回。
  31. 40138f: ba 01 00 00 00 mov $0x1,%edx

    • 将常量 1 移动到 %edx,表示字符串不相等。
  32. 401394: eb 05 jmp 40139b <strings_not_equal+0x63>

    • 跳转到 40139b,将结果返回。
  33. 401396: ba 01 00 00 00 mov $0x1,%edx

    • 将常量 1 移动到 %edx,表示字符串不相等。
  34. 40139b: 89 d0 mov %edx,%eax

    • %edx 的值移动到 %eax,准备返回结果。
  35. 40139d: 5b pop %rbx

    • 恢复之前保存的 %rbx 的值。
  36. 40139e: 5d pop %rbp

    • 恢复之前保存的 %rbp 的值。
  37. 40139f: 41 5c pop %r12

    • 恢复之前保存的 %r12 的值。
  38. 4013a1: c3 retq

    • 返回到调用者。

 最后我们看一下string_length的汇编语言:

000000000040131b <string_length>:
  40131b:    80 3f 00                 cmpb   $0x0,(%rdi)
  40131e:    74 12                    je     401332 <string_length+0x17>
  401320:    48 89 fa                 mov    %rdi,%rdx
  401323:    48 83 c2 01              add    $0x1,%rdx
  401327:    89 d0                    mov    %edx,%eax
  401329:    29 f8                    sub    %edi,%eax
  40132b:    80 3a 00                 cmpb   $0x0,(%rdx)
  40132e:    75 f3                    jne    401323 <string_length+0x8>
  401330:    f3 c3                    repz retq 
  401332:    b8 00 00 00 00           mov    $0x0,%eax
  401337:    c3                       retq  

下面我们看一下每一行的解释:

  1. 40131b: 80 3f 00 cmpb $0x0,(%rdi)

    • 比较寄存器 %rdi 指向的内存地址中的字节与 0%rdi 通常指向一个字符串的起始地址。
  2. 40131e: 74 12 je 401332 <string_length+0x17>

    • 如果字符串的第一个字节为 0(即空字符串),则跳转到 401332,直接返回字符串长度 0
  3. 401320: 48 89 fa mov %rdi,%rdx

    • %rdi 的值(字符串的起始地址)复制到 %rdx
  4. 401323: 48 83 c2 01 add $0x1,%rdx

    • %rdx 增加 1,指向下一个字符。
  5. 401327: 89 d0 mov %edx,%eax

    • %rdx 的值(当前字符地址)复制到 %eax
  6. 401329: 29 f8 sub %edi,%eax

    • %rdi(字符串起始地址)的值从 %eax 中减去,这样 %eax 中保存的就是当前字符的偏移量,即字符串的长度。
  7. 40132b: 80 3a 00 cmpb $0x0,(%rdx)

    • 比较 %rdx 指向的当前字符与 0
  8. 40132e: 75 f3 jne 401323 <string_length+0x8>

    • 如果当前字符不是 0,则跳回 401323,继续处理下一个字符。
  9. 401330: f3 c3 repz retq

    • 返回。这个指令等同于 retq,因为 repz 前缀对 retq 没有影响。这里可能是为了对齐或编译器生成的优化。
  10. 401332: b8 00 00 00 00 mov $0x0,%eax

    • 0 移动到 %eax,表示字符串长度为 0
  11. 401337: c3 retq

    • 返回到调用者。

 分析一遍可以知道,phase1就是把一个准备好的string和我输入的string进行比较,如果两者一致,那就不会爆炸。所以我们将刚刚读取到的0x402400中的那个string拿出来,输入,然后就成功了。

 

二.实验二phase_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>
  400f0a:    83 3c 24 01              cmpl   $0x1,(%rsp)
  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 

下面我们看一下每一行对应的解释:

  1. 400efc: 55 push %rbp

    • %rbp 寄存器的值压入栈中,保存它的原始值。
  2. 400efd: 53 push %rbx

    • %rbx 寄存器的值压入栈中,保存它的原始值。
  3. 400efe: 48 83 ec 28 sub $0x28,%rsp

    • 调整栈指针,分配 40 字节的栈空间(0x28 = 40)。
  4. 400f02: 48 89 e6 mov %rsp,%rsi

    • 将当前栈指针的值存入 %rsi,为调用 read_six_numbers 函数准备参数。
  5. 400f05: e8 52 05 00 00 callq 40145c <read_six_numbers>

    • 调用 read_six_numbers 函数,从用户输入中读取六个整数并存储在栈中。
  6. 400f0a: 83 3c 24 01 cmpl $0x1,(%rsp)

    • 比较栈中第一个整数是否等于 1。
  7. 400f0e: 74 20 je 400f30 <phase_2+0x34>

    • 如果第一个整数是 1,则跳转到 400f30
  8. 400f10: e8 25 05 00 00 callq 40143a <explode_bomb>

    • 否则,调用 explode_bomb 函数。
  9. 400f15: eb 19 jmp 400f30 <phase_2+0x34>

    • 跳转到 400f30,继续执行。
  10. 400f17: 8b 43 fc mov -0x4(%rbx),%eax

    • %rbx 前一个整数的值(即当前整数的前一个整数)存储到 %eax
  11. 400f1a: 01 c0 add %eax,%eax

    • %eax 的值加倍(乘以 2)。
  12. 400f1c: 39 03 cmp %eax,(%rbx)

    • 比较 %eax(前一个整数的两倍)与 %rbx 当前指向的整数。
  13. 400f1e: 74 05 je 400f25 <phase_2+0x29>

    • 如果相等,跳转到 400f25,继续检查下一个整数。
  14. 400f20: e8 15 05 00 00 callq 40143a <explode_bomb>

    • 否则,调用 explode_bomb 函数。
  15. 400f25: 48 83 c3 04 add $0x4,%rbx

    • %rbx 增加 4,指向下一个整数。
  16. 400f29: 48 39 eb cmp %rbp,%rbx

    • 比较 %rbx%rbp 的值(指向第六个整数的地址)。
  17. 400f2c: 75 e9 jne 400f17 <phase_2+0x1b>

    • 如果 %rbx 未达到 %rbp,跳回到 400f17,继续检查下一个整数。
  18. 400f2e: eb 0c jmp 400f3c <phase_2+0x40>

    • 跳转到 400f3c,清理栈并返回。
  19. 400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx

    • 将栈中第一个整数之后的地址(第二个整数的地址)加载到 %rbx
  20. 400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp

    • 将栈中最后一个整数的地址(第六个整数的地址)加载到 %rbp
  21. 400f3a: eb db jmp 400f17 <phase_2+0x1b>

    • 跳回到 400f17,开始检查整数序列。
  22. 400f3c: 48 83 c4 28 add $0x28,%rsp

    • 恢复栈指针,释放之前分配的 40 字节栈空间。
  23. 400f40: 5b pop %rbx

    • 恢复 %rbx 的原始值。
  24. 400f41: 5d pop %rbp

    • 恢复 %rbp 的原始值。
  25. 400f42: c3 retq

    • 返回到调用者。

接下来我们看一下read_six_numbers这个函数: 

000000000040145c <read_six_numbers>:
  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 <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 
下面我们解释一下每一行汇编:

  1. 40145c: 48 83 ec 18 sub $0x18,%rsp

    • 调整栈指针,分配 24 字节的栈空间(0x18 = 24)。
  2. 401460: 48 89 f2 mov %rsi,%rdx

    • %rsi 的值(指向存储六个整数的缓冲区的指针)复制到 %rdx
  3. 401463: 48 8d 4e 04 lea 0x4(%rsi),%rcx

    • %rsi 加上 4 的地址加载到 %rcx,即第二个整数的位置。
  4. 401467: 48 8d 46 14 lea 0x14(%rsi),%rax

    • %rsi 加上 20 的地址加载到 %rax,即第六个整数的位置。
  5. 40146b: 48 89 44 24 08 mov %rax,0x8(%rsp)

    • %rax(第六个整数的位置)存储到栈中偏移 8 的位置。
  6. 401470: 48 8d 46 10 lea 0x10(%rsi),%rax

    • %rsi 加上 16 的地址加载到 %rax,即第五个整数的位置。
  7. 401474: 48 89 04 24 mov %rax,(%rsp)

    • %rax(第五个整数的位置)存储到栈中的偏移 0 的位置。
  8. 401478: 4c 8d 4e 0c lea 0xc(%rsi),%r9

    • %rsi 加上 12 的地址加载到 %r9,即第四个整数的位置。
  9. 40147c: 4c 8d 46 08 lea 0x8(%rsi),%r8

    • %rsi 加上 8 的地址加载到 %r8,即第三个整数的位置。
  10. 401480: be c3 25 40 00 mov $0x4025c3,%esi

    • 0x4025c3(格式字符串的地址)加载到 %esi
  11. 401485: b8 00 00 00 00 mov $0x0,%eax

    • 0 加载到 %eax,准备调用 sscanf
  12. 40148a: e8 61 f7 ff ff callq 400bf0 <__isoc99_sscanf@plt>

    • 调用 __isoc99_sscanf 函数,读取六个整数。格式字符串是 "%d %d %d %d %d %d"
  13. 40148f: 83 f8 05 cmp $0x5,%eax

    • 比较 sscanf 返回的值(读取到的整数数量)是否大于 5。
  14. 401492: 7f 05 jg 401499 <read_six_numbers+0x3d>

    • 如果读取到的整数数量大于 5(即正好是 6),跳转到 401499
  15. 401494: e8 a1 ff ff ff callq 40143a <explode_bomb>

    • 否则,调用 explode_bomb 函数。
  16. 401499: 48 83 c4 18 add $0x18,%rsp

    • 恢复栈指针,释放之前分配的 24 字节栈空间。
  17. 40149d: c3 retq

    • 返回到调用者。

这个phase是考循环的,输入六个数,第一个数强制要求是1,并且每次倍增,自然而然,得出这六个数是1 2 4 8 16 32。 

三.实验三phase_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     

下面我们看一下每一行的解释:

  1. 400f43: 48 83 ec 18 sub $0x18,%rsp

    • 调整栈指针,分配 24 字节的栈空间(0x18 = 24)。
  2. 400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx

    • 计算栈上偏移 0xC(12)的地址,并将其加载到 %rcx
  3. 400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx

    • 计算栈上偏移 0x8(8)的地址,并将其加载到 %rdx
  4. 400f51: be cf 25 40 00 mov $0x4025cf,%esi

    • 将立即数 0x4025cf(格式字符串的地址)加载到 %esi
  5. 400f56: b8 00 00 00 00 mov $0x0,%eax

    • 将 0 加载到 %eax,准备调用 sscanf
  6. 400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt>

    • 调用 __isoc99_sscanf 函数,读取两个整数。格式字符串是 "%d %d"
  7. 400f60: 83 f8 01 cmp $0x1,%eax

    • 比较 sscanf 返回的值是否大于 1(即正好是 2)。
  8. 400f63: 7f 05 jg 400f6a <phase_3+0x27>

    • 如果读取到的整数数量大于 1,跳转到 400f6a
  9. 400f65: e8 d0 04 00 00 callq 40143a <explode_bomb>

    • 否则,调用 explode_bomb 函数。
  10. 400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp)

    • 比较栈上偏移 0x8(第二个整数)与 7。
  11. 400f6f: 77 3c ja 400fad <phase_3+0x6a>

    • 如果第二个整数大于 7,跳转到 400fad
  12. 400f71: 8b 44 24 08 mov 0x8(%rsp),%eax

    • 将栈上偏移 0x8(第二个整数)加载到 %eax
  13. 400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)

    • 根据 %eax 的值,从地址 0x402470 开始按索引跳转到相应的分支。
  14. 400f7c: b8 cf 00 00 00 mov $0xcf,%eax

    • 将立即数 0xcf(207)加载到 %eax
  15. 400f81: eb 3b jmp 400fbe <phase_3+0x7b>

    • 跳转到 400fbe
  16. 400f83: b8 c3 02 00 00 mov $0x2c3,%eax

    • 将立即数 0x2c3(707)加载到 %eax
  17. 400f88: eb 34 jmp 400fbe <phase_3+0x7b>

    • 跳转到 400fbe
  18. 400f8a: b8 00 01 00 00 mov $0x100,%eax

    • 将立即数 0x100(256)加载到 %eax
  19. 400f8f: eb 2d jmp 400fbe <phase_3+0x7b>

    • 跳转到 400fbe
  20. 400f91: b8 85 01 00 00 mov $0x185,%eax

    • 将立即数 0x185(389)加载到 %eax
  21. 400f96: eb 26 jmp 400fbe <phase_3+0x7b>

    • 跳转到 400fbe
  22. 400f98: b8 ce 00 00 00 mov $0xce,%eax

    • 将立即数 0xce(206)加载到 %eax
  23. 400f9d: eb 1f jmp 400fbe <phase_3+0x7b>

    • 跳转到 400fbe
  24. 400f9f: b8 aa 02 00 00 mov $0x2aa,%eax

    • 将立即数 0x2aa(682)加载到 %eax
  25. 400fa4: eb 18 jmp 400fbe <phase_3+0x7b>

    • 跳转到 400fbe
  26. 400fa6: b8 47 01 00 00 mov $0x147,%eax

    • 将立即数 0x147(327)加载到 %eax
  27. 400fab: eb 11 jmp 400fbe <phase_3+0x7b>

    • 跳转到 400fbe
  28. 400fad: e8 88 04 00 00 callq 40143a <explode_bomb>

    • 调用 explode_bomb 函数。
  29. 400fb2: b8 00 00 00 00 mov $0x0,%eax

    • 将立即数 0 加载到 %eax
  30. 400fb7: eb 05 jmp 400fbe <phase_3+0x7b>

    • 跳转到 400fbe
  31. 400fb9: b8 37 01 00 00 mov $0x137,%eax

    • 将立即数 0x137(311)加载到 %eax
  32. 400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax

    • 将栈上偏移 0xC(第一个整数)与 %eax 进行比较。
  33. 400fc2: 74 05 je 400fc9 <phase_3+0x86>

    • 如果相等,跳转到 400fc9
  34. 400fc4: e8 71 04 00 00 callq 40143a <explode_bomb>

    • 否则,调用 explode_bomb 函数。
  35. 400fc9: 48 83 c4 18 add $0x18,%rsp

    • 恢复栈指针,释放之前分配的 24 字节栈空间。
  36. 400fcd: c3 retq

    • 返回到调用者。

根据上面的分析可以知道,这是一个Switch语句类似的跳转表的结构,输入的第一个数字决定了后面跳到哪里,也就决定了第二个数字是多少。第一个数字可以是0~7的任意一个数字,我们可以选0,对应的是0xcf (207),输入这两个数就可以不引爆炸弹。

综上,此题多解,而且只要前两个数是对的,再多输入几个数也不影响,比如可以输0 207。

 

 四.实验四phase_4

我们先看一下phase_4的代码:

000000000040100c <phase_4>: 40100c: 48 83 ec 18 sub $0x18,%rsp

这行代码将栈指针 %rsp 减去 0x18 (24),为函数调用分配 24 字节的栈空间。

401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx

%rsp 加上 0xc (12),然后将结果加载到 %rcx。这通常用于准备参数。

401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx

%rsp 加上 0x8 (8),然后将结果加载到 %rdx。这也通常用于准备参数。

40101a: be cf 25 40 00 mov $0x4025cf,%esi

将立即数 0x4025cf 移动到 %esi 寄存器。这是一个地址,可能是一个格式字符串。

40101f: b8 00 00 00 00 mov $0x0,%eax

将 0 移动到 %eax。这是为 sscanf 准备的调用约定的一部分。

401024: e8 c7 fb ff ff callq 400bf0 <__isoc99_sscanf@plt>

调用 __isoc99_sscanf 函数,使用之前准备的参数。这个函数从字符串中解析输入。

401029: 83 f8 02 cmp $0x2,%eax

%eax 与 2 比较,检查 sscanf 返回的值是否是 2 (表示成功解析了两个值)。

40102c: 75 07 jne 401035 <phase_4+0x29>

如果 %eax 不等于 2,跳转到 401035,即执行炸弹爆炸的函数。

40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp)

将栈上偏移 0x8 处的值与 0xe (14) 比较。

401033: 76 05 jbe 40103a <phase_4+0x2e>

如果栈上偏移 0x8 处的值小于或等于 0xe,则继续执行,否则跳转到炸弹爆炸函数。

401035: e8 00 04 00 00 callq 40143a <explode_bomb>

调用 explode_bomb 函数,表示条件不满足,程序失败。

40103a: ba 0e 00 00 00 mov $0xe,%edx

将 0xe (14) 移动到 %edx。这是为后续函数调用准备的参数。

40103f: be 00 00 00 00 mov $0x0,%esi

将 0 移动到 %esi,这也是为后续函数调用准备的参数。

401044: 8b 7c 24 08 mov 0x8(%rsp),%edi

将栈上偏移 0x8 处的值移动到 %edi,这也是为后续函数调用准备的参数。

401048: e8 81 ff ff ff callq 400fce <func4>

调用 func4 函数,使用之前准备的参数。func4 是一个自定义函数,具体功能不详。

40104d: 85 c0 test %eax,%eax

测试 %eax 的值,看是否为零。func4 的返回值存储在 %eax 中。

40104f: 75 07 jne 401058 <phase_4+0x4c>

如果 %eax 不为零,跳转到炸弹爆炸函数。

401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp)

将栈上偏移 0x0c 处的值与 0 比较。

401056: 74 05 je 40105d <phase_4+0x51>

如果栈上偏移 0x0c 处的值为 0,则继续执行;否则跳转到炸弹爆炸函数。

401058: e8 dd 03 00 00 callq 40143a <explode_bomb>

调用 explode_bomb 函数,表示条件不满足,程序失败。

40105d: 48 83 c4 18 add $0x18,%rsp

将栈指针 %rsp 加上 0x18 (24),释放之前分配的栈空间。

401061: c3 retq

返回调用者,phase_4 函数结束。

 再看一下func4的汇编及解释:

0000000000400fce <func4>: 400fce: 48 83 ec 08 sub $0x8,%rsp

这行代码将栈指针 %rsp 减去 0x8 (8),为函数调用分配 8 字节的栈空间。

400fd2: 89 d0 mov %edx,%eax

%edx 的值移动到 %eax

400fd4: 29 f0 sub %esi,%eax

%esi 的值从 %eax 中减去。

400fd6: 89 c1 mov %eax,%ecx

%eax 的值移动到 %ecx

400fd8: c1 e9 1f shr $0x1f,%ecx

%ecx 右移 31 位(0x1f 是十六进制的31)。这是为了获取符号位。

400fdb: 01 c8 add %ecx,%eax

%ecx 的值加到 %eax 上。这是为了处理有符号整数的算术右移。

400fdd: d1 f8 sar %eax

%eax 算术右移一位(即右移一位并保持符号位)。

400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx

%rax%rsi 相加,并将结果存储到 %ecx。这相当于 %ecx = %rax + %rsi

400fe2: 39 f9 cmp %edi,%ecx

%ecx%edi 比较。

400fe4: 7e 0c jle 400ff2 <func4+0x24>

如果 %ecx 小于或等于 %edi,跳转到 400ff2。

400fe6: 8d 51 ff lea -0x1(%rcx),%edx

%rcx - 1 的值存储到 %edx

400fe9: e8 e0 ff ff ff callq 400fce <func4>

递归调用 func4,传递更新后的参数。

400fee: 01 c0 add %eax,%eax

%eax 的值加到 %eax 上(即 %eax = 2 * %eax)。

400ff0: eb 15 jmp 401007 <func4+0x39>

跳转到 401007,函数的结束部分。

400ff2: b8 00 00 00 00 mov $0x0,%eax

将 0 移动到 %eax

400ff7: 39 f9 cmp %edi,%ecx

%ecx%edi 比较。

400ff9: 7d 0c jge 401007 <func4+0x39>

如果 %ecx 大于或等于 %edi,跳转到 401007。

400ffb: 8d 71 01 lea 0x1(%rcx),%esi

%rcx + 1 的值存储到 %esi

400ffe: e8 cb ff ff ff callq 400fce <func4>

递归调用 func4,传递更新后的参数。

401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax

2 * %rax + 1 的值存储到 %eax

401007: 48 83 c4 08 add $0x8,%rsp

将栈指针 %rsp 加上 0x8 (8),释放之前分配的栈空间。

40100b: c3 retq

返回调用者,func4 函数结束。

其中,可以最快走出递归且让返回值为 0的一条道路可以是一直走图中最左侧的道路,即第一次调用就满足%ecx<=%edi且%ecx>=%edi也就是%ecx=%edi,追踪寄存器可以知道此时的%ecx=%edi=7(其实这是由于调用函数之前放里边的14经过算数右移1位得到的),当func4运行到结尾时,rax中的值还是0,满足条件。

此题是多解的,若不使用第一个数是7的方法,继续分析,从左向右数第二条路中,会继续递归func4函数且将返回值赋值为1,故此路不通。

其他情况下,只有走最右侧道路是合理的,即第一个传入的数(此时在%rdi里边)必须满足小于7,又因为函数递归中的  shr  $0x1f,%ecx这一条指令是取符号位,当负数的时候就和正数的时候不同,所以我们可以在0~6这七个数中直接试正确性。经试验,得知6, 3, 1, 0 等都可被接受。

故,其中一种可行的答案是7 0。

五.实验五phase_5 

我们看一下下面几行的汇编语言:

0000000000401062 <phase_5>: 401062: 53 push %rbx

将寄存器 %rbx 的当前值压入栈中,保存它的值以便稍后恢复。

401063: 48 83 ec 20 sub $0x20,%rsp

将栈指针 %rsp 减去 32(0x20)字节,分配栈空间。

401067: 48 89 fb mov %rdi,%rbx

将传递给 phase_5 函数的第一个参数(在 %rdi 中)保存到 %rbx 中。

40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 401071: 00 00

从线程本地存储中加载值(存储在 %fs 段寄存器的偏移 0x28 处)到 %rax 中。这通常用于栈保护。

401073: 48 89 44 24 18 mov %rax,0x18(%rsp)

%rax 的值保存到栈上偏移 0x18 处,保存栈保护的值。

401078: 31 c0 xor %eax,%eax

%eax 置零。

40107a: e8 9c 02 00 00 callq 40131b <string_length>

调用 string_length 函数,计算 %rdi 指向的字符串的长度。

40107f: 83 f8 06 cmp $0x6,%eax

将字符串长度与 6 进行比较。

401082: 74 4e je 4010d2 <phase_5+0x70>

如果字符串长度等于 6,则跳转到 4010d2。

401084: e8 b1 03 00 00 callq 40143a <explode_bomb>

否则,调用 explode_bomb 函数。

401089: eb 47 jmp 4010d2 <phase_5+0x70>

无条件跳转到 4010d2。

40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx

将字符串中的一个字符(无符号扩展为 32 位)加载到 %ecx 中,索引为 %rax

40108f: 88 0c 24 mov %cl,(%rsp)

%ecx 的低 8 位(即 %cl)存储到栈顶。

401092: 48 8b 14 24 mov (%rsp),%rdx

将刚才存储的值加载到 %rdx 中。

401096: 83 e2 0f and $0xf,%edx

%rdx 的值与 0xf 做与运算,只保留低 4 位。

401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx

将偏移 0x4024b0 处 %rdx 的值(无符号扩展为 32 位)加载到 %edx 中。这是一个查表操作,使用 %rdx 的值作为索引。

4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1)

%dl%edx 的低 8 位)存储到栈上偏移 0x10 加上 %rax 的位置。

4010a4: 48 83 c0 01 add $0x1,%rax

%rax 加 1,准备处理下一个字符。

4010a8: 48 83 f8 06 cmp $0x6,%rax

%rax 与 6 进行比较。

4010ac: 75 dd jne 40108b <phase_5+0x29>

如果 %rax 不等于 6,则跳转回 40108b 处理下一个字符。

4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp)

将 0 存储到栈上偏移 0x16 处,作为字符串的终止符。

4010b3: be 5e 24 40 00 mov $0x40245e,%esi

将字符串 0x40245e 的地址存储到 %esi 中。

4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi

将栈上偏移 0x10 的地址加载到 %rdi 中,这是转换后的字符串的地址。

4010bd: e8 76 02 00 00 callq 401338 <strings_not_equal>

调用 strings_not_equal 函数,比较两个字符串。

4010c2: 85 c0 test %eax,%eax

测试比较结果(strings_not_equal 的返回值)。

4010c4: 74 13 je 4010d9 <phase_5+0x77>

如果字符串相等(%eax 为 0),跳转到 4010d9。

4010c6: e8 6f 03 00 00 callq 40143a <explode_bomb>

否则,调用 explode_bomb 函数。

4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

无操作指令,用于对齐。

4010d0: eb 07 jmp 4010d9 <phase_5+0x77>

无条件跳转到 4010d9。

4010d2: b8 00 00 00 00 mov $0x0,%eax

将 0 存储到 %eax 中。

4010d7: eb b2 jmp 40108b <phase_5+0x29>

无条件跳转回 40108b,重新开始处理下一个字符。

4010d9: 48 8b 44 24 18 mov 0x18(%rsp),%rax

将栈上偏移 0x18 处的值加载到 %rax 中,恢复栈保护值。

4010de: 64 48 33 04 25 28 00 xor %fs:0x28,%rax 4010e5: 00 00

%fs:0x28 处的值与 %rax 异或,检查栈保护是否被修改。

4010e7: 74 05 je 4010ee <phase_5+0x8c>

如果栈保护未被修改,跳转到 4010ee。

4010e9: e8 42 fa ff ff callq 400b30 <__stack_chk_fail@plt>

如果栈保护被修改,调用 __stack_chk_fail 函数,表示栈破坏错误。

4010ee: 48 83 c4 20 add $0x20,%rsp

将栈指针 %rsp 加 32(0x20)字节,释放栈空间。

4010f2: 5b pop %rbx

将栈顶的值弹出到 %rbx 中,恢复 %rbx 的值。

4010f3: c3 retq

返回调用者,phase_5 函数结束。

 

  1. 从 phase_4 结尾处继续,随便输入一些字符,回车,进入 phase_5 断点,layout asm 显示反汇编窗口
    0x40106a 有指令 mov %fs:0x28,%rax ,可以阅读 StackOverflow 的相关问题,这就是书中 3.10.4 对抗缓冲区溢出攻击 (Thwarting Buffer Overflow Attacks) 中的第二种方法 栈破坏检测 (Stack Corruption Detection) 的哨兵值/金丝雀值。
  2. 浏览 phase_5 函数,能判断出它的正确输入是 长度为 7 的字符串,和前面一样,行尾的换行算一个,所以字符数量共有 6 个。
  3. 由 phase_1 经验可知,在调用 strings_not_equal 时, %rsi 中存放的是正确答案字符串地址,%rdi 中存放的是用户输入字符串地址。在 0x4010b3 处有 mov $0x40245e,%esi 、lea 0x10(%rsp),%rdi ,后面就调用了 strings_not_equal ,所以我们可以推出 0x40245e 处的字符串就是正确答案字符串,0x10(%rsp) 处起 6 字节存储的是用户输入字符串。查看 0x40245e 处内容,可以知道最终字符串为 flyers

 

已知待匹配字符串是“flyers”

原料字符串是“maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?”

想要拼出flyer可以分别取原料字符串的第(十六进制表示)9 f e 5 6 7个位置的字符,

即用4bit表示这六个数是 1001;1111;1110;0101;0110;01111;

查询ASCII码表可以得到后4bit对应的六个字符(不唯一)

1001->i I y Y 9 )

1111->? / _ o O

1110->> n N .

0101->u U e E %

0110->f F v V &

0111->’ 7 g G w W

上面字符任意组合即可,以“)?>%&’”为例,输入后可见正确。

现在,我们能够大概理解这个过程了,用户输入字符只有最低四位(假设为 x)才有意义,用来在 0x4024b0 处挑选第 x 个字符,然后这些挑选出的字符组合成一个字符串,这个字符串应该是 flyers ,这样才能与答案字符串对上。
flyers 的对应数字是 9H FH EH 5H 6H 7H ,从 ASCII 码表 中选择低位满足要求的字符组合,不妨取小写字母 ionefg。

 六.实验六phase_6

  1. 从 phase_5 结尾处继续,随便输入一些字符,回车,进入 phase_6 断点,layout asm 显示反汇编窗口
    虽然这个比前面 5 个都要长上不少,但不要畏惧,这并不复杂
  2. 从 read_six_numbers 可知,我们需要输入的依旧是六个以空格隔开的数字,而且这六个数分别存储在 %rsp 、%rsp+4 、%rsp+8 、%rsp+12 、%rsp+16 、%rsp+20 位置
    0x401117 处对第一个数字做出要求:小于等于 6,不然就引爆炸弹
    0x401138 处开始遍历第二个到第六个数字,要求它们均与第一个数字不相等,不然就引爆炸弹
    0x401121 处用的是 jbe ,说明这些数字是无符号数
    0x40111b 处令输入的数字首先减去 1,如果是 0 的话,会得到 FFFF...FFFF,引爆炸弹,故每个数字均需大于 0
    跳回到 0x401114 发现,这是对每个数字都有相同的要求:小于等于 6,大于 0,且两两不相等,故 6 个数字是 1、2、3 、4、5、6 的排列组合之一
  3. 满足上面条件后,跳转到 0x401153 ,到 0x40116d ,这部分实现的是"7- 第 x 个数的值后,把该值放在第 x 个数原来的位置, x 从 0 到 6 遍历一遍",用高级语言就是 a[i] = 7 - a[i]
  4. 0x40116f 到 0x4011a9 这一部分,这部分一眼看上去非常难以理解,实际上它确实挺复杂。
    我们注意到中间出现了一个地址 0x6032d0,先看一下内容再说

可以看出,一共有六个节点,每个节点占据 16 个字节内存空间,前 8 个字节存储内容值,后 8 个字节存储地址。而且从方框中的数据能够猜测这应该是个链表(前一个节点保存着后一个节点的地址)
该部分流程图如下:

 

 接下来看 0x4011ab 到 0x 4011d 2 处指令,这是把节点链接起来,即第一个数对应的结点的指针域(后 8 字节)存储第二个数对应节点的地址,依此类推,最后让 数6对应节点的指针域指向 Null

0x4011d9 后的指令完成一件事:数 1 对应节点的内容(取 4 个字节,而不是 8 个字节)大于数 2 对应节点的内容,数 2 对应节点的内容大于数 3 对应节点的内容,以此类推。

 

根据第 6 步的结论"数 1 对应的节点的内容 > 数 2 对应节点的内容 > ... > 数 6 对应节点的内容",节点链接情况是:node3 -> node4-> node5-> node6-> node1-> node2
再根据第 4 步得出的结论"如果 某处存储的数大小是 m,那么它对应的就是第 m 个节点",所以数 1 到数 6 分别是:3、4、5、6、1、2。
又由于在第 3 步中用 7 减去输入的数,所以输入的数应该是 4 3 2 1 6 5。 

七.隐藏实验

  1. 还记得 bomb.c 速览 时最后几行注释吗? 它暗示了作者还藏了暗雷!
    但是我们通过正常手段无法直接运行到这个暗雷相关的代码,那要怎么找出呢? 如果你比较细心的话,会发现在 phase_6 代码后面有个 fun7 函数,我们记得 phase_4 阶段调用过 func4 函数,所以这似乎是在强烈暗示我们暗雷会调用 func7
  2. 这样找 fun7 实在是太麻烦了,能不能把整个可执行文件反汇编成汇编语言文件,然后用 VS Code 等编辑器阅读? 当然可以,使用命令 objdump -d bomb >> bomb.s (如果没有 objdump 工具可自行安装),在当前目录得到 bomb.s 汇编语言文件。
  3. 使用搜索功能找到 fun7

 

可以看到 func 7 下方有个 secret_phase,看来它就是那个暗雷了
搜索 secret_phase 可以发现是在 phase_defused 函数中调用的,phase_defused 在每次 phase_i 调用结束后被调用。

 

从图中可以看出,0x 4015d8 处注释 num_input_strings 给了我们很大提示,当输入字符串数量不等于 6 时,不进行其他操作,直接返回;当等于 6 时,通过一系列判断是否调用 secret_phase。即在 phase_6 调用结束后,才能执行 0x 4015e1 后的若干条指令。

把汇编代码中设计到的字符串使用 x/s 地址 查看:

 其中 0x402619 处的 %d %d %s 给了我们一个暗示,前两个是数字,与第 3、4 个密码格式相同(我是没想到,在这试了半天答案都没能让 0x 4015ff 处的 %eax 变成 3,然后就看别人帖子去了,至于怎么联想到第 3、4 个密码,我没看到特别好的解释,可能是这个神奇的 __isoc99_sscanf@plt 函数调用有我不清楚的细节吧...)
第三个是字符串,结合下面的 0x402622 处内容和后面的 strings_not_equal 调用能推出字符串为 DrEvil
分别在第 3、4 个密码后添上空格、DrEvil,经过测试,第 4 个密码为 0 0 DrEvil (看到这个 0 0 ,我突然意识到 0x603870 处内容也是 0 0,所以这也算一个强暗示?)时能正确进入 secret phase。

  1. 分析 secret_phase 部分,首先是 read_line 函数读取用户输入,然后调用 strtol@plt 函数将用户输入转换成一个长整型数返回到 %rax。然后调用 fun7(参数分别是 %edi(0x6030f0),%esi(用户输入整数)),只有当 fun7 的返回值为 2 时才能避免炸弹爆炸
  2. 分析 fun7 部分,首先查看 0x6030f0 处内容。

 可以发现,这个数据分布非常有规律性,结合我们在 phase_6 的经验,可以发现这是若干个节点,每个节点占据 32 节点,分别是 内容值(4 字节)、填充(4 字节)、地址(8 字节)、地址(8 字节)、填充(8 字节)。每个节点有两个地址,合理猜测这是一个二叉树。如果第一个地址是该节点的左子树,第二个地址是该节点的右子树,把这棵二叉树画出来,如下图

 

 

其逆向等价 C 语言

int fun7(int cmp, Node* addr){if(addr == 0){return -1;}int v = addr->value;if (v == cmp){return 0;}else if( v < cmp){return 1 + 2*fun7(cmp, addr->right);}else{return 2*func7(cmp, addr->left);}
}


当用户输入的值在树中时,本次 fun7 的返回值(%eax ) 是 0,然后返回到 主调函数 调用 本次 fun7 后
当用户输入的值不在树中时,叶子节点处返回一个 -1(0xffffffff),层层返回,最终返回的还是一个负数值
每次调用进入左子树返回后,%eax 的值都会翻倍;调用右子树返回后,%eax 的值都会翻倍并 +1
我们最终希望返回值是 2,所以倒推一下,我们输入的数一定在树中(否则就返回负值),然后经过这个节点是其父节点的右子节点(02+1=1),然后他的父节点是祖父节点的子节点(1*2 = 2),此时返回值恰好是 2,故祖父节点就是根节点。所以目标节点(0x24)是根节点的左子节点(0x8)的右子节点(0x16),所以最终期待用户输入值是 0x16,即 22

当前目录下新建一个文件(不妨叫做 secret.txt ),内容如下

 

和 ans.txt 的区别主要是第 4 行后添加了 DrEvil 字符串,第 7 行是 secret_phase 的拆解密码,注意第 7 行后需要回车新建一行(不然第 7 行的数读不进去)

删除或禁用相关断点(注意保留阻止炸弹爆炸的那个断点以防不测),r secret.txt。

​​​​​​​

 注意到比解开 phase_6 时多了三行输出,证明我们成功发现并解开暗雷,所有炸弹都拆解完毕!

完结撒花!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/18919.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI 画图真刺激,手把手教你如何用 ComfyUI 来画出刺激的图

目前 AI 绘画领域的产品非常多&#xff0c;比如 Midjourney、Dalle3、Stability AI 等等&#xff0c;这些产品大体上可以分为两类&#xff1a; 模型与产品深度融合&#xff1a;比如 Midjourney、Dalle3 等等。模型与产品分离&#xff1a;比如 SD Web UI、ComfyUI 等等。 对于…

宏基因组分析流程(Metagenomic workflow)202405|持续更新

Logs 增加R包pctax内的一些帮助上游分析的小脚本&#xff08;2024.03.03&#xff09;增加Mmseqs2用于去冗余&#xff0c;基因聚类的速度非常快&#xff0c;且随序列量线性增长&#xff08;2024.03.12&#xff09;更新全文细节&#xff08;2024.05.29&#xff09; 注意&#x…

LeetCode2336无限集中的最小数字

题目描述 现有一个包含所有正整数的集合 [1, 2, 3, 4, 5, …] 。实现 SmallestInfiniteSet 类&#xff1a;SmallestInfiniteSet() 初始化 SmallestInfiniteSet 对象以包含 所有 正整数。int popSmallest() 移除 并返回该无限集中的最小整数。void addBack(int num) 如果正整数 …

mac m1安装homebrew管理工具(brew命令)完整流程

背景 因为mac上的brew很久没用了&#xff0c;版本非常旧&#xff0c;随着mac os的更新&#xff0c;本机的homebrew大部分的功能都无法使用&#xff0c;幸好过去通过brew安装的工具比较少&#xff0c;于是决定重新安装一遍brew。 卸载旧版brew 法一&#xff1a;通过使用线上…

力扣:104. 二叉树的最大深度

104. 二叉树的最大深度 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例 2&#xff1a; 输入&#xff1a…

C++语言·list链表(下)

还是之前说的&#xff0c;因为要写模板&#xff0c;为了避免链接出现问题&#xff0c;我们将所有内容都写到一个文件中去。首先就是画出链表的框架 链表本身只需要一个头节点就足以找到整条链表&#xff0c;而需要它拼接的节点我们再写一个模板。而我们知道list是一个带头双向循…

Verilog HDL基础知识(一)

引言&#xff1a;本文我们介绍Verilog HDL的基础知识&#xff0c;重点对Verilog HDL的基本语法及其应用要点进行介绍。 1. Verilog HDL概述 什么是Verilog&#xff1f;Verilog是IEEE标准的硬件描述语言&#xff0c;一种基于文本的语言&#xff0c;用于描述最终将在硬件中实现…

数据库设计实例---学习数据库最重要的应用之一

一、引言【可忽略】 在学习“数据库系统概述”这门课程时&#xff0c;我一直很好奇&#xff0c;这样一门必修课&#xff0c;究竟教会了我什么呢&#xff1f; 由于下课后&#xff0c;&#xff0c;没有拓展自己的眼界&#xff0c;上课时又局限于课堂上老师的讲课水平&#xff0c;…

探索数组处理:奇数的筛选与替换

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、数组中的奇数筛选 二、将奇数替换为负一 总结 一、数组中的奇数筛选 在处理数组数据时…

【Qt】初识

一、使用Label显示Hello World 1.ui设计 可以在Qt Designer中拖拽方式进行创建 2.代码方式 在myqwidget.cpp文件中添加下列代码 二、对象树 我们在堆上创建了QLabel类的对象。但是我们没有去delete&#xff0c;这样会产生内存泄漏吗&#xff1f; 答案是不会。label对象会在…

ChatGPT的基本原理是什么?又该如何提高其准确性?

在深入探索如何提升ChatGPT的准确性之前&#xff0c;让我们先来了解一下它的工作原理吧。ChatGPT是一种基于深度学习的自然语言生成模型&#xff0c;它通过预训练和微调两个关键步骤来学习和理解自然语言。 在预训练阶段&#xff0c;ChatGPT会接触到大规模的文本数据集&#x…

输入输出(1)——C++的输入输出概述

目录 一、C的输入输出 (一) C的输入输出 (二&#xff09;C语言的scanf和printf 二、C的输入输出流 (一) iostream类库中有关的类 (二&#xff09; iostream.h头文件的流对象和重载运算符 一、C的输入输出 (一) C的输入输出 之前用到的输入输出&#xff0c;都是以终端…

在做题中学习(62):矩阵区域和

1314. 矩阵区域和 - 力扣&#xff08;LeetCode&#xff09; 解法&#xff1a;二维前缀和 思路&#xff1a;读题画图才能理解意思&#xff1a;dun点点的是mat中的一个数&#xff0c;而要求的answer同位置的数 以点为中心上下左右延长 k 个单位所围成长方形的和。 因为最后answ…

IPV4地址介绍

4.1IP地址简介 目前的全球因特网所采用的协议族是TCP/IP协议族。IP是TCP/IP协议族中网络层的协议&#xff0c;是TCP/IP协议族的核心协议。IP协议定义了一种地址编码&#xff0c;称为IP地址&#xff0c;它是网络中网络段、网络设备接口、主机的编码&#xff0c;它并不是一种物理…

Linux离线一键安装Docker及docker-compose环境

背景&#xff1a; 在当前软件部署运维环境中由于Docker容器化优势越来越明显&#xff0c;因些被许多公司运维所采用&#xff0c;那首先如何快速安装Docker及docker-compose基础环境就第一时间被人们关注&#xff0c;本人同样在经过多次手工逐条用命令安装的过程&#xff0c;整理…

基于51单片机的温湿度控制系统

一.硬件方案 本设计采用51单片机每2秒钟从DHT11温湿度传感器中读入温度和湿度&#xff0c;在液晶屏上即时显示。液晶屏上同时显示温湿度上限值&#xff0c;该上限值保存外外部EEPROM存储器中&#xff0c;掉电不失&#xff0c;并且可以通过四只按键上调或下调。当温度或湿度值超…

[猫头虎分享21天微信小程序基础入门教程]第21天:小程序的社交分享与消息推送

[猫头虎分享21天微信小程序基础入门教程]第21天&#xff1a;小程序的社交分享与消息推送 第21天&#xff1a;小程序的社交分享与消息推送 &#x1f4f2; 自我介绍 大家好&#xff0c;我是猫头虎&#xff0c;一名全栈软件工程师。今天我们继续微信小程序的学习&#xff0c;重…

MQ第②讲~保证消息可靠性

前言 上一讲我们讲了MQ实际工作中常见的应用场景&#xff0c;这一节讲一下消息的可靠性&#xff0c;如果对MQ掌握程度比较高的铁子&#xff0c;可以不用看&#xff0c;节省您宝贵的时间。 消息的大致链路 消息从投递到消费需要考虑如下几个问题 生产者的消息是否成功投递到消…

虚拟机改IP地址

使用场景&#xff1a;当你从另一台电脑复制一个VMware虚拟机过来&#xff0c;就是遇到一个问题&#xff0c;虚拟的IP地址不一样&#xff08;比如&#xff0c;一个是192.168.1.3&#xff0c;另一个是192.168.2.4&#xff0c;由于‘1’和‘2’不同&#xff0c;不是同一网段&#…

浅谈路由器转发数据包

当路由器转发数据包时&#xff0c;它会经历一系列步骤&#xff0c;包括接收数据包、路由表查询、以及转发数据包。以下是详细的步骤描述&#xff1a; 1. 接收数据包 以太网帧到达端口&#xff1a;当一个以太网帧到达路由器的某个网络接口&#xff08;端口&#xff09;时&#…