CSAPP Bomb Lab
Bomb Lab Procedure
This is an x86-64 bomb for self-study students.
ATTENTION:
if gdb
says its authority not enough,
sudo su
chmod 777 bomb
准备工作
首先使用Linux objdump
工具反汇编保存bomb二进制文件的反汇编代码bomb.asm
1 | objdump -d bomb > bomb.asm |
再开启gdb
调试bomb二进制文件
1 | gdb bomb |
注意
gdb run
可能会提示权限不够,要修改成可读可写可执行,输入命令
1 | sudo su; |
查看代码
C 代码
首先查看.c
代码,由于缺少头文件和其他文件,只能查看bomb
的整体逻辑
这一段起始代码是尝试进行读文件操作,大概意识是拆除炸弹可以把所有的炸弹密钥字符串写入一个文档,然后将路径作为参数传递给bomb
启动执行,如果没有传递参数,那么炸弹密钥通过命令行读取
1 | int main(int argc, char *argv[]) |
下面这一段是初始化炸弹initialize_bomb();
想必是一个初始化函数,然后打印开始提示信息
1 | /* Do all sorts of secret stuff that makes the bomb harder to defuse. */ //66行 |
主体部分,程序分为6个phase,每一个都需要你输入一行字符串,然后对应调用phase_n()函数进行判断是否触发炸弹
1 | /* Hmm... Six phases must be more secure than one phase! */ //72 |
反汇编代码
先定位到main
1 | 0000000000400da0 <main>: |
查看read_line
1 | 000000000040149e <read_line>: |
strings_not_equal
函数
1 | 0000000000401338 <strings_not_equal>: |
第一个炸弹
1 | 0000000000400ee0 <phase_1>: |
结合phase1的代码
1 | ;; 获取输入字符串,rax返回值是字符串地址 |
read_line
函数会将读入字符串地址存放在rdi
和rsi
中,strings_not_equa
l函数会使用edi
和esi
中的值当做两个字符址,并且判断他们是否相等,相等返回0
edi
存放的是callq 400b10 <puts@plt>
要打印的信息的地址,用gdb调试
1 | (gdb) print (char*)(0x4023a8) |
可见打印的是成功拆除的信息。因此,炸弹1的整体逻辑就是
1 | graph LR |
于是,重点在于找到给rsi
赋地址的语句,这个地址存储的就是密钥字符串
1 | 0000000000400ee0 <phase_1>: |
esi
是rsi
的低32位,400ee4给esi
赋值就是给rsi
赋值为密钥字符串地址
gdb
调试
1 | (gdb) print (char*) (0x402400) |
第一个炸弹的密钥就是**“Border relations with Canada have never been better.”**
第二个炸弹
查看phase_2
代码
1 | 0000000000400efc <phase_2>: |
查看read_six_number
代码
内部调用了sscanf
这个函数(功能是从一个字符串中读取一定格式的数据,和 scanf
一样,除了 scanf
是从标准输入流中读取)。参数顺序分别是,待读取内容的字符串(rdi
)、用于格式读取的格式化字符串(rsi
),还有各个变量读取后存放的地址(rdx
)。返回读到的有效数据个数
%rdx
由%rsi
给出,%rsi
又由phrase2
的%rsp
给出,所以phrase2
中的%rsp
地址处存放sscanf
中第1个输入的值。
1 | 000000000040145c <read_six_numbers>: |
-
%rcx
,phrase2
中的%rsp+0x4
处存放第2个值 -
%r8
,phrase2
中的%rsp+0x8
存放第3个值 -
%r9
,phrase2
中的%rsp+0xc
存放第4个值 -
第5个、第6个值所在的地址需要通过压栈传参,参数的入栈顺序是从右至左。可知,
phrase2
中的%rsp+0x10
存放第五个值、phrase2
中的%rsp+0x14
存放第六个值。第六个值先入栈,更加靠近栈底(高地址)。
打印0x4025c3处的值
1 | (gdb) x /4wd 0x4025c3 |
乱七八糟,尝试用字符串打印
1 | (gdb) print (char*)0x4025c3 |
这说明sscanf
读取的确实是6个数字。可以确定,第2个炸弹的密钥是1 2 4 8 16 32
第三个炸弹
查看phase_3
代码
1 | 0000000000400f43 <phase_3>: |
代码开头就调用了sscanf
,所以这个函数前面一定会有一个字符串常量存储需要读取的数据格式,函数的第二个参数用rsi(esi)
存储,400f51:
处代码告诉了esi
的字符串首地址是0x4025cf
,
使用gdb
调试
1 | (gdb) print (char*) 0x4025cf |
确定这个炸弹的输入是两个数字
继续查看代码,发现其cmp,jmp
等跳转指令很多,而且极其的有规律,猜测是个switch语句
1 | 400f60: 83 f8 01 cmp $0x1,%eax ; eax与1比较,应该是返回值sscanf返回读到的2数据个数 |
因此本题答案可不唯一,第一个数字应该无符号小于等于7,
gdb
打印跳转表
1 | (gdb) x /8gx 0x402470 |
因此 假设第一个数字是0,那么跳转到的是case1
,eax=0xcf
,第二个数字得是0xcf=207
第一个数字是1,跳转到?,eax=0x137=311
第四个炸弹
查看phase_4
代码,考察的是函数递归调用
1 | 000000000040100c <phase_4>: |
又调用了sscanf
函数,esi
与phase_3
中地址相同,因此输入格式又是"%d %d"
其调用了一个函数func4
1 | 0000000000400fce <func4>: |
尝试将func4由汇编翻译回C
代码,
1 |
|
1 | > gcc -o test .\bombPhase4.c |
因此,7 0、3 0 、1 0等均可
第五个炸弹
查看phase_5
代码
1 | 0000000000401062 <phase_5>: |
- 要求输入6个字符,然后依次循环这个输入的字符数组
- 每一轮循环取一个字符,然后取这个字符的后四位作为索引,在第二个字符常量
0x4024b0
处取一个字符依次存放到%rsp+0x10+i
处
1 | (gdb) print (char*)0x4024b0 |
3. 将新0x10(%rsp)
处的字符串和0x40245e
处的字符串比较,相同则通过,否则爆炸
1 | (gdb) print (char*)0x40245e |
由于低4位只能表示0-15,因此只能从<array>: maduiersnfotvbyl
取得
f :array[9] 1001=9
,查找ASCII
表低位为9的字符:)9IYiy
l :array[15] 1111
=F, /?O_0
y :array[14] 1110=E
, .>N^n~
e :array[5] 5
,%5EUeu
r :array[6] 6
, &6FVfv
s :array[7] 7
, ,7GWgw
答案多种,如**YONEFw
、y?nevw
**
string_length
代码
1 | 000000000040131b <string_length>: |
结合read_line
,应该是获取rdi
为地址的字符串的长度返回给eax(rax)
第六个炸弹
查看phase_6
代码
1 | 00000000004010f4 <phase_6>: |
用gdb
查看0x6032d0
开始的数据,根据401176
以8字节为单位查看
1 | (gdb) x /6gx 0x6032d0 |
访问一个链表,链表的首地址为 0x6032d0
,针对输入的第 i
个数,按照 a[i]
的值获取链表第 a[i]
个节点,并把节点首地址放入 %rsp+0x20+0x0
~ 到 %rsp+0x20+0x28
刚好是 6 个节点。
地址 | 数据 |
---|---|
0x6032d0 | 0x0000014c |
0x6032d4 | 0x00000001 |
0x6032d8 | 0x006032e0 |
0x6032dc | 0x00000000 |
0x6032e0 | 0x000000a8 |
0x6032e4 | 0x00000002 |
0x6032e8 | 0x006032f0 |
0x6032ec | 0x00000000 |
0x6032f0 | 0x0000039c |
1 | (gdb) x /wx 0x6032d0 |
一共6个结点,第一个结点地址数据存在%rsp+0x20+0x0
处,这个地址数据指向的结点data应该最大,由于比较cmp
的是32位数据,只需要注意地址指向数据的低32位。
最大的数据是0x0000039c
,是第3个结点, 此时的num1=3,
第2大的数据是0x000002b3
,第4个结点 num2=4
第3大的数据是0x000001dd
,第5个结点 num3=5
第4大的数据是0x000001bb
,第6个结点 num4=6
第5大的数据是0x0000014c
,第1个结点 num5=1
第6大的数据是0x000000a8
,第2个结点 num3=2
num=[3,4,5,6,1,2]
但是注意这是把输入的num
进行了num=7-num
所以输入是
num=[4,3,2,1,6,5]
最终答案是**4 3 2 1 6 5 **