惡意代碼分析之注入技術(shù)
? 在很多時候為了能夠?qū)δ繕?biāo)進(jìn)程空間數(shù)據(jù)進(jìn)行修改,或者使用目標(biāo)進(jìn)程的名稱來執(zhí)行自己的代碼,實現(xiàn)危害用戶的操作,通常是將一個DLL文件或者ShellCode注入到目標(biāo)進(jìn)程中去執(zhí)行。這里分享四種常用的注入技術(shù),其中使用DLL注入的方法最為普遍。
全局鉤子注入
? 在Windows中大部份的應(yīng)用程序都是基于消息機制的,他們都有一個消息過程函數(shù),根據(jù)消息完成不同的功能。Windows操作系統(tǒng)提供的鉤子機制就是用來截獲和監(jiān)視這些消息的。按照鉤子的范圍不同,它們又可以分為局部鉤子和全局鉤子,局部鉤子是針對某個線程的;而全局鉤子則是作用于整個系統(tǒng)的基于消息的應(yīng)用。全局鉤子需要使用DLL文件,在DLL中實現(xiàn)相應(yīng)的鉤子函數(shù)。
關(guān)鍵函數(shù)安裝鉤子程序SetWindowsHookEx()
WINUSERAPIHHOOKWINAPI SetWindowsHookExA( _In_ int idHook, // 要安裝的鉤子的類型例如鍵盤 鼠標(biāo) 對話框等 _In_ HOOKPROC lpfn, // 一個指向鉤子程序的指針 _In_opt_ HINSTANCE hmod,// 包含lpfn參數(shù)指向的鉤子過程的DLL句柄 _In_ DWORD dwThreadId); // 與鉤子程序相關(guān)聯(lián)的線程標(biāo)識符
成功返回DLL句柄,失敗返回NULL
卸載鉤子函數(shù)UnhookWindowsHookEx
BOOL UnhookWindowsHookEx( HHOOK hhk);鉤子回調(diào)函數(shù)
// 表示將當(dāng)前的鉤子傳遞給鉤子鏈中的下一個鉤子LRESULTWINAPI CallNextHookEx( _In_opt_ HHOOK hhk, _In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam);遠(yuǎn)程線程注入
遠(yuǎn)程線程注入是指一個進(jìn)程在另一個進(jìn)程中創(chuàng)建線程的技術(shù),是一種經(jīng)典的注入技術(shù)
函數(shù)OpenProcess()——打開目標(biāo)進(jìn)程
HANDLEWINAPI OpenProcess( _In_ DWORD dwDesiredAccess, // 訪問進(jìn)程對象 _In_ BOOL bInheritHandle, // 若此值為true,則此進(jìn)程創(chuàng)建的進(jìn)程將繼承該句柄 _In_ DWORD dwProcessId // 要打開的本地進(jìn)程的PID );// 返回值: 若成功返回句柄,失敗返回NULL函數(shù)VirtualAllocEx()
指定進(jìn)程的虛擬地址空間內(nèi)保留、提交或者更改內(nèi)存的狀態(tài)
LPVOIDWINAPIVirtualAllocEx( _In_ HANDLE hProcess, // 進(jìn)程句柄 _In_opt_ LPVOID lpAddress, // 指定要分配頁面所需的起始指針,為NULL自動分配 _In_ SIZE_T dwSize, // 要分配內(nèi)存的大小 _In_ DWORD flAllocationType, // 內(nèi)存分配的類型:保留、提交和更改 _In_ DWORD flProtect // 頁面區(qū)域的內(nèi)存保護(hù) );// 返回值:函數(shù)成功返回分配的基址,失敗返回NULL函數(shù)WriteProcessMemory()——在指定的進(jìn)程中將數(shù)據(jù)寫入內(nèi)存區(qū)域
BOOLWINAPI WriteProcessMemory( _In_ HANDLE hProcess, // 要修改的進(jìn)程句柄 _In_ LPVOID lpBaseAddress, // 指向指定進(jìn)程中寫入數(shù)據(jù)的基地址指針 _In_reads_bytes_(nSize) LPCVOID lpBuffer, // 指向緩沖區(qū)的指針 _In_ SIZE_T nSize, // 要寫入指定進(jìn)程的字節(jié)數(shù) _Out_opt_ SIZE_T* lpNumberOfBytesWritten // 指向變量的指針,該變量接收傳輸?shù)街付ㄟM(jìn)程的字節(jié)數(shù) );// 返回值: 函數(shù)成功 != 0;失敗返回0 // 注意:寫入?yún)^(qū)域的內(nèi)存要可訪問,否則操作失敗函數(shù)CreateRemoteThread()——實現(xiàn)注入的核心函數(shù)在另一個進(jìn)程的虛擬地址中創(chuàng)建運行的線程
HANDLEWINAPI CreateRemoteThread( _In_ HANDLE hProcess, // 要創(chuàng)建線程的進(jìn)程的句柄 _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, // 指向安全描述符的指針 _In_ SIZE_T dwStackSize, // 堆棧的初始大小,若為0則新線程使用可執(zhí)行文件的默認(rèn)大小 _In_ LPTHREAD_START_ROUTINE lpStartAddress, // 指向由線程執(zhí)行類型為LPTHREAD_START_ROUTINE的應(yīng)用程序定義的函數(shù)指針,并表示遠(yuǎn)程進(jìn)程 中線程的起始地址,該函數(shù)必須存在于遠(yuǎn)程進(jìn)程中 _In_opt_ LPVOID lpParameter, // 要傳遞給線程函數(shù)的變量的指針 _In_ DWORD dwCreationFlags, // 控制線程創(chuàng)建的標(biāo)志 _Out_opt_ LPDWORD lpThreadId // 接收線程標(biāo)志符變量的指針 );// 返回值: 成功 新線程的句柄,失?。悍祷豊ULL
? 從以上這些函數(shù)的作用我們實現(xiàn)的原理就很清晰了,先在指定進(jìn)程申請一段地址然后將準(zhǔn)備好的shellcode或者一個DLL文件寫入到這塊內(nèi)存空間中。
? 注意:對于一些系統(tǒng)服務(wù)這樣通常會注入失敗,由于系統(tǒng)存在SESSION 0隔離的安全機制,需調(diào)用一個更加底層的ZwCreateThreadEx()來實現(xiàn)。
APC隊列注入
? APC(Asynchronus Procedure Call)為異步過程調(diào)用,是指函數(shù)在特定線程中被異步執(zhí)行。在Windows系統(tǒng)中,APC是一種并發(fā)機制,用于異步IO或者定時器。
每一個線程都有自己的APC隊列,使用QueueUserAPC函數(shù)把一個APC函數(shù)壓入APC隊列中。當(dāng)處于用戶模式的APC壓入線程APC隊列后,該線程并不直接調(diào)用APC函數(shù),除非該線程處于可通知狀態(tài),調(diào)用的順序為先入先出。
函數(shù)
WINBASEAPI語言方法
72796JQ2GywG8D
EIwRA
緒川里緒87422010.09.08 05-16-01
DWORD WINAPI QueueUserAPC( _In_ PAPCFUNC pfnAPC, // 指向APC函數(shù)的指針 _In_ HANDLE hThread, // 線程句柄 _In_ ULONG_PTR dwData // 由pfnAPC參數(shù)指向的APC函數(shù)的單個值 );// 返回值 成功非0; 失敗返回0
? APC的注入原理是利用當(dāng)線程被喚醒時APC中的注冊函數(shù)會執(zhí)行的機制,并以此去執(zhí)行DLL加載代碼,進(jìn)而完成DLL注入。為了增加成功率,可以向目標(biāo)進(jìn)程中的所有線程都插入APC。
自定義HOOK
自定義HOOK大致可以分為兩類inlineHOOK
IATHOOK
inlineHook是一種通過修改機器碼的方式來實現(xiàn)HOOK的技術(shù)
原理:對于一個正常的程序如下圖,通過CALL指令來調(diào)用函數(shù)。關(guān)于CALL指令相當(dāng)于push 當(dāng)前函數(shù)地址和jmp要執(zhí)行的指令位置,即 push 0171B7B3 jmp 0171B430,這是我們正常執(zhí)行00.0171B430這個函數(shù)的樣子。
我們在hook的時候就是將CALL指令直接改成jmp指令,跳到我們自己編寫的函數(shù)的位置,執(zhí)行完成之后跳回函數(shù)原來指令的下一條指令0171B7B3,需要注意的是跳轉(zhuǎn)偏移要多計算5個字節(jié)
計算公式: 跳轉(zhuǎn)偏移 = 目標(biāo)地址 - jmp所在的地址 - 5
實現(xiàn)方法
獲取函數(shù)的實際地址
修改內(nèi)存分頁屬性
計算跳轉(zhuǎn)偏移,修改目標(biāo)地址,還原內(nèi)存屬性
獲取實際地址返回
void OnHook() { //獲取函數(shù)實際地址 HMODULE Module = GetModuleHandleA("kernel32.dll"); LPVOID func = GetProcAddress(Module, "OpenProcess"); //保存5個字節(jié) memcpy(g_oldCode, func, 5); //修改內(nèi)存分頁屬性,由于代碼段是不可寫的,所有必須先將它的屬性變成可寫 DWORD dwProtect; VirtualProtect(func, 5, PAGE_EXECUTE_READWRITE, &dwProtect); //計算跳轉(zhuǎn)偏移 *(DWORD*)&g_newCode[1] = (DWORD)MyOpenProcess - (DWORD)func - 5; //修改目標(biāo)地址 memcpy(func, g_newCode, 5); //還原內(nèi)存分頁屬性 VirtualProtect(func, 5, dwProtect, &dwProtect); };用戶層的IATHook是通過替換IAT表中函數(shù)的原始地址從而實現(xiàn)的Hook
? 與普通的InlineHook不一樣,IATHook需要充分理解PE文件的結(jié)構(gòu)才能完成,關(guān)于相對虛擬地址(RVA)、文件偏移地址(FOA)和加載基址等概念可以自行查閱相關(guān)資料。
實現(xiàn)方法
//獲取指定dll導(dǎo)出地址表的中函數(shù)地址 DWORD * GetIatAddress(const char * dllName, const char* funName) { // 1. 獲取加載基址并轉(zhuǎn)換成DOS頭 auto DosHeader = (PIMAGE_DOS_HEADER)GetModuleHandle(NULL); // 2. 通過 DOS 頭的后一個字段 e_lfanew 找到 NT 頭的偏移 auto NtHeader = (PIMAGE_NT_HEADERS)(DosHeader->e_lfanew + (DWORD)DosHeader); // 3. 在數(shù)據(jù)目錄表下標(biāo)為[1]的地方找到導(dǎo)入表的RVA DWORD ImpRVA = NtHeader->OptionalHeader.DataDirectory[1].VirtualAddress; // 4. 獲取到導(dǎo)入表結(jié)構(gòu)體,因為程序已經(jīng)運行了,所以不需要轉(zhuǎn)FOA auto ImpTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)DosHeader + ImpRVA); // 遍歷導(dǎo)入表,以一組全0的結(jié)構(gòu)結(jié)尾 while (ImpTable->Name) { // 獲取當(dāng)前導(dǎo)入表結(jié)構(gòu)描述的結(jié)構(gòu)體的名稱 CHAR* Name = (CHAR*)(ImpTable->Name + (DWORD)DosHeader); // 忽略大小寫進(jìn)行比較,查看是否是需要的導(dǎo)入表結(jié)構(gòu) if (!_stricmp(Name, dllName)) { // 找到對應(yīng)的 INT 表以及 IAT 表 DWORD* IntTable = (DWORD*)((DWORD)DosHeader + ImpTable->OriginalFirstThunk); DWORD* IatTable = (DWORD*)((DWORD)DosHeader + ImpTable->FirstThunk); // 遍歷所有的函數(shù)名稱,包括有/沒有名稱 for (int i = 0; IntTable[i] != 0; ++i) { // 比對函數(shù)是否存在函數(shù)名稱表中 if ((IntTable[i] & 0x80000000) == 0) { // 獲取到導(dǎo)入名稱結(jié)構(gòu) auto Name = (PIMAGE_IMPORT_BY_NAME)((DWORD)DosHeader + IntTable[i]); // 比對函數(shù)的名稱 if (!strcmp(funName, Name->Name)) { // 返回函數(shù)在IAT中保存的地址 return &IatTable[i]; } } } } ImpTable++; } return 0; }總結(jié)
? 鉤子技術(shù)總結(jié)起來就是通過各種手段來修改代碼或者地址從而讓程序來執(zhí)行我們自己編寫的代碼,在分析惡意程序時關(guān)注一下這些敏感的API函數(shù)組合,在查看程序基本信息的時候就可以大致做出猜測。下一篇繼續(xù)分享常見的啟動和隱藏技術(shù),繼續(xù)刨析病毒的實現(xiàn)原理。