通過自制調試器來理解其原理
- 先來查看一段簡單的調試器代碼
#include "stdafx.h"
#include <Windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
PROCESS_INFORMATION pi;
STARTUPINFO si;
if(argc < 2){
fprintf(stderr, "C:\\>%s <sample.exe>\n", argv[0]);
return 1;
}
memset(&pi, 0, sizeof(pi));
memset(&si, 0, sizeof(si));
si.cb = sizeof(STARTUPINFO);
//1
BOOL r = CreateProcess(
NULL, argv[1], NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_SUSPENDED | DEBUG_PROCESS,
NULL, NULL, &si, &pi);
if(!r)
return -1;
//2
ResumeThread(pi.hThread);
while(1) {
DEBUG_EVENT de;
if(!WaitForDebugEvent(&de, INFINITE))
break;
DWORD dwContinueStatus = DBG_CONTINUE;
switch(de.dwDebugEventCode)
{
case CREATE_PROCESS_DEBUG_EVENT:
printf("CREATE_PROCESS_DEBUG_EVENT\n");
break;
case CREATE_THREAD_DEBUG_EVENT:
printf("CREATE_THREAD_DEBUG_EVENT\n");
break;
case EXIT_THREAD_DEBUG_EVENT:
printf("EXIT_THREAD_DEBUG_EVENT\n");
break;
case EXIT_PROCESS_DEBUG_EVENT:
printf("EXIT_PROCESS_DEBUG_EVENT\n");
break;
case EXCEPTION_DEBUG_EVENT:
if(de.u.Exception.ExceptionRecord.ExceptionCode !=
EXCEPTION_BREAKPOINT)
{
dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
}
printf("EXCEPTION_DEBUG_EVENT\n");
break;
case OUTPUT_DEBUG_STRING_EVENT:
printf("OUTPUT_DEBUG_STRING_EVENT\n");
break;
case RIP_EVENT:
printf("RIP_EVENT\n");
break;
case LOAD_DLL_DEBUG_EVENT:
printf("LOAD_DLL_DEBUG_EVENT\n");
break;
case UNLOAD_DLL_DEBUG_EVENT:
printf("UNLOAD_DLL_DEBUG_EVENT\n");
break;
}
if(de.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
break;
ContinueDebugEvent(
de.dwProcessId, de.dwThreadId, dwContinueStatus);
}
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return 0;
}
- 程序通過CreateProcess函數啓動調試目標進程,調試目標進程也叫調試對象或者調試程序。
- 調用CreateProcess時,如果設置了DEBUG_PROCESS或DEBUG_ONLY_THIS_PROCESS標誌,則啓動的進程中所產生的異常都會被調試器捕捉到。
- 上述兩個標誌的區別:
DEBUG_PROCESS標識:調試對象所產生的子進程,以及子進程的子進程都作爲調試對象
DEBUG_ONLY_THIS_PROCESS:啓動的那一個進程作爲調試對象
- CreatProcess函數的第1個參數或者第2個參數可用於傳遞目標程序的路徑,然後便可啓動進程。
- CreateProcess函數:
- 通過CREATE_SUSPENDED標誌可以讓進程在啓動後進入掛起狀態。
- 當設置這一標誌時,CreateProcess函數調用完成之後,新進程中的所有線程都會暫停,儘管程序沒有運行,但程序的可執行文件已經被載入內存,這是我們可以在運行之前對調試對象的數據進行改寫。
- 示例程序中,沒有進行任何操作,而是直接調用了ResumeThread這個函數,調試對象的所有線程就會恢復運行。
- ResumeThread函數
- 當調試對象程序開始運行後,調試器就開始等待捕捉異常,調試事件會通過WaitForDebugEvent函數來進行接收。
- WaitForDebugEvent函數
- WaitForDebugEvent函數的第1個參數傳遞了一個DEBUG_EVENT結構體,捕捉到的調試事件會被存放在這個結構體中。第2個參數dwMilliseconds如果設置爲INFINITE則表示一直等待。
- DEBUG_EVENT結構體
typedef struct _DEBUG_EVENT {
DWORD dwDebugEventCode;
DWORD dwProcessId;
DWORD dwThreadId;
union {
EXCEPTION_DEBUG_INFO Exception;
CREATE_THREAD_DEBUG_INFO CreateThread;
CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
EXIT_THREAD_DEBUG_INFO ExitThread;
EXIT_PROCESS_DEBUG_INFO ExitProcess;
LOAD_DLL_DEBUG_INFO LoadDll;
UNLOAD_DLL_DEBUG_INFO UnloadDll;
OUTPUT_DEBUG_STRING_INFO DebugString;
RIP_INFO RipInfo; } u;
} DEBUG_EVENT, *LPDEBUG_EVENT;
- 其中第一個成員dwDebugEventCode代表調試事件編號。
- dwProcessId爲進程ID,dwThreadId爲線程ID
- 接下來的數據會隨着dwDebugEventCode的不同而發生變化,dwDebugEventCode可以取下列值:
- 示例程序中,當接收到調試事件時,會使用printf函數將事件的內容顯示出來,通過訪問union定義的結構體就可以獲取調試對象的信息。
- 當處理被交給調試器時,調試對象會暫停運行,因此,在我們的調試器顯示消息的過程中,調試對象是處於暫停狀態的。
- 調用ContinueDebugEvent函數可以讓調試對象恢復運行,這是調試器又回到WaitForDebugEvent函數等待下一條調試事件。
- 運行示例
C:\>wdbg01a.exe "C:\Program Files\Internet Explorer\iexplore.exe"
- 可以看到,創建進程、線程以及加載、卸載DLL等事件都被調試器捕捉到了。
- 實現反彙編功能
- 希望在發生異常時,能夠顯示異常的地址以及當前寄存器的值,同時,我們還希望顯示發生異常所執行的指令,因此我們來實現反彙編功能。
- wdbg02a.cpp
#include "stdafx.h"
#include <Windows.h>
#include "udis86.h"
#pragma comment(lib, "libudis86.lib")
int disas(unsigned char *buff, char *out, int size)
{
ud_t ud_obj;
ud_init(&ud_obj);
ud_set_input_buffer(&ud_obj, buff, 32);
ud_set_mode(&ud_obj, 32);
ud_set_syntax(&ud_obj, UD_SYN_INTEL);
if(ud_disassemble(&ud_obj)){
sprintf_s(out, size, "%14s %s",
ud_insn_hex(&ud_obj), ud_insn_asm(&ud_obj));
}else{
return -1;
}
return (int)ud_insn_len(&ud_obj);
}
int exception_debug_event(DEBUG_EVENT *pde)
{
DWORD dwReadBytes;
HANDLE ph = OpenProcess(
PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_VM_OPERATION,
FALSE, pde->dwProcessId);
if(!ph)
return -1;
HANDLE th = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT,
FALSE, pde->dwThreadId);
if(!th)
return -1;
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_ALL;
GetThreadContext(th, &ctx);
char asm_string[256];
unsigned char asm_code[32];
ReadProcessMemory(ph, (VOID *)ctx.Eip, asm_code, 32, &dwReadBytes);
if(disas(asm_code, asm_string, sizeof(asm_string)) == -1)
asm_string[0] = '\0';
printf("Exception: %08x (PID:%d, TID:%d)\n",
pde->u.Exception.ExceptionRecord.ExceptionAddress,
pde->dwProcessId, pde->dwThreadId);
printf(" %08x: %s\n", ctx.Eip, asm_string);
printf(" Reg: EAX=%08x ECX=%08x EDX=%08x EBX=%08x\n",
ctx.Eax, ctx.Ecx, ctx.Edx, ctx.Ebx);
printf(" ESI=%08x EDI=%08x ESP=%08x EBP=%08x\n",
ctx.Esi, ctx.Edi, ctx.Esp, ctx.Ebp);
SetThreadContext(th, &ctx);
CloseHandle(th);
CloseHandle(ph);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
if(argc < 2){
fprintf(stderr, "C:\\>%s <sample.exe>\n", argv[0]);
return 1;
}
memset(&pi, 0, sizeof(pi));
memset(&si, 0, sizeof(si));
si.cb = sizeof(STARTUPINFO);
BOOL r = CreateProcess(
NULL, argv[1], NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_SUSPENDED | DEBUG_PROCESS,
NULL, NULL, &si, &pi);
if(!r)
return -1;
ResumeThread(pi.hThread);
int process_counter = 0;
do{
DEBUG_EVENT de;
if(!WaitForDebugEvent(&de, INFINITE))
break;
DWORD dwContinueStatus = DBG_CONTINUE;
switch(de.dwDebugEventCode)
{
case CREATE_PROCESS_DEBUG_EVENT:
process_counter++;
break;
case EXIT_PROCESS_DEBUG_EVENT:
process_counter--;
break;
case EXCEPTION_DEBUG_EVENT:
if(de.u.Exception.ExceptionRecord.ExceptionCode !=
EXCEPTION_BREAKPOINT)
{
dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
}
exception_debug_event(&de);
break;
}
ContinueDebugEvent(
de.dwProcessId, de.dwThreadId, dwContinueStatus);
}while(process_counter > 0);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return 0;
}
- disas函數負責對機器語言進行反彙編,這裏使用了udis86的功能。
- exception_debug_event函數會在發生異常時運行,其中調用了下列函數:
OpenProcess
ReadProceeMemory
OpenThread
GetThreadContext
SetThreadContext
- 上面這些函數,再加上WriteProcessMemory函數,就是用於訪問其他進程的必備工具包。
- 在Windows中,即使我們的程序不是作爲調試器掛載在目標進程上,只要能夠獲取目標進程的句柄,就可以隨意讀寫該進程的內存空間,當然如果用戶沒有相應的權限,調用OpenProcess會失敗,但是只要能夠通過其他方法獲取進程句柄,也可以自由讀寫該進程的內存空間。
- OpenProcess函數
HANDLE OpenProcess(DWORD dwDesiredAcess,BOOL bInheritHandle,DWORD dwProcessId)
//params:訪問標誌,句柄繼承選項,進程ID
- 在exception_debug_event函數中,爲了獲取發生異常時所執行的指令,我們需要使用ReadProcessMemory函數:
BOOL ReadProcessMemory(HANDLE hProcess,LPCVOID lpBaseAddress,LPVOID lpBUffer,DWORD nSize,LPDWORD lpNumberOFBytesRead)
//pamars:進程句柄,讀取起始地址,用於存放數據的緩衝區,要讀取的字節數,實際讀取的字節數
- WriteProcessMemory函數
BOOL WriteProcessMemory(HANDLE hProcess,LPCVOID lpBaseAddress,LPVOID lpBUffer,DWORD nSize,LPDWORD lpNumberOFBytesRead)
//params:進程句柄,寫入起始地址,數據緩衝區,要寫入的字節數,實際寫入的字節數
- 用OpenThread打開線程後,可以通過GetThreadContext和SeatThreadContext來讀寫寄存器。
- OpenThread函數
HANDLE OpenThread(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwThreadId);
//params:訪問標誌,句柄繼承選項,線程ID
- GetThreadContext函數:
BOOL GetThreadContext(HANDLE hThread,LPCONTEXT lpContext);
//params:擁有上下文的線程句柄,接受上下文的結構體地址
- SetThreadContext
BOOL SetThreadContext(HANDLE hThread,CONST CONTEXT *lpContext);
//params:擁有上下文的線程句柄,存放上下文的結構體地址
- 使用這些API函數就可以隨意干預其他進程
- 運行改良版的調試器
- 運行一下wdbg02a.exe,準備一個會發生異常的程序test.exe,然後將這個程序作爲參數來運行wdbg02a.exe。
- test.cpp
int main(int argc, char *argv[])
{
char *s = NULL;
*s = 0xFF; return 0;
}
- 可以看到mov byte[eax],0xff的地方發生了2個異常,這裏對應源代碼中的*s=0xFF。
在其他進程中運行任意代碼:代碼注入
- 在其他進程中運行任意代碼的手法,統稱爲代碼注入(code injection)。在使用DLL的情況下,一般叫做“DLL注入”,但“在其他進程中運行自己的代碼”這一點是共同的。
- 用SetWindowsHookEx劫持系統消息
- 使用下面三個API函數,我們就可以劫持系統消息:(可以用於單個線程,也可以用於進程)
SetWindowsHookEx
HHOOK SetWindowsHookEx(int idHook,HOOKPORC lpfn,HINSTANCE hMod,DWORD dwThreadId);
//params:鉤子類型,鉤子過程,應用程序示例的句柄,線程ID
CallNextHookEx
LRESULT CallNextHookEx(HHOOK hhk,int nCode,WPARAM wParam,LPARAM lParam);
//當前鉤子的句柄,傳遞給鉤子過程的代碼,傳遞給鉤子過程的值,傳遞給鉤子過程的值
UnhookWindowsHookEx
BOOL UnhookWindowsHookEx(HHOOK hhk);
//要解除的對象的鉤子過程句柄
- 試一下SetWindowsHookEx
loging.h
#ifdef LOGING_EXPORTS
#define LOGING_API extern "C" __declspec(dllexport)
#else
#define LOGING_API extern "C" __declspec(dllimport)
#endif
LOGING_API int CallSetWindowsHookEx(VOID);
LOGING_API int CallUnhookWindowsHookEx(VOID);
loging.cpp
#include "stdafx.h"
#include "loging.h"
HHOOK g_hhook = NULL;
//系統消息再傳遞給目標線程原有的窗口過程之前,先由GetMsgProc來進行處理
static LRESULT WINAPI GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
{
return(CallNextHookEx(NULL, code, wParam, lParam));
//調用了CallNextHookEx函數,這時消息會繼續傳遞給下一個鉤子過程
}
LOGING_API int CallSetWindowsHookEx(VOID)
{
if(g_hhook != NULL)
return -1;
MEMORY_BASIC_INFORMATION mbi;
if(VirtualQuery(CallSetWindowsHookEx, &mbi, sizeof(mbi)) == 0)
return -1;
HMODULE hModule = (HMODULE) mbi.AllocationBase;
g_hhook = SetWindowsHookEx(
WH_GETMESSAGE, GetMsgProc, hModule, 0);
//SetWindowsHookEx的功能是將原來傳遞給窗口過程的消息劫持下來,交給的第2個參數指定的函數來進行處理
if(g_hhook == NULL)
return -1;
return 0;
}
LOGING_API int CallUnhookWindowsHookEx(VOID)
{
if(g_hhook == NULL)
return -1;
UnhookWindowsHookEx(g_hhook);
g_hhook = NULL;
return 0;
}
- 這些API是用來劫持消息的,但如果要劫持其他進程的窗口過程消息,那麼就需要再其他進程中加載我們的DLL。
- 可以將loging.cpp編譯成DLL,然後調用SetWindowsHookEx,將其第4參數(dwThreadId)設爲0,這樣,我們就可以對持有窗口過程的進程和線程應用鉤子,也就是讓這些進程加載我們的DLL。
- dllmain.cpp
#include "stdafx.h"
int WriteLog(TCHAR *szData)
{
TCHAR szTempPath[1024];
GetTempPath(sizeof(szTempPath), szTempPath);
lstrcat(szTempPath, "loging.log");
TCHAR szModuleName[1024];
GetModuleFileName(GetModuleHandle(NULL),
szModuleName, sizeof(szModuleName));
TCHAR szHead[1024];
wsprintf(szHead, "[PID:%d][Module:%s] ",
GetCurrentProcessId(), szModuleName);
HANDLE hFile = CreateFile(
szTempPath, GENERIC_WRITE, 0, NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile == INVALID_HANDLE_VALUE)
return -1;
SetFilePointer(hFile, 0, NULL, FILE_END);
DWORD dwWriteSize;
WriteFile(hFile, szHead, lstrlen(szHead), &dwWriteSize, NULL);
WriteFile(hFile, szData, lstrlen(szData), &dwWriteSize, NULL);
CloseHandle(hFile);
return 0;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
WriteLog("DLL_PROCESS_ATTACH\n");
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
WriteLog("DLL_PROCESS_DETACH\n");
break;
}
return TRUE;
}
- 向dllmain.cpp中添加一些代碼,使得在DLL成功加載之後,向%TEMP%目錄輸出一個名爲loging.log的日誌文件,日誌的內容包括進程ID和模塊路徑。
- 將dllmaincpp、loging.cpp和loging.h進程編譯,然後我們編寫另一個程序,加載這個DLL並調用CallSetWindowsHookEx。
- setwindowshook.cpp
#include "stdafx.h"
#include <Windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
if(argc < 2){
fprintf(stderr, "%s <DLL Name>\n", argv[0]);
return 1;
}
//從命令行傳入要載入的DLL
HMODULE h = LoadLibrary(argv[1]);
if(h == NULL)
return -1;
int (__stdcall *fcall) (VOID);
fcall = (int (WINAPI *)(VOID))
GetProcAddress(h, "CallSetWindowsHookEx");
if(fcall == NULL){
fprintf(stderr, "ERROR: GetProcAddress\n");
goto _Exit;
}
int (__stdcall *ffree) (VOID);
ffree = (int (WINAPI *)(VOID))
GetProcAddress(h, "CallUnhookWindowsHookEx");
if(ffree == NULL){
fprintf(stderr, "ERROR: GetProcAddress\n");
goto _Exit;
}
if(fcall()){
fprintf(stderr, "ERROR: CallSetWindowsHookEx\n");
goto _Exit;
}
printf("Call SetWindowsHookEx\n");
getchar();
if(ffree()){
fprintf(stderr, "ERROR: CallUnhookWindowsHookEx\n");
goto _Exit;
}
printf("Call UnhookWindowsHookEx\n");
_Exit:
FreeLibrary(h);
return 0;
}
- 運行示例
- 打開文件C:\Users\ 用戶名 \AppData\Local\Temp\loging.log
- 可以看到其他進程以及加載了loging.dll
- 將DLL路徑配置到註冊表的AppInit_DLLS項
- SetWindowsHookEx可以在調用時將DLL映射到其他進程中,不過如果我們將DLL的路徑配置在註冊表的APPInit_DLLS項中,就可以在系統啓動時將任意DLL加載到其他進程中。
- 運行regedit,找到下面的路徑
- Windows7中多了一個叫做RequireSignedAppInit_DLLs的項,這一項代表只允許加載經過簽名的DLL。
- 在64位系統中,關於32位程序的相關設定已被重定向到Wow6432Node中。
- AppInit_DLLs中所配置的DLL是通過user32.dll來加載的,因此,對於原本就不依賴user32.dll的進程來說,這一配置是無效的。
- writeappinit.cpp
#include "stdafx.h"
#include <Windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
if(argc < 2){
fprintf(stderr, "%s <DLL Name>\n", argv[0]);
return 1;
}
HKEY hKey;
LSTATUS lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows",
NULL, KEY_ALL_ACCESS, &hKey);
if(lResult != ERROR_SUCCESS){
printf("Error: RegOpenKeyEx failed.\n");
return -1;
}
DWORD dwSize, dwType;
TCHAR szDllName[256];
RegQueryValueEx(hKey, "AppInit_DLLs", NULL, &dwType, NULL, &dwSize);
RegQueryValueEx(hKey, "AppInit_DLLs", NULL, &dwType, (LPBYTE)szDllName, &dwSize);
printf("AppInit_DLLs: %s -> ", szDllName);
lstrcpy(szDllName, argv[1]);
lResult = RegSetValueEx(hKey, "AppInit_DLLs",
0, REG_SZ, (PBYTE)szDllName, lstrlen(szDllName) + 1);
if(lResult != ERROR_SUCCESS){
printf("Error: RegSetValueEx failed.\n");
}
RegQueryValueEx(hKey, "AppInit_DLLs", NULL, &dwType, NULL, &dwSize);
RegQueryValueEx(hKey, "AppInit_DLLs", NULL, &dwType, (LPBYTE)szDllName, &dwSize);
printf("%s\n", szDllName);
RegCloseKey(hKey);
return 0;
}
- writeappinit.cpp可以向註冊表的LoadAppInit_DLLs項寫入任意值,可以指定loging.dll的路徑並運行這個程序。
- 凡是加載了user32.dll的進程,同時也會加載loging.dll。
- 通過CreateRomoteThread在其他進程中創建線程
- 可以用CreateRomoteThread這個API函數在其他進程中創建線程,這個函數可以在新線程中運行LoadLibrary,從而使得其他進程強制加載某個DLL。
HANDLE CreateRemoteThread(
HANDLE hProcess, //進程句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize, // 棧初始長度
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter, // 新線程的參數指針
DWORD dwCreationFlags, //創建標誌
LPDWORD lpThreadId // 分配的線程ID指針
);
- LoadLibrary的參數必須位於目標進程內部,因此,LoadLibrary所需要的參數字符串必須事先寫入目標進程的內存空間。
- injectcode.h
int InjectDLLtoProcessFromName(TCHAR *szTarget, TCHAR *szDllPath);
//按照可執行文件名找到相應的進程並注入DLL
int InjectDLLtoProcessFromPid(DWORD dwPid, TCHAR *szDllPath);
//按照進程ID找到相應的進程並注入DLL
int InjectDLLtoNewProcess(TCHAR *szCommandLine, TCHAR *szDllPath);
//創建新的進程並注入DLL
- dllinjection.cpp
#include "stdafx.h"
#include <tlhelp32.h>
#include "injectcode.h"
DWORD GetProcessIdFromName(TCHAR *szTargetProcessName)
{
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if(hSnap == INVALID_HANDLE_VALUE)
return 0;
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
DWORD dwProcessId = 0;
BOOL bResult = Process32First(hSnap, &pe);
while(bResult){
if(!lstrcmp(pe.szExeFile, szTargetProcessName)){
dwProcessId = pe.th32ProcessID;
break;
}
bResult = Process32Next(hSnap, &pe);
}
CloseHandle(hSnap);
return dwProcessId;
}
int InjectDLL(HANDLE hProcess, TCHAR *szDllPath)
{
int szDllPathLen = lstrlen(szDllPath) + 1;
PWSTR RemoteProcessMemory = (PWSTR)VirtualAllocEx(hProcess,
NULL, szDllPathLen, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
if(RemoteProcessMemory == NULL)
return -1;
BOOL bRet = WriteProcessMemory(hProcess,
RemoteProcessMemory, (PVOID)szDllPath, szDllPathLen, NULL);
if(bRet == FALSE)
return -1;
PTHREAD_START_ROUTINE pfnThreadRtn;
pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(
GetModuleHandle("kernel32"), "LoadLibraryA");
if(pfnThreadRtn == NULL)
return -1;
//創建一個新的線程
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
pfnThreadRtn, RemoteProcessMemory, 0, NULL);
if(hThread == NULL)
return -1;
WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hProcess,
RemoteProcessMemory, szDllPathLen, MEM_RELEASE);
CloseHandle(hThread);
return 0;
}
int InjectDLLtoExistedProcess(DWORD dwPid, TCHAR *szDllPath)
{
HANDLE hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_VM_READ | PROCESS_VM_WRITE |
PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION , FALSE, dwPid);
if(hProcess == NULL)
return -1;
/*
BOOL bJudgeWow64;
IsWow64Process(hProcess, &bJudgeWow64);
if(bJudgeWow64 == FALSE){
CloseHandle(hProcess);
return -1;
}
*/
if(InjectDLL(hProcess, szDllPath))
return -1;
CloseHandle(hProcess);
return 0;
}
int InjectDLLtoProcessFromName(TCHAR *szTarget, TCHAR *szDllPath)
{
DWORD dwPid = GetProcessIdFromName(szTarget);
if(dwPid == 0)
return -1;
if(InjectDLLtoExistedProcess(dwPid, szDllPath))
return -1;
return 0;
}
int InjectDLLtoProcessFromPid(DWORD dwPid, TCHAR *szDllPath)
{
if(InjectDLLtoExistedProcess(dwPid, szDllPath))
return -1;
return 0;
}
int InjectDLLtoNewProcess(TCHAR *szCommandLine, TCHAR *szDllPath)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
BOOL bResult = CreateProcess(NULL, szCommandLine, NULL, NULL,
FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
if(bResult == FALSE)
return -1;
int nRet = -1;
/*
BOOL bJudgeWow64;
IsWow64Process(pi.hProcess, &bJudgeWow64);
if(bJudgeWow64 == FALSE)
goto _Exit;
*/
if(InjectDLL(pi.hProcess, szDllPath))
goto _Exit;
nRet = 0;
_Exit:
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return nRet;
}
- 運行示例
C:\>dllinjection.exe Name iexplore.exe "C:\\sampledll.dll"
- 在iexplore.exe中加載C:\sampledll.dll。(sampledll.dll只是顯示一條對話框消息),sampledll.dll是一個能夠顯示DLL加載/卸載狀態消息的程序,dllinjection.exe 運行時以及 IE 關閉時都會彈出相應的消息框。
- 注入函數
- CreateRomoteThread調用了LoadLibrary,當然,不僅僅是DLL,只要我們能夠將任意函數(代碼)事先複製到目標進程內部,就可以用CreateRemoteThread來運行它。
#include "stdafx.h"
#include <windows.h>
typedef HWND (WINAPI *GETFORGROUNDWINDOW)(void);
typedef int (WINAPI *MSGBOX)(HWND, PCTSTR, PCTSTR, UINT);
typedef struct _injectdata {
TCHAR szTitle[32];
TCHAR szMessage[32];
HANDLE hProcess;
PDWORD pdwCodeRemote;
PDWORD pdwDataRemote;
MSGBOX fnMessageBox;
GETFORGROUNDWINDOW fnGetForegroundWindow;
} INJECTDATA, *PINJECTDATA;
static DWORD WINAPI func(PINJECTDATA myAPI)
{
myAPI->fnMessageBox((HWND)myAPI->fnGetForegroundWindow(),
myAPI->szMessage, myAPI->szTitle, MB_OK);
/*
if(myAPI->pCodeRemote != NULL)
VirtualFreeEx(myAPI->hProcess,
myAPI->pCodeRemote, 0, MEM_RELEASE);
if(myAPI->pDataRemote != NULL)
VirtualFreeEx(myAPI->hProcess,
myAPI->pDataRemote, 0, MEM_RELEASE);
*/
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
HMODULE h = LoadLibrary("user32.dll");
if(h == NULL){
printf("ERR: LoadLibrary\n");
return -1;
}
INJECTDATA id;
id.fnGetForegroundWindow = (GETFORGROUNDWINDOW)
GetProcAddress(
GetModuleHandle("user32"), "GetForegroundWindow");
id.fnMessageBox = (MSGBOX)
GetProcAddress(
GetModuleHandle("user32"), "MessageBoxA");
lstrcpy(id.szTitle, "Message");
lstrcpy(id.szMessage, "Hello World!");
HWND hTarget = FindWindow("IEFrame", NULL);
if(hTarget == NULL){
printf("ERR: FindWindow\n");
goto _END1;
}
DWORD dwPID; // PID of iexplore.exe
GetWindowThreadProcessId(hTarget, (DWORD *)&dwPID);
id.hProcess = OpenProcess(PROCESS_CREATE_THREAD |
PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION |
PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, dwPID);
if(id.hProcess == NULL){
printf("ERR: OpenProcess\n");
goto _END1;
}
DWORD dwLen;
if((id.pdwCodeRemote = (PDWORD)VirtualAllocEx(id.hProcess,
0, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) == NULL)
{
printf("ERR: VirtualAllocEx(pdwCodeRemote)\n");
goto _END2;
}
if((id.pdwDataRemote = (PDWORD)VirtualAllocEx(id.hProcess,
0, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) == NULL)
{
printf("ERR: VirtualAllocEx(pdwDataRemote)\n");
goto _END3;
}
WriteProcessMemory(id.hProcess,
id.pdwCodeRemote, &func, 4096, &dwLen);
WriteProcessMemory(id.hProcess,
id.pdwDataRemote, &id, sizeof(INJECTDATA), &dwLen);
HANDLE hThread = CreateRemoteThread(id.hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)id.pdwCodeRemote, id.pdwDataRemote,
0, &dwLen);
if(hThread == NULL){
printf("ERR: CreateRemoteThread\n");
goto _END4;
}
WaitForSingleObject(hThread, INFINITE);
GetExitCodeThread(hThread, (PDWORD)&dwPID);
CloseHandle(hThread);
_END4:
VirtualFreeEx(id.hProcess, id.pdwDataRemote, 0, MEM_RELEASE);
_END3:
VirtualFreeEx(id.hProcess, id.pdwCodeRemote, 0, MEM_RELEASE);
_END2:
CloseHandle(id.hProcess);
_END1:
FreeLibrary(h);
return 0;
}
- 運行codeinjection.exe時,就會將func函數注入到IE中並運行它,func函數的功能時顯示一個Hello World!消息框。
- 在Windows中,只要擁有足夠的權限,就可以隨意訪問其他進程的內存空間,因此我們基本上可以自由地向其他進程注入代碼,而且即便我們的程序不是調試器,也可以比較容易地騙過其他進程。
API鉤子
- 在程序中插入額外的邏輯稱爲鉤子,而其中對API插入額外邏輯稱爲“API鉤子”。
- API鉤子大體上可分爲兩種類型:
- 改寫目標函數開頭幾個字節
- 改寫IAT(Import Address Table,導入地址表)
- 用Detours實現一個簡單的API鉤子
- 使用Detours庫我們用幾十行就可以實現一個API鉤子,只要我們知道DLL所導出的函數,就可以在運行時對該函數的調用進行劫持。
- detourshook.h
#ifdef DETOURSHOOK_EXPORTS
#define DETOURSHOOK_API __declspec(dllexport)
#else
#define DETOURSHOOK_API __declspec(dllimport)
#endif
DETOURSHOOK_API int WINAPI HookedMessageBoxA(HWND hWnd,
LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
- dllmain.cpp
#include "stdafx.h"
#include "detours.h"
#include "detourshook.h"
static int (WINAPI * TrueMessageBoxA)(HWND hWnd, LPCTSTR lpText,
LPCTSTR lpCaption, UINT uType) = MessageBoxA;
DETOURSHOOK_API int WINAPI HookedMessageBoxA(HWND hWnd,
LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
int nRet = TrueMessageBoxA(hWnd, lpText, "Hooked Message", uType);
return nRet;
}
int DllProcessAttach(VOID)
{
DetourRestoreAfterWith();
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)TrueMessageBoxA, HookedMessageBoxA);
if(DetourTransactionCommit() == NO_ERROR)
return -1;
return 0;
}
int DllProcessDetach(VOID)
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)TrueMessageBoxA, HookedMessageBoxA);
DetourTransactionCommit();
return 0;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
DllProcessAttach();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
DllProcessDetach();
break;
}
return TRUE;
}
- 上面代碼可以將user32.dll導出的函數MessageBoxA替換成HookedMessageBoxA,將下列文件添加到工程並編譯:
detours.cpp
detours.h
disasm.cpp
modules.cpp
detver.h
- 當DllMain收到DLL_PROCESS_ATTACH消息時,會調用DllProcessAttach()函數,也就是說,當DLL被加載到進程中時,API鉤子就開始生效了。
- DllProcessAttach用於掛載鉤子,DllProcessDetach用於解除鉤子,在函數內部,會先調用DetourTransactionBegin和DetourUpdateThread,然後再用DetourAttach或DetourDetach來掛載或解除鉤子。
- 最後程序調用DetourTransctionCommint函數並推出。
- 修改消息框的標題欄
- HookedMessageBoxA函數的內部會調用TureMessageBoxA,也就是原始的MessageBoxA函數。
- 按下面的代碼編寫一段簡單的程序並運行:
#include "stdafx.h"
#include <Windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
HMODULE h = LoadLibrary("detourshook.dll");
MessageBoxA(GetForegroundWindow(),
"Hello World! using MessageBoxA", "Message", MB_OK);
FreeLibrary(h);
return 0;
}
- 運行
- 標題欄從Message變成了Hooked Message
- 鉤子的原理時將函數開頭的幾個字節替換成jmp指令,強制跳轉到另一個函數。
- 上面講到的API鉤子基本上只適用於運行在用戶領域的DLL所導致的函數,但我們也可以通過劫持非公開的API等方式,對運行在內核領域(Ring0)的驅動程序掛載鉤子。