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
2
mov    %fs:0x28,%rax
mov %rax,0x18(%rsp)

这个应该是金丝雀值,使用段寻址把金丝雀值放到rsp+18位置,然后用xor校验,对解题没啥大的影响。

总的来说逻辑是要输入一个字符串,字符串长度必须是六个字符,然后和内存中0x40245e位置的字符串比较,相同的话就能通过。简简单单x /s 0x40245e发现这个地方的字符串是flyers。于是我兴冲冲填上flyers,发现炸弹还是炸了。

怪!

用gdb仔细调试了一波发现,这段代码会把输入的字符串加密,比如我输入的 flyers,经过加密就变成了 rvfedu 这串怪字符,这该咋办。我试着输入其他的字符串,然后看看加密后的结果。最终我发现字符串的加密很可能是一一映射的,于是我想了一个笨方法,只要找到能够映射到 flyers 的字符不就好了吗?暴力破解一通,找到了如下映射。

1
2
3
4
5
6
y : f
o : l
n : y
e : e
f : r
g : s

输入 yonefg 运行,测试通过!

这题到这好像完了,又好像没完,这个加密的逻辑到底是啥,等我做完phase6先

内存结构

前面做题也差不多知道了,栈底的地址是0x7fffffffffff,转化成二进制也就是47个1。x86-64机器限制内存只用64位中的47位,即使这样也有大约128TB容量。(当然这是虚拟内存,实际内存肯定没有那么大)

  • 栈(Stack):从0x7fffffffffff向下增长,最大8MB。

  • 堆(Heap):低至0x600000附近,高至栈附近,都是堆的可能空间,堆既可以从0x600000附近向上增长,也可以从栈附近向下增长。

  • Data:存放静态变量

  • Text:存放编译后的机器代码,起点是0x400000

Phase6

这提和Phase1一样,都调用了“读六个数”函数

  1. 共要有等于6个参数
  2. 每个参数在1~6之间
  3. 参数间两两不能相同
  4. 会把参数顺序反过来

在看内存的时候,发现有一块位置标记为“node1”,而且这个node1的大小是两个整数的长度,很奇怪。不过接着往下看,发现像node1一样的内存位置一共有6个,而且每个node的后8位看起来像是地址。。。写出来是这样式的:

1
2
3
4
5
6
7
节点			数据区			地址位
node1: 0x14c
node2: 0xa8
node3: 0x39c
node4: 0x2b3
node5: 0x1dd
node6: 0x1bb

node。。。地址。。。这玩意不会是链表吧!

果然这玩意还真是链表,输入的六个数字,规定了这6个节点的链接顺序,妙。而为了解决问题,这六个节点的前八个字节,也就是数据区必须以从大到小的顺序排列,那么就是3-4-5-6-1-2的顺序。填入3-4-5-6-1-2,Boom!炸了。经过第五题的洗礼我突然意识到这个逼题可能也有加密,尝试了多种加密方法后,发现他的加密逻辑是7-输入的数字,那么答案就是5-6-1-2-3-4,测试通过!

什么?phase5的加密逻辑?你说什么我听不懂啊~XD