介绍:
参考学习视频 郭郭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。
Comments NOTHING