硬件断点HOOK
一些软件会通过一个线程来循环检测代码的正确性,当你HOOK去修改dll或exe中的代码时会触发这个检测导致被HOOK软件失败。
通过硬件HOOK过检测:
1)不修改检测线程
2)不修改挂钩子函数的代码
在要HOOK的函数地址(函数地址可以用LoadLiberal系列函数获取)处下一个硬件断点,通过我们上面的异常可以获取到这个异常和CONTEXT,可以修改Esp、Eip等寄存器,获取了Esp就是拿到了堆栈,即参数、返回地址等。Eip可以跳到我们想要执行的位置。
可以通过设置顶层异常处理过滤函数来捕获这个异常,最好也在dll注入后设置。至于硬件断点我们只需要在DLL注入时在我们想要HOOK的函数地址通过DR7与空闲的DR0~3中下上断点就好。
可能需要调试知识:软件调试
部分函数说明
ChangeContext函数:
ChangeContext函数主要是通过寄存器修改堆栈,使函数执行我们想执行的功能。因为我们是在函数开头的的位置下的断点即:
push ebp
mov ebp, esp
这两行函数开头的代码都不会执行,所以此时我们栈顶用ESP去修改,因为并没有执行
push ebp
这时我们的ESP指向的是CALL指令执行时,push到堆栈里的返回地址,因为函数入栈从后向前push参数,所以ESP为:
esp = 返回地址
esp + 0x4 = 第一个参数
esp + 0x8 = 第二个参数
以此类推。
OriginalFun函数:
OriginalFun函数主要是为了让我们修改堆栈的值后,跳过我们下断点的位置,否则会死循环。
主程序
主程序是一个DLL,通过 CreateRemoteThread 函数进行注入:Dll注入–CreateRemoteThread。
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include "tlhelp32.h"
DWORD g_dwHookFunAdd = 0;
DWORD g_dwHookFunAddOffset = 0;
void _declspec(naked) OriginalFun(void)
{
//不能从开始执行,已经下了断点,跳过断点执行
_asm
{
mov edi, edi
jmp [g_dwHookFunAddOffset]
}
}
void ChangeContext(PCONTEXT pContext)
{
/*
if (!(pContext.Dr6 & 0xF)) //判断是我们引起的中断
{
return;
}
*/
char szNewText[] = "Hook Success!";
LPSTR lpsstrText = NULL;
DWORD dwLength = 0;
DWORD dwOldProtect = 0;
DWORD dwNewProtect = 0;
//获取要修改MessageBoxA参数地址,此时还没执行push ebp 和mov ebp, esp 所以esp指向函数返回地址
//MessageBoxA的参数反向入栈所以 +0x8 为文本参数
lpsstrText = (LPSTR)(*(DWORD*)(pContext->Esp + 0x8));
dwLength = strlen(lpsstrText); //获取长度
VirtualProtect(lpsstrText, dwLength, PAGE_EXECUTE_READWRITE, &dwOldProtect); //修改权限
memcpy(lpsstrText, szNewText, dwLength);
VirtualProtect(lpsstrText, dwLength, dwOldProtect, &dwNewProtect); //将权限修改回去
return;
}
//顶层异常处理函数
LONG WINAPI HookExceptionFilter(PEXCEPTION_POINTERS pException)
{
if (pException->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) //单步异常
{
if ((DWORD)pException->ExceptionRecord->ExceptionAddress == g_dwHookFunAdd) //判断地址是否为我们加断点的位置
{
PCONTEXT pContext =pException->ContextRecord;
ChangeContext(pContext); //修改上下文,即修改函数参数等操作
//修改EIP从断点位置继续执行
pException->ContextRecord->Eip = (DWORD)(&OriginalFun);
return EXCEPTION_CONTINUE_EXECUTION;
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
DWORD GetThreadHandle(HANDLE &hHookThread, DWORD dwThreadCount)
{
HANDLE hThreadSnap = NULL;
DWORD dwordRet = 0;
do
{
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hThreadSnap == INVALID_HANDLE_VALUE)
{
break;
}
THREADENTRY32 thread32 = { 0 };
thread32.dwSize = sizeof(THREADENTRY32);
DWORD dwCount = 0; //线程计数,一个进程下有多个线程
if (Thread32First(hThreadSnap, &thread32))
{
do
{
if (thread32.th32OwnerProcessID == GetCurrentProcessId()) //判断是当前进程
{
dwCount++;
if (dwCount == dwThreadCount) //查找进程中的第几个线程
{
hHookThread = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_QUERY_INFORMATION,
FALSE, thread32.th32ThreadID);
}
}
} while (Thread32Next(hThreadSnap, &thread32));
}
} while (FALSE);
if (hThreadSnap != NULL)
{
CloseHandle(hThreadSnap);
}
return dwordRet;
}
BOOL SetHardwareBreakPointHook()
{
HANDLE hHookThread = NULL;
//获取Message地址
//已经映射进来了直接用GetModuleHandle,否则用LoadLibrary
g_dwHookFunAdd = (DWORD)GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
g_dwHookFunAddOffset = g_dwHookFunAdd + 2;
//遍历线程找到要HOOK的线程
GetThreadHandle(hHookThread, 1);
if (hHookThread == INVALID_HANDLE_VALUE)
{
return FALSE;
}
//设置断点和异常处理函数
(void)SetUnhandledExceptionFilter(HookExceptionFilter); //设置一个顶层异常
//获取线程上下文
CONTEXT context;
memset(&context, 0, sizeof(CONTEXT));
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
GetThreadContext(hHookThread, &context);
//设置断点位置
context.Dr0 = (DWORD)g_dwHookFunAdd;
context.Dr7 |= 1; //设置为局部断点
//清零
context.Dr7 &= 0xFFF0FFFF; //清零 16 - 19位 LEN0 和 RWE0
//设置断点长度
context.Dr7 |= 0x00000000; //18 - 19 置0 -- 1字节 (多大都行主要是断下)
//设置断点类型
context.Dr7 |= 0x00000000; //执行断点(17、18位置零)
SetThreadContext(hHookThread, &context);
CloseHandle(hHookThread);
return TRUE;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
SetHardwareBreakPointHook();
break;
case DLL_PROCESS_DETACH:
//卸载HOOK在这添加,需要还原DR0和DR7寄存器和修在顶层处理异常函数
break;
}
return TRUE;
}