BombLab
一、 Lab介绍
-
官网地址
在这里首先给出BombLab的官网地址:http://csapp.cs.cmu.edu/3e/labs.html
进入官网后即可看到BombLab的介绍:
简单翻译一下:BombLab其实就是只提供给你一个可执行文件,让你利用对可执行文件进行逆向,分析逆向出来的汇编代码,以提高机器级编程的能力。BombLab总共有六关,难度也是越来越高,但一个个分析出来的话,成就感是非常高的!!!
-
前置知识
1、 了解Linux操作系统的基本使用,能熟练使用Linux的基本指令。
2、 熟练掌握x86汇编语言(如果不熟悉x86汇编语言,可以去CSAPP第三章学习)
3、 熟练掌握gdb
二、 环境准备
-
下载相关资源
我们用到的资源也就两个:Writeup和Self-Study Handout- Writeup里面放置着Lab的介绍
- Self-StudyHandout里面即是资源文件(一个c文件、一个可执行文件、一个README文档)。
Self-Study Handout下载下来解压完成之后如下:
-
Linux环境搭建
因为该Lab必须基于Linux操作系统,所以我建议完全不了解Linux的小伙伴先去补一下Linux的基础知识,再返回来做这个Lab。在这里推荐一下韩顺平老师的Linux课程,在B站有视频。
Linux环境有了之后,之后就是检查Linux系统是否有我们开发所需的工具。
最关键的两个工具分别是objdump和gdb。这里我们就只演示gdb,objdump也是一样的道理。
首先需要判断本机是否有gdb。使用命令which gdb
。
如果下面显示出gdb的路径来:如usr/bin/gdb
,则说明本机已有gdb。如果不是,则使用命令yum install gdb
命令下载gdb。
好的,以上所有关于环境的问题,我们就都解决了,下面我们就可以正式开启BombLab了!!!
三、 具体内容
-
Level 1
首先,将资源文件放入Linux系统后,我们需要做的一件事便是查看各个资源文件:
README文件如下:
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.
***************************************************************************/
#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
*/
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. */
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. */
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;
}
通过分析C文件我们可以发现,分别调用6个函数,分别代表六关,每一关都需要我们输入一个字符串,然后将该字符串的首地址传入函数,如果传入的字符串正确,函数正常返回,进入下一关。如果不正确,则闯关失败!
可执行文件不可直接查看,我们将其反汇编并写入bomb.txt中。
使用命令:objdump -S -d bomb > bomb.txt
即可完成上面的操作。
查看bomb.txt就可以发现可执行文件所需的所有函数的反汇编代码都可以看到,当然我们只需要对我们有用的就够了。由于代码太多,我这里就不全放过来了。
部分代码如下:
接下来,我们就可以分析第一关的反汇编代码了。
400ee0:即对栈帧进行初始化。
400ee4:将$0x402400传入%esi中
400ee9:调用strings_not_equal函数,将%rdi和%rsi传入,%rdi即为我们输入的字符串的首地址,%rsi即为$0x402400
400eee:将strings_not_equal函数的返回值%eax与0进行比较
400ef0:若%eax为0跳转到400ef7,成功返回函数
400ef2:若%eax不为0,执行这句,调用explode_bomb,即引爆炸弹,闯关失败。
通过以上分析,我们可以得出:只需满足$0x402400里存放的字符串与我们输入的字符串相等即可。
我们通过gdb运行程序bomb,在0x400ee9处打上断点。暂时随便输入一个字符串,查看0x402400处存放的字符串。
可以看到,0x402400处存放的字符串为:“Border relations with Canada have never been better.”
所以第一关的答案就是"Border relations with Canada have never been better."
我们输入答案,可以看到进入第二关了!!!
- Level 2
首先,我们分析第二关的反汇编代码:
400efc~400efe:对栈帧进行初始化
400f02:将栈指针放入%rsi中
400f05:调用read_six_numbers函数,将%rdi和%rsi传入,%rdi即为我们输入的字符串首地址,%rsi即为栈指针
400f0a:比较栈指针指向的数据是否等于1,若等于1,跳转到400f30,若不等于1,接着执行400f10,即引爆炸弹,闯关失败。
400f30:%rsp + 0x4 赋值给 %rbx
400f35:%rsp + 0x18 赋值给 %rbp
400f3a:无条件跳转到400f17
400f17:将(%rbx - 0x4)处的值赋值给%eax
400f1a:将%eax的值乘2
400f1c:比较%eax和(%rbx)处的值比较,若相等,则跳转到400f25,若不相等,则继续执行400f20,即引爆炸弹,闯关失败。
400f25:将%rbx的值+4
400f29:比较%rbp和%rbx的值,如果不相等,则继续跳转到400f17,如果相等则跳转到400f3c,即成功返回函数。
通过以上分析,我们得出结论,我们需要输入一个包含6个数字的字符串,而read_six_numbers函数会将我们字符串的六个数字保存在栈中,这六个数字需要满足的条件是:1、第一个数字为1。2、 之后每个是之前的2倍。所以得出第二关的答案:1 2 4 8 16 32
我们输入答案,可以看到进入第三关了!!!
-
Level 3
首先,我们分析第三关的反汇编代码:
400f43:栈帧的初始化
400f47:将 %rsp + 0xc 赋值给%rcx
400f4c:将 %rsp + 0x8 赋值给%rdx
400f51:将 0x4025cf 赋值给%esi
400f56:将 0x0 赋值给%eax
400f5b:调用函数__isoc99_sscanf@plt
400f60:比较0x1和%eax的大小,若大于1,跳转到400f6a,反之,引爆炸弹,闯关失败。
400f6a:比较0x7和(0x8 + %rsp)的大小,若大于7,则引爆炸弹,闯关失败,若不大于7,继续执行400f71。
400f71:将(%rsp + 0x8)赋值给%eax
400f75:跳转到(0x402070 + 0x8 * %rax)处保存的地址处,即(0x402070 + 0x8 * %rax)是一个地址,我们要去的地方的地址,存在这个地址的数据处。
400f7c~400fab:这部分即上一步要跳转的地址,将特定数据赋值给%eax,然后跳转到400fbe
400fbe:比较(0xc + %rsp)中的值和%eax,若等于,则跳转到400fc9,即函数正常结束。若不等于,则继续执行400fc4,即引爆炸弹,闯关失败。
通过以上分析,我们可以得出:我们输入的字符串需要两个整数,(0x8 + %rsp)中存放的便是第一个数字,(0xc + %rsp)中存放的便是第二个数字。第一个数字的取值不可大于7,故取值范围为:0~7。第一个数字的每个对应的第二个数字都在400f7c – 400fab处告诉我们了。所以第三关的答案不止一个。
下面列出所有答案:
0 207
1 311
2 707
3 256
4 389
5 206
6 682
7 327
我们输入答案,可以看到进入第四关了!!!
-
Level 4
首先,我们分析第四关的反汇编代码:
40100c:栈帧的从初始化
401010:将 %rsp + 0xc 赋值给%rcx
401015:将 %rsp + 0x8 赋值给%rdx
40101a:将 0x4025cf 赋值给%esi
40101f:将 0 赋值给%eax
401024:调用函数__isoc99_sscanf@plt
401029:比较函数返回值%eax和0x2的大小,若不等于2,则跳转到401035,即引爆炸弹,闯关失败。若等于2,则继续执行40102e
40102e:比较(0x8 + %rsp)处的值和 0xe 的大小,若小于等于,则跳转到40103a。反之,则继续执行,即引爆炸弹,闯关失败。
40103a:将0xe赋值给%edx
40103f:将0x0赋值给%esi
401044:将(0x8 + %rsp)赋值给%edi
401048:跳转到400fce,调用func4函数,参数分别为%rdi,%rsi,%rdx
400fce:func4函数栈帧的初始化
400fd2:将%edx赋值给%eax
400fd4:%eax减去%esi
400fd6:将%eax赋值给%ecx
400fd8:将%ecx逻辑右移0x1f位
400fdb:将 %eax + %ecx 赋值给 %eax
400fdd:将 %eax 算术右移一位
400fdf: 将 %rax + %rsi * 1 赋值给%ecx
400fe2:比较%ecx和%edi,若%ecx小于等于%edi,则跳转到400ff2。反之,将%rcx - 0x1赋值给%edx,继续递归调用func4函数。
400ff2: 将0x0赋值给%eax
400ff7: 比较%ecx和%edi的值,若%ecx大于等于%edi,则跳转到401007,函数正常返回,转而执行40104d。反之,将%rcx + 0x1赋值给%esi,继续递归调用func4函数。
40104d:比较0x0与返回值%eax的大小,若不等于,则调用401058,即引爆炸弹,闯关失败。反之继续执行401051
401051:比较(0xc + %rsp)的值与0x0的大小,若相等,函数正常结束,若不相等,引爆炸弹,闯关失败。
通过以上分析,我们可以得出:这一关我们输入的字符串须是两个数字,并且第二个数字必须是0,第一个数字必须小于等于0xe。通过分析func4中的代码,我们可以发现当第一个数字为0x7时,func4函数可以正常返回0。故第四关的答案为:7 0
我们输入答案,可以看到进入第五关了!!!
- Level 5
首先,我们分析第五关的反汇编代码:
401062~40106a:栈帧的初始化
401073:将%rax存入到(%rsp + 0x18)
401078:将%rax置零
40107a:调用string_length函数,将%rdi(我们输入的字符串的首地址)传入
40107f: 比较%eax与0x6的大小,若相等,则跳转到4010d2,若不相等,则引爆炸弹,闯关失败。
4010d2:将0x0赋值给%eax
4010d7:无条件跳转到40108b
40108b:使用零拓展数据传送指令,将(%rbx + %rax * 1)的值赋值给%ecx
40108f:将%cl中的值赋值给(%rsp)
401092:将(%rsp)中的值赋值给%rdx
401096:将寄存器 %edx 与 0xf 做与运算的值赋值给 %edx
401099:使用零拓展数据传送指令,将(%rdx + 0x4024b0)的值赋值给%edx
4010a0:将 %dl 中的值赋值给(0x10 + %rsp + %rax * 1)
4010a4:将 %rax 的值+1
4010a8:比较 %rax 的值与0x6,若相等,则继续执行4010ae,若不等,则返回40108b。
4010ae:将0x0赋值给(0x16 + %rsp)
4010b3:将0x40245e赋值给%esi
4010b8:将 0x10 + %rsp 赋值给%rdi
4010bd:调用函数string_not_equal,第一个参数为我们输入的字符串经修改后的地址,第二个参数为正确答案所在的字符串的地址
4010c2:测试%eax是否等于0,若等于0,则跳转到4010d9,即函数正常返回,若不等于0,则引爆炸弹,闯关失败。
通过以上分析,我们可以得出,我们输入的字符串的长度必须为6,并且经过变换之后可以得到目标字符串:“flyers”,经计算,最后得出第五关的答案为:ionefg
我们输入答案,可以看到进入第六关了!!!
-
Level 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>
40110b: 49 89 e6 mov %rsp,%r14
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d
401114: 4c 89 ed mov %r13,%rbp
401117: 41 8b 45 00 mov 0x0(%r13),%eax
40111b: 83 e8 01 sub $0x1,%eax
40111e: 83 f8 05 cmp $0x5,%eax
401121: 76 05 jbe 401128 <phase_6+0x34>
401123: e8 12 03 00 00 callq 40143a <explode_bomb>
401128: 41 83 c4 01 add $0x1,%r12d
40112c: 41 83 fc 06 cmp $0x6,%r12d
401130: 74 21 je 401153 <phase_6+0x5f>
401132: 44 89 e3 mov %r12d,%ebx
401135: 48 63 c3 movslq %ebx,%rax
401138: 8b 04 84 mov (%rsp,%rax,4),%eax
40113b: 39 45 00 cmp %eax,0x0(%rbp)
40113e: 75 05 jne 401145 <phase_6+0x51>
401140: e8 f5 02 00 00 callq 40143a <explode_bomb>
401145: 83 c3 01 add $0x1,%ebx
401148: 83 fb 05 cmp $0x5,%ebx
40114b: 7e e8 jle 401135 <phase_6+0x41>
40114d: 49 83 c5 04 add $0x4,%r13
401151: eb c1 jmp 401114 <phase_6+0x20>
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi
401158: 4c 89 f0 mov %r14,%rax
40115b: b9 07 00 00 00 mov $0x7,%ecx
401160: 89 ca mov %ecx,%edx
401162: 2b 10 sub (%rax),%edx
401164: 89 10 mov %edx,(%rax)
401166: 48 83 c0 04 add $0x4,%rax
40116a: 48 39 f0 cmp %rsi,%rax
40116d: 75 f1 jne 401160 <phase_6+0x6c>
40116f: be 00 00 00 00 mov $0x0,%esi
401174: eb 21 jmp 401197 <phase_6+0xa3>
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx
40117a: 83 c0 01 add $0x1,%eax
40117d: 39 c8 cmp %ecx,%eax
40117f: 75 f5 jne 401176 <phase_6+0x82>
401181: eb 05 jmp 401188 <phase_6+0x94>
401183: ba d0 32 60 00 mov $0x6032d0,%edx
401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2)
40118d: 48 83 c6 04 add $0x4,%rsi
401191: 48 83 fe 18 cmp $0x18,%rsi
401195: 74 14 je 4011ab <phase_6+0xb7>
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx
40119a: 83 f9 01 cmp $0x1,%ecx
40119d: 7e e4 jle 401183 <phase_6+0x8f>
40119f: b8 01 00 00 00 mov $0x1,%eax
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx
4011a9: eb cb jmp 401176 <phase_6+0x82>
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx
4011b0: 48 8d 44 24 28 lea 0x28(%rsp),%rax
4011b5: 48 8d 74 24 50 lea 0x50(%rsp),%rsi
4011ba: 48 89 d9 mov %rbx,%rcx
4011bd: 48 8b 10 mov (%rax),%rdx
4011c0: 48 89 51 08 mov %rdx,0x8(%rcx)
4011c4: 48 83 c0 08 add $0x8,%rax
4011c8: 48 39 f0 cmp %rsi,%rax
4011cb: 74 05 je 4011d2 <phase_6+0xde>
4011cd: 48 89 d1 mov %rdx,%rcx
4011d0: eb eb jmp 4011bd <phase_6+0xc9>
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx)
4011d9: 00
4011da: bd 05 00 00 00 mov $0x5,%ebp
4011df: 48 8b 43 08 mov 0x8(%rbx),%rax
4011e3: 8b 00 mov (%rax),%eax
4011e5: 39 03 cmp %eax,(%rbx)
4011e7: 7d 05 jge 4011ee <phase_6+0xfa>
4011e9: e8 4c 02 00 00 callq 40143a <explode_bomb>
4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx
4011f2: 83 ed 01 sub $0x1,%ebp
4011f5: 75 e8 jne 4011df <phase_6+0xeb>
4011f7: 48 83 c4 50 add $0x50,%rsp
4011fb: 5b pop %rbx
4011fc: 5d pop %rbp
4011fd: 41 5c pop %r12
4011ff: 41 5d pop %r13
401201: 41 5e pop %r14
401203: c3 retq
第六关略显复杂,若要一步一步仔细讲解,会花费大量篇幅,而且也不一定能讲明白(因为太过复杂ε(┬┬﹏┬┬)3)。这里直接给出第六关的答案:4 3 2 1 6 5
。小伙伴们如果觉得第六关过于复杂,可以看情况跳过,把前五个弄明白就可以了,当然想挑战自己的小伙伴也可以尝试一下,并不难,只是特麻烦,多花点时间也不是问题。
四、 结尾语
以上就是BombLab的全部内容了。通过这个Lab我们可以更加深入的理解了机器级编程,对计算机底层有了更深的认识。希望本人写的这篇文章对您有所帮助,谢谢!!!