HOOK专题
HOOK
HOOK是用来获取或更改程序执行时的某些数据,或者是用于更改程序执行流程的一种技术
HOOK主要有以下两种形式:
1.修改函数代码:如Inline HOOK,HotFix HOOK
2.修改函数地址:如IAT HOOK, 虚表HOOK
简单的讲,如果一个函数被HOOK,那么这个函数就可以随意的被修改
接下来我们将讲解常见的HOOK手法
Inline HOOK
Inline hook(内联钩子)是一种在程序运行时修改函数执行流程的技术。它通过修改函数的原始代码,将目标函数的执行路径重定向到自定义的代码段,从而实现对目标函数的拦截和修改。
当我们需要修改某API的参数时,有两种方法:
1.API传参时修改内存
2.Inline HOOK:API传参以后,在函数内部执行之前,拦截函数进行参数修改
接下来我们将通过一个实例讲解Inline HOOK
如下已知的源代码
#include <iostream>
#include <Windows.h>int main()
{MessageBoxA(NULL, "rkvir", "success", MB_OK);system("pause");MessageBoxA(NULL, "rkvir", "success", MB_OK);return 0;
}
接下来我们将通过第二种方法Inline HOOK来实现MessageBox参数的修改
1.找到API汇编实现处
将该exe文件拖入x32dbg
如图是上文程序调用MessageBoxA的汇编代码:首先压入了四个参数,然后调用MessageBoxA
如图是MessageBoxA的实现代码
根据上图内存地址可知,前三行代码正好五个字节
2.Inline HOOK
原理:当我们需要通过HOOK修改API的传参时,便要进入API内部,在功能实现前做拦截:修改前五个字节为jmp到目标内存处,使得当程序进入API内部时,在功能实现前便会跳转到目标内存处而不会执行原先的实现代码
注意:32位程序内存地址四个字节,jmp指令一个字节,一共五个字节
实现流程:
1.定位目标函数的地址:通过函数名或者导入表等方式找到目标函数在内存中的地址。
2.修改目标函数的内存权限:将目标函数的内存权限修改为可写可执行,以便后续修改函数的指令。
3.备份目标函数的原始指令:将目标函数的原始指令备份到自定义的缓冲区中。
4.修改目标函数的指令:将目标函数的指令修改为跳转到自定义代码的指令,以实现拦截和修改
5.编写自定义代码:编写自定义的代码,实现对目标函数的拦截和修改逻辑。
6.执行自定义代码:将自定义的代码插入到目标函数的执行流程中,使其被调用时执行自定义逻辑。
7.恢复目标函数的原始指令:在自定义代码执行完毕后,恢复目标函数的原始指令,以确保目标函数的正常执行。
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>//保存目标函数的地址(被HOOK的函数)
PROC m_FunAddress = NULL;
//保存MessageBoxA老的五个字节
BYTE m_OldBytes[5] = { 0 };
//保存MessageBoxA新的五个字节
BYTE m_NewBytes[5] = { 0 };//Hook
//pszModuleName:目标函数所处模块的名称
//pszFuncName:目标函数名
//pfnHookFunc:劫持流程函数的地址
BOOL Hook(const char * pszModuleName, const char * pszFuncName, PROC pfnHookFunc)
{HMODULE hModule = GetModuleHandleA(pszModuleName);//获取目标函数所处模块的句柄m_FunAddress = GetProcAddress(hModule, pszFuncName);//获取目标函数地址if (m_FunAddress == NULL){return FALSE;}DWORD dwRet = 0;BOOL bRet = ReadProcessMemory(GetCurrentProcess(), m_FunAddress, m_OldBytes, 5, &dwRet);//读取MessageBoxA老的五个字节if (!bRet){return FALSE;}m_NewBytes[0] = '\xE9';//jmp的硬编码//jmp跳转的地址为相对地址//相对地址 = 劫持函数地址 - 目标函数地址 - 指令长度*(DWORD *)(m_NewBytes + 1) = (DWORD)pfnHookFunc - (DWORD)m_FunAddress - 5;bRet = WriteProcessMemory(GetCurrentProcess(), m_FunAddress, m_NewBytes, 5, &dwRet);//在目标函数处写入新的五个字节if (!bRet){return FALSE;}else{return TRUE;}}VOID UnHook()//卸载hook
{if (m_FunAddress!= NULL){DWORD dwRet = 0;WriteProcessMemory(GetCurrentProcess(), m_FunAddress, m_OldBytes, 5, &dwRet);}
}VOID ReHook()//恢复hook
{if (m_FunAddress != NULL){DWORD dwRet = 0;WriteProcessMemory(GetCurrentProcess(), m_FunAddress, m_NewBytes, 5, &dwRet);}
}int
WINAPI
MyMessageBoxA//HOOK的函数
(_In_opt_ HWND hWnd,_In_opt_ LPCSTR lpText,_In_opt_ LPCSTR lpCaption,_In_ UINT uType
)
{//卸载HOOKUnHook();int bRet = MessageBoxA(hWnd, "hook", "hook", uType);//重新挂钩ReHook();return bRet;
}BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH://dll加载时Hook("user32.dll", "MessageBoxA", (PROC)MyMessageBoxA);break;case DLL_THREAD_ATTACH:break;case DLL_THREAD_DETACH:break;case DLL_PROCESS_DETACH://dll卸载时UnHook();break;}return TRUE;
}
HotFix HOOK
InoolineHook需要读写两次内存(先HOOK,再还原),这种Hook方式,性能比较低,具有局限性。今天所讲的HotFixHOOK(热补丁)是InlineHook的升级版
Win32 API的实现代码有这样的特点:起始指令为1个MOV EDI,EDI,上方有多个int3(称之为内存空白区),这些指令实际没有任何意义。
接下来我们通过观察MessageBoxA的反汇编代码来验证这个现象
如图所示,确实存在无意义的指令
我们从上文了解到,Win32 API的实现代码中起始位置处存在一些无意义的指令,因此我们可以通过修改这些无意义的指令来实现HOOK操作。这种方法可以使得进程处于运行状态时临时更改进程内存中的库文件,因此被称为打热补丁,即HotFix HOOK
如下图是MessageBoxA的汇编代码实现处,我们借助这个图方便我们理解HOOK
修改Win32 API实现代码中的第一行指令为jmp,使之可以跳转到上方内存空白处。上方内存空白处修改为我们自己的代码,如执行某API。此时每当程序调用该API时,在执行第一行指令时,都会跳转到上方内存空白处执行我们自己的代码,而不会影响原API实际的函数功能实现
接下来我们创建dll文件,实现HotFix HOOK
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>BOOL Hook(const char * pszModuleName, const char * pszFuncName, PROC pfnHookFunc)//HOOK
{BYTE ShortJmp[2] = { 0xEB,0xF9 };//用于替换函数头两个字节mov edi, edi(8B FF)实现短跳上方五个字节BYTE NewCodes[5] = { 0xE9,0, };//替换短跳后的五个字节:一字节指令,四字节函数地址HMODULE hModule = GetModuleHandleA(pszModuleName);//获取目标HOOK函数所处模块的句柄FARPROC FunAddress = GetProcAddress(hModule, pszFuncName);//获取目标HOOK函数地址DWORD dwOldProtect = 0;//保存原有的内存属性VirtualProtect((LPVOID)((DWORD)FunAddress - 5), 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);//修改五字节空白内存读写属性,注意修改的两个头字节一共七个字节DWORD dwFuncAddr = ((DWORD)pfnHookFunc - (DWORD)FunAddress);//计算要跳转的函数相对地址//注意换算结果:跳转函数地址 - E8指令地址处 - E8指令长度 == 跳转函数地址 - (原函数地址处 - E8指令长度) - E8指令长度 == 跳转函数地址 - 原函数地址处*(DWORD *)(NewCodes + 1) = dwFuncAddr;memcpy((LPVOID)((DWORD)FunAddress - 5), NewCodes, 5);memcpy(FunAddress, ShortJmp, 2);//修改内存VirtualProtect((LPVOID)((DWORD)FunAddress - 5), 7, dwOldProtect, &dwOldProtect);//恢复原有的内存属性//此时该程序中所有的原函数都被我们修改了,只要调用该函数,都会被HOOKreturn TRUE;
}BOOL UnHook(const char * pszModuleName, const char * pszFuncName)//卸载HOOK
{BYTE ShortJmp[2] = { 0x8B,0xFF };//还原原有字节BYTE NewCodes[5] = { 0x90,0x90,0x90,0x90,0x90 };//恢复空白内存的一种方式:NOPHMODULE hModule = GetModuleHandleA(pszModuleName);FARPROC = GetProcAddress(hModule, pszFuncName);DWORD dwOldProtect = 0;VirtualProtect((LPVOID)((DWORD)FunAddress - 5), 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);memcpy((LPVOID)((DWORD)FunAddress - 5), NewCodes, 5);memcpy(FunAddress, ShortJmp, 2);VirtualProtect((LPVOID)((DWORD)FunAddress - 5), 7, dwOldProtect, &dwOldProtect);//此时原函数恢复原来的代码,卸载了HOOKreturn TRUE;
}typedef int //声明函数指针
(WINAPI
* fnMessageBoxA)(_In_opt_ HWND hWnd,_In_opt_ LPCSTR lpText,_In_opt_ LPCSTR lpCaption,_In_ UINT uType);int
WINAPI
MyMessageBoxA(_In_opt_ HWND hWnd,_In_opt_ LPCSTR lpText,_In_opt_ LPCSTR lpCaption,_In_ UINT uType)
{fnMessageBoxA NewFunc = (fnMessageBoxA)((DWORD)MessageBoxA + 2);//这是我们用于HOOK后执行的函数,其指向MessageBoxA并跳过头两个字节,防止被短跳而反复HOOK进入死循环。此时我们调用这个函数时,可以执行原来的MessageBoxA的功能 int bRet = NewFunc(hWnd, "hook", "hook", uType);//执行我们定义的函数,实际上就是执行MessageBoxA的有效部分return bRet;
}BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:Hook("user32.dll", "MessageBoxA", (PROC)MyMessageBoxA);break;case DLL_THREAD_ATTACH:break;case DLL_THREAD_DETACH:break;case DLL_PROCESS_DETACH://实际上我们并不需要卸载HOOK,因此我们所注入进程的所有的非内核级的HOOK都会随着进程的结束而自动消失。因此当我们结束进程时便自动卸载了HOOK,所以此处的卸载HOOK显得有点多余了UnHook("user32.dll", "MessageBoxA");break;}return TRUE;
}
接下来我们将通过如下的代码,演示HotFix HOOK
#include <iostream>
#include <Windows.h>int main()
{MessageBoxA(NULL, "text", "text", MB_OK);system("pause");MessageBoxA(NULL, "text", "text", MB_OK);return 0;
}
首先运行程序,我们发现正常运行程序时,两次弹窗都是原来的窗口:
接下来我们再次重新运行程序,第一次弹窗是原来的窗口:
此时我们注入上文编写的dll文件,然后再次弹窗,发现弹窗变了,HOOK成功了:
软件断点HOOK
我们在HotFix HOOK中了解到,Win32 API的第一个指令是无效指令,不影响API的实现,因此我们可以修改第一个指令为int 3,触发异常,然后跳转异常处理程序,完成HOOK。
我们仍然看着MessageBoxA内部实现的反汇编图来完成这个异常HOOK
如下是调用MessageBoxA前的反汇编代码:
注意此时压栈的四个参数
如下是MessageBoxA内部实现的代码
我们要修改的指令就是上图的8BFF move edi, edi,将其修改为int 3,当执行到int 3触发异常,从而转向异常处理程序,完成HOOK
接下来我们要实现的异常HOOK内容为修改MessageBoxA的第一个参数
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include<Windows.h>size_t MessageBoxAddr = (size_t)GetProcAddress(GetModuleHandle(L"User32.dll"), "MessageBoxA");//获取要HOOK的函数:MessageBoxA的地址struct EXECEPTION_HOOK
{//保存要Hook的地址ULONG_PTR ExceptionAddress;//原来位置的代码/硬编码UCHAR OldCode;
};EXECEPTION_HOOK HookInfo;//在要HOOK的函数起始地址处设置int3断点
VOID SetHook(ULONG_PTR Address)
{DWORD dwProtect = 0;//修改函数起始处第一个字节内存属性,int3一字节大小VirtualProtect((LPVOID)Address, 1, PAGE_EXECUTE_READWRITE, &dwProtect);//保存要Hook的地址,即函数地址HookInfo.ExceptionAddress = Address;//保存函数地址头一字节的硬编码HookInfo.OldCode = *(UCHAR*)Address;//将函数内部第一个字节修改为int3*(UCHAR*)Address = 0xCC;//int 3的硬编码为CC//修复内存保护属性VirtualProtect((LPVOID)Address, 1, dwProtect, &dwProtect);
}//异常处理程序
LONG Hander(struct _EXCEPTION_POINTERS* ExceptionInfo
)
{//判断异常触发类型if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT){//程序中会有很多int 3异常,因此需要判断这里触发的异常是不是我们写的int3异常if ((ULONG_PTR)ExceptionInfo->ExceptionRecord->ExceptionAddress == HookInfo.ExceptionAddress){//这是修改MessageboxA的第二个参数const WCHAR* szStr = L"Hook";//开始修改MessageboxA的第二个参数//ESP:栈顶指针寄存器//当压栈数据类型为整数型时,直接压栈//当压栈数据类型为指针型时,地址压栈*(DWORD*)(ExceptionInfo->ContextRecord->Esp + 0x8) = (DWORD)szStr;//当栈中某位置已经有参数时,后续正常压栈碰到该位置时不会再次覆盖此位置//跳过int 3异常 ExceptionInfo->ContextRecord->Eip += 2;//返回触发异常的位置return EXCEPTION_CONTINUE_EXECUTION;}}
}BOOL APIENTRY DllMain(HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved
)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:AddVectoredExceptionHandler(1, (PVECTORED_EXCEPTION_HANDLER)Hander);SetHook(MessageBoxAddr);break;case DLL_THREAD_ATTACH:break;case DLL_THREAD_DETACH:break;case DLL_PROCESS_DETACH:break;}return TRUE;
}
硬件断点HOOK
如图是intel操作手册中有关调试寄存器的内容:
从图中我们发现有八个寄存器:DR0-DR7,其中有用的是DR0,DR1,DR2,DR3,DR6和DR7
DE6和DR7是调试寄存器,DR4和DR5是DR6和DR7的影子寄存器(别名),但是当调试扩展开启时,调用DR4和DR5会造成异常
DR0-DR3是地址寄存器,用于保存硬件断点地址。
接下来我们分析DR7各个位的含义:
L0和G0对应DR0,L0为局部断点的开启关闭(L0 == 1时开启,L0 == 0时关闭),G0为全局断点的开启关闭。其余L1,G1等等不再赘述,同一个道理
LE和GE废弃了
灰色的0和1是保留位
GD:访问检测位,当GD = 1时,开启访问检测功能,此时修改DR寄存器的指令时CPU会触发异常
R/W:读写位,R/W0-R/W3对应DR0-DR3,每个R/W占两位长度,因此存在四种状态:
00:在断点地址处执行指令时中断(硬件执行断点)
01:在断点地址处写数据时中断(硬件写入断点)
10:I/O中断,该中断需要开启调试拓展
11:在断点地址处读写数据时中断,但是读写指令不会中断(硬件访问断点)
LEN:长度域,LEN0-LEN3对应DR0-DR3,每个LEN占两位长度,因此对应四种状态:
00:断点触发长度范围1字节
01:断点触发长度范围2字节
10:断点触发长度范围8字节
11:断点触发长度范围4字节
当设置4字节长执行断点时,首先将断点地址写入DR0,DR7的L0位设置1,RW位设置00,LEN位设置11,此时便开启断点
接下来我们分析DR6各个位的含义:
B0-B3:例如B0被设置为1,那么DR0地址处,R/W,LEN条件满足
接下来我们演示硬件断点的使用:
每个线程都有一套调试寄存器,因此单次修改调试寄存器只能适用于单个线程
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>
#include <TlHelp32.h>//获取要HOOK函数的地址
size_t MessageBoxAddr = (size_t)GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");LONG Handler(struct _EXCEPTION_POINTERS *ExceptionInfo
)
{//判断触发的地址是不是属于我们自己if ((ULONG_PTR)ExceptionInfo->ExceptionRecord->ExceptionAddress == MessageBoxAddr){const char * szStr = "rkvir";//修改了函数的第二个参数*(DWORD *)(ExceptionInfo->ContextRecord->Esp + 0x8) = (DWORD)szStr;ExceptionInfo->ContextRecord->Eip += 2;return EXCEPTION_CONTINUE_EXECUTION;}else//加固断点,防止由于各种意外导致断点消失{ExceptionInfo->ContextRecord->Dr0 = MessageBoxAddr;ExceptionInfo->ContextRecord->Dr7 = 0x405;return EXCEPTION_CONTINUE_SEARCH;}
}//单线程HOOK
VOID SetThreadHook(HANDLE hThread)
{CONTEXT ctx;ctx.ContextFlags = CONTEXT_ALL;//获取所有寄存器GetThreadContext(hThread, &ctx);///获取当前线程上下文ctx.Dr0 = MessageBoxAddr;//设置断点地址//DR0 1字节 硬件执行断点ctx.Dr7 = 1;SetThreadContext(hThread, &ctx);//设置线程上下文
}VOID SetHook()//遍历线程使得每个线程都能硬件断点
{HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());THREADENTRY32 te32 = { sizeof(THREADENTRY32) };HANDLE hThread = NULL;while (Thread32Next(hSnap,&te32))//遍历进程的所有线程{if (GetCurrentProcessId() == te32.th32OwnerProcessID)//线程遍历会遍历系统所有的线程,此处我们只要在当前进程进行硬件断点{hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);SetThreadHook(hThread);//硬件断点HOOKCloseHandle(hThread);}}CloseHandle(hThread);
}BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:AddVectoredExceptionHandler(1, (PVECTORED_EXCEPTION_HANDLER)Handler);SetHook();break;case DLL_THREAD_ATTACH:SetThreadHook(GetCurrentThread());//当起始HOOK完以后,如果进程创建了一个新的线程时再次HOOKbreak;case DLL_THREAD_DETACH:break;case DLL_PROCESS_DETACH:break;}return TRUE;
}
IAT HOOK
所谓的IAT HOOK就是修改IAT表,属于修改函数地址的方式的HOOK
当我们在程序中使用其他模块中的函数时,我们以程序调用MessageBoxA()为例,进行演示。程序运行后我们进入反汇编开始观察。
我们通过汇编代码可知,一个函数的调用可以是直接调用,也可以是间接调用。而我们使用导入表的中函数时,是通过间接调用去调用该函数的。
我们在内存中查找上图中的间接调用的函数地址
我们发现,这个地址内容是指向了IAT表,当程序编译后,该表存储的是函数的名称。但当程序加载内存以后,IAT表存储的便是函数的真实地址了。此时程序调用MessageBoxA就是调用它的真实地址。
具体流程如下:
1.装载PE文件到内存:当PE文件运行后,系统先装载.exe,再装载各个使用到的.dll到PE文件的虚拟内存中。
2.调用GetProcAddress()函数:内存装载完成后,系统首先遍历导出表中每个结构的INT表,无论遍历到的是函数名还是序号,都会作为其中一个参数传给系统函数GetProcAddress()
3. IAT表修改:GetProcAddress()函数根据函数名或序号返回对应函数在内存中的绝对地址,接着把绝对地址存入到IAT表的对应位置
4.修改call语句地址:
PE文件运行前:call [0x....]后面间接寻址的地址0x....是目标函数在IAT表的地址,该地址上内容为空,充当一个占位符
PE文件运行后:call [0x....]后面间接寻址的地址0x....是目标函数在IAT表的地址,该地址上内容由空变为了目标函数的真实地址
这就意味着当我们找个这个表时,也就能够控制该程序的所有函数了
代码实现如下:
#include "pch.h"
#include <Windows.h>typedef int //声明一个函数指针,指向函数MessageBoxA
(WINAPI *FuncMessageBoxA)(_In_opt_ HWND hWnd,_In_opt_ LPCSTR lpText,_In_opt_ LPCSTR lpCaption,_In_ UINT uType);
FuncMessageBoxA OldMessageBoxA = NULL;//初始化函数变量int
WINAPI
MyMessageBoxA(_In_opt_ HWND hWnd,_In_opt_ LPCSTR lpText,_In_opt_ LPCSTR lpCaption,_In_ UINT uType)
{return OldMessageBoxA(hWnd, "XXXXXX", "XXXXXX", MB_OK);
}//用于替换MessageBoxA的函数VOID IATHook()//用于将原始的MessageBoxA函数地址替换为MyMessageBoxA函数地址
{//保存原来的函数地址OldMessageBoxA = (FuncMessageBoxA)GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");//获取当前程序内存中user32.dll模块中MessageBoxA函数地址PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)GetModuleHandle(NULL);//获取模块基址PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((DWORD)pDos + (DWORD)pDos->e_lfanew);//可选头PIMAGE_OPTIONAL_HEADER pOptionHeader = (PIMAGE_OPTIONAL_HEADER)&pNt->OptionalHeader;//导入表的RVADWORD dwImportTableOffset = pOptionHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;//获取导入表RVAPIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDos + dwImportTableOffset);//获取导入表地址PIMAGE_THUNK_DATA FirstThunk;//设置IAT表结构while (pImport->Characteristics && pImport->FirstThunk != NULL)//该循环用于遍历所有导入表,将MessageBoxA地址替换为MyMessageBoxA地址{FirstThunk = (PIMAGE_THUNK_DATA)(pImport->FirstThunk + (DWORD)pDos);//获取第一个IAT表while (*(DWORD *)FirstThunk != NULL)//判断IAT表是否为空{if (*(DWORD *)FirstThunk == (DWORD)OldMessageBoxA)//判断某IAT表中导入函数地址是否为OldMessageBoxA的函数地址{DWORD dwOldProtect = 0;//保存原来的属性VirtualProtect(FirstThunk, 0x1000, PAGE_EXECUTE_READWRITE, &dwOldProtect);//修改目标区域属性为可读可写DWORD dwFimcAddr = (DWORD)MyMessageBoxA;//保存我们要们要调用的函数地址memcpy(FirstThunk, (DWORD *)&dwFimcAddr, 4);//将我们要调用的函数地址放到目标IAT表中VirtualProtect(FirstThunk, 0x1000, dwOldProtect, &dwOldProtect);}FirstThunk++;}pImport++;}
}BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH: //此处是PE文件加载我们创建.dll文件时进行的操作IATHook();break;case DLL_THREAD_ATTACH:break;case DLL_THREAD_DETACH:break;case DLL_PROCESS_DETACH:break;}return TRUE;
}
虚表HOOK
所谓的虚表HOOK就是修改虚函数表,属于修改函数地址的方式的HOOK
虚函数表Hook是一种利用C++的动态多态性机制来修改类行为的技术。在C++中,虚函数表是用于实现动态多态性的重要机制,每个对象都有一个指向虚函数表的指针,该表中存储了虚函数的地址,通过修改这个虚函数表,实际上是替换虚函数表中存储的函数指针,我们可以改变类的行为,使其调用不同的函数。
虚函数表HOOK分为以下三个步骤:
1.获得虚表指针
2.修改虚表的内存保护属性
3.修改虚表中的虚函数地址为我们的函数地址.
接下来我们通过代码显示它:
#include <windows.h>
class MyTest
{
public:int m_Number;
public:MyTest(){printf("MyTest::MyTest()\r\n");}~MyTest(){printf("MyTest::~MyTest()\r\n");}void print(){printf("void MyTest::print()\r\n");}virtual void ShowHelloWorld(){printf("Hello World!\n");}};void ShowData() //我们将虚表中的函数地址换为我们的函数地址
{printf("hook\r\n");
}int main(int argc, char* argv[])
{MyTest test;MyTest &obj = test; //可以虚调用 int DwAddress = *(int *)&test; //第一步,获取自己的虚表指针obj.ShowHelloWorld(); //虚函数调用,测试作用DWORD dwOld = 0; //第二步修改虚表指针的内存保护属性,下方更改虚表VirtualProtect((void *)DwAddress,0x1000,PAGE_EXECUTE_READWRITE,&dwOld);//修改内存保护属性,其地址是虚表指针地址*(int *)DwAddress = (int)ShowData;//第三步,HOOK,也就是将我们的函数地址,写入到虚表中VirtualProtect((void *)DwAddress,0x1000,dwOld,&dwOld);//恢复内存保护属性obj.ShowHelloWorld(); //重新调用,看看是否被HOOKreturn 0;
}