**钩子编程**（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( &base[0], 4, PAGE\_EXECUTE\_READWRITE, &oldProtection ); 
 //save the original function 
 g\_org\_VirtualFunction01 = (VirtualFunction01\_t)base[0]; 
 //overwrite 
 base[0] = &hk\_VirtualFunction01; 
 VirtualProtect( &base[0], 4, oldProtection, 0 );

 //call the virtual function (now hooked) from our class instance 
 myTable->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 & 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/函数钩子拦截

下述例子使用JMP（英语：）指令，修改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
\*/
#include <windows.h> 
#define SIZE 6

 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, &oldProtect); 
 memcpy(oldBytes, pOrigMBAddress, SIZE); // make backup
 memcpy(&JMP[1], &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内核的网络交通。

```
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>

#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/in.h>
#include <linux/netfilter.h>
#include <linux/netfilter\_ipv4.h>

/\* 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->protocol != IPPROTO\_TCP)
 return NF\_ACCEPT;

 tcph = skb\_header\_pointer(\*pskb, ip\_hdrlen(\*pskb), sizeof(\*tcph), &tcpbuf);
 if (tcph == NULL)
 return NF\_ACCEPT;

 return (tcph->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(&nfho);
}

static \_\_exit void my\_exit(void)
{
 nf\_unregister\_hook(&nfho);
}

module\_init(my\_init);
module\_exit(my\_exit);

```

### 内部IAT钩子

下例展示通过修改可执行程序的输入地址表（IAT），把钩子函数替代原函数。 

```
#include <windows.h>

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->e\_lfanew);
 PIMAGE\_IMPORT\_DESCRIPTOR pImgImportDesc = (PIMAGE\_IMPORT\_DESCRIPTOR)((LPBYTE)pImgDosHeaders + pImgNTHeaders->OptionalHeader.DataDirectory[IMAGE\_DIRECTORY\_ENTRY\_IMPORT].VirtualAddress);

 if (pImgDosHeaders->e\_magic != IMAGE\_DOS\_SIGNATURE)
 printf("libPE Error : e\_magic is no valid DOS signature\n");

 for (IMAGE\_IMPORT\_DESCRIPTOR \*iid = pImgImportDesc; iid->Name != NULL; iid++) {
 for (int funcIdx = 0; \*(funcIdx + (LPVOID\*)(iid->FirstThunk + (SIZE\_T)module)) != NULL; funcIdx++) {
 char \*modFuncName = (char\*)(\*(funcIdx + (SIZE\_T\*)(iid->OriginalFirstThunk + (SIZE\_T)module)) + (SIZE\_T)module + 2);
 const uintptr\_t nModFuncName = (uintptr\_t)modFuncName;
 bool isString = !(nModFuncName & (sizeof(nModFuncName) == 4 ? 0x80000000 : 0x8000000000000000));
 if (isString) {
 if (!\_stricmp(function, modFuncName))
 return funcIdx + (LPVOID\*)(iid->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, &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, &newrights);
}

```

### Windows API提供的挂钩函数

* SetWinEventHook：基本没有权限问题，也就是说这个API可以Hook到高权限程序的事件，同时支持进程内(WINEVENT\_INCONTEXT)和进程外(WINEVENT\_OUTOFCONTEXT)2种Hook方式，可以进程外的方式Hook到64位程序的事件。
* SetWindowsHookEx：64位编程情形，32位DLL没法直接注入到64位的应用程序里面， 因为地址空间完全不一样。UAC打开的情况下低权限程序没法Hook高权限程序。对于64位问题，解决方法是提供2个DLL，分别可以Hook32和64位程序。对于权限问题，通过注册系统服务，由服务进程创建工作进程。因为Windows Vista开始有了Session隔离机制，服务进程运行在Session 0，用户程序运行在Session 1, Session 2等，如果直接在服务程序里干活，我们就只能在Session 0里工作。通过创建进程，可以在DuplicateTokenEx后将Token的SessionID设置成目标Session，并且在CreateProcessAsUser时指定目标WinStation和Desktop， 这样就获得了System权限，并且也可以和当前桌面进程交互。

参见
--

* 回调函数
* 委托 (编程)
* 终止并驻留（英语：）
* User exit（英语：）
* WinAPIOverride32（英语：）

参考文献
----

1. ↑  For more information, see  （页面存档备份，存于）

* Jonathan Daniel.. 2013-11-27  [2014-01-01]. （原始内容存档于2018-05-03）.
* Binh Nguyen.. 2004-08-16  [2008-02-23]. （原始内容存档于2021-03-22）. Hook
* Author: Holy Father.  (PDF). 2002-06-10  [2008-02-21]. （原始内容存档 (PDF)于2020-10-28）.

外部链接
----

### Windows

* Information on Import Address Table function hooking. （页面存档备份，存于）
* Information from Microsoft on hooking （页面存档备份，存于）
* Information and various techniques regarding x86 hooking. （页面存档备份，存于）
* APISpy32 （页面存档备份，存于） is an application used to hook win32 API.
* Detours （页面存档备份，存于） is a general purpose function hooking library created by Microsoft Research which works in C / C++.
* winspy （页面存档备份，存于） Three ways to inject code into another process.
* HookTool SDK (ACF SDK) （页面存档备份，存于） Provides a comprehensive overview on API hooking and code injection. A commercial product available too.
* madCodeHook （页面存档备份，存于） is a commercial x86 and x64 API hooking and DLL injection library for C++ and Delphi.
* EasyHook （页面存档备份，存于） is an open source hooking engine supporting x86 and x64 in Windows in both user and kernel land.
* SpyStudio Application Trace （页面存档备份，存于） SpyStudio is an Application tracer which hook calls, displaying the results in a structured way.
* rohitab.com API Monitor （页面存档备份，存于） is a freeware application that can hook and display 10,000+ Windows APIs and COM Interfaces in 32-bit and 64-bit applications and services.
* Deviare API Hook （页面存档备份，存于） Deviare is a freeware inter-process hook framework that can be used to intercept other processes' API calls and show full-parameter information or create API monitors.
* WinAPIOverride （页面存档备份，存于） WinAPIOverride is a freeware for non commercial use. It can hook win32 API, COM, OLE, ActiveX, .NET in 32-bit and 64-bit processes. It includes monitoring post analysis tools.
* urmem （页面存档备份，存于） C++11 cross-platform library (x86) for working with memory (hooks, patches, pointer's wrapper, signature scanner etc.)

### Linux

* A student research project that utilizes hooking.
* Functionality that allows a piece of software to observe and control the execution of another process.
* （页面存档备份，存于） Use of LD\_PRELOAD to hook shared library calls.

### Emacs

* Emacs Hooks （页面存档备份，存于） Hooks are an important mechanism for customization of Emacs. A hook is a Lisp variable which holds a list of functions, to be called on some well-defined occasion. (This is called running the hook.)

### OS X 与 iOS

* Cydia Substrate （页面存档备份，存于） is a framework for jailbroken iOS devices allowing developers to hook into any other framework or application.
* harpoon （页面存档备份，存于） is an OS X library for runtime function hooking.

### Depth API Hooking

* x86 API Hooking Demystified （页面存档备份，存于） Article on various API Hooking methods, for the x86 architecture.