有趣的二進制-自由控制程序運行方式的編程技巧

通過自制調試器來理解其原理

  1. 先來查看一段簡單的調試器代碼
#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等事件都被調試器捕捉到了。
  1. 實現反彙編功能
  • 希望在發生異常時,能夠顯示異常的地址以及當前寄存器的值,同時,我們還希望顯示發生異常所執行的指令,因此我們來實現反彙編功能。
  • 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函數就可以隨意干預其他進程
  1. 運行改良版的調試器
  • 運行一下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。

在其他進程中運行任意代碼:代碼注入

  1. 在其他進程中運行任意代碼的手法,統稱爲代碼注入(code injection)。在使用DLL的情況下,一般叫做“DLL注入”,但“在其他進程中運行自己的代碼”這一點是共同的。
  2. 用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
  1. 將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。
  1. 通過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 關閉時都會彈出相應的消息框。
  1. 注入函數
  • 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鉤子

  1. 在程序中插入額外的邏輯稱爲鉤子,而其中對API插入額外邏輯稱爲“API鉤子”。
  2. API鉤子大體上可分爲兩種類型:
  • 改寫目標函數開頭幾個字節
  • 改寫IAT(Import Address Table,導入地址表)
  1. 用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函數並推出。
  1. 修改消息框的標題欄
  • 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)的驅動程序掛載鉤子。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章