一、 实验目标与要求

  1. 理解程序(控制语句、函数、返回值、堆栈结构)是如何运行的
  2. 掌握GDB调试工具和objdump反汇编工具

二、实验环境

  1. 计算机(Intel CPU)
  2. Linux64位操作系统(Ubuntu 17)
  3. GDB调试工具
  4. objdump反汇编工具

三、实验方法与步骤

本实验设计为一个黑客拆解二进制炸弹的游戏。我们仅给黑客(同学)提供一个二进制可执行文件bomb_64和主函数所在的源程序bomb_64.c,不提供每个关卡的源代码。程序运行中有6个关卡(6个phase),每个关卡需要用户输入正确的字符串或数字才能通关,否则会引爆炸弹(打印出一条错误信息,并导致评分下降)!

要求同学运用GDB调试工具和objdump反汇编工具,通过分析汇编代码找到在每个phase程序段中,引导程序跳转到“explode_bomb”程序段的地方,并分析其成功跳转的条件,以此为突破口寻找应该在命令行输入何种字符串来通关。

本实验需解决Phase_1(15)、Phase_2(15)、Phase_3(15)、Phase_4(15)、Phase_5(15)、Phase_6(10)。通过截图+文字的形式把实验过程写在实验报告上,最后并撰写实验结论与心得(15)。

四、实验过程及内容

1.对二进制文件进行反汇编,将结果保存到1.txt。

2.打开1.txt文件,观察main函数定位各个关卡汇编代码位置。

3.接下来开始闯关

(1)第一关(知识点:string,函数调用,栈)

观察phase_1部分,接下来逐行解析汇编代码意义:

400e70:    48 83 ec 08              sub    $0x8,%rsp

这条指令将栈指针%rsp减去 8,为函数的局部变量或临时变量腾出 8 字节的空间。

400e74:  be f8 1a 40 00           mov    $0x401af8,%esi

这条指令将立即数 0x401af8 移动到寄存器 %esi中。该立即数是一个地址,用于与函数中的其他数据进行比较或处理。

400e79:  e8 bf 03 00 00   call   40123d <strings_not_equal>

这条指令调用名为 `strings_not_equal` 的函数,函数地址是 0x40123d。该函数用于比较字符串。

400e7e:  85 c0     test   %eax,%eax

这条指令对 `%eax` 寄存器中的值进行逻辑与运算。

400e80:  74 05        je     400e87 <phase_1+0x17>

这条指令根据前一条指令的结果,如果结果为零(即 `%eax` 的值为零),则跳转到地址 0x400e87 处执行,否则继续执行下一条指令。

400e82:  e8 b6 07 00 00   call 40163d <explode_bomb>

如果前一条指令的结果非零,那么将调用`explode_bomb` 的函数。

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

这条指令将栈指针 `%rsp` 增加 8,恢复之前被分配的局部变量或临时变量的空间。

400e8b:  c3   ret

这条指令是函数的返回指令,它告诉程序将控制流返回到调用该函数的位置。

综上所述,该函数会判断输入内容和0x401af8的值是否相同,若相同则通关,否则引爆炸弹。

    进入gdb调试,查看该地址内容,得知字符串内容。

   输入字符串,提示正确。

(2)第二关(知识点:循环语句,数组)

观察phase_2部分汇编代码

分析汇编代码:

1. 函数开始时,它将一些寄存器的值保存到栈上的特定位置,以便稍后恢复这些值。

2. 函数分配了一个 `0x48` 字节大小的栈帧空间,并将栈指针 `%rsp` 的值保存在 `%rsi` 寄存器中。

3. 接下来,它调用名为 `read_six_numbers` 的函数,将 `%rsp` 作为参数传递给它。这可能是为了读取六个数字。

4. 函数将 `%rsp` 的值移动到 `%rbp` 寄存器中。

5. 使用 `%rsp` 偏移 `0xc` 的地址,计算出一个地址并将其存储在 `%r13` 寄存器中。

6. 函数将立即数 `0` 移动到 `%r12d` 寄存器中。

7. 将 `%rbp` 寄存器的值移动到 `%rbx` 寄存器中。

8. 通过将 `%rbp` 偏移 `0xc` 的地址处的值移动到 `%eax` 寄存器中,函数获取一个数字。

9. 将 `%eax` 寄存器的值与 `%rbp` 偏移 `0x0` 的地址处的值进行比较。如果相等,跳转到 `400eca` 处。

10. 如果比较结果不相等,调用 `explode_bomb` 函数,即引发异常或终止程序。

11. 在 `400eca` 处,将 `%rbx` 寄存器指向的值与 `%r12d` 寄存器相加,并将结果存储在 `%r12d` 寄存器中。

12. 将 `%rbp` 寄存器的值加上 `0x4`。

13. 比较 `%r13` 寄存器的值与 `%rbp` 寄存器的值。如果不相等,跳转到 `400eba` 处。

14. 将 `%r12d` 寄存器的值与其自身进行逻辑与运算。如果结果不为零,跳转到 `400ee0` 处。

15. 调用 `explode_bomb` 函数,即引发异常或终止程序。

16. 在 `400ee0` 处,将 `%rsp` 偏移 `0x28` 处的值移动到 `%rbx` 寄存器中。

17. 将 `%rsp` 偏移 `0x30` 处的值移动到 `%rbp` 寄存器中。

18. 将 `%rsp` 偏移 `0x38` 处的值移动到 `%r12` 寄存器中。

19. 恢复之前保存在栈上的寄存器的值。

20. 函数结束。

根据这些推测,`phase_2` 函数可能是一个对输入的六个数字进行处理和比较的函数。观察到跳出循环且不爆炸的地址为400ee0,再观察跳转条件为`%r12d` 寄存器的值不为0,而`%r12d`寄存器存储的值为数字的累加结果。

另外每次循环都会判断[rbp]和[rbp+0xc]的值是否相等,又rbp最初存储的是第一个数的地址,那么循环判断的即是前三个数字和后三个数字是否相等。

因此,总的来说,这个循环从第一个数字到第三个数字,并且判断是否满足两个条件,一个是当前数字与当前数字后的 第三个数字是否相等,若不相等则引爆炸弹;另外一个是会循环的数字进行累加,并判断是否出现累加结果为0的情况,若出现则引爆炸弹。

666666满足以上两个条件,注意数字之间要有空格。

(3)第三关(知识点: switch语句)

观察phase_3汇编代码

分析汇编代码:

400ef9: 48 83 ec 18        sub    $0x18,%rsp        ; 在栈上分配 24 字节的空间

400efd: 48 8d 4c 24 08     lea    0x8(%rsp),%rcx   ; 将栈上地址 8(%rsp) 的值加载到 %rcx 寄存器中

400f02: 48 8d 54 24 0c     lea    0xc(%rsp),%rdx   ; 将栈上地址 12(%rsp) 的值加载到 %rdx 寄存器中

400f07: be be 1e 40 00     mov    $0x401ebe,%esi   ; 将立即数 0x401ebe 移动到 %esi 寄存器中

400f0c: b8 00 00 00 00     mov    $0x0,%eax        ; 将立即数 0 移动到 %eax 寄存器中

400f11: e8 9a fb ff ff     call   400ab0 <__isoc99_sscanf@plt> ; 调用 sscanf 函数

400f16: 83 f8 01           cmp    $0x1,%eax        ; 比较 %eax 寄存器的值和立即数 1

400f19: 7f 05              jg     400f20 <phase_3+0x27>  ; 如果大于 1,跳转到 400f20

400f1b: e8 1d 07 00 00     call   40163d <explode_bomb>   ; 否则调用 explode_bomb 函数

400f20: 83 7c 24 0c 07     cmpl   $0x7,0xc(%rsp)  ; 比较栈上地址 12(%rsp) 的值和立即数 7

400f25: 77 3c              ja     400f63 <phase_3+0x6a>  ; 如果大于 7,跳转到 400f63

400f27: 8b 44 24 0c        mov    0xc(%rsp),%eax   ; 将栈上地址 12(%rsp) 的值移动到 %eax 寄存器中

400f2b: ff 24 c5 60 1b 40 00  jmp    *0x401b60(,%rax,8)   ; 通过跳转表进行跳转

400f32: b8 17 02 00 00     mov    $0x217,%eax      ; 将立即数 0x217 移动到 %eax 寄存器中

400f37: eb 3b              jmp    400f74 <phase_3+0x7b>  ; 无条件跳转到 400f74

400f39: b8 d6 00 00 00     mov    $0xd6,%eax       ; 将立即数 0xd6 移动到 %eax 寄存器中

400f3e: eb 34              jmp    400f74 <phase_3+0x7b>  ; 无条件跳转到 400f74

400f40: b8 53 01 00 00     mov    $0x153,%eax      ; 将立即数 0x153 移动到 %eax 寄存器中

400f45: eb 2d              jmp    400f74 <phase_3+0x7b>  ; 无条件跳转到 400f74

400f47: b8 77 00 00 00     mov    $0x77,%eax       ; 将立即数 0x77 移动到 %eax 寄存器中

400f4c: eb 26              jmp    400f74 <phase_3+0x7b>  ; 无条件跳转到 400f74

400f4e: b8 60 01 00 00     mov    $0x160,%eax      ; 将立即数 0x160 移动到 %eax 寄存器中

400f53: eb 1f              jmp    400f74 <phase_3+0x7b>  ; 无条件跳转到 400f74

400f55: b8 97 03 00 00     mov    $0x397,%eax      ; 将立即数 0x397 移动到 %eax 寄存器中

400f5a: eb 18              jmp    400f74 <phase_3+0x7b>  ; 无条件跳转到 400f74

400f5c: b8 9c 01 00 00     mov    $0x19c,%eax      ; 将立即数 0x19c 移动到 %eax 寄存器中

400f61: eb 11              jmp    400f74 <phase_3+0x7b>  ; 无条件跳转到 400f74

400f63: e8 d5 06 00 00     call   40163d <explode_bomb>   ; 如果大于 7,调用 explode_bomb 函数

400f68: b8 00 00 00 00     mov    $0x0,%eax        ; 将立即数 0 移动到 %eax 寄存器中

400f6d: eb 05              jmp    400f74 <phase_3+0x7b>  ; 无条件跳转到 400f74

400f6f: b8 9e 03 00 00     mov    $0x39e,%eax      ; 将立即数 0x39e 移动到 %eax 寄存器中

400f74: 3b 44 24 08        cmp    0x8(%rsp),%eax   ; 比较栈上地址 8(%rsp) 的值和 %eax 寄存器的值

400f78: 74 05              je     400f7f <phase_3+0x86>  ; 如果相等,跳转到 400f7f

400f7a: e8 be 06 00 00     call   40163d <explode_bomb>   ; 否则调用 explode_bomb 函数

400f7f: 48 83 c4 18        add    $0x18,%rsp       ; 从栈上释放 24 字节的空间

400f83: c3                 ret                      ; 返回

总的来说,该函数输入两个数a和b。a的值会影响跳转表跳转,跳转完后会存入一个值到%eax寄存器中,最后判断该值是否和b相等。由分支数可知总共有8组数据可以通过测试。

查看0x401b60所存放地址和该地址存入寄存器数值,可知一组数据0,535

  输入0 535,提示正确

(4)第四关(知识点:递归)

    观察func4函数和phase_4函数

func4` 是一个递归函数,它接收一个整数参数 `%edi`(通过 `%ebx` 寄存器传递),并返回一个整数结果(通过 `%eax` 寄存器返回)。

函数的功能是计算一个特定的递归表达式,并返回计算结果。

1. 首先,将 `%rbx` 寄存器的值保存到栈上的位置 `-0x10(%rsp)`。

2. 将 `%rbp` 寄存器的值保存到栈上的位置 `-0x8(%rsp)`。

3. 为局部变量和参数分配栈空间,通过将 `%rsp` 寄存器减去 0x18,即 `sub $0x18,%rsp`。

4. 将参数 `%edi` 的值复制到 `%ebx` 寄存器中,以便在递归调用中使用。

5. 将 `%eax` 寄存器设置为 1,作为初始返回值。

6. 比较 `%edi` 的值与 1 进行比较,通过 `cmp $0x1,%edi`。

7. 如果 `%edi` 的值小于等于 1(`jle` 指令的条件满足),则跳转到 `400fb2`,执行相应的返回操作。

8. 如果 `%edi` 的值大于 1,则进行递归调用:

   - 首先,将 `%edi` 的值减去 1,并存储到 `%edi` 中,以便作为递归调用的参数。

   - 然后,通过 `call` 指令调用 `func4` 函数,进行递归调用。

   - 将返回值存储到 `%ebp` 中,以便后续计算使用。

   - 将 `%edi` 的值减去 2,并存储到 `%edi` 中,作为另一个递归调用的参数。

   - 再次通过 `call` 指令调用 `func4` 函数,进行递归调用。

   - 将返回值与之前存储的 `%ebp` 相加,得到最终的返回值。

9. 执行到 `400fb2`,恢复之前保存的 `%rbx` 和 `%rbp` 的值。

10. 通过 `add` 指令将之前计算的结果 `%ebp` 加到 `%eax` 中,作为最终的返回值。

11. 通过 `ret` 指令从函数中返回。

总结起来,`func4` 函数通过递归计算一个表达式,首先处理边界情况,然后通过两个递归调用分别计算 `%edi-1` 和 `%edi-2` 的结果,最后将两个结果相加返回。该函数是求斐波那契数列第n项的值。

`phase_4` 函数是一个阶段性任务,它执行了以下操作:

1. 为局部变量和参数分配栈空间,通过将 `%rsp` 寄存器减去 0x18,即 `sub $0x18,%rsp`。

2. 将 `%rsp+0xc` 的地址存储到 `%rdx` 寄存器中,以备后续使用。

3. 将一个字符串的地址 `$0x401ec1` 存储到 `%esi` 寄存器中,准备作为参数传递给 `__isoc99_sscanf` 函数。

4. 将 `%eax` 寄存器清零,作为 `__isoc99_sscanf` 函数的返回值的初始值。

5. 调用 `__isoc99_sscanf` 函数,将用户输入的值按照指定的格式解析并存储。

6. 检查 `__isoc99_sscanf` 函数的返回值,与 1 进行比较(`cmp $0x1,%eax`)。如果不相等,则跳转到 `400fe5`,执行异常处理函数 `explode_bomb`。

7. 将 `%rsp+0xc` 的值与 0 进行比较(`cmpl $0x0,0xc(%rsp)`)。如果大于 0,则跳转到 `400fea`。

8. 将 `%rsp+0xc` 的值作为参数传递给 `func4` 函数,并调用该函数。

9. 将 `func4` 函数的返回值与 0x37 进行比较(`cmp $0x37,%eax`)。

10. 如果相等,则跳转到 `400ffd`,否则跳转到 `400ff8`,执行异常处理函数 `explode_bomb`。

11. 执行到 `400ffd`,恢复栈上的空间,通过 `add $0x18,%rsp`。

12. 使用 `ret` 指令从函数中返回。

综上所述,`phase_4` 函数主要是解析用户输入的值,并通过调用 `func4` 函数进行计算。最后,检查计算结果是否等于 55,如果是则通过,否则触发异常处理函数 `explode_bomb`。

  因此只要知道斐波那契数列第几项的值为55即可,查阅资料得知为第9项。

  输入9即可,结果正确。

(5)第五关(知识点:字串变换,ascii转换,寻址)

观察phase_5函数汇编代码

分析汇编代码:

`phase_5` 函数是一个阶段性任务,它执行了以下操作:

1. 为局部变量和参数分配栈空间,通过将 `%rsp` 寄存器减去 0x18,即 `sub $0x18,%rsp`。

2. 将 `%rsp+0x8` 的地址存储到 `%rcx` 寄存器中,以备后续使用。

3. 将 `%rsp+0xc` 的地址存储到 `%rdx` 寄存器中,以备后续使用。

4. 将一个字符串的地址 `$0x401ebe` 存储到 `%esi` 寄存器中,准备作为参数传递给 `__isoc99_sscanf` 函数。

5. 将 `%eax` 寄存器清零,作为 `__isoc99_sscanf` 函数的返回值的初始值。

6. 调用 `__isoc99_sscanf` 函数,将用户输入的值按照指定的格式解析并存储。

7. 检查 `__isoc99_sscanf` 函数的返回值,与 1 进行比较(`cmp $0x1,%eax`)。如果小于等于 1,则跳转到 `401024`,执行异常处理函数 `explode_bomb`。

8. 从 `%rsp+0xc` 位置读取一个字节,将其与 0xf 进行按位与操作,结果存储回 `%rsp+0xc`。

9. 检查 `%eax` 的值与 0xf 进行比较(`cmp $0xf,%eax`)。如果相等,则跳转到 `401065`。

10. 将 `%ecx` 和 `%edx` 寄存器清零作为计数器。

11. 增加 `%edx` 的值 1(`add $0x1,%edx`)。

12. 将 `%edx` 的值作为索引乘以 4,加上基地址 `0x401ba0`,并读取对应内存的值到 `%eax` 寄存器。

13. 将 `%eax` 的值加到 `%ecx` 寄存器上(`add %eax,%ecx`)。

14. 检查 `%eax` 的值与 0xf 进行比较(`cmp $0xf,%eax`)。如果不相等,则跳转到 `401043` 继续循环。

15. 将 `%eax` 的值存储回 `%rsp+0xc`。

16. 检查 `%edx` 的值与 0xc 进行比较(`cmp $0xc,%edx`)。如果不相等,则跳转到 `401065`。

17. 比较 `%ecx` 的值与 `%rsp+0x8` 的内存值进行比较(`cmp 0x8(%rsp),%ecx`)。如果相等,则跳转到 `40106a`。

18. 执行到 `401065`,调用异常处理函数 `explode_bomb`。

19. 执行到 `40106a`,恢复栈上的空间,通过 `add $0x18,%rsp`。

20. 使用 `ret` 指令从函数中返回。

综上所述,`phase_5` 函数主要是解析用户输入的值,并根据一定的逻辑进行计算和比较。最后,检查计算结果与指定的值是否相等,如果相等则通过,否则触发异常处理函数 `explode_bomb`。

分析函数可以得知有一个跳转数组实现类似k = next[k]的功能,查看该跳转数组。

分析函数可知总跳转次数为12,第二个输入值等于每次跳转的目标的累加和,最后跳转的值为15。因此答案为7 93,输入后结果正确。

(6)第六关(知识点:寻址)

观察fun6和phase_6两个函数的汇编代码

分析汇编代码:

fun6函数的流程如下:

1. 从 `%rdi` 中加载 8 字节的值到 `%r8` 中。

2. 将 `%rdi` 的 8 字节设置为零。

3. 将 `%rdi` 的值存储到 `%rax` 和 `%rcx` 中。

4. 检查 `%r8` 的值是否为零,如果不为零,则跳转到标签 `4010c6` 处。

5. 如果 `%r8` 的值为零,则将 `%rdi` 的值存储到 `%rax` 中并返回。

6. 将 `%rdx` 的值存储到 `%rcx` 中。

7. 从 `%rcx` 偏移 8 字节处加载值到 `%rdx` 中。

8. 检查 `%rdx` 的值是否为零,如果为零,则跳转到标签 `40109f` 处。

9. 将 `%esi` 和 `%rdx` 的值进行比较,如果 `%esi` 大于 `%rdx`,则跳转到标签 `40108a` 处。

10. 将 `%rcx` 的值存储到 `%rdi` 中,并跳转到标签 `4010a2` 处。

11. 检查 `%rdi` 和 `%rdx` 的值是否相等,如果相等,则跳转到标签 `4010ad` 处。

12. 将 `%r8` 的值存储到 `%rdi` 偏移 8 字节处。

13. 跳转到标签 `4010b0` 处。

14. 将 `%r8` 的值存储到 `%rax` 中。

15. 从 `%r8` 偏移 8 字节处加载值到 `%rcx` 中。

16. 将 `%rdx` 的值存储到 `%r8` 偏移 8 字节处。

17. 检查 `%rcx` 的值是否为零,如果为零,则跳转到标签 `4010d7` 处。

18. 将 `%rcx` 的值存储到 `%r8` 中。

19. 将 `%rax` 的值存储到 `%rcx` 和 `%rdi` 中。

20. 将 `%rcx` 的值存储到 `%rdx` 中。

21. 检查 `%rcx` 的值是否为零,如果为零,则跳转到标签 `4010a2` 处。

22. 从 `%r8` 中加载值到 `%esi` 中。

23. 将 `%esi` 和 `%rcx` 的值进行比较,如果 `%esi` 大于 `%rcx`,则跳转到标签 `40108d` 处。

24. 跳转到标签 `4010a2` 处。

25. 执行 `repz ret` 指令,返回。

phase_6函数流程如下:

1. 为局部变量分配空间,将栈指针减去 8 字节。

2. 将立即数 0xa(10)移动到寄存器 `%edx`。

3. 将立即数 0x0 移动到寄存器 `%esi`。

4. 调用 `strtol` 函数,将字符串转换为长整型数值,并将结果存储在 `%eax` 中。

5. 将 `%eax` 的值存储到相对于 RIP 寄存器偏移量 0x20168e 处的内存地址中(即标签 `602780 <node0>` 处的地址)。

6. 将立即数 0x602780 移动到寄存器 `%edi`。

7. 调用 `fun6` 函数。

8. 将寄存器 `%rax` 偏移 8 字节处的值加载到 `%rax` 中。

9. 重复第 8 步两次,将寄存器 `%rax` 偏移 8 字节处的值加载到 `%rax` 中。

10. 从相对于 RIP 寄存器偏移量 0x201672 处的内存地址加载值到寄存器 `%edx` 中(即标签 `602780 <node0>` 处的地址)。

11. 将 `%edx` 和 `(rax)` 中的值进行比较。

12. 如果相等,则跳转到标签 `401117` 处。

13. 否则,调用 `explode_bomb` 函数,终止程序执行。

14. 将栈指针增加 8 字节。

15. 返回。

  因此要edx和rax的值相等才能通关,经过分析函数可知只要输入数范围为600-679均满足条件。输入650查看结果,提示正确。

五、实验结论

经过反汇编后分析汇编代码,成功拆除所有炸弹。

 

 六、心得体会

通过对应用程序进行反汇编得到汇编代码后,通过分析汇编代码可以推断出原始代码,以此达到目的。理解了通过汇编代码分析程序(控制语句、函数、返回值、堆栈结构)是如何运行的,掌握了GDB调试工具和objdump反汇编工具的初步使用,加深了对汇编代码的理解和分析。

(by 归忆)

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐