首页
社区
课程
招聘
[原创]利用调试器原理实现 API Hook(WriteFile示例)
发表于: 2025-8-30 16:37 729

[原创]利用调试器原理实现 API Hook(WriteFile示例)

2025-8-30 16:37
729


Hook是什么

Hook,中文直译就是”钩子“。在软件开发和安全领域,它是一种用来监控或修改程序执行流程的技术。

具体来说,当程序调用某个函数的时候,我们在中间”挂一个钩子“,这样执行流会先经过我们的代码,再决定是否

继续运行原本的逻辑,还是插入、修改、甚至完全阻止这次调用。

举个例子,如果对CreateProcess()函数进行Hook,就能够截获所有进程创建操作。这样我们不仅能看到目标进程

的启动参数,还能选择是否拦截或者篡改它,从而达到监控或修改的目的。

Hook的分类

以下出自《加密与解密》,个人觉得写的非常好

程序的本质是数据+指令,这两样东西只要被篡改,程序的指向结果就发生变化。相应地,出现了两大类Hook,

一类是通过修改数据(通常是引用的函数)进行Hook(IAT/EAT Hook),另一类则直接修改函数内的指令进行Hook。

这两大类也就是Address Hook与Inline Hook

类似于C语言中的指针,第1种Hook就是修改指针的值,第2种Hook就是修改指针指向的内容的值。

下面给到一个简单的程序示例,PrintChar函数本来要输出字符"A",但为了能让其输出“B”,可以采取两种方法

void PrintChar(char* pch)
{
	printf("%c\n", *pch);
}

int main()
{
	char ch = 'A';
	char ch2 = 'B';
	char* pChar;
	pChar = &ch;
	PrintChar(pChar);    //打印 A
	pChar = &ch2;
	PrinChar(pChar);     //打印 B (修改了"指针的值" 也就是修改指向)
	pChar = &ch1;
	*pChar = 'B';
	PrinChar(pChar);     //打印 B (修改了"指针指向的值")
}

而本文所讲的利用调试器进行Hook属于一种特殊的InlineHook

调试器的工作原理

上面我们了解了Hook是什么,接下来我们学习利用调试器进行Hook之前还需要学习调试器的工作原理

调试器是一类特殊的程序,当它加载或附加某个进程之后,这个被调试的进程发生调试事件(Debug Event)时,

OS就会暂停其运行,并且向调试器报告相应事件。调试器接管被调试进程的执行流

在调试相关的事件里,最重要的一类是异常事件(EXCEPTION_DEBUG_EVENT)

其中,调试器最常用的就是EXCEPTION_BREAKPOINT,也就是”断点异常“,断点对应的机器指令是INT3(在IA-32架构下它的机器码是0xCC)

当CPU执行到INT3指令时,程序会立刻中断运行,操作系统把这个异常事件交给调试器,调试器此时就能检查、修改寄存器堆栈等

调试器实现断点的方法非常简单,找到要设置断点的代码再内存种的起始地址,把第一个字节修改为0xCC就可以了

想继续调试时,再将其恢复原值。通过调试Hook的技术也就是利用了断点的这种特性

利用调试技术Hook记事本的WriteFile()函数的具体流程

首先,我们需要附加到记事本的进程上。找到WriteFile()函数的首地址,将其第一个字节保存下来,然后修改为0xCC

这样当记事本执行到WriteFile()函数时,就会触发断点异常,此时控制权转移到我们,首先判断异常发生的位置是不是WriteFile()函数

如果是的话,就说明记事本在调用WriteFile()函数。接着,我们获取线程上下文(寄存器、栈等信息)就可以进行参数的监控或篡改

然后我们要让程序恢复执行:把WriteFile()函数的第一个字节改回去,再把EIP改成WriteFile()的起始位置(因为执行了0xCC执行,所以当前的EIP往后加了一个字节)

最后恢复被调试进程,然后再次将WriteFile()函数的首地址修改为0xCC(继续挂钩)

具体实现代码

说了这么多理论,下面是具体对WriteFile()函数Hook的代码(32位记事本)

达到的效果是无论在记事本中写入什么内容保存成txt后都是kanxue.com


#include <Windows.h>
#include <stdio.h>
#include <strsafe.h>
#include <tchar.h>
#include <iostream>

LPVOID g_pfWriteFile;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_chOrgByte = 0;
BYTE g_chINT3 = 0xCC;

CHAR Buffer[] = "kanxue.com";

SIZE_T size = strlen(Buffer);

BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
	//获取WriteFile() API地址
	g_pfWriteFile = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "WriteFile");
	
	//先通过pde拿到进程相关信息
	memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));
	//读取原先的字节
	ReadProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chOrgByte, sizeof(BYTE), NULL);
	//写入0xCC
	WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chINT3, sizeof(BYTE), NULL);

	return true;
}

BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde) 
{
	CONTEXT ctx;
	PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;
	DWORD dwNumberOfBytesToWrite, dwAddrOfBuffer, i;
	PBYTE lpBuffer = NULL;

	//是断点异常(INT 3)时
	if (EXCEPTION_BREAKPOINT == per->ExceptionCode)
	{
		//判断断点地址是否是WriteFile
		if (g_pfWriteFile == per->ExceptionAddress)
		{
			// UnHook
			//将0xCC恢复为original byte
			WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chOrgByte, sizeof(BYTE), NULL);
			// 获取线程上下文
			ctx.ContextFlags = CONTEXT_CONTROL;
			GetThreadContext(g_cpdi.hThread, &ctx);

			// 获取WriteFile()的param 2、3值
			ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8), &dwAddrOfBuffer, sizeof(DWORD), NULL);
			ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC), &dwNumberOfBytesToWrite, sizeof(DWORD), NULL);
			
			LPVOID lpOrgBuffer = (LPVOID)malloc(dwNumberOfBytesToWrite+1);
			memset(lpOrgBuffer, 0, dwNumberOfBytesToWrite + 1);

			//输出原来的字符串和大小
			ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, lpOrgBuffer, dwNumberOfBytesToWrite, NULL);
			printf("\n### original string : %s size : %d\n", lpOrgBuffer,dwNumberOfBytesToWrite);

			//申请存放kanxue.com的空间
			LPVOID lpBuffer = VirtualAllocEx(g_cpdi.hProcess, NULL, size+1, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
			WriteProcessMemory(g_cpdi.hProcess, lpBuffer, Buffer, size+1, NULL);

			//改写参数
			WriteProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8), &lpBuffer, sizeof(DWORD), NULL);
			WriteProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC), &size, sizeof(DWORD), NULL);


			printf("\n### converted string : %s size : %d\n", Buffer, size);

			// 将线程上下文的EIP更改为WriteFile()首地址
			ctx.Eip = (DWORD)g_pfWriteFile;
			SetThreadContext(g_cpdi.hThread, &ctx);

			// 运行被调试进程
			ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);

			//立刻切换线程 防止同时操作WriteFile函数那块内存
			Sleep(0);

			// 继续挂钩
			WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chINT3, sizeof(BYTE), NULL);

			return TRUE;
		}
	}

	return FALSE;
}



void DebugLoop()
{
	DEBUG_EVENT de;
	DWORD dwContinueStatus;

	// 等待调试事件发生
	while (WaitForDebugEvent(&de, INFINITE))
	{
		dwContinueStatus = DBG_CONTINUE;
		//被调试进程生成或附加事件
		if (CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)
		{
			OnCreateProcessDebugEvent(&de);
		}
		//异常事件
		else if (EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode)
		{
			if (OnExceptionDebugEvent(&de))
			{
				continue;
			}
			
		}
		//被调试进程终止事件
		else if (EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)
		{
			break;
		}
		//再次执行被调试进程
		ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
	}
}
bool EnableDebugPrivilege() {
	HANDLE hToken;
	TOKEN_PRIVILEGES tp;
	LUID luid;

	// 打开当前进程的访问令牌
	if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
		std::cerr << "OpenProcessToken failed" << std::endl;
		return false;
	}

	// 获取 SeDebugPrivilege 的 LUID
	if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
		std::cerr << "LookupPrivilegeValue failed" << std::endl;
		CloseHandle(hToken);
		return false;
	}

	tp.PrivilegeCount = 1;
	tp.Privileges[0].Luid = luid;
	tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

	// 启用 SeDebugPrivilege 权限
	if (!AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL)) {
		std::cerr << "AdjustTokenPrivileges failed" << std::endl;
		CloseHandle(hToken);
		return false;
	}

	// 检查是否成功启用权限
	if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
		std::cerr << "The privilege is not assigned" << std::endl;
		CloseHandle(hToken);
		return false;
	}

	CloseHandle(hToken);
	return true;
}

int main(int argc, char* argv[])
{
	DWORD dwPID = 1;

	EnableDebugPrivilege();
	if (argc != 2)
	{
		printf("\nUSAGE : hookdbg.exe <pid>\n");
		return 1;
	}
	//附加调试进程
	dwPID = atoi(argv[1]);
	if (!DebugActiveProcess(dwPID))
	{
		printf("DebugActiveProcess(%lu) failed!!!\n"
			"Error Code = %lu\n", dwPID, GetLastError());
		return 1;
	}

	//调试器循环
	DebugLoop();

	return 0;
}

程序运行效果

总结与思考

本技术将调试器原理与Hook相结合,非常巧妙,充分展示了调试器如何通过操作进程内存与控制流,影响目标程序的行为。

通过本文章可以学习到基本的调试器原理与Hook技术


参考文献

《加密与解密》

《逆向工程核心原理》



[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回