CSAPP Bomb Lab探究与解析
字数 2009 2025-08-23 18:31:08

CSAPP Bomb Lab 深入解析与教学指南

实验概述

Bomb Lab 是《深入理解计算机系统》(CSAPP)课程中的一个经典实验,旨在帮助学生深入理解汇编语言、程序控制和数据结构。实验要求通过逆向工程分析一个"炸弹"程序,找出每一关的正确输入以"拆除炸弹"。

实验准备

  1. 获取实验材料

    • 程序下载地址: http://csapp.cs.cmu.edu/2e/labs.html (自学版本)
  2. 反汇编工具

    objdump -d bomb > bomb_code.s  # 导出汇编代码段
    objdump -s bomb > bomb_data.s  # 导出数据段
    
  3. 汇编语法风格

    • 实验使用AT&T风格的x86汇编语法
    • 与Intel风格的主要区别:
      • 操作数顺序:源在前,目的在后
      • 寄存器前加%前缀
      • 立即数前加$前缀
      • 内存引用使用()而非[]
  4. 调试工具

    • 推荐使用GDB (特别是pwndbg插件)进行动态调试

基础关卡解析

Phase 1: 字符串比较

关键点

  • 考察基本函数调用约定和字符串比较
  • 使用strings_not_equal函数比较输入与目标字符串

分析过程

  1. 查看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
    
  2. 关键数据:

    • 目标字符串地址:0x402400
    • 使用GDB查看内容:
      x/s 0x402400
      
  3. 反汇编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函数读取输入

分析过程

  1. 查看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>
    ...
    
  2. 序列规律:

    • 第一个数必须为1
    • 后续每个数是前一个数的2倍
  3. 反汇编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)实现多分支

分析过程

  1. 查看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)
    
  2. 关键数据:

    • 输入格式字符串:%d %d (地址0x4025cf)
    • 跳转表地址:0x402470
  3. 反汇编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: 递归函数

关键点

  • 考察递归函数的实现
  • 理解函数调用栈帧

分析过程

  1. 查看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>
    
  2. 递归函数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: 字符串转换

关键点

  • 考察字符串处理与数组索引
  • 理解字符编码和位操作

分析过程

  1. 查看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>
    
  2. 关键数据:

    • 字符数组:"maduiersnfotvbyl..." (地址0x4024b0)
    • 目标字符串:"flyers" (地址0x40245e)
  3. 转换过程:

    • 输入字符的低4位作为索引
    • 从字符数组中取出对应字符
    • 最终结果需匹配"flyers"

解决方案
需要字符的低4位依次为:
9, 15, 14, 5, 6, 7
例如:

IONEFG

Phase 6: 链表操作

关键点

  • 考察链表数据结构和排序算法
  • 理解指针操作和内存布局

分析过程

  1. 查看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>
    
  2. 链表结构:

    struct Node {
        int value;
        int index;
        struct Node *next;
    };
    
  3. 排序要求:

    • 输入数字经过7-x变换
    • 按变换后的顺序访问节点
    • 节点值必须按降序排列

解决方案
输入序列:

4 3 2 1 6 5

隐藏关卡解析

Secret Phase: 二叉树遍历

关键点

  • 考察二叉树数据结构和递归算法
  • 理解函数指针和复杂数据结构

分析过程

  1. 触发条件:

    • phase_defused函数中检查特定输入
    • 需要在phase 4输入后附加字符串"DrEvil"
  2. 查看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>
    
  3. 二叉树结构:

    struct Node {
        long value;
        struct Node *left;
        struct Node *right;
    };
    
  4. 递归函数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);
        }
    }
    

解决方案

  1. 触发隐藏关卡:

    • 在phase 4输入:7 0 DrEvil
  2. 隐藏关卡答案:

    22
    

实验总结与技巧

逆向分析技巧

  1. 由外向内分析

    • 先理解函数调用关系
    • 再分析函数内部逻辑
  2. 关键点定位

    • 关注函数调用前后的寄存器变化
    • 注意比较指令和跳转指令
  3. 数据追踪

    • 使用GDB查看内存内容
    • 注意字符串和数组的存储方式

调试技巧

  1. 断点设置

    break phase_1
    break *0x400ee9
    
  2. 寄存器查看

    info registers
    
  3. 内存查看

    x/s 0x402400  # 查看字符串
    x/8gx 0x6030f0 # 查看数据结构
    

常见汇编模式

  1. 函数调用

    • 参数传递:rdi, rsi, rdx, rcx, r8, r9
    • 返回值:rax
  2. 循环结构

    • 通常使用cmp和jcc指令组合
    • 注意循环计数器更新
  3. 条件分支

    • test + je/jne
    • cmp + jg/jl/jge/jle
  4. 栈帧管理

    • 函数开头:sub rsp, N
    • 函数结尾:add rsp, N

学习建议

  1. 循序渐进

    • 从简单关卡开始,逐步增加难度
    • 先理解整体流程,再深入细节
  2. 实践为主

    • 亲手调试每一关
    • 尝试修改输入观察程序行为
  3. 总结规律

    • 记录常见的汇编模式
    • 建立逆向分析的思维框架
  4. 扩展学习

    • 学习更多x86-64汇编指令
    • 研究编译器优化对代码的影响
    • 探索更复杂的数据结构实现

通过本实验的系统学习,学生可以深入理解程序在机器级别的表示和行为,为后续的计算机系统安全、逆向工程等高级课程打下坚实基础。

CSAPP Bomb Lab 深入解析与教学指南 实验概述 Bomb Lab 是《深入理解计算机系统》(CSAPP)课程中的一个经典实验,旨在帮助学生深入理解汇编语言、程序控制和数据结构。实验要求通过逆向工程分析一个"炸弹"程序,找出每一关的正确输入以"拆除炸弹"。 实验准备 获取实验材料 : 程序下载地址: http://csapp.cs.cmu.edu/2e/labs.html (自学版本) 反汇编工具 : 汇编语法风格 : 实验使用AT&T风格的x86汇编语法 与Intel风格的主要区别: 操作数顺序:源在前,目的在后 寄存器前加%前缀 立即数前加$前缀 内存引用使用()而非[ ] 调试工具 : 推荐使用GDB (特别是pwndbg插件)进行动态调试 基础关卡解析 Phase 1: 字符串比较 关键点 : 考察基本函数调用约定和字符串比较 使用 strings_not_equal 函数比较输入与目标字符串 分析过程 : 查看 phase_1 汇编代码: 关键数据: 目标字符串地址:0x402400 使用GDB查看内容: 反汇编C代码: 解决方案 : 输入字符串必须完全匹配: Phase 2: 六数字序列 关键点 : 考察循环结构和数组处理 使用 read_six_numbers 函数读取输入 分析过程 : 查看 phase_2 汇编代码: 序列规律: 第一个数必须为1 后续每个数是前一个数的2倍 反汇编C代码: 解决方案 : 输入必须为1开始的等比数列: Phase 3: Switch语句 关键点 : 考察switch语句的汇编实现 使用跳转表(jump table)实现多分支 分析过程 : 查看 phase_3 汇编代码: 关键数据: 输入格式字符串: %d %d (地址0x4025cf) 跳转表地址:0x402470 反汇编C代码: 解决方案 : 共有8组有效解,例如: Phase 4: 递归函数 关键点 : 考察递归函数的实现 理解函数调用栈帧 分析过程 : 查看 phase_4 汇编代码: 递归函数 func4 分析: 解决方案 : 需要使 func4 返回0,有效解包括: Phase 5: 字符串转换 关键点 : 考察字符串处理与数组索引 理解字符编码和位操作 分析过程 : 查看 phase_5 汇编代码: 关键数据: 字符数组: "maduiersnfotvbyl..." (地址0x4024b0) 目标字符串: "flyers" (地址0x40245e) 转换过程: 输入字符的低4位作为索引 从字符数组中取出对应字符 最终结果需匹配"flyers" 解决方案 : 需要字符的低4位依次为: 9, 15, 14, 5, 6, 7 例如: Phase 6: 链表操作 关键点 : 考察链表数据结构和排序算法 理解指针操作和内存布局 分析过程 : 查看 phase_6 汇编代码: 链表结构: 排序要求: 输入数字经过7-x变换 按变换后的顺序访问节点 节点值必须按降序排列 解决方案 : 输入序列: 隐藏关卡解析 Secret Phase: 二叉树遍历 关键点 : 考察二叉树数据结构和递归算法 理解函数指针和复杂数据结构 分析过程 : 触发条件: 在 phase_defused 函数中检查特定输入 需要在phase 4输入后附加字符串"DrEvil" 查看 secret_phase 汇编代码: 二叉树结构: 递归函数 fun7 : 解决方案 : 触发隐藏关卡: 在phase 4输入: 7 0 DrEvil 隐藏关卡答案: 实验总结与技巧 逆向分析技巧 由外向内分析 : 先理解函数调用关系 再分析函数内部逻辑 关键点定位 : 关注函数调用前后的寄存器变化 注意比较指令和跳转指令 数据追踪 : 使用GDB查看内存内容 注意字符串和数组的存储方式 调试技巧 断点设置 : 寄存器查看 : 内存查看 : 常见汇编模式 函数调用 : 参数传递: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汇编指令 研究编译器优化对代码的影响 探索更复杂的数据结构实现 通过本实验的系统学习,学生可以深入理解程序在机器级别的表示和行为,为后续的计算机系统安全、逆向工程等高级课程打下坚实基础。