钩子编程

钩子编程（hooking），也称作“挂钩”，是计算机程序设计术语，指通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码，被称为钩子（hook）。
钩子编程有多种用途，如调试、扩展功能。例如在键盘或鼠标事件到达应用程序之前拦截这些事件；拦截应用程序或其他模块的操作系统调用以监督行为、修改功能。也广泛用于benchmarking程序，如度量3D游戏的帧率。 
钩子编程也被用于恶意代码，如rootkit是各种通过假扮系统API调用输出来隐藏自身进程的可见性的工具与技术；游戏外挂是另一类例子。
方法.
物理修改.
在应用程序执行之前，物理修改可执行程序，这典型通过找到函数调用入口点，修改入口点使之在函数被执行前先执行其他的代码。另一种挂钩的方法是修改可执行程序的输入表（import table）。还有一种挂钩方法是采用包装库，使得应用程序不需修改即可调用该包装库完成其功能，而在包装库中插入钩子然后再调用原有的库。 
运行时修改.
操作系统与软件可提供方法，在运行时插入事件钩子。Microsoft Windows允许插入钩子以处理或修改对话框、滚动条、菜单等的系统事件、应用程序事件；插入、删除、处理或修改键盘鼠标事件。Linux允许类似的钩子通过NetFilter以处理网络事件。
如果没有上述机制或权限，也可拦截进程的库函数调用，在函数调用开始处注入代码。这可通过修改内存中的进程的中断向量表或输入表（import table）实现。
示例代码.
虚函数表挂钩.
C++使用虚函数，因此可在运行时直接修改虚函数表的内容来挂钩。 Window上很多软件库以COM方式提供的（如DirectX）， 所以有需求拦截COM调用的COM Hook。COM里的接口是C++虚表的形式提供的，所以COM的Hook其实就是虚表（vtable）的Hook。ATL就是用接口替代的方式来调试和记录COM接口引用计数的次数
class VirtualTable { // example class 
 public: 
 virtual void VirtualFunction01( ticket ); 
void VirtualTable::VirtualFunction01( ticket ) { 
 printf("VirtualFunction01 called"); 
typedef void ( __thiscall* VirtualFunction01_t )( ticket* thisptr ); 
VirtualFunction01_t g_org_VirtualFunction01;
 //our detour function 
void __fastcall hk_VirtualFunction01( ticket* thisptr, int edx ) { 
 printf("Custom function called"); 
 //call the original function 
 g_org_VirtualFunction01(thisptr); 
int _tmain(int argc, _TCHAR* argv[]) {
 DWORD oldProtection;
 VirtualTable* myTable = new VirtualTable(); 
 void** base = *(void***)myTable;
 VirtualProtect( &amp;base[0], 4, PAGE_EXECUTE_READWRITE, &amp;oldProtection ); 
 //save the original function 
 g_org_VirtualFunction01 = (VirtualFunction01_t)base[0]; 
 //overwrite 
 base[0] = &amp;hk_VirtualFunction01; 
 VirtualProtect( &amp;base[0], 4, oldProtection, 0 );
 //call the virtual function (now hooked) from our class instance 
 myTable-&gt;VirtualFunction01();
 return 0; 
C#键盘事件钩子.
using System.Runtime.InteropServices;
namespace Hooks
 public class KeyHook
 /* Member variables */
 protected static int Hook;
 protected static LowLevelKeyboardDelegate Delegate;
 protected static readonly object Lock = new object();
 protected static bool IsRegistered = false;
 /* DLL imports */
 [DllImport("user32")]
 private static extern int SetWindowsHookEx(int idHook, LowLevelKeyboardDelegate lpfn,
 int hmod, int dwThreadId);
 [DllImport("user32")]
 private static extern int CallNextHookEx(int hHook, int nCode, int wParam, KBDLLHOOKSTRUCT lParam);
 [DllImport("user32")]
 private static extern int UnhookWindowsHookEx(int hHook);
 /* Types &amp; constants */
 protected delegate int LowLevelKeyboardDelegate(int nCode, int wParam, ref KBDLLHOOKSTRUCT lParam);
 private const int HC_ACTION = 0;
 private const int WM_KEYDOWN = 0x0100;
 private const int WM_KEYUP = 0x0101;
 private const int WH_KEYBOARD_LL = 13;
 [StructLayout(LayoutKind.Sequential)]
 public struct KBDLLHOOKSTRUCT
 public int vkCode;
 public int scanCode;
 public int flags;
 public int time;
 public int dwExtraInfo;
 /* Methods */
 private static int LowLevelKeyboardHandler(int nCode, int wParam, ref KBDLLHOOKSTRUCT lParam)
 if (nCode == HC_ACTION)
 if (wParam == WM_KEYDOWN)
 System.Console.Out.WriteLine("Key Down: " + lParam.vkCode);
 else if (wParam == WM_KEYUP)
 System.Console.Out.WriteLine("Key Up: " + lParam.vkCode);
 return CallNextHookEx(Hook, nCode, wParam, lParam);
 
 public static bool RegisterHook()
 lock (Lock)
 if (IsRegistered)
 return true;
 Delegate = LowLevelKeyboardHandler;
 Hook = SetWindowsHookEx(
 WH_KEYBOARD_LL, Delegate,
 Marshal.GetHINSTANCE(
 System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0]
 ).ToInt32(), 0
 if (Hook != 0)
 return IsRegistered = true;
 Delegate = null;
 return false;
 public static bool UnregisterHook()
 lock (Lock)
 return IsRegistered = (UnhookWindowsHookEx(Hook) != 0);
使用跳转指令的API/函数钩子拦截.
下述例子使用指令，修改windows API中的MessageBoxW函数的前6个字节执行其他代码。这段代码编译为DLL文件，采用DLL注入技术让应用程序使用。微软提供了封装好的Detours库用于此目的。
/*
 This idea is based on chrom-lib approach, Distributed under GNU LGPL License.
 Source chrom-lib: https://github.com/linuxexp/chrom-lib
 Copyright (C) 2011 Raja Jamwal
 typedef int (WINAPI *pMessageBoxW)(HWND, LPCWSTR, LPCWSTR, UINT); // Messagebox prototype
 int WINAPI MyMessageBoxW(HWND, LPCWSTR, LPCWSTR, UINT); // Our detour
 void BeginRedirect(LPVOID); 
 pMessageBoxW pOrigMBAddress = NULL; // address of original
 BYTE oldBytes[SIZE] = {0}; // backup
 BYTE JMP[SIZE] = {0}; // 6 byte JMP instruction
 DWORD oldProtect, myProtect = PAGE_EXECUTE_READWRITE;
 INT APIENTRY DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved) 
 switch(Reason) 
 case DLL_PROCESS_ATTACH: // if attached
 pOrigMBAddress = (pMessageBoxW) 
 GetProcAddress(GetModuleHandle("user32.dll"), // get address of original 
 "MessageBoxW"); 
 if(pOrigMBAddress != NULL) 
 BeginRedirect(MyMessageBoxW); // start detouring
 break;
 case DLL_PROCESS_DETACH: 
 memcpy(pOrigMBAddress, oldBytes, SIZE); // restore backup
 case DLL_THREAD_ATTACH: 
 case DLL_THREAD_DETACH: 
 break; 
 return TRUE; 
 void BeginRedirect(LPVOID newFunction) 
 BYTE tempJMP[SIZE] = {0xE9, 0x90, 0x90, 0x90, 0x90, 0xC3}; // 0xE9 = JMP 0x90 = NOP 0xC3 = RET
 memcpy(JMP, tempJMP, SIZE); // store jmp instruction to JMP
 DWORD JMPSize = ((DWORD)newFunction - (DWORD)pOrigMBAddress - 5); // calculate jump distance
 VirtualProtect((LPVOID)pOrigMBAddress, SIZE, // assign read write protection
 PAGE_EXECUTE_READWRITE, &amp;oldProtect); 
 memcpy(oldBytes, pOrigMBAddress, SIZE); // make backup
 memcpy(&amp;JMP[1], &amp;JMPSize, 4); // fill the nop's with the jump distance (JMP,distance(4bytes),RET)
 memcpy(pOrigMBAddress, JMP, SIZE); // set jump instruction at the beginning of the original function
 VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, NULL); // reset protection
 int WINAPI MyMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uiType) 
 VirtualProtect((LPVOID)pOrigMBAddress, SIZE, myProtect, NULL); // assign read write protection
 memcpy(pOrigMBAddress, oldBytes, SIZE); // restore backup
 int retValue = MessageBoxW(hWnd, lpText, lpCaption, uiType); // get return value of original function
 memcpy(pOrigMBAddress, JMP, SIZE); // set the jump instruction again
 VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, NULL); // reset protection
 return retValue; // return original return value
Netfilter钩子.
使用Netfilter钩子修改Linux内核的网络交通。
/* Port we want to drop packets on */
static const uint16_t port = 25;
/* This is the hook function itself */
static unsigned int hook_func(unsigned int hooknum,
 struct sk_buff **pskb,
 const struct net_device *in,
 const struct net_device *out,
 int (*okfn)(struct sk_buff *))
 struct iphdr *iph = ip_hdr(*pskb);
 struct tcphdr *tcph, tcpbuf;
 if (iph-&gt;protocol != IPPROTO_TCP)
 return NF_ACCEPT;
 tcph = skb_header_pointer(*pskb, ip_hdrlen(*pskb), sizeof(*tcph), &amp;tcpbuf);
 if (tcph == NULL)
 return NF_ACCEPT;
 return (tcph-&gt;dest == port) ? NF_DROP : NF_ACCEPT;
/* Used to register our hook function */
static struct nf_hook_ops nfho = {
 .hook = hook_func,
 .hooknum = NF_IP_PRE_ROUTING,
 .pf = NFPROTO_IPV4,
 .priority = NF_IP_PRI_FIRST,
static __init int my_init(void)
 return nf_register_hook(&amp;nfho);
static __exit void my_exit(void)
 nf_unregister_hook(&amp;nfho);
module_init(my_init);
module_exit(my_exit);
内部IAT钩子.
下例展示通过修改可执行程序的输入地址表（IAT），把钩子函数替代原函数。 
typedef int(__stdcall *pMessageBoxA) (HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); //This is the 'type' of the MessageBoxA call.
pMessageBoxA RealMessageBoxA; //This will store a pointer to the original function.
void DetourIATptr(const char* function, void* newfunction, HMODULE module);
int __stdcall NewMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) { //Our fake function
 printf("The String Sent to MessageBoxA Was : %s\n", lpText);
 return RealMessageBoxA(hWnd, lpText, lpCaption, uType); //Call the real function
int main(int argc, CHAR *argv[]) {
 DetourIATptr("MessageBoxA",(void*)NewMessageBoxA,0); //Hook the function
 MessageBoxA(NULL, "Just A MessageBox", "Just A MessageBox", 0); //Call the function -- this will invoke our fake hook.
 return 0;
void **IATfind(const char *function, HMODULE module) { //Find the IAT (Import Address Table) entry specific to the given function.
 int ip = 0;
 if (module == 0)
 module = GetModuleHandle(0);
 PIMAGE_DOS_HEADER pImgDosHeaders = (PIMAGE_DOS_HEADER)module;
 PIMAGE_NT_HEADERS pImgNTHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)pImgDosHeaders + pImgDosHeaders-&gt;e_lfanew);
 PIMAGE_IMPORT_DESCRIPTOR pImgImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((LPBYTE)pImgDosHeaders + pImgNTHeaders-&gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
 if (pImgDosHeaders-&gt;e_magic != IMAGE_DOS_SIGNATURE)
 printf("libPE Error : e_magic is no valid DOS signature\n");
 for (IMAGE_IMPORT_DESCRIPTOR *iid = pImgImportDesc; iid-&gt;Name != NULL; iid++) {
 for (int funcIdx = 0; *(funcIdx + (LPVOID*)(iid-&gt;FirstThunk + (SIZE_T)module)) != NULL; funcIdx++) {
 char *modFuncName = (char*)(*(funcIdx + (SIZE_T*)(iid-&gt;OriginalFirstThunk + (SIZE_T)module)) + (SIZE_T)module + 2);
 const uintptr_t nModFuncName = (uintptr_t)modFuncName;
 bool isString = !(nModFuncName &amp; (sizeof(nModFuncName) == 4 ? 0x80000000 : 0x8000000000000000));
 if (isString) {
 if (!_stricmp(function, modFuncName))
 return funcIdx + (LPVOID*)(iid-&gt;FirstThunk + (SIZE_T)module);
 return 0;
void DetourIATptr(const char *function, void *newfunction, HMODULE module) {
 void **funcptr = IATfind(function, module);
 if (*funcptr == newfunction)
 return;
 DWORD oldrights, newrights = PAGE_READWRITE;
 //Update the protection to READWRITE
 VirtualProtect(funcptr, sizeof(LPVOID), newrights, &amp;oldrights);
 RealMessageBoxA = (pMessageBoxA)*funcptr; //Some compilers require the cast like "MinGW" not sure about MSVC
 *funcptr = newfunction;
 //Restore the old memory protection flags.
 VirtualProtect(funcptr, sizeof(LPVOID), oldrights, &amp;newrights);