CSAPP: Bomb Lab
熟悉GDB
Preparation
建议在实验大致看一下lab相关的pdf。使用man
手册来查看库函数,callq … <_exit@plt>
类型为C的库函数。注意商用的architecture是以字节为单位编址寻址。
参考CS61C所了解到的Tool, 做足了准备工作。安装CGDB
, 相比较于GDB
的tui
在调试lab时显示不会出现乱码的情况。从ubuntu
中apt-get
下载到的版本较低,一些功能不适用。参考CGDB官网下载最新版本的CGDB
, 简单浏览了一下CGDB官方手册, 以及对应CGDB
在~/.cgdb/cgdbrc
文件中做了如下的配置足以满足调试需求:
1 | :set disasm |
另外gdb相关内容再推荐这两本书籍Debugs Hacks
和Debugging with GDB
。
需要用chmod
命令修改一下bomb
二进制文件的可执行权限
接下来可以愉快地开始bomb lab
了~
运行cgdb
且在对应phase打上断点后可以用一个放入输入信息的文本文件标准输入重定向到传递给主函数的参数中,这样就可以避免卡死在读取输入的systemcall上。更多关于重定向
1 | (gdb)run < in |
Phase_1
1 | 0000000000400ee0 <phase_1>: |
分析一下strings_not_equal
,判断两个字符串是否相等。
1 | 0000000000401338 <strings_not_equal>: |
观察phase_1
函数,需要进入函数string_not_equal
,并通过该函数的返回结果来决定是否引爆炸弹。进入该函数后,可以发现在函数中比较的是rdi
和rsi
两个字符串参数。rdi
是标准输入中输入的字符串。string_length
的返回值保存在寄存器eax
中为两字符串的长度。长度不相等即表示字符串肯定不相等,则返回寄存器edx
中的1。初步可以判断phase_1
要求比较的两个字符串要相等才能defuse
。再仔细观察phase_1
函数中test %eax, %eax
测试寄存器eax
非0则跳转到bomb
。因此要想不爆炸必须得保证存在于($rdi)
, ($rsi)
中的两个字符串相等,即strings_not_equal
的返回值为0。
用x/s
查看对应memory
中地址单元的的字符串内容, 使标准输入参数rdi
等于下述字符串即可defuse
。
1 | (gdb) x/s 0x402400 |
经分析答案为:
1 | Border relations with Canada have never been better. |
Phase_2
x86
ISA规定,当参数超过6个的时候就会使用栈来保存参数。print (char*) 0x4025c3
打印由地址0x4025c3起始的字符串,显示的结果可以发现字符串为"%d %d %d %d %d %d"
格式串。
1 | 0000000000400efc <phase_2>: |
read_six_numbers
中会调用C库函数sscanf
,man
手册中返回值的描述,返回输入数字匹配的个数。
On success, these functions return the number of input items success‐
fully matched and assigned; this can be fewer than provided for, or
even zero, in the event of an early matching failure.
注意x86
栈帧由低地址到高地址,先是返回地址,再到局部变量,再到save register。第1个参数在栈帧低地址 立即数为32bit
, 按字节变址寻址, 函数参数为caller save
。
1 | 000000000040145c <read_six_numbers>: |
查看0x4025c3
地址单元中的内容为格式串
1 | (gdb) x/s 0x4025c3 |
经分析答案为:
1 | 1 2 4 8 16 32 |
Phase_3
1 | 0000000000400f43 <phase_3>: |
jmp *Operand
为Indirect Jump, 跳转的target存放在寄存器中或者内存中。查看内存中存放的target:
再结合上面的汇编代码,可以得出8个答案:
1 | 0 207 |
Phase_4
1 | 000000000040100c <phase_4>: |
1 | 0000000000400fce <func4>: |
经过调试分析, 可得出四个答案, 除此之外可能还有其他答案就不另行分析了:
1 | 7 0 |
Phase_5
ASCII转化为十六进制时(因为所取数字的下标大于10)需要查看ASCII编码表。关于stack canary的解释可以参考文章和文章使用rax寄存器作为间接传递的原因是因为x86中不存在内存到内存的mov指令。
1 | 0000000000401062 <phase_5>: |
1 | (gdb) x/s 0x4024b0 |
1 | (gdb) x/s 0x40245e |
输入正确结果后内存中存放的内容:
1 | (gdb) x/s $rsp+0x10 |
最后的答案为:
1 | 9?>567 |
Phase_6
1 | 00000000004010f4 <phase_6>: |
查看0x6032d0处地址内容(gdb中的字默认为4字节, 而x86的字默认为2字节), 可以猜测出第一个8字节中高4字节为node号,低4字节为值域,第二个8字节为next指针域,下述示例传递的参数为1 2 3 4 5 6
:
1 | (gdb) x/12xg 0x6032d0 |
6个node的值, 再将其由大到小排序:
1 | node1: 0x14c |
由7-nodeNumber得到答案为:
1 | 4 3 2 1 6 5 |