CSAPP-BombLab
CSAPP-BombLab:拆包战士
最近在做csapp的实验,刚做完datalab(还差浮点数部分的三道)感觉自己智商不够用。bomblab更是震撼我妈一百年,先不说要看反汇编代码,光是调试工具什么
gdb
我都不会用。害,当然是要从头学习了
BombLab题目是说有 one 个 Evil 人 create 了一个 bomb,这个 bomb 总共有6道 phase,要work out完所有的六道 phase 才能 defuse 炸弹。
Phase1
看汇编代码上来就把0x402400
塞到了%esi
里,显然这是一个十六进制数,是内存地址的可能性非常大。而反汇编代码程序区的最高地址位是0x4022ac
,那么根据C语言内存结构来看:
这个地址很可能是常量,在gdb中使用x /s 0x402400
查看这个地址的常量。
第一个Phase的答案就是这行字符串
Border relations with Canada have never benn better.
gdb指令x表示查看内存内容,/s表示以字符串形式查看
Phase2
gdb的另一个指令“layout regs”可以显示寄存器中的值
从这部分开始,不会用gdb就真的做不出来了(我们gdb真是绝绝子啊
首先注意到phase_2
的函数调用了一个名叫read_six_numbers
的函数,顾名思义是读入了6个整数。于是测试一下,在第二行输入1 2 3 4 5 6
测试,并且用gdb -layout regs边看寄存器地址边调试,发现输入的6个数据被存储在了%rsp
附近。(具体为什么存在栈里而不是寄存器里,可能就要看read_line了,我不想看)
然后phase_2
的代码里面有一段循环,大概意思是%rbx
每次+4,直到%rbp=%rbx
时候跳出循环,循环内容是%eax
值每次*2并与栈中数字比较。0x400f0a
这一行可以看出第一个值是1,因此答案是1 2 4 8 16 32
别看写的少,做题好难哦
思考:以这一题里的例子,for循环计数的一种实现方法可以是用 rbx 和 rbp 两个寄存器来实现。
栈帧
在程序运行的每个阶段,只需要栈中一部分的数据(一般就是最下面那一段)。栈帧用来记录调用未返回的位置,同时也能标记正在运行的过程栈的边界。%rbp
被称为基指针(Optional)

为什么%rbp是optional的?
对于一般的程序来说,编译器在编译时能够知道被调用的过程所需要的确切的栈的大小,比如18个比特,然后在栈释放的时候只需要释放同样的数值即可。此时无需**%rbp**。
但是有些时候编译器无法提前预知栈的大小,比如可变数组或内存缓冲区,此时必须要用到**%rbp**
lea?好怪哦
1 | leaq 8(%rsp), %rdi |
按理说加括号代表取这个地址的值吧,那应该翻译成 “ 把内存中%rsp+8
位置的值赋给%rdi
” ;但是leaq怪得很,他不读内存,这句的意思是 “ 把%rsp
的值+8赋给%rdi
”
这样的话LEA与ADD有啥子区别吗?
LEA由处理寻址的单元之一(在流水线的早期发生),而ADD则进入ALU(算术/逻辑单元) ,以及后期发生。
地址计算发生在流水线的前期,没有必要去使用成本更高的ADD指令。况且LEA指令除了做加法,也能简便地表示乘法与位移。
Phase3
做着做着感觉渐入佳境了,上来看到0x4025cf,二话不说gdb: x /s 0x4025cf
,发现这个地方是两个整数占位符,那这题可能就是要输入两个整数咯。
然后看到要用到rsp,于是用gdb
缓缓打出一个break *0x400f6a
设置断点,再拿layout regs
做一个隐藏甜品778,康康寄存器是什么东西。发现%rsp+8
的位置其实是我输入的第一个数据,于是乎第一个数据需要<7,这个条件就出来了,顺手查出第二个数据存储在%rsp+12
。我们gdb真的是绝绝子呀,好用到跺jiojio!
然后一顿操作答案就出来了:1 311
gdb x用法
用gdb查看内存
格式: x /nfu
说明
x 是 examine 的缩写n表示要显示的内存单元的个数
f表示显示方式, 可取如下值
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
i 指令地址格式
c 按字符格式显示变量。
f 按浮点数格式显示变量。u表示一个地址单元的长度
b表示单字节,
h表示双字节,
w表示四字节,
g表示八子节
Phase4
Halfway there!
点开phase_4的代码,一眼看到熟悉的
1 | lea 0xc(%rsp),&rcx |
1 | lea 0x8(%rsp),%rdx |
肯定两个参数是存储在这个地方的(能不能有点新意啊好简单哦
然后那个看不太明白的函数400bf0 <__isoc99_sscanf@plt>
,根据推测应该是将输入的参数个数赋值给%eax
,也就是说这题依然是需要两个参数。
正当我轻轻松松看汇编代码的时候,眼看phase_4就要被破解了,突然它一个转身拐进了另一个函数
仔细观摩了代码之后发现除非x = 7
,否则会陷入一圈又一圈的循环,跳出循环后,需要y = 0
综上,第四题答案是7 0
总感觉。。。大的要来力!
Phase5
看到了一个没见过的东西:
1 | mov %fs:0x28,%rax |
这个应该是金丝雀值
,使用段寻址把金丝雀值放到rsp+18
位置,然后用xor校验,对解题没啥大的影响。
总的来说逻辑是要输入一个字符串,字符串长度必须是六个字符,然后和内存中0x40245e
位置的字符串比较,相同的话就能通过。简简单单x /s 0x40245e
发现这个地方的字符串是flyers
。于是我兴冲冲填上flyers,发现炸弹还是炸了。
怪!
用gdb仔细调试了一波发现,这段代码会把输入的字符串加密,比如我输入的 flyers,经过加密就变成了 rvfedu 这串怪字符,这该咋办。我试着输入其他的字符串,然后看看加密后的结果。最终我发现字符串的加密很可能是一一映射的,于是我想了一个笨方法,只要找到能够映射到 flyers 的字符不就好了吗?暴力破解一通,找到了如下映射。
1 | y : f |
输入 yonefg 运行,测试通过!
这题到这好像完了,又好像没完,这个加密的逻辑到底是啥,等我做完phase6先
内存结构
前面做题也差不多知道了,栈底的地址是0x7fffffffffff
,转化成二进制也就是47个1。x86-64机器限制内存只用64位中的47位,即使这样也有大约128TB容量。(当然这是虚拟内存,实际内存肯定没有那么大)
栈(Stack):从
0x7fffffffffff
向下增长,最大8MB。堆(Heap):低至
0x600000
附近,高至栈附近,都是堆的可能空间,堆既可以从0x600000
附近向上增长,也可以从栈附近向下增长。Data:存放静态变量
Text:存放编译后的机器代码,起点是
0x400000
Phase6
这提和Phase1一样,都调用了“读六个数”函数
- 共要有等于6个参数
- 每个参数在1~6之间
- 参数间两两不能相同
- 会把参数顺序反过来
在看内存的时候,发现有一块位置标记为“node1”,而且这个node1的大小是两个整数的长度,很奇怪。不过接着往下看,发现像node1一样的内存位置一共有6个,而且每个node的后8位看起来像是地址。。。写出来是这样式的:
1 | 节点 数据区 地址位 |
node。。。地址。。。这玩意不会是链表吧!
果然这玩意还真是链表,输入的六个数字,规定了这6个节点的链接顺序,妙。而为了解决问题,这六个节点的前八个字节,也就是数据区必须以从大到小的顺序排列,那么就是3-4-5-6-1-2的顺序。填入3-4-5-6-1-2,Boom!炸了。经过第五题的洗礼我突然意识到这个逼题可能也有加密,尝试了多种加密方法后,发现他的加密逻辑是7-输入的数字
,那么答案就是5-6-1-2-3-4,测试通过!