CSAPP实验bomb-lab

Aki 发布于 2022-11-04 267 次阅读


介绍:

参考学习视频 郭郭wg的个人空间_哔哩哔哩_bilibili

CSAPP第三章大实验bomb-lab是一个反汇编实验。要求了解一些寄存器和汇编代码的知识还有会使用Linux上的调试工具gdb才能够完成。

实验有两个文件,一个是bomb可执行文件,一个是bomb.c文件。

文件链接:https://pan.baidu.com/s/1h84YEjQ4xLRht-2tD5g2dw?pwd=pn26,提取码:pn26

//bomb.c
/***************************************************************************
 * Dr. Evil's Insidious Bomb, Version 1.1
 * Copyright 2011, Dr. Evil Incorporated. All rights reserved.
 *
 * LICENSE:
 *
 * Dr. Evil Incorporated (the PERPETRATOR) hereby grants you (the
 * VICTIM) explicit permission to use this bomb (the BOMB).  This is a
 * time limited license, which expires on the death of the VICTIM.
 * The PERPETRATOR takes no responsibility for damage, frustration,
 * insanity, bug-eyes, carpal-tunnel syndrome, loss of sleep, or other
 * harm to the VICTIM.  Unless the PERPETRATOR wants to take credit,
 * that is.  The VICTIM may not distribute this bomb source code to
 * any enemies of the PERPETRATOR.  No VICTIM may debug,
 * reverse-engineer, run "strings" on, decompile, decrypt, or use any
 * other technique to gain knowledge of and defuse the BOMB.  BOMB
 * proof clothing may not be worn when handling this program.  The
 * PERPETRATOR will not apologize for the PERPETRATOR's poor sense of
 * humor.  This license is null and void where the BOMB is prohibited
 * by law.
 ***************************************************************************/

/*邪恶博士的阴险炸弹,1.1版
版权所有2011,Dr.Evil Incorporated。保留所有权利。
许可证:
邪恶博士公司(PERPETRATOR)特此授予您(
受害者)明确允许使用该炸弹(炸弹)。这是一个
限时许可证,在受害者死亡时到期。
PERPETRATOR对损坏、挫折、,
精神错乱、虫眼、腕管综合征、失眠或其他
对受害者的伤害。除非演员想获得荣誉,
也就是说,受害者不得将此炸弹源代码分发给
PERPETRATOR的任何敌人。没有VICTIM可以调试,
反向工程、运行“字符串”、反编译、解密或使用任何
其他技术,以获得知识并化解炸弹。炸弹
处理此程序时,不得穿防护服。这个
PERPETRATOR不会为PERPETTRATOR对
幽默如果禁止使用BOMB,本许可证无效
根据法律。*/

#include <stdio.h>
#include <stdlib.h>
#include "support.h"
#include "phases.h"


Note to self: Remember to erase this file so my victims will have no
* idea what is going on, and so they will all blow up in a
* spectaculary fiendish explosion. -- Dr. Evil
Note to self: Remember to erase this file so my victims will have no
 * idea what is going on, and so they will all blow up in a
 * spectaculary fiendish explosion. -- Dr. Evil 

/*自我提醒:记得删除这个文件,这样我的受害者就不会有
知道发生了什么,所以他们都会爆炸
可怕的爆炸。——邪恶博士
自我提醒:记得删除这个文件,这样我的受害者就不会有
知道发生了什么,所以他们都会爆炸
可怕的爆炸。——邪恶博士*/



FILE *infile;

int main(int argc, char *argv[])
{
    char *input;

    /* Note to self: remember to port this bomb to Windows and put a 
     * fantastic GUI on it. */

    /* When run with no arguments, the bomb reads its input lines 
     * from standard input. */

/*自我提醒:记得将此炸弹移植到Windows,并将
这是一个很棒的GUI
/当无参数运行时,炸弹读取其输入行
从标准输入*/

    if (argc == 1) {  
	infile = stdin;
    } 

    /* When run with one argument <file>, the bomb reads from <file> 
     * until EOF, and then switches to standard input. Thus, as you 
     * defuse each phase, you can add its defusing string to <file> and
     * avoid having to retype it. */

/*当使用一个参数<file>运行时,炸弹从<file>读取
直到EOF,然后切换到标准输入。因此,作为你
化解每个阶段的风险,您可以将其化解字符串添加到<file>中,然后
避免重新键入*/

    else if (argc == 2) {
	if (!(infile = fopen(argv[1], "r"))) {
	    printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]);
	    exit(8);
	}
    }

    /* You can't call the bomb with more than 1 command line argument. */
    /*你不能用一个以上的命令行参数调用炸弹*/
    else {
	printf("Usage: %s [<input_file>]\n", argv[0]);
	exit(8);
    }

    /* Do all sorts of secret stuff that makes the bomb harder to defuse. */
     /*做各种让炸弹更难拆除的秘密工作*/
    initialize_bomb();

    printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
    printf("which to blow yourself up. Have a nice day!\n");

    /* Hmm...  Six phases must be more secure than one phase! */
    /*嗯……六个炸弹肯定比一个更安全*/

    input = read_line();             /* Get input                   */
    phase_1(input);                  /* Run the phase               */
    phase_defused();                 /* Drat!  They figured it out!
				      * Let me know how they did it. */
    printf("Phase 1 defused. How about the next one?\n");

    /* The second phase is harder.  No one will ever figure out
     * how to defuse this... */
    /*第二阶段更难。没人会知道如何化解这个*/

    input = read_line();
    phase_2(input);
    phase_defused();
    printf("That's number 2.  Keep going!\n");

    /* I guess this is too easy so far.  Some more complex code will
     * confuse people. */
    input = read_line();
    phase_3(input);
    phase_defused();
    printf("Halfway there!\n");

    /* Oh yeah?  Well, how good is your math?  Try on this saucy problem! */
    input = read_line();
    phase_4(input);
    phase_defused();
    printf("So you got that one.  Try this one.\n");
    
    /* Round and 'round in memory we go, where we stop, the bomb blows! */
    input = read_line();
    phase_5(input);
    phase_defused();
    printf("Good work!  On to the next...\n");

    /* This phase will never be used, since no one will get past the
     * earlier ones.  But just in case, make this one extra hard. */
    input = read_line();
    phase_6(input);
    phase_defused();

    /* Wow, they got it!  But isn't something... missing?  Perhaps
     * something they overlooked?  Mua ha ha ha ha! */
    
    return 0;
}

gdb常用的指令:

break/b :设置断点,接函数名或者*地址

x :查看内存地址中的数据,可以指定数据类型,如x/s为查看字符串,x/d为查看10进制数等等

disassemble/disas : 查看当前函数的汇编代码

finish :运行程序,直到当前函数(function)执行完成。

stepi (简称 si):执行完一条机器指令,然后停止并返回到调试器。

print :可以输出寄存器和变量的值

run:程序开始执行

continue/c:继续执行程序

开始拆炸弹:

第一个bomb:根据文件和提示共有6个炸弹,每一个phase对应着一个炸弹。先拆第一个炸弹phase_1。

使用gdb查看对应phase_1函数的反汇编代码,得到:

(gdb) disassemble phase_1
Dump of assembler code for function phase_1:
   0x0000000000400ee0 <+0>:     sub    $0x8,%rsp
   0x0000000000400ee4 <+4>:     mov    $0x402400,%esi
   0x0000000000400ee9 <+9>:     callq  0x401338 <strings_not_equal>
   0x0000000000400eee <+14>:    test   %eax,%eax
   0x0000000000400ef0 <+16>:    je     0x400ef7 <phase_1+23>
   0x0000000000400ef2 <+18>:    callq  0x40143a <explode_bomb>
   0x0000000000400ef7 <+23>:    add    $0x8,%rsp
   0x0000000000400efb <+27>:    retq   
End of assembler dump.

根据分析汇编代码得到提示 string_not_equal ,这大概是判断一个字符串不相等的情况。test 指令,对两个数做操作&,当AND操作的结果为零时,TEST设置ZF为zero。如果两个操作数相等,则当两个操作数都为零时,它们的位与为零。当在结果中设置了最高位时,TEST还设置符号标志SF;当设置的位数为偶数时,设置奇偶校验标志PF,在这里的作用是用于检查eax中寄存器的值是否为0,如果为0则跳转到+23部分,程序结束,不为0就引爆炸弹。

eax寄存器为0也就是说明两个字符串相等,函数类似于strcmp(),返回值保存在寄存器eax中。在看汇编代码,有一条mov指令,将一个值移动到esi寄存器上,这个寄存器用于保存函数第一个参数,查看 $0x402400 的值,如下得到一个字符串:

(gdb) x/s 0x402400
0x402400:       "Border relations with Canada have never been better."

所以这个就是第一关的答案。

第二个bomb:

(gdb) disassemble phase_2
Dump of assembler code for function phase_2:
   0x0000000000400efc <+0>:     push   %rbp
   0x0000000000400efd <+1>:     push   %rbx
   0x0000000000400efe <+2>:     sub    $0x28,%rsp
   0x0000000000400f02 <+6>:     mov    %rsp,%rsi
   0x0000000000400f05 <+9>:     callq  0x40145c <read_six_numbers>
   0x0000000000400f0a <+14>:    cmpl   $0x1,(%rsp)
   0x0000000000400f0e <+18>:    je     0x400f30 <phase_2+52>
   0x0000000000400f10 <+20>:    callq  0x40143a <explode_bomb>
   0x0000000000400f15 <+25>:    jmp    0x400f30 <phase_2+52>
   0x0000000000400f17 <+27>:    mov    -0x4(%rbx),%eax
   0x0000000000400f1a <+30>:    add    %eax,%eax
   0x0000000000400f1c <+32>:    cmp    %eax,(%rbx)
   0x0000000000400f1e <+34>:    je     0x400f25 <phase_2+41>
   0x0000000000400f20 <+36>:    callq  0x40143a <explode_bomb>
   0x0000000000400f25 <+41>:    add    $0x4,%rbx
   0x0000000000400f29 <+45>:    cmp    %rbp,%rbx
   0x0000000000400f2c <+48>:    jne    0x400f17 <phase_2+27>
   0x0000000000400f2e <+50>:    jmp    0x400f3c <phase_2+64>
   0x0000000000400f30 <+52>:    lea    0x4(%rsp),%rbx
   0x0000000000400f35 <+57>:    lea    0x18(%rsp),%rbp
   0x0000000000400f3a <+62>:    jmp    0x400f17 <phase_2+27>
   0x0000000000400f3c <+64>:    add    $0x28,%rsp
   0x0000000000400f40 <+68>:    pop    %rbx
   0x0000000000400f41 <+69>:    pop    %rbp
   0x0000000000400f42 <+70>:    retq   
End of assembler dump.

观察汇编语句,发现 read_six_numbers ,说明答案是6个数字,一般是int的4个字节的数字。

cmpl $0x1,(%rsp) 是比较栈顶指针与立即数1的值,相等就跳转到别的地方,不相等就炸弹爆炸,所以rsp的值必定是1。根据栈的用法,栈从高地址向低地址增加,6个数字存放在栈顶指针向上连续的六个地方。如果相等的话,跳到2+52地方,然后将rsp指针+4,即向上移动4个字节的值保存在寄存器rbx中,然后将rsp+18的值保存在寄存器rbp中,跳转到2+27段。然后把rbx-4的值放入到eax中,即rsp的值1,此时eax的值为1,然后eax的值翻倍为2,然后比较eax的值与rbx的值,相等就跳转,不相等炸弹爆炸,所以rbx必定为2。下面就是接着重复类似的操作。。。。。。结果就像下面的这样,这个类似于一个循环。

所以答案是 1 2 4 8 16 32。

第三个炸弹:

Dump of assembler code for function phase_3:
   0x0000000000400f43 <+0>:     sub    $0x18,%rsp
   0x0000000000400f47 <+4>:     lea    0xc(%rsp),%rcx
   0x0000000000400f4c <+9>:     lea    0x8(%rsp),%rdx
   0x0000000000400f51 <+14>:    mov    $0x4025cf,%esi
   0x0000000000400f56 <+19>:    mov    $0x0,%eax
   0x0000000000400f5b <+24>:    callq  0x400bf0 <__isoc99_sscanf@plt>
   0x0000000000400f60 <+29>:    cmp    $0x1,%eax
   0x0000000000400f63 <+32>:    jg     0x400f6a <phase_3+39>
   0x0000000000400f65 <+34>:    callq  0x40143a <explode_bomb>
   0x0000000000400f6a <+39>:    cmpl   $0x7,0x8(%rsp)
   0x0000000000400f6f <+44>:    ja     0x400fad <phase_3+106>
   0x0000000000400f71 <+46>:    mov    0x8(%rsp),%eax
   0x0000000000400f75 <+50>:    jmpq   *0x402470(,%rax,8)
   0x0000000000400f7c <+57>:    mov    $0xcf,%eax
   0x0000000000400f81 <+62>:    jmp    0x400fbe <phase_3+123>
   0x0000000000400f83 <+64>:    mov    $0x2c3,%eax
   0x0000000000400f88 <+69>:    jmp    0x400fbe <phase_3+123>
   0x0000000000400f8a <+71>:    mov    $0x100,%eax
   0x0000000000400f8f <+76>:    jmp    0x400fbe <phase_3+123>
   0x0000000000400f91 <+78>:    mov    $0x185,%eax
   0x0000000000400f96 <+83>:    jmp    0x400fbe <phase_3+123>
   0x0000000000400f98 <+85>:    mov    $0xce,%eax
   0x0000000000400f9d <+90>:    jmp    0x400fbe <phase_3+123>
   0x0000000000400f9f <+92>:    mov    $0x2aa,%eax
   0x0000000000400fa4 <+97>:    jmp    0x400fbe <phase_3+123>
   0x0000000000400fa6 <+99>:    mov    $0x147,%eax
   0x0000000000400fab <+104>:   jmp    0x400fbe <phase_3+123>
   0x0000000000400fad <+106>:   callq  0x40143a <explode_bomb>
   0x0000000000400fb2 <+111>:   mov    $0x0,%eax
   0x0000000000400fb7 <+116>:   jmp    0x400fbe <phase_3+123>
   0x0000000000400fb9 <+118>:   mov    $0x137,%eax
   0x0000000000400fbe <+123>:   cmp    0xc(%rsp),%eax
   0x0000000000400fc2 <+127>:   je     0x400fc9 <phase_3+134>
   0x0000000000400fc4 <+129>:   callq  0x40143a <explode_bomb>
   0x0000000000400fc9 <+134>:   add    $0x18,%rsp
   0x0000000000400fcd <+138>:   retq   
End of assembler dump.

观察反汇编代码,发现一行 callq 0x400bf0 <__isoc99_sscanf@plt>,查阅资料知实际上调用了scanf()函数,往前推得到参数存放在 $0x4025cf 这个位置。使用命令 x/s 查看值发现是两个 %d,%d,说明输入是两个十进制数字,我假设输入 6 和 100 两个数字。

在 0x0000000000400f63 处打断点,查看 %eax 的值,p $eax,发现是2,其实这个值就是输入的数字的个数,然后比较大小跳转到 0x0000000000400f6a。

在 0x0000000000400f6f 处打断点, 查看 0x8(%rsp) 的值,p *(int*)($rsp+0x8) 发现是6,与我输入第一个数相同,且用来和7比较,此时发现如果输入的第一个数大与7的话就会跳转到炸弹引爆,所以第一个数必须小于7。

接着执行程序,把6赋值给eax。然后程序跳转到 0x0000000000400f9f 处,把值0x2aa赋值给eax,然后跳转到 0x0000000000400fbe 比较 eax 和 0xc(%rsp) 是否值相同,我们输出 0xc(%rsp) ,发现是我们输入的第二个数100,相等就跳转到 0x0000000000400fc9 处,不相等炸弹爆炸,所以第二个数一定是是 0x2aa 即是 682。

所以本题答案为两个十进制数字,第一个数小于7,第二个是682,本体结构类似于switch结构。

第四个炸弹:

(gdb) disassemble phase_4
Dump of assembler code for function phase_4:
   0x000000000040100c <+0>:     sub    $0x18,%rsp
   0x0000000000401010 <+4>:     lea    0xc(%rsp),%rcx
   0x0000000000401015 <+9>:     lea    0x8(%rsp),%rdx
   0x000000000040101a <+14>:    mov    $0x4025cf,%esi
   0x000000000040101f <+19>:    mov    $0x0,%eax
   0x0000000000401024 <+24>:    callq  0x400bf0 <__isoc99_sscanf@plt>
   0x0000000000401029 <+29>:    cmp    $0x2,%eax
   0x000000000040102c <+32>:    jne    0x401035 <phase_4+41>
   0x000000000040102e <+34>:    cmpl   $0xe,0x8(%rsp)
   0x0000000000401033 <+39>:    jbe    0x40103a <phase_4+46>
   0x0000000000401035 <+41>:    callq  0x40143a <explode_bomb>
   0x000000000040103a <+46>:    mov    $0xe,%edx   
   0x000000000040103f <+51>:    mov    $0x0,%esi  
   0x0000000000401044 <+56>:    mov    0x8(%rsp),%edi 
   0x0000000000401048 <+60>:    callq  0x400fce <func4>
   0x000000000040104d <+65>:    test   %eax,%eax
   0x000000000040104f <+67>:    jne    0x401058 <phase_4+76>
   0x0000000000401051 <+69>:    cmpl   $0x0,0xc(%rsp)
   0x0000000000401056 <+74>:    je     0x40105d <phase_4+81>
   0x0000000000401058 <+76>:    callq  0x40143a <explode_bomb>
   0x000000000040105d <+81>:    add    $0x18,%rsp
   0x0000000000401061 <+85>:    retq   
End of assembler dump.

(gdb) disassemble func4
Dump of assembler code for function func4:
   0x0000000000400fce <+0>:     sub    $0x8,%rsp
   0x0000000000400fd2 <+4>:     mov    %edx,%eax   //ecx = 0
   0x0000000000400fd4 <+6>:     sub    %esi,%eax   //eax = eax - esi
   0x0000000000400fd6 <+8>:     mov    %eax,%ecx   //ecx = eax,
   0x0000000000400fd8 <+10>:    shr    $0x1f,%ecx  //ecx >>31 即ecx = 0
   0x0000000000400fdb <+13>:    add    %ecx,%eax   //eax = eax + ecx
   0x0000000000400fdd <+15>:    sar    %eax        
   0x0000000000400fdf <+17>:    lea    (%rax,%rsi,1),%ecx
   0x0000000000400fe2 <+20>:    cmp    %edi,%ecx
   0x0000000000400fe4 <+22>:    jle    0x400ff2 <func4+36>
   0x0000000000400fe6 <+24>:    lea    -0x1(%rcx),%edx
   0x0000000000400fe9 <+27>:    callq  0x400fce <func4>
   0x0000000000400fee <+32>:    add    %eax,%eax
   0x0000000000400ff0 <+34>:    jmp    0x401007 <func4+57>
   0x0000000000400ff2 <+36>:    mov    $0x0,%eax
   0x0000000000400ff7 <+41>:    cmp    %edi,%ecx
   0x0000000000400ff9 <+43>:    jge    0x401007 <func4+57>
   0x0000000000400ffb <+45>:    lea    0x1(%rcx),%esi
   0x0000000000400ffe <+48>:    callq  0x400fce <func4>
   0x0000000000401003 <+53>:    lea    0x1(%rax,%rax,1),%eax
   0x0000000000401007 <+57>:    add    $0x8,%rsp
   0x000000000040100b <+61>:    retq   
End of assembler dump.

炸弹四稍微有些复杂,一个函数调用了另一个函数。观察反汇编代码,发现一行callq 0x400bf0 <__isoc99_sscanf@plt>,查阅资料知实际上调用了scanf()函数,往前推得到参数存放在 $0x4025cf 这个位置。使用命令 x/s 查看值发现是两个 %d,%d,说明输入是两个十进制数字,我假设输入 5 和 9 两个数字。

分析一下,得到 0x8(%rsp) 保存第一个数字, 0xc(%rsp) 保存第二个数字。

在 0x000000000040102c 处打断点,查看eax的值,发现是2,其实就是我们输入的数字的个数,不是2个炸弹就直接爆炸,是两个就继续执行代码。

在 0x0000000000401033 打断点,查看 0x8(%rsp) 的值,p *(int*)($rsp+0x8) 发现是5,是我们输入的第一个数字,这个指令比较 5 和 0xe 的值,5 小于等于14 的话就跳转到别的地方,否则炸弹引爆,即我们输入的第一个数字必须小于等于14。

在 0x0000000000401048 处打断点,发现前面有三条 mov 指令,且本条指令调用了一个函数 func4,发现这个函数有三个参数,其中 edi 保存的值是5,esi 保存的是0,edx保存的是14。

在 func4 函数内打断点 0x0000000000400fe4,发现是 edi 和 ecx 的值比较,查看这两个寄存器保存的值分别是 5 和 7,如果 ecx 的值小于等于 rdi 的值就跳转到 0x0000000000400ff2,否则的话就调用 func4 进行一系列的计算,发现最后 ecx 的值为 3,符合跳转条件。

在 0x0000000000400ff9 处打断点,发现是比较 edi 和 ecx 的值,分别查看这两个值为5和7,不符合跳转条件,往下执行代码,又调用了自身func4,又进行了一系列计算,最后到 0x0000000000400ff9 处,发现ecx 的值为5,符合跳转条件,跳转至函数最后,函数返回 eax 的值为0,在 0x0000000000400ff2 处把0x0赋值为eax了。

在 0x000000000040104f 处打断点,发现是 test 指令,如果eax不等于0则跳转,但是 func4 函数返回的是0,刚好存放在eax中,所以不会跳转。接着比较 0x0 和 0xc(%rsp) 的值,查看 0xc(%rsp) 的值,p *(int*)($rsp+0xc),发现是我们输入的第二个数字9,而且如果不等于0的话,炸弹就会引爆,所以第二个数字一定是0。

所以炸弹四的答案为两个十进制数字,第一个数字是1,第二个是0。