钩子编程
✍ dations ◷ 2024-12-23 08:37:49 #控制流程,DLL注入
钩子编程(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 }