CSAPP Bomb Lab探究与解析
字数 2009 2025-08-23 18:31:08
CSAPP Bomb Lab 深入解析与教学指南
实验概述
Bomb Lab 是《深入理解计算机系统》(CSAPP)课程中的一个经典实验,旨在帮助学生深入理解汇编语言、程序控制和数据结构。实验要求通过逆向工程分析一个"炸弹"程序,找出每一关的正确输入以"拆除炸弹"。
实验准备
-
获取实验材料:
- 程序下载地址: http://csapp.cs.cmu.edu/2e/labs.html (自学版本)
-
反汇编工具:
objdump -d bomb > bomb_code.s # 导出汇编代码段 objdump -s bomb > bomb_data.s # 导出数据段 -
汇编语法风格:
- 实验使用AT&T风格的x86汇编语法
- 与Intel风格的主要区别:
- 操作数顺序:源在前,目的在后
- 寄存器前加%前缀
- 立即数前加$前缀
- 内存引用使用()而非[]
-
调试工具:
- 推荐使用GDB (特别是pwndbg插件)进行动态调试
基础关卡解析
Phase 1: 字符串比较
关键点:
- 考察基本函数调用约定和字符串比较
- 使用
strings_not_equal函数比较输入与目标字符串
分析过程:
-
查看
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 -
关键数据:
- 目标字符串地址:0x402400
- 使用GDB查看内容:
x/s 0x402400
-
反汇编C代码:
void phase_1(char *input) { if(strings_not_equal(input, "Border relations with Canada have never been better.")) { explode_bomb(); } }
解决方案:
输入字符串必须完全匹配:
Border relations with Canada have never been better.
Phase 2: 六数字序列
关键点:
- 考察循环结构和数组处理
- 使用
read_six_numbers函数读取输入
分析过程:
-
查看
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> ... -
序列规律:
- 第一个数必须为1
- 后续每个数是前一个数的2倍
-
反汇编C代码:
void phase_2(char *input) { int arr[6]; read_six_numbers(input, arr); if(arr[0] != 1) { explode_bomb(); } for(int i=1; i<6; i++) { if(arr[i] != 2*arr[i-1]) { explode_bomb(); } } }
解决方案:
输入必须为1开始的等比数列:
1 2 4 8 16 32
Phase 3: Switch语句
关键点:
- 考察switch语句的汇编实现
- 使用跳转表(jump table)实现多分支
分析过程:
-
查看
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> ... 400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8) -
关键数据:
- 输入格式字符串:
%d %d(地址0x4025cf) - 跳转表地址:0x402470
- 输入格式字符串:
-
反汇编C代码:
void phase_3(char *input) { int a, b; int count = sscanf(input, "%d %d", &a, &b); if(count <= 1) explode_bomb(); if(a > 7) explode_bomb(); int key; switch(a) { case 0: key = 0xcf; break; case 1: key = 0x137; break; case 2: key = 0x2c3; break; case 3: key = 0x100; break; case 4: key = 0x185; break; case 5: key = 0xce; break; case 6: key = 0x2aa; break; case 7: key = 0x147; break; default: explode_bomb(); } if(b != key) { explode_bomb(); } }
解决方案:
共有8组有效解,例如:
0 207
1 311
2 707
3 256
4 389
5 206
6 682
7 327
Phase 4: 递归函数
关键点:
- 考察递归函数的实现
- 理解函数调用栈帧
分析过程:
-
查看
phase_4汇编代码: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 40101f: b8 00 00 00 00 mov $0x0,%eax 401024: e8 c7 fb ff ff callq 400bf0 <__isoc99_sscanf@plt> ... 401048: e8 81 ff ff ff callq 400fce <func4> -
递归函数
func4分析:int func4(int a, int b, int c) { int temp = c - b; int value = (temp + (unsigned int)temp>>31) >> 1 + b; if(value <= a) { if(value >= a) { return 0; } b = value + 1; return 2 * func4(a, b, c) + 1; } return 2 * func4(a, b, value - 1); }
解决方案:
需要使func4返回0,有效解包括:
7 0
3 0
1 0
0 0
Phase 5: 字符串转换
关键点:
- 考察字符串处理与数组索引
- 理解字符编码和位操作
分析过程:
-
查看
phase_5汇编代码:0000000000401062 <phase_5>: 401062: 53 push %rbx 401063: 48 83 ec 20 sub $0x20,%rsp 401067: 48 89 fb mov %rdi,%rbx 40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%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> -
关键数据:
- 字符数组:
"maduiersnfotvbyl..."(地址0x4024b0) - 目标字符串:
"flyers"(地址0x40245e)
- 字符数组:
-
转换过程:
- 输入字符的低4位作为索引
- 从字符数组中取出对应字符
- 最终结果需匹配"flyers"
解决方案:
需要字符的低4位依次为:
9, 15, 14, 5, 6, 7
例如:
IONEFG
Phase 6: 链表操作
关键点:
- 考察链表数据结构和排序算法
- 理解指针操作和内存布局
分析过程:
-
查看
phase_6汇编代码: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 401100: 49 89 e5 mov %rsp,%r13 401103: 48 89 e6 mov %rsp,%rsi 401106: e8 51 03 00 00 callq 40145c <read_six_numbers> -
链表结构:
struct Node { int value; int index; struct Node *next; }; -
排序要求:
- 输入数字经过7-x变换
- 按变换后的顺序访问节点
- 节点值必须按降序排列
解决方案:
输入序列:
4 3 2 1 6 5
隐藏关卡解析
Secret Phase: 二叉树遍历
关键点:
- 考察二叉树数据结构和递归算法
- 理解函数指针和复杂数据结构
分析过程:
-
触发条件:
- 在
phase_defused函数中检查特定输入 - 需要在phase 4输入后附加字符串"DrEvil"
- 在
-
查看
secret_phase汇编代码: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> -
二叉树结构:
struct Node { long value; struct Node *left; struct Node *right; }; -
递归函数
fun7:int fun7(struct Node *node, long num) { if(node == NULL) return -1; if(node->value <= num) { if(node->value == num) return 0; else { node = node->right; return 2 * fun7(node, num) + 1; } } else { node = node->left; return 2 * fun7(node, num); } }
解决方案:
-
触发隐藏关卡:
- 在phase 4输入:
7 0 DrEvil
- 在phase 4输入:
-
隐藏关卡答案:
22
实验总结与技巧
逆向分析技巧
-
由外向内分析:
- 先理解函数调用关系
- 再分析函数内部逻辑
-
关键点定位:
- 关注函数调用前后的寄存器变化
- 注意比较指令和跳转指令
-
数据追踪:
- 使用GDB查看内存内容
- 注意字符串和数组的存储方式
调试技巧
-
断点设置:
break phase_1 break *0x400ee9 -
寄存器查看:
info registers -
内存查看:
x/s 0x402400 # 查看字符串 x/8gx 0x6030f0 # 查看数据结构
常见汇编模式
-
函数调用:
- 参数传递:rdi, rsi, rdx, rcx, r8, r9
- 返回值:rax
-
循环结构:
- 通常使用cmp和jcc指令组合
- 注意循环计数器更新
-
条件分支:
- test + je/jne
- cmp + jg/jl/jge/jle
-
栈帧管理:
- 函数开头:sub rsp, N
- 函数结尾:add rsp, N
学习建议
-
循序渐进:
- 从简单关卡开始,逐步增加难度
- 先理解整体流程,再深入细节
-
实践为主:
- 亲手调试每一关
- 尝试修改输入观察程序行为
-
总结规律:
- 记录常见的汇编模式
- 建立逆向分析的思维框架
-
扩展学习:
- 学习更多x86-64汇编指令
- 研究编译器优化对代码的影响
- 探索更复杂的数据结构实现
通过本实验的系统学习,学生可以深入理解程序在机器级别的表示和行为,为后续的计算机系统安全、逆向工程等高级课程打下坚实基础。