小工具知识网

当前位置: 首页 > 行业知识 > 【CSAPP】BombLab

行业知识

【CSAPP】BombLab

2026-11-07 04:03:52 来源:互联网转载

​

诗赋清音:烛龙翔夜啸,翼舞星空深。 巍峨山川壮丽志,激荡热血梦侠心。

目录

1. CSAPP与Bomb简介

1.1 CSAPP

1.2 Bomb

2.bomb

2.1 实验环境

2.2 实验过程

2.2.1 phase_1

2.2.2 phase_2

2.2.3 phase_3

2.2.4 Phase_4

2.2.5 phase_5

2.2.6 phase_6

第一部分

第二部分

第三部分

第四部分

2.2.7 Secret_phase

2.3 实验结果

2.4 实验体会


1. CSAPP与Bomb简介

1.1 CSAPP

《CSAPP》是指计算机系统基础课程的经典教材《Computer Systems: A Programmer's Perspective》,由Randal E. Bryant和David R. O'Hallaron编写。该书的主要目标是帮助深入理解计算机系统的工作原理,包括硬件和软件的相互关系,其涵盖了计算机体系结构、汇编语言、操作系统、计算机网络等主题,旨在培养学生系统级编程和分析的能力。

1.2 Bomb

"Bomb实验" 是与CSAPP教材相关的一项编程实验。它是一种反汇编和逆向工程任务,旨在教授如何分析和解决复杂的程序问题。Bomb实验的目标是解开一系列的"炸弹",每个炸弹都有不同的解锁方法,需要分析程序的汇编代码,理解其工作原理,并找到正确的输入来解除炸弹。这个实验教授了计算机系统的底层知识,包括汇编语言和程序执行的原理。

资源获取:关注公众号【科创视野】回复 csappbomblab


2.bomb

2.1 实验环境

  • VMware Workstation虚拟机环境下的Ubuntu 64位。

    2.2 实验过程

    实验准备阶段:首先需要使用ubuntu联网环境跳转到链接下载实验所需的bomblab:Bomblab源文件

    下载bomblab压缩包并输入

    tar –xvf bomb.tar

    进行解压缩,进入该目录所有文件如下所示:

    在终端输入

    sudo apt-get install gdb

    安装调试器。基本用法参考下图:

    实验过程阶段:

    “Binary bombs”是一个可在Linux系统上运行的C程序,它由6个不同的阶段(phase1~phase6)组成。在每个阶段,程序会要求输入一个特定的字符串。如果输入的字符串符合程序的预期输入,那么这个阶段的炸弹就会被“解除”,否则炸弹就会“爆炸”,并输出“BOOM!!!”的提示信息。实验的目的是尽可能多地解除这些炸弹的阶段。

    每个炸弹阶段考察了机器级语言程序的一个不同方面,难度逐级递增:

    * 阶段1:字符串比较

    * 阶段2:循环

    * 阶段3:条件/分支

    * 阶段4:递归调用和栈

    * 阶段5:指针

    * 阶段6:链表/指针/结构

    在炸弹拆除任务中,还存在一个隐藏阶段。然而,只有在第四个阶段解决后添加特定的字符串后,该隐藏阶段才会出现。为了完成任务,需要使用gdb调试器和objdump反汇编炸弹的可执行文件,然后单步跟踪每个阶段的机器代码,理解每个汇编语言的行为或作用。这将帮助“推断”出拆除炸弹所需的目标字符串。为了调试,可以在每个阶段的开始代码前和引爆炸弹的函数前设置断点。

    在终端输入

    objdump -d bomb > bomb.asm

    得到bomb的反汇编文件bomb.asm如下所示。


    2.2.1 phase_1

    phase_1是一个二进制炸弹,需要输入一个字符串作为密码才能解除炸弹,否则炸弹会爆炸。

    进入反汇编文件bomb.asm,

    vim bomb.asm

    在未插入处输入下列指令以查找phase_1的位置。

    /phase_1

    分析反汇编代码的phase_1部分内容如下:

    1.Sub $0x8,%rsp含义是把将栈顶指针(%rsp)向下移动8个字节,相当于在栈顶分配8个字节的空间。这通常用于在程序中为局部变量或者函数参数分配空间。由于栈是向下生长的,这条指令会将栈顶指针减去8,使得栈顶指针指向的位置向下移动8个字节。

    2.mov $0x402400,%esi:将立即数0x402400移动到寄存器%esi中。在汇编语言中,%esi是通用寄存器之一,用于存储指针或者数据,这条指令的作用是将0x402400这个数值存储到%esi寄存器中,以便在后续的指令中使用。

    3.callq 401338 :将strings_not_equal函数调用的返回地址压入栈中,并跳转到strings_not_equal函数的起始地址开始执行。在strings_not_equal函数执行完成后,执行流程会返回到该指令之后的下一条指令,继续执行程序。

    4.test %eax,%eax:将寄存器%eax中的值与自身进行按位与运算,并更新标志寄存器的状态该指令执行的结果会影响标志寄存器中的零标志(ZF)和符号标志(SF)的状态,这两个标志是CPU用于判断运算结果是否为零或者是否为负数的标志。 如果%eax的值为零,则ZF标志将被设置为1,否则将设置为0;如果%eax的值为负数,则SF标志将被设置为1,否则将设置为0。 test %eax, %eax经常用于条件分支的判断,例如在判断%eax是否为0的时候,可以使用test %eax, %eax指令来判断。如果ZF标志被设置为1,则说明%eax等于0,否则%eax不等于0。

    5.je 400ef7 :根据前面cmp指令的比较结果,如果两个字符串相等,那么跳转到phase_1 + 0x17地址处继续执行。如果两个字符串不相等,则不会跳转,继续执行下一条指令。在该程序中,phase_1 + 0x17地址处是程序正常执行的下一条指令,也就是当输入的字符串正确时,程序会继续执行下去,否则会引爆炸弹,程序结束。

    6.callq  40143a :调用"explode_bomb"函数,发生爆炸。

    7.add $0x8,%rsp: 将栈指针寄存器 %rsp 的值加上常量值 0x8,以释放栈上的8个字节空间。在程序执行过程中,栈指针寄存器 %rsp 会随着程序的运行而不断变化,当需要释放栈上的空间时,就需要执行这条指令来调整栈指针的值,以释放相应的空间。

    8.retq: 从函数中返回。

    分析发现phase_1函数调用了函数strings_not_equal。在该函数被调用之前,程序将用户输入的字符串作为第一个参数传递给了strings_not_equal函数。这个字符串是用户在运行程序时输入的,它将作为密码来解除炸弹。这个字符串保存在%eax寄存器中,因此可以推断:程序会将用户输入的字符串作为第一个参数传递给strings_not_equal函数。

    查看strings_not_equal函数的实现,该函数会将两个字符串进行比较,并返回比较结果。在反汇编代码中,可以看到调用strcmp函数的汇编指令。strcmp函数返回0表示两个字符串相等,否则它们不相等。因此可以得出结论:strings_not_equal函数返回一个非零值表示两个字符串不相等,返回0表示两个字符串相等。

    根据汇编代码中的操作,可以发现password字符串保存在了内存地址0x402400处,需要将该地址中的字符串作为比较的第二个参数传递给strings_not_equal函数,故可以得出当比较结果为0时,程序不会引爆炸弹,只需要输入password字符串即可解除炸弹。

    打开终端输入

    gdb bomb

    在gdb输入

    print (char*)0x402400

    结果返回得到密钥“Border relations with Canada have never been better.”。

    在终端输入

    ./bomb

    填入phase_1返回的密钥:

    Border relations with Canada have never been better.

    结果显示phase_1通关。

    综上所述,解决phase_1的过程是分析反汇编代码,找到password字符串的位置,并将该字符串与输入的字符串进行比较。如果比较结果为0,则炸弹解除;否则,炸弹会爆炸。


    2.2.2 phase_2

    phase_2是一个需要解密密码的关卡,需要输入一个字符串来解锁该关卡。如果输入的字符串符合要求,则可以成功通关,否则会引爆炸弹,导致失败。

    进入反汇编文件bomb.asm

    vim bomb.asm

    在未插入处输入下列指令查找phase_2的位置。

    /phase_2

    分析反汇编代码的phase_2部分内容如下:

    1.push   %rbp: 将寄存器%rbp的值压入栈中。

    2.push   %rbx: 将寄存器%rbx的值压入栈中。

    3.sub    $0x28,%rsp:修改栈指针寄存器 %rsp 的值,以便在栈上分配一段新的空间。

    3.callq  40145c :调用一个名为"read_six_numbers"的函数。

    5.je     400f30 :进行条件跳转。在该指令中,je 表示条件跳转指令,400f30 是跳转目标地址,表示如果前面的条件成立,则会跳转到这个地址处执行后续的指令

    ……

    可以发现phase_2函数有以下几个步骤:

    1. 将数据压入栈中;
    2. 减少栈指针40,为局部变量提供空间;
    3. 将栈指针存入%rsi寄存器中;
    4. 调用两个explode_bomb函数;
    5. 调用read_six_numbers函数读取6个数字。

    尝试随意输入6个数字,如下:

    为了解题,需要使%rsp的值为1,以便程序执行jmp指令跳过explode_bomb函数。jmp指令的目标地址是400f30,对应的指令为lea,用于加载有效地址。在调用read_six_numbers函数时,需要输入6个数字,该函数的地址为40145c,可以发现401480: mov $0x4025c3,%esi ,类似Phase 1向寄存器中转入数据(对应字符串),于是输入print (char*)0x4025c3进行测试,结果如下:

    说明密钥需要6个整型数。回到phase_2反汇编进行分析,可以分析有一个循环,此时,结果上述循环,各寄存器对应的值如下:

    %rbx

    %rbp

    %eax

    %rsp+4

    %rsp+24

    (%rsp)*2=2

    %rsp+8

    (%rsp+4)*2=4

    %rsp+12

    (%rsp+8)*2=8

    %rsp+16

    (%rsp+12)*2=16

    %rsp+20

    (%rsp+16)*2=32

    %rsp+24

    循环部分对应C代码

    int i;int a[6];a[0] = 1;for(i=1;i31;  t=(t+k)>>1;  k=t+y;  if(k>x)  {    z=k-1;    func4(x,y,z);    t=2t;    return t;  }  else   {     t=0;     if(k567(答案不唯一)。   

    综上所述,在解决phase_5挑战的过程中需要深入理解程序的运行逻辑和各个函数的作用,从而逆向出输入字符串中每个字符的原始值。


    2.2.6 phase_6

    phase_6是一道比较难的反汇编题目,需要使用逆向工程的技巧来解决。

    为了便于表示,将phase_6拆分成四部分,分别使用对应的C代码进行描述。


    第一部分

    该部分具有两层循环,说明输入的每个数字要求不大于6,且互不相同。

    r14 = 0;r13 = 0;r12d = 0;while(1){  rbp = r13;  if(num[r13] - 1 > 5)    goto bomb;  r12d++;  if(r12d == 6)    break;  for(ebx = r12d; ebx  第二项数据 >…,根据gdb调试输入   

    x /28 0x6032d0

    查看地址0x6032d0为:

    图2-42

    得node[i].value排序为:

    node[3]>node[4]>node[5]>node[6]>node[1]>node[2]

    因为这个顺序,是经过了numx = 0x7 - numx 则原输入数据应该是:

    4 3 2 1 6 5

    终端验证:

    系统显示通关成功。

    系统的注释详细如下:

    00000000004010f4 ://第一部分:  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    x50,%rsp  //申请空间  401100:  49 89 e5          mov    %rsp,%r13    //%r13=%rsp  401103:  48 89 e6          mov    %rsp,%rsi    //%rsi=%rsp                                            //4010f4~401103为保存参数,分配栈帧  401106:  e8 51 03 00 00    callq     //输入6个数,调用的结果是调用者的栈上按顺序存储输入的6个数  40110b:  49 89 e6          mov    %rsp,%r14    //%r14=%rsp  40110e:  41 bc 00 00 00 00 mov    x0,%r12d  //%r12d=0   %r12d当做数组索引,类似i=0  401114:  4c 89 ed          mov    %r13,%rbp    //初始 %rbp=%r13=%rsp  401117:  41 8b 45 00       mov    0x0(%r13),%eax  //%eax=num[i]  40111b:  83 e8 01          sub    x1,%eax  40111e:  83 f8 05          cmp    x5,%eax  401121:  76 05             jbe    401128   //无符号数比较,说明num为无符号数,即大于等于0,40111b~401121 num[i]-1

上一篇:网易服务器怎么降低gb?

下一篇:怎么尝试连接服务器?