buffer_overflow 第3章 筆記1 第一個shellcode
第三章 shellcode
shellcode 也就是一隻程式,你需要這個程式做出什摸樣的動作。
shellcode 是用組合語言來編寫的,編寫完後轉換為相對應的opcode利用buffer_overflow 攻擊定位到EIP暫存器之後,在把流程跳到shellcode上,如此一來shellcode就會被執行。
在此之前,需要先了解DLL
DLL是動態函數連接庫,一個DLL裡面有好多微軟提供的API 函數。
當應用程式被執行加載時,如果該程式有使用到某個DLL裡面的API,應用程式就會載入該DLL,DLL可以使用應用程式的資源,應用程式可以使用該DLL的API。
那我們就寫一個打印出hello world 然後結束的程式,觀察一下用組合語言的角度會長什摸樣子。
<<<<用DEV c++編譯>>>>
int main()
{
printf("hello world\n");
exit(0);
}
用OllyDbg打開:
框起來的為主程式:
可以在 004012C1看到 CALL <JMP.&msvcrt.printf> 代表他要呼叫printf,我們可以在 004012C1下中斷點按F2 在按F9 讓程式執行到中斷點,接著F7進入看看 printf 真正位置。
框起來的為主程式:
可以在 004012C1看到 CALL <JMP.&msvcrt.printf> 代表他要呼叫printf,我們可以在 004012C1下中斷點按F2 在按F9 讓程式執行到中斷點,接著F7進入看看 printf 真正位置。
可以在 004012CD看到 CALL <JMP.&msvcrt.exit> 代表他要呼叫exit,我們可以在 004012CD下中斷點按F2 在按F9 讓程式執行到中斷點,接著F7進入看看 exit 真正位置。
可以觀察到:
printf()函數的真正位址在 msvcrt.dll 內 位置為 77C1186A
exit() 函數的真正位址在 msvcrt.dll 內 位置為 77C09E7E
知道了函數的位址我們只要先把參數準備好,在呼叫函數位址就可以了
printf()需要用的參數 字串 " Hello, World!\n" 在 ASCII 碼表示為:
H e l l o , 空白 W o r l d ! \n
[48] [65] [6C] [6C] [6F] [2C] [20] [57] [6F] [72] [6C] [64] [21] [0A]
在組合語言角度中,呼叫某個函數如果有參數的話,會先利用push 指令將需要的參數推入堆疊,在用call指令呼叫函數來實現。
由於 32位下的暫存器為32位元 所以一次可以存4位元組,所以一次可以推入4位元組。
推入的順序,字串的頭也就是H [48] ...... 必須最後推入,
PUSH 0x210A0000
PUSH 0x6F726C64
PUSH 0x6D2C2057
PUSH 0x48656C6C
-------------------------------------------------------------------------------------------------------------
由於windows 是little endian 儲存數值 相反過來,所以變成
PUSH 0x00000A21
PUSH 0x646C726F
PUSH 0x57202C6F
PUSH 0x6C6C6548
---------------------------------------------------------------------------------------------------------------
由於printf() 接收的參數必需是一個指向字串的指標,也就是一個記憶體位置,並非字串本身所以
PUSH 0x00000A21
PUSH 0x646C726F
PUSH 0x57202C6F
PUSH 0x6C6C6548
PUSH ESP //執行完PUSH ESP此時堆疊最頂端的存放該字串頭的記憶體 // 位置
------------------------------------------------------------------------------------------------------------------
再來 exit() 需要一個參數0
xor eax,eax //讓eax 變0
-------------------------------------------------------------------------------------------------------------------
全部加起來:
全部加起來:
PUSH 0x00000A21
PUSH 0x646C726F
PUSH 0x57202C6F
PUSH 0x6C6C6548
PUSH ESP
MOV ECX , 0x77C1186A // 把printf位置給 ecx
CALL ECX // 呼叫printf
XOR EAX , EAX // 讓eax 變0
PUSH EAX // 把推0推進堆疊頂端
MOV ECX, 77C09E7E // 把 exit位置放進 0x77
CALL ECX // 呼叫 ecx
---------------------------------------------------------------------------------------------------------------------
把上面程式碼轉換為OPCODE 型態:
使用:Immunity Debugger 外掛模組mona.py 轉換:
打開Immunity Debugger 輸入!mona
接著 在下方命令列輸入 !mona assemble -s 加上 剛剛的組合語言,每個指令前面用一個#號隔開:
!mona asseble -s PUSH 0x00000A21 # PUSH 0x646C726F # PUSH 0x57202C6F # PUSH 0x6C6C6548# PUSH ESP # MOV ECX ,0x77C1186A # CALL ECX # XOR EAX,EAX # PUSH EAX # MOV ECX,77c09e7e # CALL ECX
按下ALT+L 叫出log data視窗
可以看到我們指令對照OPCODE的樣子
把他複製下來 我們接下來要來執行這個shellcode 。
把這個shellcode,放在DEV c++執行看看會不會印出Hellow, World,並且透過exit()離開
#include <cstdio>
using namespace std;
char shellcode []=
"\x68\x21\x0a\x00\x00" //PUSH 0x00000A21
"\x68\x6f\x72\x6c\x64" //PUSH 0x646C726F
"\x68\x6f\x2c\x20\x57" //PUSH 0x57202C6F
"\x68\x48\x65\x6c\x6c" //PUSH 0x6C6C6548
"\x54" //PUSH ESP
"\xc7\xc1\x6a\x18\xc1\x77" //MOV ECX , 0x77C1186A
"\xff\xd1" //CALL ECX
"\x33\xc0" //XOR EAX, EAX
"\x50" //PUSH EAX
"\xc7\xc1\x7e\x9e\xc0\x77" //MOV ECX,77c09e7e
"\xff\xd1"; //CALL ECX
typedef void (*FUNCPTR)();
int main()
{
printf("<<shellcode started >> \n");
FUNCPTR fp = (FUNCPTR)shellcode;
fp();
printf("you can't see me because exit() ");
//system("pause"); 暫停沒用 exit()早就執行了
}
其中:
1.
定義了typedef void (*FUNCPTR) (); 函數 回傳值為void ,之後把存放shellcode的陣列強制轉換為FUNCPTR 型態,執行了 fp()那一行 就代表進入到函數也就是執行了我們shellcode
2.
最後一行 printf("you can't see me because exit() ") 沒辦法看到 因為shellcode中的exit() 已經把程式結束掉了
3.
在dev c++下會一閃而過因為沒有設定system("pause")讓程式暫停,為什摸不用system("pause")
原因很簡單,想一下我們的shellcode會先執行exit() ,程序早就結束了。
所以我們用cmd來執行 我們的這個Test_shellcode.exe

使用:Immunity Debugger 外掛模組mona.py 轉換:
打開Immunity Debugger 輸入!mona
接著 在下方命令列輸入 !mona assemble -s 加上 剛剛的組合語言,每個指令前面用一個#號隔開:
!mona asseble -s PUSH 0x00000A21 # PUSH 0x646C726F # PUSH 0x57202C6F # PUSH 0x6C6C6548# PUSH ESP # MOV ECX ,0x77C1186A # CALL ECX # XOR EAX,EAX # PUSH EAX # MOV ECX,77c09e7e # CALL ECX
按下ALT+L 叫出log data視窗
可以看到我們指令對照OPCODE的樣子
把他複製下來 我們接下來要來執行這個shellcode 。
把這個shellcode,放在DEV c++執行看看會不會印出Hellow, World,並且透過exit()離開
#include <cstdio>
using namespace std;
char shellcode []=
"\x68\x21\x0a\x00\x00" //PUSH 0x00000A21
"\x68\x6f\x72\x6c\x64" //PUSH 0x646C726F
"\x68\x6f\x2c\x20\x57" //PUSH 0x57202C6F
"\x68\x48\x65\x6c\x6c" //PUSH 0x6C6C6548
"\x54" //PUSH ESP
"\xc7\xc1\x6a\x18\xc1\x77" //MOV ECX , 0x77C1186A
"\xff\xd1" //CALL ECX
"\x33\xc0" //XOR EAX, EAX
"\x50" //PUSH EAX
"\xc7\xc1\x7e\x9e\xc0\x77" //MOV ECX,77c09e7e
"\xff\xd1"; //CALL ECX
typedef void (*FUNCPTR)();
int main()
{
printf("<<shellcode started >> \n");
FUNCPTR fp = (FUNCPTR)shellcode;
fp();
printf("you can't see me because exit() ");
//system("pause"); 暫停沒用 exit()早就執行了
}
其中:
1.
定義了typedef void (*FUNCPTR) (); 函數 回傳值為void ,之後把存放shellcode的陣列強制轉換為FUNCPTR 型態,執行了 fp()那一行 就代表進入到函數也就是執行了我們shellcode
2.
最後一行 printf("you can't see me because exit() ") 沒辦法看到 因為shellcode中的exit() 已經把程式結束掉了
3.
在dev c++下會一閃而過因為沒有設定system("pause")讓程式暫停,為什摸不用system("pause")
原因很簡單,想一下我們的shellcode會先執行exit() ,程序早就結束了。
所以我們用cmd來執行 我們的這個Test_shellcode.exe






留言
張貼留言