CS:APP Bomb Lab记录
前置知识
objdump
objdump -d可将二进制文件转换为汇编代码(反汇编)
GDB
类似于图形界面的debugger,通过命令行执行。这里针对汇编进行debug。使用tui进行查看。
基本流程
1 |
|
查看修改信息
1 |
|
程序运行
寄存器设置
%r/eax 为返回值 %r/ebx 被调用者保存 %r/edi r/esi r/edx r/ecx 4对应第四个-第一个参数 %r/ebp 栈尾 %r/esp 栈顶部
调用函数
1 |
|
1 |
|
汇编指令
主要基于x86指令集架构,也可以不分析,小数据可以借助gdb调试判断。
1 |
|
数据结构
在处理输入时,若不做限制,可能导致栈溢出,无法return的情况。因为接受输入的栈指针对应栈帧的大小有限。因此为了防止栈溢出覆写数据,跳转到攻击代码的情况,会出现%fs:40,采用段寻址的只读代码,又名为金丝雀值,使用系统随机生成,与栈相应位置进行对比,判断是否相同。
具体过程
Quick Start
进入网站,下载write_up.pdf,并在可运行环境下下载文件。
1 |
|
从bomb.c可知需要输入六句话。只能通过反汇编得到要输入的文件。由write_up的hints可知需要使用到gdb与objdump。有两个方向,通过objdump -d直接分析汇编代码。
1 |
|
bomb1
先分析汇编
1 |
|
可以得到与rdi比较的16进制表示为0x402400,因此需要输入read_line(string) = 0x402400。先通过gdb观察,输入123,输出的0x603780,而1的ascii码为49 0x31,似乎没有明显联系。
1 |
|
输入1231和abc查看所有的寄存器的区别,发现只有rcx、rbp有不同,似乎是长度,莫非入栈了? 看来需要阅读skip的源码。
1 |
|
于是问题转换到fgets的数据存在了哪里。原来0x603780指的不是数据,而是内存的地址...
0x603780 0a|33|32|31 按字节编址 'a' 31 'b' 32 'c' 33 a '' 0 '0',从右往左显示...!体现了小端序,这里为了显示数字转置了。
0x603781 00|0a|33|32 0x603782 00|00|0a|33
所以只需要在比较之前设个断点,看0x402400对应的内存数组就可以了。 x/60s 0x402400。得到结果。Border relations with Canada have never been better.
bomb2
从源码可以看出,在read_six_number中进行判断。
1 |
|
1 |
|
较第一问轻松了不少,熟悉了指令集的分析方式。
bomb3
照例先分析汇编找方向,从scanf入手
1 |
|
1 |
|
bomb4
同理,scanf 入手
1 |
|
后分析汇编
1 |
|
化为C语言分析,reg容易混乱
1 |
|
bomb5
1 |
|
1 |
|
bomb6
1 |
|
1 |
|
收获
当涉及复杂递归时,可以转换为C语言后优化代码,再通过枚举的方式求出符合条件的解(第四题)。
当分支过多时,可以先将汇编代码分段,尝试一个简单的解,借助gdb,跑一下流程,枚举两三次特殊情况,渐渐就有了基本的思路(第六题)。且最好记忆各个参数的含义和上次更新的值,否则要不断回溯,时间较久。
借助gdb可以节省分析一些没必要分析的函数的时间,类似于状态机的思想,只要知道返回后的寄存器状态,就大概函数的作用(很多负责输入输出的函数)。
一般寄存器的值可能代表地址也可能代表值。另外,机器是64位小端序,注意可能有效位需要借助与mask做与操作取得,可能没有意义(第五题)。
借助文档和教材了解x86指令集。注意由于bomb实验的普及性,很多由两条指令集组成的跳转指令大概率可以直接搜到,而实际状况往往需要自己分析各个寄存器的状态。当分支足够多的时候,若每个分支都要用3的思想,则需要2*分支数的时间,较为低效。
借助gdb显示格式,如字符串、char、16bit字符串,能够更快明白数据/地址的意义;tui模式更方便debug;多开终端,gdb与源码同时分析;直接打出大段地址,便于分析。
一开始分不清地址还是数据,返回值和参数,不断对一些不相关的readline函数或标准库函数进行分析耗时较长。后面逐渐驾轻就熟,花费的时间则合理了不少。
实验涉及的数值变换较少,一般都只涉及8bit以内的正数,实际大大简化了问题,把rdi、edi甚至di都可以看做是一个数,也更容易理解(虽然汇编代码都使用了很多考虑到有符号数,符号拓展的方式等的函数,实际由于实验的数据较弱,不需要考虑边界情况)。通过gdb不断追踪值也可以确认这种判断。
每隔一段时间容易混淆16进制表示和10进制表示,尤其在计算地址距离时。
汇编指令容易混淆mov对应的地址与值,a, b -> b = a,寄存器同理。区分立即数,reg直接,reg间接的写法。mov reg_1, reg_2 -> reg_1 = reg_2; mov reg_1, (reg_2) *reg_1 = **reg_2, 即把reg_1的值存到reg_2上。容易把reg的值当成了地址。一般省略,直接写成reg_1 = reg_2即可。而lea只做值的运算,mv采用取地址,两者也容易混淆。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!