钩子编程

钩子编程（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( &base0, 4, PAGE_EXECUTE_READWRITE, &oldProtection );  
//save the original function  
g_org_VirtualFunction01 = (VirtualFunction01_t)base0;  
//overwrite  
base0 = &hk_VirtualFunction01;  
VirtualProtect( &base0, 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   
#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 oldBytesSIZE = {0};                                         // backup
BYTE JMPSIZE = {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 tempJMPSIZE = {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(&JMP1, &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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
/* 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 
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.DataDirectoryIMAGE_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

**参考文献**

**外部链接**

**Windows**
  Information on Import Address Table function hooking. date=20181119011240 -  Information from Microsoft on hooking date=20160308050422 -  Information and various techniques regarding x86 hooking. date=20210120043628 -  APISpy32 date=20210507232836  is an application used to hook win32 API.-  Detours date=20120428131944  is a general purpose function hooking library created by Microsoft Research which works in C / C++.-  winspy date=20080720034310  Three ways to inject code into another process.-  HookTool SDK (ACF SDK) date=20081014114733  Provides a comprehensive overview on API hooking and code injection. A commercial product available too.-  madCodeHook date=20210228203250  is a commercial x86 and x64 API hooking and DLL injection library for C++ and Delphi.-  EasyHook date=20100314205620  is an open source hooking engine supporting x86 and x64 in Windows in both user and kernel land.-  SpyStudio Application Trace date=20201129081230  SpyStudio is an Application tracer which hook calls, displaying the results in a structured way.-  rohitab.com API Monitor date=20210519101107  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 date=20201111190627  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 date=20210126151406  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 date=20180612203121  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.-  date=20180201001741  Use of LD_PRELOAD to hook shared library calls.

**Emacs**
  Emacs Hooks date=20210424214210  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 date=20130515002540  is a framework for jailbroken iOS devices allowing developers to hook into any other framework or application.-  harpoon date=20201126130447  is an OS X library for runtime function hooking.

**Depth API Hooking**
  x86 API Hooking Demystified date=20210120043628  Article on various API Hooking methods, for the x86 architecture.