准备工作
深入理解计算机系统(CSAPP)的实验三是Attack Lab。实验分为两个部分,分别对应一种攻击方式:代码注入攻击(Code Injection Attacks)和ROP攻击()。我们的任务是完成五个这两类攻击。
实验提供了五个文件,其作用如下:
- ctarget:用来做代码注入攻击的程序
- rtarget: 用来做 ROP 攻击的程序
- cookie.txt: 一个 8 位的 16 进制代码,用来作为攻击的标识符
- farm.c: 用来找寻 gadget 的源文件
- hex2raw: 用来生成攻击字符串的程序
Part I: Code Injection Attacks
这一部分有三个phase,我们用特定的字符串攻击ctarget程序。
Level 1
Level 1不需要我们注入新的代码,但是需要输入的字符串使程序执行一个已有的代码段(不是正常执行的代码)。ctarget中的test调用一个getbuf函数,用于从控制台输入字符串.
test函数C代码如下:
|
|
getbuf的C代码如下:
|
|
当getbuf执行完毕时,程序会返回test函数中,但是我们需要改变这个行为,使getbuf函数执行完毕后返回touch1函数:
|
|
实验提供了一些建议:
- 本关所需要的所有信息都可以在 ctarget 的汇编代码中找到
- 具体要做的是把 touch1 的开始地址放到 ret 指令的返回地址中
- 注意字节的顺序
- 可以用 gdb 在 getbuf 的最后几条指令设置断点,来看程序有没有完成所需的功能
- 具体 buf 在栈帧中的位置是由 BUFFER_SIZE 决定的,需要检查汇编代码来查看具体位置
从上述建议中我们知道,先将ctarget反汇编再查看具体代码就能解决完成这个攻击,具体步骤如下:
1) 查看getbuf的反汇编代码:
|
|
分析getbuf的C代码和汇编代码可以看出BUFFER_SIZE是rsp移动的位置-0x28,也就是40位。
2) 查看touch1的反汇编代码:
|
|
touch1的入口地址是0x4017c0。
3) 设置导致溢出的字符串:
知道了BUFFER_SIZE的大小和touch1的入口地址,我们可以制造缓冲区溢出攻击,也就是输入一个大小大于40位的字符串,将buf填满的同时,把touch1函数的入口地址覆盖getbuf原有的返回地址,这样getbuf返回时就会执行touch1函数。于是设置字符串如下:
|
|
保存到文件phase1.txt中
4) 将字符串转化为字节码:
使用hex2raw工具将phase1.txt转化为字节码,保存到文件phase1_input.txt中:
|
|
5) 再执行ctarget,查看结果:
使用如下命令测试,同时查看结果:
|
|
Level 1通过。
Level 2
Level 2需要插入一小段代码。ctarget中有一小段程序touch1,其C代码如下:
|
|
我们的任务同样是让ctarget程序执行touch2而不是返回test,但是touch2函数需要传入一个参数,且实验要求这个参数是是我们的Cookie,上一个phase已经知道Cookie:0x59b997fa。实验也提供了一些建议:
- 使用ret指令调用touch2函数
- touch2的参数要存在rdi寄存器中
- 插入的代码应该设置寄存器存着Cookie,然后再返回touch2
- 不用jmp和call指令
- 使用gcc和objdump生成要插入代码的字节码格式
具体步骤如下:
1) 查看touch2的入口地址:
|
|
touch2的入口地址位0x4017ec
2) 需要注入的代码:
|
|
3) 生成机器码
|
|
查看phase2.d如下所示:
|
|
可以看出这一段程序的机器码为:
|
|
4) 生成字节码并注入程序
注入过程可以类似Level 1,但是这回不是返回touch2的入口地址,而是需要返回我们要注入的这段程序。其实把这段程序当作getbuf输入的字符,输入进getbuf函数的栈帧中,再使用缓冲区溢出的方法,将ret的返回地址设置为缓冲区的入口地址即可:
|
|
由上可知:缓冲区地址为0x5561dc78,按照Level 1的做法,把这个地址放在第41到44个字节的位置(小端模式),生成输入字符如下:
|
|
存入文件phase2.txt中,再使用hex2ram生成字节码:
|
|
5) 再执行ctarget,查看结果:
使用如下命令测试,同时查看结果:
|
|
Level 2通过。
Level 3
Level 3类似与Level 2,也是需要插入一段代码,是程序运行touch3,而不是返回test。但是这一关多了一个函数hexmatch,两段C程序代码如下:
|
|
同时,不像Level 2,这一关将Cookie的字符串地址作为参数传入touch3函数。首先将Cookie转化为ASCII码形式,如下:
|
|
使用Level 2的方式进入touch3,查看hexmatch函数改动的缓冲区数据,将上述ASCII码插入正确的缓冲区即可:
phase3.s:
|
|
phase3.txt:
|
|
|
|
在0x0000000000401916 <+28>:处设置断点,continue后查看缓冲区数据:
|
|
观察发现缓冲区被覆盖了,所以不能把Cookie的ASCII码放在缓冲区开头,可以放在0x5561dca8处的0x00401f24之后,有足够的空间存放,最终phase3.txt如下:
|
|
使用如下命令测试,同时查看结果:
|
|
Level 3通过。
Part II: Return-Oriented Programming
这一部分攻击rtarget程序,但是这个程序使用了两种技术防止代码注入攻击:
- 每次栈的位置是随机的,于是我们没有办法确定需要跳转的地址
- 即使我们能够找到规律注入代码,但是栈是不可执行的,一旦执行,则会遇到段错误
所以只能利用已有的可执行的代码,来完成我们的操作,称为retrun-oriented programming(ROP)
,策略就是找到现存代码中的若干条指令,这些指令后面跟着指令ret,每次return相当于从一个gadget跳转到另一个gadget中,然后通过这样不断跳转来完成我们想要的操作。
Level 1
这一关要求我们重复上一部分Level 2的攻击,但是无法对rtarget进行代码注入攻击,我们只能使用ROP攻击:利用farm.c中的程序的gadget,构造我们需要的指令,在rtarget中执行,farm段的反汇编代码在下面这一部分:
|
|
使用如下几个gadget构造ROP程序:
|
|
使用如下地址:
|
|
构造成的phase4.txt如下:
|
|
使用如下命令测试,同时查看结果:
|
|
Level 1通过。
Level 2
这一关要求我们重复上一部分Level 3的攻击,使用ROP攻击的形式。
使用如下几个gadget构造ROP程序:
|
|
使用如下地址:
|
|
构造成的phase5.txt如下:
|
|
使用如下命令测试,同时查看结果:
|
|
Level 2通过。