type
status
date
slug
summary
tags
category
icon
password
Catagory
Materials
Retired
Retired
Due date
Jun 15, 2024 12:43 PM
Status
Belong in
Shellcode簡介
作者把BOF攻擊流程分為三個部分
開發shellcode時自問下列問題
- 有多少opcode在裡面? 有些opcode會產生更多opcode
- 分別是哪些指令?
- 某些shellcode在載入記憶體時會自行改變,因為指令可以自行修改指令
- 自行修改指令是必要的,如前述multibyte環境下需要額外加上
INC EDX
讓解析出來的指令不會因BAD CHAR導致無法執行
- 是否有一些參數或資料在裡面? victim環境中是否有shellcode所需執行的必要條件,如dll, function, parameters in different version dll,沒有是不是要自己寫或透過loader的方式進行
- 是否有一些不必要的指令在裡面? 如前述multibyte環境下需要額外加上
INC EDX
讓解析出來的指令不會因BAD CHAR導致無法執行
- 是否有一些編碼或解碼器在裡面? encoder/decoder可以將原本的shellcode經過編碼,舉例去除bad chars的shell code進行編碼,並將解碼指令放到shellcode之前,當載入到記憶體中的時候先執行解碼指令再執行shellcode,看起來像是動態修改指令
尋找opcode可以用windbg 或是 Immunity Debugger外掛mona.py
從 C 語言到 Shellcode
分析Function
語系中文環境
可以看到從004012C1~004012CD是主要程式碼,通常以CALL+JMP來實現DLL Function call(請參考 )。
Printf function 位址
exit function 位址
綜上針對printf、exit function分析可知這2個function 在msvcrt.dll中,且實際執行的記憶體位址分別在77C186A、77C09E7E,這2個記憶體位址在不同的作業系統版本中可能會改變,即使在同一個環境下也極有可能改變。原因如下:
- 這2個位址可能會隨著msvcrt.dll被載入的記憶體的基底位址不同而不同。在windows xp基本不太會改變。但vista之後至windows7微軟預設在作業系統中加入ASLR(Address Space Layout Randomization)機制,導致每次重開機後dll載入的基底位址會不一樣。
- 函式位址也可能會因為dll檔案版本不同導致內部所有函式的位址也跟著改變
分析參數傳遞
Shellcod.00403000為OllyDbg表示式,即表示在模組shellcod裡面記憶體00403000的位址
004012BA準備printf參數
Hello World!\n
字串,將Shellcod.00403000位址拷貝進[ESP](即表示ESP指向00403000;又00403000的值位Hello World!\n
字串),從下圖ESP=0022FF60;[ESP]=00403000語系英文環境
Printf function 位址
在Windows XP sp3語系設為英文的環境又使用Dev-C++,msvcrt.printf位址在77C4186A
exit function 位址
在Windows XP sp3語系設為英文的環境又使用Dev-C++,msvcrt.exit位址在77C39E7E
延伸shellcode撰寫概念
以下記憶體位址使用語系中文的環境
可以看到
Hello, World!\n
字串被配置了一個00403000~00403008記憶體區(in stack),再撰寫shellcode時其實os並不會幫我們的shellcode配置記憶體,沒辦法使用像MOV DWORD PTR SS:[ESP],ShellCod.00403000
直接複製配置好的記憶體位址到[ESP]。How?將shellcode一段一段push到stack裡面,再從stack中抓取指令。下面示範如何將
printf("Hello World!\n");
、exit(0);
寫成shellcode準備參數
Hello, World!\n 字串參數
參數 0 for exit function
Function call
組語的 CALL 指令有個特點,就是會把下一行組語的位址紀錄在堆疊中,以至於呼叫的函式執行結束之後,其 function epilogue 的 RETN 指令會返回到當初呼叫它的地方
通常並不會直接使用 CALL <function address>的方式進行處理,常用的方式是先將位址儲存在暫存器中在call address。
因此前面分析printf function實際位址在(77C186A);exit function 實際位址在(77C09E7E)
綜合上述寫完的opcode如下
取得opcode
使用Windbg取得opcode
使用Immunity Debugger外掛模組mona.py取得opcode
The shellcode in opcode
分析TestShellcode
組語的 CALL 指令有個特點,就是會把下一行組語的位址紀錄在堆疊中,以至於呼叫的函式執行結束之後,其 function epilogue 的 RETN 指令會返回到當初呼叫它的地方
004012C6~004012D0是我們使用函式指標指向char shellcode[] ,004012D0
CALL EAX
即fp()
;下圖可以看到[EAX]儲存00402000位址,其ASCII值
h!\n
字元,[ESP]因為是函式指標故return address儲存的是指向的function實際位址(00403000),使用Metasploit的nasm_shell.rb取得opcode
metasploit framework提供的使用Ruby寫的script,可用來產生opcode
原則上opcode越短越好
使用NASM取得opcode
直接寫組語的code,並利用nasm組譯器編譯成二進位的檔案,再透過工具讀取二進位檔案並轉換成字元陣列的形式。
利用nasm將shellcode001.nasm編譯成二進位檔
利用HxD查看binary file,就可以看到我們所寫的opcode
作者寫了一個小工具類似metasploit裡的generic/none編碼器,用來將bin二進位檔案轉換成c++ 的字元陣列格式。
重複將產出的opcode改成TestShellcode,產出結果會與前面一樣。
作者最推薦使用nasm產opcode,因產出bin二進位檔案,可同步比較記憶體中的shellcode是否完整。
討論Shellcode v1
前面使用的shellcode會有下列幾個問題:
- 預先假設執行環境是Windows的cmd.exe
- 含有NULL字元(\x00)
- 使用絕對記憶體0x77C1186A(msvcrt.printf)、0x77c09e7e(msvcrt.exit)
- 預先假設函式printf、exit一定會被call,換句話說預先假設msvcrt.dll一定會被載入到記憶體中
Q1 - 利用AllocConsole()API來產生Console
因為目前shellcode均使用cmd.exe來呼叫執行,故先將問題簡化成確保我們執行shellcode均有console可以讓我們執行。
Q2 - 排除Bad Characters(本章不會提到)
Q3、Q4 - dll被載入到的記憶體位址不同而改變
printf、exit function均在msvcrt.dll中,當msvcrt.dll載入的記憶體位址不同,其中的function位址也會不同,目前實驗環境語系是中文環境
Compiled With Dev-C++
上圖不管是使用immunity debugger或是用WinDbg可以看到載入4個模組到記憶體中,其中TestShellcode.exe的基底位址是00400000,大小長度在00006000,另外msvcrt.dll記憶體基底位址在77BE0000,大小長度在00058000(語系設定中文環境),從中可以看到0x77C1186A(printf)、0x77C09E7E(exit)function是在msvcrt.dll裡面。
作者實驗結果,在Windows XP SP3底下使用Dev-C++編譯,幾乎都會載入msvcrt.dll,基礎位址大多會在77c10000(語系英文環境)
Compiled with VS C++ Express
這裡使用VC++ 2010 Express進行編譯,VC++ 2010預設都會有2組編譯和連結設定檔案,一組是Debug;另一組是Release版。
預設Build Solution是Debug版,Debug版本內含偵錯偵錯處理
從下圖可以看到Debug version載入的是MSVCR100D.dll,base address在10200000,長度為00172000
編譯成Release版,此版本編譯出來的執行檔會經過最佳化處理
下圖看到release版本載入的MSVCR100.dll,base address在78AA0000,長度在000BF000
若是我們將前面的shellcode使用VC++編譯
EIP=77C1186A是我們LAB中msvcrt.dll,printf function的位址,因為VC++編譯出的執行檔並未載入故導致找不到該位址,故程式執行到這裡會造成異常終止
結論
- function 位址會隨著載入的dll位址變化而不同
- 不一定會載入所想要的dll,故也不一定能夠呼叫到想要的function
- solution 3 - 需要動態決定記憶體位址(base address+offset相對位址)
- solution 4 - 如果記憶體中沒有想要的dll,就在shellcode中自己載入
【坑】回頭看
透過PEB手法找到Kernel32.dll的基底位址 - 探索OS內部
因為實驗環境windows xp sp3微軟不更新了,為了要讓Windbg可以搜尋的到符號文件,參考 。
在Window中Process會有一個資料結構PEB(Process Environment Block),而Thread本身也會有TEB(Thread Environment Block)
Deep dive in the _TEB structure
在Windbg使用
dt ntdll!_TEB
來查看teb資料結構如下,其真正名字是_TEB
,型別為C語言的結構(struct),也可看到下圖也有輸出其offsetTEB中offset 0x000成員NtTib
從下圖可知在_TEB中第一個成員_NT_TIB結構中第一個成員ExceptionList(offset 0x00)是一個指向_EXCEPTION_REGISTRATION_RECORD結構的32位元指標,主要負責例外處理的註冊動作(後面會在細講)。
另一個在_NT_TIB結構中最後一個成員Self(offset 0x18)指向自己的32位元指標
TEB中offset 0x030成員ProcessEnvironmentBlock
從Kernel32.dll基底位址找到LoadLibraryA函式位址 - 攀爬PE結構
函式名稱的hash值
Shellcode v2
Metasploit attack shellcode - Hello World Message box
Windows 32 bit vs Windows 64 bit
Reference
- C library function
- NASM
- CFF Explore
- Windows Internals
- TEB、PEB、SEH
- windbg