鉤子程式設計

鉤子程式設計。鉤子程式設計有多種用途，如除錯、擴充套件功能。例如在鍵盤或滑鼠事件到達應用程式之前攔截這些事件；攔截應用程式或其他模組的作業系統呼叫以監督行為、修改功能。也廣泛用於benchmarking程式，如度量3D遊戲的幀率。鉤子程式設計也被用於惡意程式碼，如rootkit是各種透過假扮系統API呼叫輸出來隱藏自身程序的可見性的工具與技術；遊戲外掛是另一類例子。方法.物理修改.在應用程式執行之前，物理修改可執行程式，這典型透過找到函式呼叫入口點，修改入口點使之在函式被執行前先執行其他的程式碼。另一種掛鉤的方法是修改可執行程式的輸入表。還有一種掛鉤方法是採用包裝庫，使得應用程式不需修改即可呼叫該包裝庫完成其功能，而在包裝庫中插入鉤子然後再呼叫原有的庫。執行時修改.作業系統與軟體可提供方法，在執行時插入事件鉤子。Microsoft Windows允許插入鉤子以處理或修改對話方塊、捲軸、選單等的系統事件、應用程式事件；插入、刪除、處理或修改鍵盤滑鼠事件。Linux允許類似的鉤子透過NetFilter以處理網路事件。如果沒有上述機制或許可權，也可攔截程序的庫函式呼叫，在函式呼叫開始處注入程式碼。這可透過修改記憶體中的程序的或輸入表實現。示例程式碼.虛擬函式表掛鉤.C++使用虛擬函式，因此可在執行時直接修改虛擬函式表的內容來掛鉤。 Window上很多軟體庫以COM方式提供的的Hook。ATL就是用介面替代的方式來除錯和記錄COM介面引用計數的次數class VirtualTable { // example classpublic: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 functionvoid __fastcall hk_VirtualFunction01( ticket* thisptr, int edx ) {printf("Custom function called");//call the original functiong_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 functiong_org_VirtualFunction01 = (VirtualFunction01_t)base[0];//overwritebase[0] = &amp;hk_VirtualFunction01;VirtualProtect( &amp;base[0], 4, oldProtection, 0 );//call the virtual function (now hooked) from our class instancemyTable-&gt;VirtualFunction01();return 0;C#鍵盤事件鉤子.using System.Runtime.InteropServices;namespace Hookspublic 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 KBDLLHOOKSTRUCTpublic 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(), 0if (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-libCopyright (C) 2011 Raja Jamwaltypedef int (WINAPI *pMessageBoxW)(HWND, LPCWSTR, LPCWSTR, UINT); // Messagebox prototypeint WINAPI MyMessageBoxW(HWND, LPCWSTR, LPCWSTR, UINT); // Our detourvoid BeginRedirect(LPVOID);pMessageBoxW pOrigMBAddress = NULL; // address of originalBYTE oldBytes[SIZE] = {0}; // backupBYTE JMP[SIZE] = {0}; // 6 byte JMP instructionDWORD oldProtect, myProtect = PAGE_EXECUTE_READWRITE;INT APIENTRY DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved)switch(Reason)case DLL_PROCESS_ATTACH: // if attachedpOrigMBAddress = (pMessageBoxW)GetProcAddress(GetModuleHandle("user32.dll"), // get address of original"MessageBoxW");if(pOrigMBAddress != NULL)BeginRedirect(MyMessageBoxW); // start detouringbreak;case DLL_PROCESS_DETACH:memcpy(pOrigMBAddress, oldBytes, SIZE); // restore backupcase 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 = RETmemcpy(JMP, tempJMP, SIZE); // store jmp instruction to JMPDWORD JMPSize = ((DWORD)newFunction - (DWORD)pOrigMBAddress - 5); // calculate jump distanceVirtualProtect((LPVOID)pOrigMBAddress, SIZE, // assign read write protectionPAGE_EXECUTE_READWRITE, &amp;oldProtect);memcpy(oldBytes, pOrigMBAddress, SIZE); // make backupmemcpy(&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 functionVirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, NULL); // reset protectionint WINAPI MyMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uiType)VirtualProtect((LPVOID)pOrigMBAddress, SIZE, myProtect, NULL); // assign read write protectionmemcpy(pOrigMBAddress, oldBytes, SIZE); // restore backupint retValue = MessageBoxW(hWnd, lpText, lpCaption, uiType); // get return value of original functionmemcpy(pOrigMBAddress, JMP, SIZE); // set the jump instruction againVirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, NULL); // reset protectionreturn retValue; // return original return valueNetfilter鉤子.使用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鉤子.下例展示透過修改可執行程式的輸入地址表，把鉤子函式替代原函式。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 functionprintf("The String Sent to MessageBoxA Was : %s\n", lpText);return RealMessageBoxA(hWnd, lpText, lpCaption, uType); //Call the real functionint main(int argc, CHAR *argv[]) {DetourIATptr("MessageBoxA",(void*)NewMessageBoxA,0); //Hook the functionMessageBoxA(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 READWRITEVirtualProtect(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);