Attack Lab记录

by Gu Wei
2021年10月

0. 准备

我们要对ctarget和rtarget进行一共五次的攻击,让其中的test()函数不再正常返回,这里就是不再输出"No exploit. Getbuf returned 0x1\n"了,test()函数原型如下:

如何做到这一点,突破口在其中的getbuf()函数:

而getbuf()函数的关键在于:get()函数是不检查缓存区大小的。比如:我向a[5]读入5个数据,最后一个数据就产生了缓存区溢出。于是就有了两种攻击的方法:

由于1)栈随机化使得程序每次运行时栈的地址都不相同,从而我们无法得知我们注入的攻击代码的地址;2)标记内存中的栈段为不可执行,从而注入在栈中的代码无法被程序执行。所以代码注入攻击有其局限性。

于是,ctarget要求我们利用代码注入攻击进入touch1、2、3这三个函数。而rtarget虽然也要进入touch2和touch3,但是由于防护措施我们需要采用ROP攻击。


 

1. 代码注入攻击

大体思路是:通过缓存区溢出修改ret的地址,使程序运行我们在栈帧中写的攻击代码。

1.1 Level 1

level1只要求我们进入touch1函数,不需要代码注入,touch1()源码给出如下(虽然并无用)

查看getbuf和touch1的汇编有:

getbuf函数分配了40字节的栈帧,那我们只要修改ret的地址就可以完成攻击。ret的地址为touch1的地址,也就是0x4017c0。根据handout的提示,创建txt:

再利用handout给出的hex2raw程序,将文本文件转换为二进制文件:

然后重定向输入到ctarget即可:

注意要输入-q参数,让电脑不去连接服务器(我们也没有服务器)

1.2 Level 2

先记录一下如何注入代码。先是手写汇编example.s,然后gcc -c example.s生成example.o,再objdump -d example.o > example.d,最后整合一下即可。可以看Level3,写的比较详细。

level 2原来寻思着不难,没想到bug出了一些。对于这些bug我也只能做一点猜测。

touch2()源码和汇编给出如下:

可见在前去touch2函数的地址前,我们需要修改%rdi参数使其等于0x59b997fa(自学材料统一的cookie)。handout建议我们不要用jmp或者call而是用ret,那就姑且听handout的。首先在getbuf函数的0x4017ac处打断点,查看%rsp的值为0x5561dc78。接下来分享我尝试过的三种会产生segmentation fault的方法(然而这些答案在网上博主那可以PASS):

1)法一(fail)

这个方法直接!执行顺序7->1->2->8行,先执行我们的注入代码,然后再ret到touch2的地址。然而我的输出为:

可见我们成功进入了touch2并传入了正确的参数,但是产生了segmentation fault。是破坏了前面的栈帧?但是我又不返回调用者的栈帧了呀。我单步调试感觉是在call server的过程中出现了问题?是%rsp变量比正常高了八字节所以出了问题吗?

2)法二(fail)

执行顺序:7->1->2->3->6,但是产生同样的segmentation fault:

这里是%rsp比正常低了八字节。莫非问题出在这?

2)修改后的法二(correct)

针对%rsp变量我写了这样的汇编准备注入:

这里我调整了%rsp使其与正常调用返回一致,均为$0x5561dca0,而之前两个方法分别是0x5561dc98和0x5561dca8。故有:

然后就:

很有意思的是,我发现%rsp为0x5561dca0、0x5561dcb0、0x5561dcc0、0x5561dcd0都可以PASS,然而0x5561dc98、0x5561dca8、0x5561dcb8、0x5561dcc8都会FAIL。不是很懂呢~

3)法三(best)

法三利用了push来漂亮地避免了%rsp在返回时不为0x5561dca0的情况。

上面这个答案可以成功PASS。这个做法巧妙保证了%rsp值与正常返回的值相同。

然而有个傻瓜把pushq $0x4017ec写成了pushq 0x4017ec,结果转换为的二进制是ff 34 25 ec 17 40 00,而不是68 ec 17 40 00。这个答案会直接segmentation fault。他还去在Intel用户手册的Vol. 2B 4-511中,发现push立即数的指令应当为0x68,而0xFF应当对应ModRM寻址模式,浪费了一个下午。。。不要问我怎么知道的!

1.3 Level 3

touch3()和相关的hexmatch()给出,为了节省篇幅没有给出汇编:

这里要给touch3传入一个字符串的指针,然后调用hexmatch函数比较这个字符串与cookie是否相等。由于hexmatch的栈帧是覆盖getbuf栈帧的,所以在存放这个字符串的时候不能选择在getbuf的栈帧中。于是我们可以选择将字符串存在getbuf的父函数test之中。栈结构如下(excel真香)

简单地理一下思路:

  1. getbuf函数ret时,%rsp为0x5561dca0,%rip指向%rsp地址的内存0x5561dc78。ret完后%rsp为0x5561dca8。
  2. 开始执行0x5561dc78处的代码:%rdi为cookie的指针;0x4018fa压入0x5561dca0地址,%rsp同时也为0x5561dca0
  3. ret后%rip指向%rsp地址的内存0x4018fa,%rsp变成0x5561dca8,进入touch3

还是比较简单的。下面开始注入代码:

gcc编译生成CI-L3.o:

objdump反编译:

查看CI-L3.d文件:

查ASCII表,把0x59b997fa变成ASCII码有:35 39 62 39 39 37 66 61 00。注意后面加了00,是字符串的'\0'含义。

整合有:

转换为二进制文件:

ATTACK!:

丝滑!!!


 

2. ROP攻击

大体思路为:通过栈溢出修改ret的地址,将其指向原程序中gadget 1的代码,gadget1最后要有c3也就是ret,这样就可以去执行gadget2的代码了,依次类推完成攻击。

2.1 Level 2

这里就直接记录答案了。注意到在两个指令之间可以有nop,也就是0x90,nop(no operation)只有把PC增加一的功能。

注释栈帧名
0x4017ectouch2函数地址test
0x4019a2 or 0x4019c5gadget2的地址。movq %rax, %rdi + ret 48 89 c7 + c3 
0x59b997facookie的值 
0x4019cc or 0x4019abgadget1的地址。popq %rax + ret 58 + c3 
00...00填满getbufgetbuf

于是乎就出来答案了:

2.2 Level 3

handout说,这次的attack只算一个credit,超出了课程的预期。于是我也就学习一下网上的做法,记录下来罢了。(就是懒吗)

栈帧如下:

八个gadget分别是:

值得注意的是,在gadget5中传入的是89 d1 08 db c0,我们虽然只需要89 d1作为movl %edx, %ecx,以及c0作为ret,但是中间的08 db编码是orb %bl, %bl,对寄存器没有任何影响。

于是答案有:


 

3. 写在后面

本人于2021/10/19完成了Attack Lab,耗时三天。不得不说Attack Lab相比Bomb Lab体量就小多了,但是还是颇为不错!

个人主要学习或强化了以下知识:

还有一点疑惑:

关于栈随机地址,我通过观察rsp指针,发现ctarget的栈地址是固定的,而rtarget的栈地址是随机的。而ctarget和rtarget的函数们的地址都是固定的。之前有点小模糊。

预计接下来: