windows安全:hook

296 阅读13分钟

内联hook

内联hook的原理是在目标函数的头部,插入一段二进制指令.一般是jmp ret 来实现跳转到hook函数,先进行hook函数的逻辑,然后再取消hook,跳回到目标函数,最后重新hook.再返回. 整体的思路是在原函数的中间,增加一层hook逻辑.增加的方式是修改函数开头部分的二进制来实现.

32位inline-hook
#define FUNC_LEN sizeof(DWORD_PTR)

static PROC GProcFunc = NULL;
static BYTE oldHead[FUNC_LEN + 1] = { 0 };
static BYTE hookHead[FUNC_LEN + 1] = { 0 };

// hook 某个函数
static BOOL Hook(const char* pszModuleName, const char* pszFuncName, PROC pfnHookFunc);

// 取消hook
static BOOL UnHook();

// 重新hook
static BOOL ReHook();


// HOOK 函数
static int WINAPI HookMessageBox(
	_In_opt_ HWND hWnd,
	_In_opt_ LPCSTR lpText,
	_In_opt_ LPCSTR lpCaption,
	_In_ UINT uType)
{
	// 先执行自定义的逻辑
	printf("Hook MessageBox: %s - %s\n", lpText, lpCaption);

	// 取消HOOK
	UnHook();

	// 调用原函数
	std::string strText(lpText);
	std::string strCaption(lpCaption);
	strText = strText + " - Hooked";
	strCaption = strCaption + " - Hooked";

	int rest = MessageBoxA(hWnd, strText.c_str(), strCaption.c_str(), uType);

	// 重新HOOK
	ReHook();

	return rest;
}


static BOOL Hook(const char* pszModuleName, const char* pszFuncName, PROC pfnHookFunc)
{
	// 获取需要hook的原函数地址
	HMODULE hModule = GetModuleHandleA(pszModuleName);
	if (hModule == NULL)
	{
		printf("GetModuleHandleA failed: %d\n", GetLastError());
		return FALSE;
	}
	GProcFunc = GetProcAddress(hModule, pszFuncName);
	if (GProcFunc == NULL)
	{
		printf("GetProcAddress failed: %d\n", GetLastError());
		return FALSE;
	}

	// 保存原函数头5个字节
	DWORD dwOldProtect;
	VirtualProtectEx(GetCurrentProcess(), oldHead, FUNC_LEN + 1, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	SIZE_T nRead;
	ReadProcessMemory(GetCurrentProcess(), GProcFunc, oldHead, FUNC_LEN + 1, &nRead);
	if (nRead != (FUNC_LEN + 1))
	{
		printf("ReadProcessMemory failed: %d\n", GetLastError());
		return FALSE;
	}
	// 修改原函数头5个字节为跳转指令 jmp 0x12345678
	hookHead[0] = 0xE9;
	DWORD_PTR dwOffset = (DWORD_PTR)pfnHookFunc - (DWORD_PTR)GProcFunc - (FUNC_LEN + 1);

	memcpy(hookHead + 1, &dwOffset, FUNC_LEN);
	printf("jmp address: %llx\n", *(DWORD_PTR*)(hookHead + 1));

	// *(DWORD_PTR*)(hookHead + 1) = dwOffset;

	// 写入修改后的指令
	WriteProcessMemory(GetCurrentProcess(), GProcFunc, hookHead, FUNC_LEN + 1, &nRead);
	return TRUE;
}


static BOOL UnHook()
{
	if (GProcFunc != NULL)
	{
		// 恢复原函数头5个字节
		DWORD dwOldProtect;
		BOOL protect = VirtualProtectEx(GetCurrentProcess(), GProcFunc, FUNC_LEN + 1, PAGE_EXECUTE_READWRITE, &dwOldProtect);
		if (protect == FALSE)
		{
			printf("VirtualProtectEx failed: %d\n", GetLastError());
			return FALSE;
		}
		SIZE_T nWrite;
		WriteProcessMemory(GetCurrentProcess(), GProcFunc, oldHead, FUNC_LEN + 1, &nWrite);
		if (nWrite != (FUNC_LEN + 1))
		{
			printf("WriteProcessMemory failed: %d\n", GetLastError());
			return FALSE;
		}
		// 这里直接给 可读可写可执行权限,恢复原权限 测试时候抛出了异常
		// VirtualProtectEx(GetCurrentProcess(), GProcFunc, FUNC_LEN + 1, dwOldProtect, &dwOldProtect);
	}
	return TRUE;
}


static BOOL ReHook()
{
	if (GProcFunc != NULL)
	{
		DWORD dwOldProtect;
		BOOL protect = VirtualProtectEx(GetCurrentProcess(), GProcFunc, FUNC_LEN + 1, PAGE_EXECUTE_READWRITE,
			&dwOldProtect);
		if (protect == FALSE)
		{
			printf("VirtualProtectEx failed: %d\n", GetLastError());
			return FALSE;
		}
		SIZE_T nWrite;
		WriteProcessMemory(GetCurrentProcess(), GProcFunc, hookHead, FUNC_LEN + 1, &nWrite);
		if (nWrite != (FUNC_LEN + 1))
		{
			printf("WriteProcessMemory failed: %d\n", GetLastError());
			return FALSE;
		}

		// VirtualProtectEx(GetCurrentProcess(), GProcFunc, FUNC_LEN + 1, dwOldProtect, &dwOldProtect);
	}
	return TRUE;
}

测试如下:

int main()
{

	// 先进行Hook
	Hook("user32.dll", "MessageBoxA", (PROC)HookMessageBox);

	// 调用
	MessageBoxA(NULL, "测试box", "啊啊啊", MB_OK | MB_HELP);


	return 0;
}
64位inline-hook

内联hook的原理如下: 在目标函数头部的插入12byte,来进行无条件跳转到hook函数.

mov rax, hook函数地址 // 48 b8 12 34 56 78 12 34 56 78 (64位地址)
push rax // 50
ret // C3

或者采用jmp rax

mov rax, hook函数地址 // 48 b8 12 34 56 78 12 34 56 78(64位地址)
jmp rax // FF E0

示例代码:


static int WINAPI HookMessageBox64(
	_In_opt_ HWND hWnd,
	_In_opt_ LPCSTR lpText,
	_In_opt_ LPCSTR lpCaption,
	_In_ UINT uType)
{
	// 先执行自定义的逻辑
	printf("Hook MessageBox: %s - %s\n", lpText, lpCaption);

	// 取消HOOK
	UnHook64();

	// 调用原函数
	std::string strText(lpText);
	std::string strCaption(lpCaption);
	strText = strText + " - Hooked";
	strCaption = strCaption + " - Hooked";

	int rest = MessageBoxA(hWnd, strText.c_str(), strCaption.c_str(), uType);

	// 重新HOOK
	ReHook64();

	return rest;
}

static PROC GProcFunc64 = NULL;
static BYTE oldHead64[12] = { 0 };
static BYTE hookHead64[12] = { 0 };




static BOOL Hook64(const char* pszModuleName, const char* pszFuncName, PROC pfnHookFunc)
{
	// 获取需要hook的原函数地址
	HMODULE hModule = GetModuleHandleA(pszModuleName);
	if (hModule == NULL)
	{
		printf("GetModuleHandleA failed: %d\n", GetLastError());
		return FALSE;
	}
	GProcFunc64 = GetProcAddress(hModule, pszFuncName);
	if (GProcFunc64 == NULL)
	{
		printf("GetProcAddress failed: %d\n", GetLastError());
		return FALSE;
	}

	// 保存原函数头12个字节
	DWORD dwOldProtect;
	VirtualProtectEx(GetCurrentProcess(), GProcFunc64, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	SIZE_T nRead;
	ReadProcessMemory(GetCurrentProcess(), GProcFunc64, oldHead64, 12, &nRead);
	if (nRead != 12)
	{
		printf("ReadProcessMemory failed: %d\n", GetLastError());
		return FALSE;
	}
	// 修改原函数头12个字节为跳转指令 mov rax,0x12345678
	hookHead64[0] = 0x48;
	hookHead64[1] = 0xB8;
	// ret 跳转的是内存地址,所以这里存入的是hook函数在内存中的地址
	memcpy(hookHead64 + 2, &pfnHookFunc, sizeof(DWORD_PTR));
	// *(DWORD_PTR*)(hookHead + 1) = dwOffset;
	hookHead64[10] = 0x50; // push rax
	hookHead64[11] = 0xC3;// ret

	// 写入修改后的指令
	WriteProcessMemory(GetCurrentProcess(), GProcFunc64, hookHead64, 12, &nRead);
	return TRUE;

}


static BOOL UnHook64()
{
	if (GProcFunc64 != NULL)
	{
		// 恢复原函数头5个字节
		DWORD dwOldProtect;
		BOOL protect = VirtualProtectEx(GetCurrentProcess(), GProcFunc64, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);
		if (protect == FALSE)
		{
			printf("VirtualProtectEx failed: %d\n", GetLastError());
			return FALSE;
		}
		SIZE_T nWrite;
		WriteProcessMemory(GetCurrentProcess(), GProcFunc64, oldHead64, 12, &nWrite);
		if (nWrite != 12)
		{
			printf("WriteProcessMemory failed: %d\n", GetLastError());
			return FALSE;
		}
		// 这里直接给 可读可写可执行权限,恢复原权限 测试时候抛出了异常
		// VirtualProtectEx(GetCurrentProcess(), GProcFunc, FUNC_LEN + 1, dwOldProtect, &dwOldProtect);
	}
	return TRUE;
}


static BOOL ReHook64()
{
	if (GProcFunc64 != NULL)
	{
		DWORD dwOldProtect;
		BOOL protect = VirtualProtectEx(GetCurrentProcess(), GProcFunc64, 12, PAGE_EXECUTE_READWRITE,
			&dwOldProtect);
		if (protect == FALSE)
		{
			printf("VirtualProtectEx failed: %d\n", GetLastError());
			return FALSE;
		}
		SIZE_T nWrite;
		WriteProcessMemory(GetCurrentProcess(), GProcFunc64, hookHead64, 12, &nWrite);
		if (nWrite != 12)
		{
			printf("WriteProcessMemory failed: %d\n", GetLastError());
			return FALSE;
		}

		// VirtualProtectEx(GetCurrentProcess(), GProcFunc, FUNC_LEN + 1, dwOldProtect, &dwOldProtect);
	}
	return TRUE;
}

测试代码:


int main()
{

	// 先进行Hook
	Hook64("user32.dll", "MessageBoxA", (PROC)HookMessageBox64);

	// 调用
	MessageBoxA(NULL, "测试box", "啊啊啊", MB_OK | MB_HELP);


	return 0;
}

HotFixHook

与内联hook的原理基本一致,区别是jmp对应的偏移是一个32位的地址.所以可以固定在函数头部偏移5byte(32位),12byte(64位)进行2次跳转来实现hook. 这种好处是对函数入侵小,仅为2byte.实际的跳转逻辑是在函数前面的5-12byte中

jmp 0X123456789 //  E9 12 34 56 78
jmp -7 // EB F9

示例代码:


static PROC GProcFunc2 = NULL;
static BYTE oldHead2[2] = { 0 };
static BYTE oldHead2_jmp[5] = { 0 };
static BYTE hookHead2[2] = { 0xEB,0XF9 };// JMP -7
static BYTE hookHead2_jmp[5] = { 0xE9 }; // JMP 0x12345678

static BOOL Hook2(const char* pszModuleName, const char* pszFuncName, PROC pfnHookFunc);

static BOOL UnHook2();

static BOOL ReHook2();

static int WINAPI HookMessageBox2(
	_In_opt_ HWND hWnd,
	_In_opt_ LPCSTR lpText,
	_In_opt_ LPCSTR lpCaption,
	_In_ UINT uType)
{
	// 先执行自定义的逻辑
	printf("Hook MessageBox: %s - %s\n", lpText, lpCaption);

	// 取消HOOK
	UnHook2();

	// 调用原函数
	std::string strText(lpText);
	std::string strCaption(lpCaption);
	strText = strText + " - Hooked";
	strCaption = strCaption + " - Hooked";

	int rest = MessageBoxA(hWnd, strText.c_str(), strCaption.c_str(), uType);

	// 重新HOOK
	ReHook2();

	return rest;
}

static BOOL Hook2(const char* pszModuleName, const char* pszFuncName, PROC pfnHookFunc)
{
	// 获取需要hook的原函数地址
	HMODULE hModule = GetModuleHandleA(pszModuleName);
	if (hModule == NULL)
	{
		printf("GetModuleHandleA failed: %d\n", GetLastError());
		return FALSE;
	}
	GProcFunc2 = GetProcAddress(hModule, pszFuncName);
	if (GProcFunc2 == NULL)
	{
		printf("GetProcAddress failed: %d\n", GetLastError());
		return FALSE;
	}

	// 保存原函数头2个字节
	DWORD dwOldProtect;
	BOOL protect = VirtualProtectEx(GetCurrentProcess(), GProcFunc2, 2, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	SIZE_T nRead;
	ReadProcessMemory(GetCurrentProcess(), GProcFunc2, oldHead2, 2, &nRead);
	if (nRead != 2)
	{
		printf("ReadProcessMemory failed: %d\n", GetLastError());
		return FALSE;
	}
	// jmp -7
	VirtualProtectEx(GetCurrentProcess(), GProcFunc2, 2, dwOldProtect, &dwOldProtect);
	WriteProcessMemory(GetCurrentProcess(), GProcFunc2, hookHead2, 2, &nRead);
	if (nRead != 2)
	{
		printf("WriteProcessMemory failed: %d\n", GetLastError());
		return FALSE;
	}

	//jmp 0x12345678
	LPVOID lpvoid = (LPVOID)((DWORD_PTR)GProcFunc2 - 5);
	protect = VirtualProtectEx(GetCurrentProcess(), lpvoid, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	ReadProcessMemory(GetCurrentProcess(), lpvoid, oldHead2_jmp, 5, &nRead);
	if (nRead != 5)
	{
		printf("ReadProcessMemory failed: %d\n", GetLastError());
		return FALSE;
	}

	DWORD_PTR dwOffset = (DWORD_PTR)pfnHookFunc - (DWORD_PTR)GProcFunc2 - 5;
	*(DWORD_PTR*)(hookHead2_jmp + 1) = dwOffset;// 等价 memcpy(hookHead2 + 1, &dwOffset, 4);

	WriteProcessMemory(GetCurrentProcess(), lpvoid, hookHead2_jmp, 5, &nRead);
	if (nRead != 5)
	{
		printf("WriteProcessMemory failed: %d\n", GetLastError());
		return FALSE;
	}
	return TRUE;

}


static BOOL UnHook2()
{
	if (GProcFunc2 != NULL)
	{
		// 恢复原函数头2个字节
		DWORD dwOldProtect;
		BOOL protect = VirtualProtectEx(GetCurrentProcess(), GProcFunc2, 2, PAGE_EXECUTE_READWRITE, &dwOldProtect);
		if (protect == FALSE)
		{
			printf("VirtualProtectEx failed: %d\n", GetLastError());
			return FALSE;
		}
		SIZE_T nWrite;
		WriteProcessMemory(GetCurrentProcess(), GProcFunc2, oldHead2, 2, &nWrite);

		VirtualProtectEx(GetCurrentProcess(), GProcFunc2, 2, dwOldProtect, &dwOldProtect);
		// 恢复原函数前5字节
		protect = VirtualProtectEx(GetCurrentProcess(), (LPVOID)((DWORD_PTR)GProcFunc2 - 5), 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
		if (protect == FALSE)
		{
			printf("VirtualProtectEx2 failed: %d\n", GetLastError());
			return FALSE;
		}
		WriteProcessMemory(GetCurrentProcess(), (LPVOID)((DWORD_PTR)GProcFunc2 - 5), oldHead2_jmp, 5, &nWrite);
		protect = VirtualProtectEx(GetCurrentProcess(), (LPVOID)((DWORD_PTR)GProcFunc2 - 5), 5, dwOldProtect, &dwOldProtect);
	}
}


static BOOL ReHook2()
{
	if (GProcFunc2 != NULL)
	{
		// 恢复原函数头2个字节
		DWORD dwOldProtect;
		BOOL protect = VirtualProtectEx(GetCurrentProcess(), GProcFunc2, 2, PAGE_EXECUTE_READWRITE, &dwOldProtect);
		if (protect == FALSE)
		{
			printf("VirtualProtectEx failed: %d\n", GetLastError());
			return FALSE;
		}
		SIZE_T nWrite;
		WriteProcessMemory(GetCurrentProcess(), GProcFunc2, hookHead2, 2, &nWrite);

		VirtualProtectEx(GetCurrentProcess(), GProcFunc2, 2, dwOldProtect, &dwOldProtect);
		// 恢复原函数前5字节
		protect = VirtualProtectEx(GetCurrentProcess(), (LPVOID)((DWORD_PTR)GProcFunc2 - 5), 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
		if (protect == FALSE)
		{
			printf("VirtualProtectEx2 failed: %d\n", GetLastError());
			return FALSE;
		}
		WriteProcessMemory(GetCurrentProcess(), (LPVOID)((DWORD_PTR)GProcFunc2 - 5), hookHead2_jmp, 5, &nWrite);
		protect = VirtualProtectEx(GetCurrentProcess(), (LPVOID)((DWORD_PTR)GProcFunc2 - 5), 5, dwOldProtect, &dwOldProtect);
	}
}

测试代码:

int main()
{

	// 先进行Hook
	Hook2("user32.dll", "MessageBoxA", (PROC)HookMessageBox2);

	// 调用
	//HookMessageBox2(NULL, "测试box", "啊啊啊", MB_OK | MB_HELP);
	MessageBoxA(NULL, "测试box", "啊啊啊", MB_OK | MB_HELP);


	return 0;
}

X64 的思路是一致的.

IATHook(利用导入地址表进行hook)

windows在调用函数时,会通过导入地址表进行动态的内存分配与调用.我们可以修改导入表指向hook函数,来实现对目标函数的hook.

代码示例:
typedef int (WINAPI* FunTargetBox)(
	_In_opt_ HWND hWnd,
	_In_opt_ LPCSTR lpText,
	_In_opt_ LPCSTR lpCaption,
	_In_ UINT uType);

FunTargetBox oldFunc3 = NULL;


static int WINAPI HookMessageBox3(
	_In_opt_ HWND hWnd,
	_In_opt_ LPCSTR lpText,
	_In_opt_ LPCSTR lpCaption,
	_In_ UINT uType)
{
	// 先执行自定义的逻辑
	printf("Hook MessageBox: %s - %s\n", lpText, lpCaption);

	// 调用原函数
	std::string strText(lpText);
	std::string strCaption(lpCaption);
	strText = strText + " - Hooked";
	strCaption = strCaption + " - Hooked";

	int rest = oldFunc3(hWnd, strText.c_str(), strCaption.c_str(), uType);
	return rest;
}

VOID Hook3(const char* pszModuleName, const char* pszFuncName, PROC pfnHookFunc);

VOID UnHook3(const char* pszModuleName, const char* pszFuncName, PROC pfnHookFunc);



VOID Hook3(const char* pszModuleName, const char* pszFuncName, PROC pfnHookFunc)
{

	oldFunc3 = (FunTargetBox)GetProcAddress(GetModuleHandleA(pszModuleName), pszFuncName);

	// 导入表函数地址获取
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)GetModuleHandle(NULL);
	PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + (DWORD)pDosHeader->e_lfanew);
	PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)&pNtHeader->OptionalHeader;
	DWORD dwImportTableOffset = pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
	// 导入表
	PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDosHeader + dwImportTableOffset);
	DWORD* pFirstThunk;
	while (pImportTable->Characteristics && pImportTable->FirstThunk != NULL)
	{
		pFirstThunk = (DWORD*)(pImportTable->FirstThunk + (DWORD)pDosHeader);
		while (*(DWORD*)pFirstThunk != NULL)
		{
			if (*(DWORD*)pFirstThunk == (DWORD)oldFunc3)
			{
				DWORD dwOldProtect = 0;
				VirtualProtect(pFirstThunk, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect);
				DWORD dwFuncAddr = (DWORD)pfnHookFunc;
				memcpy(pFirstThunk, (DWORD*)&dwFuncAddr, 4);
				VirtualProtect(pFirstThunk, 4, dwOldProtect, &dwOldProtect);
			}
			// 下一个函数
			pFirstThunk++;
		}
		pImportTable++;
	}
}

void UnHook3(const char* pszModuleName, const char* pszFuncName, PROC pfnHookFunc)
{
	if (oldFunc3 == NULL)
	{
		return;
	}
	// 导入表函数地址获取
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)GetModuleHandle(NULL);
	PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + (DWORD)pDosHeader->e_lfanew);
	PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)&pNtHeader->OptionalHeader;
	DWORD dwImportTableOffset = pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
	// 导入表
	PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDosHeader + dwImportTableOffset);
	DWORD* pFirstThunk;
	while (pImportTable->Characteristics && pImportTable->FirstThunk != NULL)
	{
		pFirstThunk = (DWORD*)(pImportTable->FirstThunk + (DWORD)pDosHeader);
		while (*(DWORD*)pFirstThunk != NULL)
		{
			if (*(DWORD*)pFirstThunk == (DWORD)pfnHookFunc)
			{
				DWORD dwOldProtect = 0;
				VirtualProtect(pFirstThunk, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect);
				DWORD dwFuncAddr = (DWORD)oldFunc3;
				memcpy(pFirstThunk, (DWORD*)&dwFuncAddr, 4);
				VirtualProtect(pFirstThunk, 4, dwOldProtect, &dwOldProtect);
			}
			// 下一个函数
			pFirstThunk++;
		}
		pImportTable++;
	}
}

测试代码

int main()
{

	// 先进行Hook
	Hook3("user32.dll", "MessageBoxA", (PROC)HookMessageBox3);

	// 调用
	MessageBoxA(NULL, "测试box", "啊啊啊", MB_OK | MB_HELP);

	UnHook3("user32.dll", "MessageBoxA", (PROC)HookMessageBox3);


	MessageBoxA(NULL, "测试box", "啊啊啊", MB_OK | MB_HELP);

	return 0;
}

X64的代码类似,只需要考虑地址位是8字节即可.

VEH-INT3(UD2/0)-Hook

我们像windows 注册一个向量异常处理程序,然后修改目标函数头部int 3或者ud2或者除0,手动触发一个异常,然后在捕获程序中,进行跳过来实现hook

这里,ESP 进行了4次push.对应的是MessageBoxA的4个参数.

image.png

这里可以看到,每次压入的是4字节,对应地址长度.字符串存储的是内存地址.

示例代码:

// 向量异常的句柄
PVOID vehExceptionHandler = NULL;
DWORD_PTR oldFun4 = NULL;
BYTE oldHead4[2] = { 0 };

// 向量异常处理函数
LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS* pExceptionInfo)
{
	// 获取异常代码
	DWORD dwExceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode;
	if (EXCEPTION_BREAKPOINT != dwExceptionCode && EXCEPTION_ILLEGAL_INSTRUCTION != dwExceptionCode)
	{
		// 不是int 3 异常,继续搜索
		return EXCEPTION_CONTINUE_SEARCH;
	}
	// 获取异常地址
	if ((DWORD)pExceptionInfo->ExceptionRecord->ExceptionAddress != oldFun4)
	{
		// 不是目标地址,继续搜索
		return EXCEPTION_CONTINUE_SEARCH;
	}
	// 这里需要解析目标函数,来进行取参,对应messagebox函数. X86 与X64 解析规则不一样,可以针对函数进行调试
	// EIP是指令地址,ESP 可以用来获取参数
	DWORD_PTR* stackPointer = (DWORD_PTR*)(pExceptionInfo->ContextRecord->Esp);
	// stackPointer[4] ==> uType
	// stackPointer[3] ==> lpCaption
	// stackPointer[2] ==> lpText
	// stackPointer[1] ==> hWnd

	LPCSTR lpText = (char*)(stackPointer[2]);
	std::string strText(lpText);

	LPCSTR lpCaption = (char*)(stackPointer[3]);
	std::string strCaption(lpCaption);

	printf("Hook MessageBox: %s - %s\n", lpText, lpCaption);

	const char* newText = strText.c_str();
	*(stackPointer + 2) = (DWORD_PTR)(&"hook-Msg");
	const char* newCap = strCaption.c_str();
	*(stackPointer + 3) = (DWORD_PTR)(&"hook-Title");

	// 跳过插入的int 3 的指令
	pExceptionInfo->ContextRecord->Eip += 2;
	// 继续执行,指向异常发生的指令,重新尝试执行
	return EXCEPTION_CONTINUE_EXECUTION;
}

VOID Hook4(const char* pszModuleName, const char* pszFuncName, PROC pfnHookFunc)
{
	oldFun4 = (size_t)GetProcAddress(GetModuleHandleA(pszModuleName), pszFuncName);
	// 修改函数第1个字节为0xCC
	DWORD dwOldProtect = 0;
	BOOL protect = VirtualProtectEx(GetCurrentProcess(), (LPVOID)oldFun4, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	if (protect == FALSE)
	{
		printf("VirtualProtectEx failed: %d\n", GetLastError());
		return;
	}
	SIZE_T nRead = 0;
	ReadProcessMemory(GetCurrentProcess(), (LPVOID)oldFun4, oldHead4, 2, &nRead);

	//oldHead4[0] = *(UCHAR*)oldFun4;
	//oldHead4[1] = *((UCHAR*)oldFun4 + 1);
	//*(UCHAR*)oldFun4 = 0xCC;//0xCC int 3 是visual 调试器拦截的异常.
	BYTE ud2[2] = { 0x0F, 0x0B };// ud2 触发EXCEPTION_ILLEGAL_INSTRUCTION 异常.

	WriteProcessMemory(GetCurrentProcess(), (LPVOID)oldFun4, ud2, 2, &nRead);

	protect = VirtualProtectEx(GetCurrentProcess(), (LPVOID)oldFun4, 1, dwOldProtect, &dwOldProtect);
	if (protect == FALSE)
	{
		printf("VirtualProtectEx failed: %d\n", GetLastError());
		return;
	}

	// 插入异常处理
	vehExceptionHandler = AddVectoredExceptionHandler(TRUE, ExceptionHandler);
}

VOID UnHook4(const char* pszModuleName, const char* pszFuncName, PROC pfnHookFunc)
{
	if (oldFun4 == NULL)
	{
		return;
	}
	// 移除异常处理
	if (vehExceptionHandler != NULL)
	{
		RemoveVectoredExceptionHandler(vehExceptionHandler);
	}
	// 恢复函数的第一个字节
	DWORD dwOldProtect = 0;
	BOOL protect = VirtualProtectEx(GetCurrentProcess(), (LPVOID)oldFun4, 2, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	if (protect == FALSE)
	{
		printf("VirtualProtectEx failed: %d\n", GetLastError());
		return;
	}
	((UCHAR*)oldFun4)[0] = oldHead4[0];
	((UCHAR*)oldFun4)[1] = oldHead4[1];
	protect = VirtualProtectEx(GetCurrentProcess(), (LPVOID)oldFun4, 2, dwOldProtect, &dwOldProtect);
}

测试代码

int main()
{

	// 先进行Hook
	Hook4("user32.dll", "MessageBoxA", (PROC)HookMessageBox3);

	// 调用
	MessageBoxA(NULL, "测试box", "啊啊啊", MB_OK | MB_HELP);


	UnHook4("user32.dll", "MessageBoxA", (PROC)HookMessageBox3);
	MessageBoxA(NULL, "测试box", "啊啊啊", MB_OK | MB_HELP);

	return 0;
}

VEH-Debug Registers (利用调试器断点进行hook)

cpu有4个调试寄存器:

  • DR0:通常用于第一个硬件断点。
  • DR1:通常用于第二个硬件断点。
  • DR2:通常用于第三个硬件断点。
  • DR3:通常用于第四个硬件断点。

利用SetThreadContext 设置线程的断点调试.

  • DR0 - DR3:用于设置断点地址。(在何处设置断点)
  • DR6DR7:用于获取调试状态和控制调试行为。

DR7 含义(转为二进制):

  • 位 0 - 1: DR0 (启用/禁用)
  • 位 1 - 0: DR1 (启用/禁用)
  • 位 2 - 1: DR2 (启用/禁用)
  • 位 3 - 0: DR3 (启用/禁用)
  • 位 16-17 - 00: DR0 (执行断点/不执行断点)
  • 位 18-19 - 00: DR1 (执行断点/不执行断点)
  • 位 20-21 - 00: DR2 (执行断点/不执行断点)
  • 位 22-23 - 00: DR3 (执行断点/不执行断点)
示例代码
// 利用调试器进行Hook
DWORD_PTR oldFun5 = NULL;
PVOID vehExceptionHandler2 = NULL;

LONG WINAPI ExceptionHandler2(EXCEPTION_POINTERS* pExceptionInfo)
{
	// 获取异常代码
	DWORD dwExceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode;
	if (EXCEPTION_BREAKPOINT != dwExceptionCode && EXCEPTION_SINGLE_STEP != dwExceptionCode)
	{
		// 不是int 3 异常,继续搜索
		return EXCEPTION_CONTINUE_SEARCH;
	}
	// 获取异常地址
	if ((DWORD_PTR)(pExceptionInfo->ExceptionRecord->ExceptionAddress) != oldFun5)
	{
		// 不是目标地址,继续搜索
		return EXCEPTION_CONTINUE_SEARCH;
	}
	DWORD_PTR* stackPointer = (DWORD_PTR*)(pExceptionInfo->ContextRecord->Esp);

	LPCSTR lpText = (char*)stackPointer[2];
	std::string strText(lpText);

	LPCSTR lpCaption = (char*)stackPointer[3];
	std::string strCaption(lpCaption);

	printf("Hook MessageBox: %s - %s\n", lpText, lpCaption);

	const char* newText = strText.c_str();
	*(stackPointer + 2) = (DWORD_PTR)(&"hook-Msg");
	const char* newCap = strCaption.c_str();
	*(stackPointer + 3) = (DWORD_PTR)(&"hook-Title");

	// 跳过插入的int 3 的指令
	pExceptionInfo->ContextRecord->Eip += 2;
	// 继续执行,指向异常发生的指令,重新尝试执行
	return EXCEPTION_CONTINUE_EXECUTION;
}

// 设置断点
VOID SetThreadBreakpoints(HANDLE hThread, DWORD_PTR funcAddr)
{
	CONTEXT ctx;
	ctx.ContextFlags = CONTEXT_ALL;
	GetThreadContext(hThread, &ctx);

	// 设置函数入口处断点
	ctx.Dr0 = funcAddr;
	ctx.Dr7 = 0x1;// DR0 寄存器设置断点
	SetThreadContext(hThread, &ctx);
}

// 取消设置断点
VOID RemoveThreadBreakpoints(HANDLE hThread, DWORD_PTR funcAddr)
{
	CONTEXT ctx;
	ctx.ContextFlags = CONTEXT_ALL;
	GetThreadContext(hThread, &ctx);

	// 设置函数入口处断点
	ctx.Dr0 = funcAddr;
	ctx.Dr7 = 0x0;// DR0 寄存器设置断点
	SetThreadContext(hThread, &ctx);
}


void Hook5(const char* pszModuleName, const char* pszFuncName, PROC pfnHookFunc)
{
	oldFun5 = (DWORD_PTR)GetProcAddress(GetModuleHandleA(pszModuleName), pszFuncName);
	// add VEH异常处理
	vehExceptionHandler2 = AddVectoredExceptionHandler(TRUE, ExceptionHandler2);

	// 创建快照
	HANDLE hThreadShot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());
	THREADENTRY32* ThreadInfo = new THREADENTRY32;
	ThreadInfo->dwSize = sizeof(THREADENTRY32);
	HANDLE hThread = NULL;

	// 遍历线程
	Thread32First(hThreadShot, ThreadInfo);
	while (Thread32Next(hThreadShot, ThreadInfo))
	{
		if (GetCurrentProcessId() == ThreadInfo->th32OwnerProcessID)
		{
			// 打开线程
			hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, ThreadInfo->th32ThreadID);
			// 设置断点
			SetThreadBreakpoints(hThread, oldFun5);
			CloseHandle(hThread);
		}
	}
}
测试代码
int main()
{

	// 先进行Hook
	Hook5("user32.dll", "MessageBoxA", (PROC)HookMessageBox3);

	// 调用
	MessageBoxA(NULL, "测试box", "啊啊啊", MB_OK | MB_HELP);

	return 0;
}

剩下的还有内核层的hook, 虚拟化hook,未完待续.....