新起点
钩子编程
2020-07-04 02:32:28

钩子编程(hooking),也称作“挂钩”,是计算机程序设计术语,指通过拦截软件模块(英语:Modular programming)间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码,被称为钩子(hook)。

钩子编程有多种用途,如调试、扩展功能。例如在键盘或鼠标事件到达应用程序之前拦截这些事件;拦截应用程序或其他模块的操作系统调用以监督行为、修改功能。也广泛用于benchmarking程序,如度量3D游戏的帧率。

钩子编程也被用于恶意代码,如rootkit是各种通过假扮系统API调用输出来隐藏自身进程的可见性的工具与技术;游戏外挂是另一类例子。

在应用程序执行之前,物理修改可执行程序,这典型通过找到函数调用入口点,修改入口点使之在函数被执行前先执行其他的代码。另一种挂钩的方法是修改可执行程序的输入表(import table)。还有一种挂钩方法是采用包装库,使得应用程序不需修改即可调用该包装库完成其功能,而在包装库中插入钩子然后再调用原有的库。

操作系统与软件可提供方法,在运行时插入事件钩子。Microsoft Windows允许插入钩子以处理或修改对话框、滚动条、菜单等的系统事件、应用程序事件;插入、删除、处理或修改键盘鼠标事件。Linux允许类似的钩子通过NetFilter以处理网络事件。

如果没有上述机制或权限,也可拦截进程的库函数调用,在函数调用开始处注入代码。这可通过修改内存中的进程的中断向量表(英语:Interrupt vector table)或输入表(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, 4, PAGE_EXECUTE_READWRITE, &oldProtection );   //save the original function   g_org_VirtualFunction01 = (VirtualFunction01_t)base;   //overwrite   base = &hk_VirtualFunction01;   VirtualProtect( &base, 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 */                private static extern int SetWindowsHookEx(int idHook, LowLevelKeyboardDelegate lpfn,            int hmod, int dwThreadId);                private static extern int CallNextHookEx(int hHook, int nCode, int wParam, KBDLLHOOKSTRUCT lParam);                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;                public struct KBDLLHOOKSTRUCT        {            public int vkCode;            public int scanCode;            public int flags;            public int time;            public int dwExtraInfo;        }        /* Methods */        static private 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()                    ).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(英语:JMP (x86 instruction))指令,修改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 = {0};                                         // backup BYTE JMP = {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 = {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, &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),把钩子函数替代原函数。

相关:

  • 循环 (控制流程)
  • 网站公告: