CSAPP BombLab
背景
大三上的《系统级程序设计》使用了CMU的经典教材CSAPP(Computer System: A Programmer’s Perspective),其实对这门课早有耳闻,是CMU的神课。正好大三上学期学校安排了这门课,而且是大三上学期唯一一门专业必修课,也让人很期待。
这门课,确实是很有质量的课。原因如下:
- 上课用的是CMU原版全英文的PPT,增加对一些常见的计算机组成的英文词汇的了解。
- 设置了助教,并且会让助教在课上演示实际代码的运行情况(很多专业课都没有)
- 老师讲的很好,能够看出来对这门课的教学经验很足
BombLab是设置的第一次实验,助教课上演示了第一关的过关方法,并提醒我们一些注意事项:每个人下载的代码都不一样等等
实验记录
软件包构成:
- bomb(可执行文件)
- bomb.c
- Readme(记录学号和编号)
前置知识:
X86-64有16个64位寄存器,每一个的用途:
X86-64的栈帧结构:https://www.jianshu.com/p/997dddb3122c
https://zhuanlan.zhihu.com/p/27339191
objdump:objdump命令是Linux下的反汇编目标文件或者可执行文件的命令,它以一种可阅读的格式让你更多地了解二进制文件可能带有的附加信息。
GDB :gdb是GNU开源组织发布的一个强大的Linux下的程序调试工具。
gdb_quickreference:
由于我使用的是macOS,又不希望安装庞大的虚拟机软件占用空间,因此选择使用docker配置环境,建立了一个ubuntu的容器。
使用docker cp 命令将文件夹copy到容器下,安装gcc,gdb,objdump,vim
apt update //升级apt
apt install build-essential
apt install binutils
apt install vim
拆弹思路:
因为每一关都是输入一段正确的字符串通关。阅读bomb.c文件,可以发现每一关都是一个函数phase_x(),却没有提供函数体,因此,需要对可执行文件进行反汇编,阅读汇编代码,找到输入的参数,再分析如何避免引爆炸弹。(其实使用IDA反编译可以直接看到源码,但是这样达不到效果,因为这个实验就是锻炼阅读汇编代码的能力,但其实不会的部分还是参考了IDA的反编译代码,后面几关,尤其是最后一关还挺难理解的)
phase_1
使用objdump命令,将bomb可执行程序反汇编,保存到bomb.txt文件
objdump -d bomb > bomb.txt
gdb -q bomb //进入调试
disas phase_1
显示结果及分析如下:
Dump of assembler code for function phase_1:
0x0000558037001204 <+0>: sub $0x8,%rsp #开栈
0x0000558037001208 <+4>: lea 0x17c1(%rip),%rsi # 0x5580370029d0 #rsi = rip + 0x17c1,rdi为第一个参数,rsi为第二个参数。接着调用string_not_equal函数,可以大胆猜测这里的rsi存放的地址就是标准答案的字符串的地址
0x000055803700120f <+11>: callq 0x558037001706 <strings_not_equal> #调用string_not_equal函数
0x0000558037001214 <+16>: test %eax,%eax #eax为string_not_eaual的返回值,为0或1,与运算后设置标志位也为0或1
0x0000558037001216 <+18>: jne 0x55803700121d <phase_1+25> #,跟据标志位跳转,两字符串不相等就跳转到explode_bomb
0x0000558037001218 <+20>: add $0x8,%rsp
0x000055803700121c <+24>: retq
0x000055803700121d <+25>: callq 0x558037001996 <explode_bomb>
0x0000558037001222 <+30>: jmp 0x558037001218 <phase_1+20>
在0x000055803700120f打断点:
b *0x000055803700120f
c //继续运行
在0x0000558037001208指令执行完后,查看所有寄存器内容:
info all-registers
可以找到rsi寄存器存放的地址为:0x5580370029d0
rsi存放的地址也可以看到
查看该地址存放的内容:
x/s 0x5580370029d0
输出:
0x5580370029d0: "I am just a renegade hockey mom."
执行程序
./bomb
输入上面的字符串,显示进入下一关:
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
I am just a renegade hockey mom.
Phase 1 defused. How about the next one?
我们也可以看rdi的值是不是输入的字符串
(gdb) x/s 0x560d15a046c0
0x560d15a046c0 <input_strings>: "test"
可以看到rdi存放的值确实是我测试输入的字符串test
小插曲:
执行./bomb时,电脑网恰好断了,程序运行不了,提示如下:
# ./bomb
Initialization error:
Error: DNS is unable to resolve server address
说明程序是联网的,果然会记录你每一关爆炸的次数(CMU原版的是这样说的,武大的版本是把爆炸次数发给课程的服务器)
phase_2
采用同样的方法:
0000000000001224 <phase_2>:
1224: 55 push %rbp
1225: 53 push %rbx
1226: 48 83 ec 28 sub $0x28,%rsp #开栈
122a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax # rax = 0x28
1231: 00 00
1233: 48 89 44 24 18 mov %rax,0x18(%rsp) ) #这里就说明了有6个参数0x18 = 24 = 4 * 6,int32是4个字节,总共有6个
1238: 31 c0 xor %eax,%eax
123a: 48 89 e6 mov %rsp,%rsi #rsi = rsp
123d: e8 90 07 00 00 callq 19d2 <read_six_numbers> #可以看出是输入6个数字
1242: 83 3c 24 00 cmpl $0x0,(%rsp) # rsp - 0,rsp此时指向第一个参数
1246: 78 0a js 1252 <phase_2+0x2e> #js:结果为负则转移,1252就是explode_bomb,说明rsp - 0 >= 0不会爆炸,即rsp >= 0,第一个参数要非负
1248: bb 01 00 00 00 mov $0x1,%ebx #ebx = 0x1
124d: 48 89 e5 mov %rsp,%rbp #rbp指向第一个参数
1250: eb 11 jmp 1263 <phase_2+0x3f>
1252: e8 3f 07 00 00 callq 1996 <explode_bomb>
1257: eb ef jmp 1248 <phase_2+0x24>
1259: 48 83 c3 01 add $0x1,%rbx # rbx = rbx + 0x1 = 0x2
125d: 48 83 fb 06 cmp $0x6,%rbx #rbx 与0x6比较
1261: 74 13 je 1276 <phase_2+0x52> #相等则跳转
1263: 89 d8 mov %ebx,%eax #从1250跳转过来,第一次,eax = ebx = 0x1
1265: 03 44 9d fc add -0x4(%rbp,%rbx,4),%eax #eax =eax + %rbp + 4%rbx -0x4 = eax, rbx在循环中为1,2,3...所以每次等于数组中的上一个元素,即eax = eax + a[i-1]
1269: 39 44 9d 00 cmp %eax,0x0(%rbp,%rbx,4) #将eax与a[i] 比较,如果a[i] != eax,爆炸
126d: 74 ea je 1259 <phase_2+0x35>
126f: e8 22 07 00 00 callq 1996 <explode_bomb>
1274: eb e3 jmp 1259 <phase_2+0x35>
1276: 48 8b 44 24 18 mov 0x18(%rsp),%rax #从1261跳转过来, rax=rsp + 0x18
127b: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
1282: 00 00
1284: 75 07 jne 128d <phase_2+0x69>
1286: 48 83 c4 28 add $0x28,%rsp
128a: 5b pop %rbx
128b: 5d pop %rbp
128c: c3 retq
128d: e8 be fb ff ff callq e50 <__stack_chk_fail@plt>
所以输入的6个数满足如下关系:
if(a[0] < 0) bomb();
b = 1;
i = 1;
while(b != 6){
a[i] = a[i-1] + b;
b++;
}
可以列举出来一个符合条件的数组如:[0, 1, 3, 6, 10, 15]
输入:0 1 3 6 10 15
程序输出:
That's number 2. Keep going!
第二关顺利通过
phase_3
汇编代码:
0000000000001292 <phase_3>:
1292: 48 83 ec 18 sub $0x18,%rsp #开栈
1296: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
129d: 00 00
129f: 48 89 44 24 08 mov %rax,0x8(%rsp)
12a4: 31 c0 xor %eax,%eax
12a6: 48 8d 4c 24 04 lea 0x4(%rsp),%rcx
12ab: 48 89 e2 mov %rsp,%rdx
12ae: 48 8d 35 a8 19 00 00 lea 0x19a8(%rip),%rsi # 2c5d <array.3415+0x21d> #输入参数
12b5: e8 36 fc ff ff callq ef0 <__isoc99_sscanf@plt> #输入
12ba: 83 f8 01 cmp $0x1,%eax #输入参数的个数 <= 1,jle跳转到引爆炸弹
12bd: 7e 1d jle 12dc <phase_3+0x4a>
12bf: 83 3c 24 07 cmpl $0x7,(%rsp)
12c3: 0f 87 99 00 00 00 ja 1362 <phase_3+0xd0>
12c9: 8b 04 24 mov (%rsp),%eax
12cc: 48 8d 15 4d 17 00 00 lea 0x174d(%rip),%rdx # 2a20 <_IO_stdin_used+0x1a0>
12d3: 48 63 04 82 movslq (%rdx,%rax,4),%rax
12d7: 48 01 d0 add %rdx,%rax
12da: ff e0 jmpq *%rax #根据rax的值在跳转表中跳转
12dc: e8 b5 06 00 00 callq 1996 <explode_bomb>
12e1: eb dc jmp 12bf <phase_3+0x2d>
12e3: b8 b3 00 00 00 mov $0xb3,%eax #case0: eax = 179
12e8: eb 05 jmp 12ef <phase_3+0x5d>
12ea: b8 00 00 00 00 mov $0x0,%eax
12ef: 2d 05 03 00 00 sub $0x305,%eax #-773,
12f4: 05 57 02 00 00 add $0x257,%eax #+599
12f9: 2d 88 01 00 00 sub $0x188,%eax #-392
12fe: 05 88 01 00 00 add $0x188,%eax #+392
1303: 2d 88 01 00 00 sub $0x188,%eax #-392
1308: 05 88 01 00 00 add $0x188,%eax #+392
130d: 2d 88 01 00 00 sub $0x188,%eax #-392
1312: 83 3c 24 05 cmpl $0x5,(%rsp) #第一个参数 > 5,炸
1316: 7f 06 jg 131e <phase_3+0x8c>
1318: 39 44 24 04 cmp %eax,0x4(%rsp) #第二个参数 != 经过处理完的数,炸
131c: 74 05 je 1323 <phase_3+0x91>
131e: e8 73 06 00 00 callq 1996 <explode_bomb>
1323: 48 8b 44 24 08 mov 0x8(%rsp),%rax
1328: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
132f: 00 00
1331: 75 3b jne 136e <phase_3+0xdc>
1333: 48 83 c4 18 add $0x18,%rsp
1337: c3 retq
1338: b8 00 00 00 00 mov $0x0,%eax #case 1
133d: eb b5 jmp 12f4 <phase_3+0x62>
133f: b8 00 00 00 00 mov $0x0,%eax #case 2
1344: eb b3 jmp 12f9 <phase_3+0x67>
1346: b8 00 00 00 00 mov $0x0,%eax #case 3
134b: eb b1 jmp 12fe <phase_3+0x6c>
134d: b8 00 00 00 00 mov $0x0,%eax #case 4
1352: eb af jmp 1303 <phase_3+0x71>
1354: b8 00 00 00 00 mov $0x0,%eax #case 5
1359: eb ad jmp 1308 <phase_3+0x76>
135b: b8 00 00 00 00 mov $0x0,%eax #case 6
1360: eb ab jmp 130d <phase_3+0x7b>
1362: e8 2f 06 00 00 callq 1996 <explode_bomb>
1367: b8 00 00 00 00 mov $0x0,%eax #case 7
136c: eb a4 jmp 1312 <phase_3+0x80>
136e: e8 dd fa ff ff callq e50 <__stack_chk_fail@plt>
这一关汇编代码比较长,但是可以看出很有规律,后面全是jmp和mov,其实是swich-case语句
先看
12ae: 48 8d 35 a8 19 00 00 lea 0x19a8(%rip),%rsi # 2c5d <array.3415+0x21d>
12b5: e8 36 fc ff ff callq ef0 <__isoc99_sscanf@plt> #输入
12b5调用了sccanf,能猜出来是在输入,在该处打断点,查看rsi的值,发现为
rsi 0x56413d802c5d 94838204673117
x/s 0x56413d802c5d
输出:
0x56413d802c5d: "%d %d" #说明输入两个整数
12bf: 83 3c 24 07 cmpl $0x7,(%rsp)
说明输入第一个的数字<7,下一条的ja说明第一个数字>=0
后面分析起来很乱,借助ida的流程图:
可以看到下面是一个跳转表,根据第一个参数的数值进行跳转的表
结合上面的汇编代码,可以知道是有多个答案的
假设第一个参数为0,则过程为:
进入case 0 –> eax = 710 –> eax -= 773 –>eax +=599 –>eax -= 392 –>eax += 392 –> eax-=392 –>eax += 392
–>eax-=392 最后eax = -387。而eax必须和输入的第二个参数相等,所以第二个参数为-387
所以其中一个答案为:0, -387
phase_4
13ac: 48 83 ec 18 sub $0x18,%rsp
13b0: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
13b7: 00 00
13b9: 48 89 44 24 08 mov %rax,0x8(%rsp)
13be: 31 c0 xor %eax,%eax
13c0: 48 89 e1 mov %rsp,%rcx
13c3: 48 8d 54 24 04 lea 0x4(%rsp),%rdx
13c8: 48 8d 35 8e 18 00 00 lea 0x188e(%rip),%rsi # 2c5d <array.3415+0x21d>
13cf: e8 1c fb ff ff callq ef0 <__isoc99_sscanf@plt>
13d4: 83 f8 02 cmp $0x2,%eax
13d7: 75 0b jne 13e4 <phase_4+0x38>
13d9: 8b 04 24 mov (%rsp),%eax
13dc: 83 e8 02 sub $0x2,%eax
13df: 83 f8 02 cmp $0x2,%eax
13e2: 76 05 jbe 13e9 <phase_4+0x3d>
13e4: e8 ad 05 00 00 callq 1996 <explode_bomb>
13e9: 8b 34 24 mov (%rsp),%esi
13ec: bf 09 00 00 00 mov $0x9,%edi
13f1: e8 7d ff ff ff callq 1373 <func4>
13f6: 39 44 24 04 cmp %eax,0x4(%rsp)
13fa: 74 05 je 1401 <phase_4+0x55>
13fc: e8 95 05 00 00 callq 1996 <explode_bomb>
1401: 48 8b 44 24 08 mov 0x8(%rsp),%rax
1406: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
140d: 00 00
140f: 75 05 jne 1416 <phase_4+0x6a>
1411: 48 83 c4 18 add $0x18,%rsp
1415: c3 retq
1416: e8 35 fa ff ff callq e50 <__stack_chk_fail@plt>
Func4:
0000000000001373 <func4>:
1373: b8 00 00 00 00 mov $0x0,%eax
1378: 85 ff test %edi,%edi
137a: 7e 07 jle 1383 <func4+0x10>
137c: 89 f0 mov %esi,%eax
137e: 83 ff 01 cmp $0x1,%edi
1381: 75 02 jne 1385 <func4+0x12>
1383: f3 c3 repz retq
1385: 41 54 push %r12
1387: 55 push %rbp
1388: 53 push %rbx
1389: 41 89 f4 mov %esi,%r12d
138c: 89 fb mov %edi,%ebx
138e: 8d 7f ff lea -0x1(%rdi),%edi
1391: e8 dd ff ff ff callq 1373 <func4>
1396: 42 8d 2c 20 lea (%rax,%r12,1),%ebp
139a: 8d 7b fe lea -0x2(%rbx),%edi
139d: 44 89 e6 mov %r12d,%esi
13a0: e8 ce ff ff ff callq 1373 <func4>
13a5: 01 e8 add %ebp,%eax
13a7: 5b pop %rbx
13a8: 5d pop %rbp
13a9: 41 5c pop %r12
13ab: c3 retq
phase_5
000000000000141b <phase_5>:
141b: 48 83 ec 18 sub $0x18,%rsp
141f: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
1426: 00 00
1428: 48 89 44 24 08 mov %rax,0x8(%rsp)
142d: 31 c0 xor %eax,%eax
142f: 48 8d 4c 24 04 lea 0x4(%rsp),%rcx
1434: 48 89 e2 mov %rsp,%rdx
1437: 48 8d 35 1f 18 00 00 lea 0x181f(%rip),%rsi # 2c5d <array.3415+0x21d>
143e: e8 ad fa ff ff callq ef0 <__isoc99_sscanf@plt>
1443: 83 f8 01 cmp $0x1,%eax
1446: 7e 5a jle 14a2 <phase_5+0x87>
1448: 8b 04 24 mov (%rsp),%eax
144b: 83 e0 0f and $0xf,%eax
144e: 89 04 24 mov %eax,(%rsp)
1451: 83 f8 0f cmp $0xf,%eax
1454: 74 32 je 1488 <phase_5+0x6d>
1456: b9 00 00 00 00 mov $0x0,%ecx
145b: ba 00 00 00 00 mov $0x0,%edx
1460: 48 8d 35 d9 15 00 00 lea 0x15d9(%rip),%rsi # 2a40 <array.3415>
1467: 83 c2 01 add $0x1,%edx
146a: 48 98 cltq
146c: 8b 04 86 mov (%rsi,%rax,4),%eax
146f: 01 c1 add %eax,%ecx
1471: 83 f8 0f cmp $0xf,%eax
1474: 75 f1 jne 1467 <phase_5+0x4c>
1476: c7 04 24 0f 00 00 00 movl $0xf,(%rsp)
147d: 83 fa 0f cmp $0xf,%edx
1480: 75 06 jne 1488 <phase_5+0x6d>
1482: 39 4c 24 04 cmp %ecx,0x4(%rsp)
1486: 74 05 je 148d <phase_5+0x72>
1488: e8 09 05 00 00 callq 1996 <explode_bomb>
148d: 48 8b 44 24 08 mov 0x8(%rsp),%rax
1492: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
1499: 00 00
149b: 75 0c jne 14a9 <phase_5+0x8e>
149d: 48 83 c4 18 add $0x18,%rsp
14a1: c3 retq
14a2: e8 ef 04 00 00 callq 1996 <explode_bomb>
14a7: eb 9f jmp 1448 <phase_5+0x2d>
14a9: e8 a2 f9 ff ff callq e50 <__stack_chk_fail@plt>
phase_6
00000000000014ae <phase_6>:
14ae: 41 55 push %r13
14b0: 41 54 push %r12
14b2: 55 push %rbp
14b3: 53 push %rbx
14b4: 48 83 ec 68 sub $0x68,%rsp
14b8: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
14bf: 00 00
14c1: 48 89 44 24 58 mov %rax,0x58(%rsp)
14c6: 31 c0 xor %eax,%eax
14c8: 49 89 e4 mov %rsp,%r12
14cb: 4c 89 e6 mov %r12,%rsi
14ce: e8 ff 04 00 00 callq 19d2 <read_six_numbers>
14d3: 41 bd 00 00 00 00 mov $0x0,%r13d
14d9: eb 25 jmp 1500 <phase_6+0x52>
14db: e8 b6 04 00 00 callq 1996 <explode_bomb>
14e0: eb 2d jmp 150f <phase_6+0x61>
14e2: 83 c3 01 add $0x1,%ebx
14e5: 83 fb 05 cmp $0x5,%ebx
14e8: 7f 12 jg 14fc <phase_6+0x4e>
14ea: 48 63 c3 movslq %ebx,%rax
14ed: 8b 04 84 mov (%rsp,%rax,4),%eax
14f0: 39 45 00 cmp %eax,0x0(%rbp)
14f3: 75 ed jne 14e2 <phase_6+0x34>
14f5: e8 9c 04 00 00 callq 1996 <explode_bomb>
14fa: eb e6 jmp 14e2 <phase_6+0x34>
14fc: 49 83 c4 04 add $0x4,%r12
1500: 4c 89 e5 mov %r12,%rbp
1503: 41 8b 04 24 mov (%r12),%eax
1507: 83 e8 01 sub $0x1,%eax
150a: 83 f8 05 cmp $0x5,%eax
150d: 77 cc ja 14db <phase_6+0x2d>
150f: 41 83 c5 01 add $0x1,%r13d
1513: 41 83 fd 06 cmp $0x6,%r13d
1517: 74 35 je 154e <phase_6+0xa0>
1519: 44 89 eb mov %r13d,%ebx
151c: eb cc jmp 14ea <phase_6+0x3c>
151e: 48 8b 52 08 mov 0x8(%rdx),%rdx
1522: 83 c0 01 add $0x1,%eax
1525: 39 c8 cmp %ecx,%eax
1527: 75 f5 jne 151e <phase_6+0x70>
1529: 48 89 54 f4 20 mov %rdx,0x20(%rsp,%rsi,8)
152e: 48 83 c6 01 add $0x1,%rsi
1532: 48 83 fe 06 cmp $0x6,%rsi
1536: 74 1d je 1555 <phase_6+0xa7>
1538: 8b 0c b4 mov (%rsp,%rsi,4),%ecx
153b: b8 01 00 00 00 mov $0x1,%eax
1540: 48 8d 15 e9 2c 20 00 lea 0x202ce9(%rip),%rdx # 204230 <node1>
1547: 83 f9 01 cmp $0x1,%ecx
154a: 7f d2 jg 151e <phase_6+0x70>
154c: eb db jmp 1529 <phase_6+0x7b>
154e: be 00 00 00 00 mov $0x0,%esi
1553: eb e3 jmp 1538 <phase_6+0x8a>
1555: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx
155a: 48 8b 44 24 28 mov 0x28(%rsp),%rax
155f: 48 89 43 08 mov %rax,0x8(%rbx)
1563: 48 8b 54 24 30 mov 0x30(%rsp),%rdx
1568: 48 89 50 08 mov %rdx,0x8(%rax)
156c: 48 8b 44 24 38 mov 0x38(%rsp),%rax
1571: 48 89 42 08 mov %rax,0x8(%rdx)
1575: 48 8b 54 24 40 mov 0x40(%rsp),%rdx
157a: 48 89 50 08 mov %rdx,0x8(%rax)
157e: 48 8b 44 24 48 mov 0x48(%rsp),%rax
1583: 48 89 42 08 mov %rax,0x8(%rdx)
1587: 48 c7 40 08 00 00 00 movq $0x0,0x8(%rax)
158e: 00
158f: bd 05 00 00 00 mov $0x5,%ebp
1594: eb 09 jmp 159f <phase_6+0xf1>
1596: 48 8b 5b 08 mov 0x8(%rbx),%rbx
159a: 83 ed 01 sub $0x1,%ebp
159d: 74 11 je 15b0 <phase_6+0x102>
159f: 48 8b 43 08 mov 0x8(%rbx),%rax
15a3: 8b 00 mov (%rax),%eax
15a5: 39 03 cmp %eax,(%rbx)
15a7: 7e ed jle 1596 <phase_6+0xe8>
15a9: e8 e8 03 00 00 callq 1996 <explode_bomb>
15ae: eb e6 jmp 1596 <phase_6+0xe8>
15b0: 48 8b 44 24 58 mov 0x58(%rsp),%rax
15b5: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
15bc: 00 00
15be: 75 0b jne 15cb <phase_6+0x11d>
15c0: 48 83 c4 68 add $0x68,%rsp
15c4: 5b pop %rbx
15c5: 5d pop %rbp
15c6: 41 5c pop %r12
15c8: 41 5d pop %r13
15ca: c3 retq
15cb: e8 80 f8 ff ff callq e50 <__stack_chk_fail@plt>