欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Hook技术:奇妙的调试Hook技术详解(附源码)

程序员文章站 2022-07-13 16:11:11
...

为什么叫做奇妙的调试Hook呢?因为在学习《****核心原理》的过程中,发现hook的方式千变万化,越觉得欣喜,相较于其他的Address Hook、Inline Hook,调试Hook让我觉得独特。

什么是调试Hook?

调试Hook是通过调试方式进行的API钩取函数。
在这之前我们先了解一点调试器的知识。
调试器用来确认被调试者是否正确运行,发现未知的错误。调试器能够逐一执行被调试者的指令,拥有对调试器与内存的所有访问权限。
每当被调试者发生调试事件时,OS就会暂停并向调试器报告此事件,调试器做适当处理后,会使被调试进程继续执行。
Hook技术:奇妙的调试Hook技术详解(附源码)
读上面,调试器能够逐一执行被调试者的指令,拥有对调试器与内存的所有访问权限这一句说明了调试hook的可行性。我们使用调试hook,就相当于实现了一个最简单的调试器,而对被调试进程进行修改内存是很正常的事件,因此被查杀可能性低。

实现原理

伪装成调试器行为进行hook,采用对目标API入口地址下断点,当调用时即断下,截获参数内容后进行修改.这时可以通过修改eip的值让其跳转到任意地址.
这种方式没有文件等其他操作,它就相当于实现了一个最简单的调试器,而对被调试进程进行修改内存是很正常的事件,因此被查杀可能性低

实现流程

基本思路是:
在“调试器—被调试者”的状态下,将被调试者的API起始部分修改为0xCC,控制权转移到调试器后执行指定操作,最后使被调试者重新进入运行状态。
具体的实现流程:
1、对想要钩取的进程进行附加操作(DebugActiveProcess),使之成为被调试者。
2、将要钩取的API的起始地址的第一个字节修改为0xcc(或者使用硬件断点)。
3、当调用目标API的时候,控制权就转移到调试器进程。
4、执行需要的操作。
5、脱钩,将API 函数的第一个字节恢复。
6、运行相应的API。
7、再次修改为0xCC,为了继续钩取
8、控制权返还被调试者

实战练习

我们以钩取Notepad.exe的WriteFile()API为例。
实现功能:在保存文件时操作输入参数,将小写字母全部转换成大写字母。也就是说,在notepad中保存的文件内容时,输入的小写字母全部变成大写字母,然后再保存。

1.首先获得Notepad.exe的PID,附加为被调试进程。
PID为7816.
Hook技术:奇妙的调试Hook技术详解(附源码)
输入Notepad.exe的PID。
Hook技术:奇妙的调试Hook技术详解(附源码)
进行附加。

if (!DebugActiveProcess(dwProcessID))
	{
		printf("DebugActiveProcess(%d) failed!!!\n"
			"Error Code = %d\n", dwProcessID, GetLastError());
		return 1;
	}

2、将要WriteFile()的起始地址的第一个字节修改为0xCC
在进程创建的时候,捕捉到异常,进行处理。

while (WaitForDebugEvent(&DebugEvent, INFINITE))
	{
		dwContinueStatus = DBG_CONTINUE;

		// 调试事件为创建进程
		if (CREATE_PROCESS_DEBUG_EVENT == DebugEvent.dwDebugEventCode)
		{
			OnCreateProcessDebugEvent(&DebugEvent);
		}

获得WriteFile的地址,将首字节修改为0xCC,下软件断点。

// WriteFile()函数地址 WriteFileAddress = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");//获得WriteFile的地址 
// API Hook - WriteFile() 
//将WriteFile函数的首个字节改为0xcc 
memcpy(&CreateProcessDebugInfomation, &pDebugEvent->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO)); 
ReadProcessMemory(CreateProcessDebugInfomation.hProcess,WriteFileAddress,&OldByte, sizeof(BYTE), NULL);//保存原函数首地址的首字节 
WriteProcessMemory(CreateProcessDebugInfomation.hProcess,WriteFileAddress,&INT3, sizeof(BYTE), NULL);//写入0xCC,下断。

3、当调用WriteFile()的时候,控制权就转移到调试器进程。
捕捉到断点异常,进入处理分发流程。

// 调试事件
		else if (EXCEPTION_DEBUG_EVENT == DebugEvent.dwDebugEventCode)
		{
			if (OnExceptionDebugEvent(&DebugEvent))
				continue;
		}

4、执行需要的操作。
这里我们执行的操作就是将小写变大写。

判断异常发生地址是否为我们的地址。

// 发生异常的地方是否为我们要钩取的函数
		if (WriteFileAddress == pExceptionRecord->ExceptionAddress)

1.脱钩,将0xCC恢复。

// #1. Unhook
			//   先恢复,以免进入死循环
			WriteProcessMemory(CreateProcessDebugInfomation.hProcess, WriteFileAddress,
				&OldByte, sizeof(BYTE), NULL);

2. 获得线程上下背景文 为了修改EIp的值,来使进程恢复正常运行

Context.ContextFlags = CONTEXT_CONTROL;
		GetThreadContext(CreateProcessDebugInfomation.hThread, &Context);

3. 获取WriteFile() 根据ESP来获得WriteFile 函数的参数,以达到修改数据的目的

Esp + 0x8:获取第二个参数
Esp + 0xC:获取第三个参数

			ReadProcessMemory(CreateProcessDebugInfomation.hProcess, (LPVOID)(Context.Esp + 0x8),&dwAddrOfBuffer, sizeof(DWORD), NULL);

			ReadProcessMemory(CreateProcessDebugInfomation.hProcess, (LPVOID)(Context.Esp + 0xC),&dwNumOfBytesToWrite, sizeof(DWORD), NULL);

4.把小写字母转换为大写字母后,覆盖WriteFile()缓冲区

获取数据缓冲区的地址和大小
之后将其内容读到调试器进程空间,把小写字母转换成大写字母。
然后将修改后的大写字母覆写到原位置。

#4.
		lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite + 1);
		memset(lpBuffer, 0, dwNumOfBytesToWrite + 1);

		// #5. WriteFile() 
		ReadProcessMemory(CreateProcessDebugInfomation.hProcess, (LPVOID)dwAddrOfBuffer,
			lpBuffer, dwNumOfBytesToWrite, NULL);
		printf("\n### original string ###\n%s\n", lpBuffer);

		// #6. 修改数据
		for (i = 0; i < dwNumOfBytesToWrite; i++)
		{
			if (0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A)
				lpBuffer[i] -= 0x20;
		}

		printf("\n### converted string ###\n%s\n", lpBuffer);

		// #7. 调用原函数
		WriteProcessMemory(CreateProcessDebugInfomation.hProcess, (LPVOID)dwAddrOfBuffer,lpBuffer, dwNumOfBytesToWrite, NULL);

5、脱钩,将API 函数的第一个字节恢复。
把线程上下文的EIP地址修改为WriteFile()的起始地址,注意EIP当前的值为0xcc的下一条指令的地址。

		Context.Eip = (DWORD)WriteFileAddress;
		SetThreadContext(CreateProcessDebugInfomation.hThread, &Context);

6、运行相应的API。

7、再次修改为0xCC,为了继续钩取
再次钩取

WriteProcessMemory(CreateProcessDebugInfomation.hProcess, WriteFileAddress,
			&INT3, sizeof(BYTE), NULL);

8、控制权返还被调试者
重启被调试进程,使之继续执行。

// 运行
			ContinueDebugEvent(pDebugEvent->dwProcessId, pDebugEvent->dwThreadId, DBG_CONTINUE);
			Sleep(0);

Hook 结果
hook前:
Hook技术:奇妙的调试Hook技术详解(附源码)
hook后:
Hook技术:奇妙的调试Hook技术详解(附源码)

优缺点

优点:
1.该技术借助“调试”钩取,所以能够进行与用户更具交互性的钩取操作。
2.它就相当于实现了一个最简单的调试器,而对被调试进程进行修改内存是很正常的事件,因此被查杀可能性低
缺点:
1.会导致程序运行速度变慢,不适用于大型程序。
2.也是注意事项,32和32匹配,64和64匹配,本文32位。

源码示例

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

using namespace std;

LPVOID WriteFileAddress = NULL;   //
CREATE_PROCESS_DEBUG_INFO CreateProcessDebugInfomation;
BYTE INT3 = 0xCC, OldByte = 0;

BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pDebugEvent)
{
	// WriteFile()函数地址
	WriteFileAddress = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");//获得WriteFile的地址

	// API Hook - WriteFile()

	//将WriteFile函数的首个字节改为0xcc
	memcpy(&CreateProcessDebugInfomation, &pDebugEvent->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));

	ReadProcessMemory(CreateProcessDebugInfomation.hProcess, WriteFileAddress,&OldByte, sizeof(BYTE), NULL);//保存原函数首地址的首字节

	WriteProcessMemory(CreateProcessDebugInfomation.hProcess, WriteFileAddress,&INT3, sizeof(BYTE), NULL);//写入0xCC,下断。

	return TRUE;
}

BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pDebugEvent)
{
	CONTEXT Context;
	PBYTE lpBuffer = NULL;
	DWORD dwNumOfBytesToWrite, dwAddrOfBuffer, i;
	PEXCEPTION_RECORD pExceptionRecord = &pDebugEvent->u.Exception.ExceptionRecord;

	// BreakPoint exception 
	if (EXCEPTION_BREAKPOINT == pExceptionRecord->ExceptionCode)
	{
		// 发生异常的地方是否为我们要钩取的函数
		if (WriteFileAddress == pExceptionRecord->ExceptionAddress)
		{
			// #1. Unhook
			//   先恢复,以免进入死循环
			WriteProcessMemory(CreateProcessDebugInfomation.hProcess, WriteFileAddress,
				&OldByte, sizeof(BYTE), NULL);

			// #2. 获得线程上下背景文  为了修改EIp的值,来使进程恢复正常运行
			Context.ContextFlags = CONTEXT_CONTROL;
			GetThreadContext(CreateProcessDebugInfomation.hThread, &Context);

			// #3. WriteFile() 根据ESP来获得WriteFile 函数的参数,以达到修改数据的目的

			ReadProcessMemory(CreateProcessDebugInfomation.hProcess, (LPVOID)(Context.Esp + 0x8),
				&dwAddrOfBuffer, sizeof(DWORD), NULL);

			ReadProcessMemory(CreateProcessDebugInfomation.hProcess, (LPVOID)(Context.Esp + 0xC),
				&dwNumOfBytesToWrite, sizeof(DWORD), NULL);

			// #4.
			lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite + 1);
			memset(lpBuffer, 0, dwNumOfBytesToWrite + 1);

			// #5. WriteFile() 
			ReadProcessMemory(CreateProcessDebugInfomation.hProcess, (LPVOID)dwAddrOfBuffer,
				lpBuffer, dwNumOfBytesToWrite, NULL);
			printf("\n### original string ###\n%s\n", lpBuffer);

			// #6. 修改数据
			for (i = 0; i < dwNumOfBytesToWrite; i++)
			{
				if (0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A)
					lpBuffer[i] -= 0x20;
			}

			printf("\n### converted string ###\n%s\n", lpBuffer);

			// #7. 调用原函数
			WriteProcessMemory(CreateProcessDebugInfomation.hProcess, (LPVOID)dwAddrOfBuffer,
				lpBuffer, dwNumOfBytesToWrite, NULL);


			free(lpBuffer);

			// 设置EIP的值来实现正常运行,注意EIP的值为0xcc的下一条指令的地址。
			Context.Eip = (DWORD)WriteFileAddress;
			SetThreadContext(CreateProcessDebugInfomation.hThread, &Context);

			// 运行
			ContinueDebugEvent(pDebugEvent->dwProcessId, pDebugEvent->dwThreadId, DBG_CONTINUE);
			Sleep(0);

			// 再次钩取
			WriteProcessMemory(CreateProcessDebugInfomation.hProcess, WriteFileAddress,
				&INT3, sizeof(BYTE), NULL);

			return TRUE;
		}
	}

	return FALSE;
}

void DebugLoop()
{
	DEBUG_EVENT DebugEvent;
	DWORD dwContinueStatus;

	// 等待调试事件
	while (WaitForDebugEvent(&DebugEvent, INFINITE))
	{
		dwContinueStatus = DBG_CONTINUE;

		// 调试事件为创建进程
		if (CREATE_PROCESS_DEBUG_EVENT == DebugEvent.dwDebugEventCode)
		{
			OnCreateProcessDebugEvent(&DebugEvent);
		}
		// 调试事件
		else if (EXCEPTION_DEBUG_EVENT == DebugEvent.dwDebugEventCode)
		{
			if (OnExceptionDebugEvent(&DebugEvent))
				continue;
		}
		// 调试进程退出
		else if (EXIT_PROCESS_DEBUG_EVENT == DebugEvent.dwDebugEventCode)
		{

			break;
		}


		ContinueDebugEvent(DebugEvent.dwProcessId, DebugEvent.dwThreadId, dwContinueStatus);
	}
}

int main(int argc, char* argv[])
{
	DWORD dwProcessID;
	cout << "Input ProcessID" << endl;
	cin >> dwProcessID;

	// Attach Process

	if (!DebugActiveProcess(dwProcessID))
	{
		printf("DebugActiveProcess(%d) failed!!!\n"
			"Error Code = %d\n", dwProcessID, GetLastError());
		return 1;
	}

	// 调试事件循环
	DebugLoop();

	return 0;
}

参考书籍:
《****核心原理》

相关标签: HOOK