Attack Lab
前言
在这个lab中,PA1、2、3是代码注入(code injection)攻击,PA4、5是回归导向(return oriented programming)攻击。
- Code Injection:将恶意代码注入到一个应用程序中的攻击。然后应用程序解释或执行该代码,影响应用程序的性能和功能。
- Return Oriented Programming:通过栈缓冲器溢出等方式控制堆栈调用以劫持程序控制流并执行针对性的机器语言指令序列。
PA 1
|
|
PA 1要求在执行test
函数时执行touch1
函数。正常情况下调用test
会直接返回:
|
|
查看getbuf
的反汇编:
|
|
可以看到,理想情况下,getbuf会开辟40个字节的栈空间,输入的字符串填入新开辟的栈空间中,示意图如下(该函数的设置比较简单,既没有帧指针,也没有金丝雀值):
为了验证这点,我们在add $0x28,%rsp
之前查看栈空间的使用情况:
可以看到,被调用者所开辟的40 bytes所有的栈空间均被输入的40个字符串(最后一个是结束符’\0’,所以实际上输入了39个字符)所占据;紧随其后的是一串数字0x401976
恰好对应汇编语句mov %eax,%edx
,也就是调用<getbuf>
后返回的下一条语句。
因此,此处需要额外输入字符串,以改写0x5561dca0
处的值,使其值为touch1
函数开始的地址。注意此处数据的存储格式为小端(little endian),即低位放在较小的地址处。在地址注入时,我们需要先输入低位的字节,首先写出16进制形式:
|
|
使用以下语句进行转换:
|
|
最终结果如下:
PA 2
PA 2在PA 1的基础上新增了传入函数参数的要求。首先查看touch2
的汇编:
|
|
对照原始C代码:
|
|
函数的第一个参数存储在$edi
中,而且需要与cookie的值相同,为0x59b997fa
。这要求在getbuf
函数在返回之前需要执行以下命令:
|
|
对其进行反汇编:
|
|
根据上一问的结果,$rsp
所在的位置是0x5561dc78
,我们需要将上述指令注入到这一位置,更具体的逻辑见下图:
注入指令之后,返回地址变成了之前$rsp
所在的位置,随后$pc
会跳转到这个位置,执行上述指令,这个时候$rsp
也会随之将新的返回地址压栈,执行完毕后顺利跳转到touch2
函数。
这里非常绕的一点在于,
$rsp
和$pc
两个本来运行在互不干涉空间的指针,由于指令的刻意引导,在同一个空间内运行!理解清楚这一点,这个问题就迎刃而解。
输入语句如下(同样注意指令的大小端放置):
|
|
最终结果:
PA 3
汇编:
|
|
C代码:
|
|
和PA 2的不同之处在于,这里传入的参数是字符串。cookie的十六进制字符串是59b997fa,也就是(注意结束符):
|
|
由于touch3
中调用了hexmatch
和strncmp
,有概率修改存放在getbuf
栈中的数据,所以考虑将数据存放在test
的栈中,再将该地址送入$rdi
中。栈空间分配如下:
查看test
函数的栈空间:
字符串需要写在地址0x5561dca8处。实际上这一位置也可以简单推算出:考虑到上一题需要写入的位置为0x5561dc78,则test应该写入的栈空间为0x5561dc78+40(getbuf开辟的栈空间)+8(因为调用函数而将返回地址压入栈空间)。最终的汇编如下:
|
|
即:
|
|
输入文件(不要忘记字符串末尾的结束符):
|
|
最终结果:
PA 4
为了对抗上文中的code injection攻击,现代编译器做出了以下改进:
-
栈随机初始化,每次运行时的缓冲区地址不同。
考虑以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include <stdio.h> void fun() { long local; printf("local in fun: %p\n", &local); } int main() { long local; printf("local in main: %p\n", &local); fun(); return 0; }
可以发现每次的运行结果不尽相同:
-
若
$pc
指向栈中的代码,触发segmentation fault。
因此,之前phase中的方法失效,我们只能利用已有的程序片断,而不是插入新的代码。我们要在已经给定的代码中找到特定的指令序列,这些序列以ret结尾,我们把这些命令叫做gadget
。每一段gadget
包含一系列指令字节,而且以ret
(0xc3)结尾,跳转到下一个gadget
,就这样连续的执行一系列的指令代码。
我们可能无法执行我们原生的指令实现预期效果,但我们可以从已有指令的字节码中提取一部分,以构造目标指令。我们将含有gadget
指令的farm.c
进行反汇编:
|
|
PA 4的目标和PA 2的相同,但我们现在需要使用已有的指令拼凑出这些指令,而不是直接注入指令。首先我们要实现将参数传入$rdi
的要求,考虑以下两种实现方案popq $rdi; retq
(5f [90] c3)和movq <>, $rdi; retq
(48 89 xx [90] c3),在汇编代码中进行搜寻:
经过排查可以知道第一种方案不符合要求(因为不在gadget
内),第二种方法可行,其对应的指令为movq $rax, $rdi; retq
。由于cookie的值是写在栈上的,所以我们需要将栈上的值先传给$rax
,再传给$rdi
。这样我们就需要寻找popq $rax; retq
(58 [90] c3)指令了:
这里需要特别注意的是,两条指令之间可以插入若干条
nop
(90),不影响执行效果。
这样,在getbuf
返回之后,总的调用逻辑如下:
|
|
可视化这一过程:
这里关注一下两个gadget
的地址情况:
|
|
所以实际的指令地址应该设计为0x4019a2和0x4019cc。构造字节序列:
|
|
结果:
PA 5
作者说PA 5是一个附加问题,因为时间问题就暂时搁置了…