鈎子編程
鈎子編程(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注入技術讓應用程序使用。[1]微軟提供了封裝好的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權限,並且也可以和當前桌面進程交互。
參見
參考文獻
- ^ For more information, see http://ntvalk.blogspot.nl/2013/11/hooking-explained-detouring-library.html (頁面存檔備份,存於網際網路檔案館)
- Jonathan Daniel. Hooking explained: detouring library calls and vtable patching in Windows/Linux/MAC-OSX. 2013-11-27 [2014-01-01]. (原始內容存檔於2018-05-03).
- Binh Nguyen. Hacking-Lexicon / Linux Dictionary V 0.16. 2004-08-16 [2008-02-23]. (原始內容存檔於2021-03-22).
Hook
- Author: Holy Father. Hooking Windows API - Technics of hooking API functions on Windows 1.1 english (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
- [1] A student research project that utilizes hooking.
- [2] Functionality that allows a piece of software to observe and control the execution of another process.
- [3] (頁面存檔備份,存於網際網路檔案館) 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.