【深圳大学计算机系统2】实验三 逆向工程实验
本实验设计为一个黑客拆解二进制炸弹的游戏。我们仅给黑客(同学)提供一个二进制可执行文件bomb_64和主函数所在的源程序bomb_64.c,不提供每个关卡的源代码。程序运行中有6个关卡(6个phase),每个关卡需要用户输入正确的字符串或数字才能通关,否则会引爆炸弹(打印出一条错误信息,并导致评分下降)!要求同学运用,通过分析汇编代码找到在每个phase程序段中,引导程序跳转到“explode_b
一、 实验目标与要求
- 理解程序(控制语句、函数、返回值、堆栈结构)是如何运行的
- 掌握GDB调试工具和objdump反汇编工具
二、实验环境
- 计算机(Intel CPU)
- Linux64位操作系统(Ubuntu 17)
- GDB调试工具
- 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 归忆)
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)