2024-10-05 21:48

钩子编程

  钩子编程(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;

  }

  using System.Runtime.InteropServices;

  namespace Hooks

  {

  public class KeyHook

  {

  protected static int Hook;

  protected static LowLevelKeyboardDelegate Delegate;

  protected static readonly object Lock = new object();

  protected static bool IsRegistered = false;

  [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);

  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;

  }

  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);

  }

  }

  }

  }

  下述例子使用JMP指令,修改windows API中的MessageBoxW函数的前6个字节执行其他代码。这段代码编译为DLL文件,采用DLL注入技术让应用程序使用。微软提供了封装好的Detours库用于此目的。

  #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 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钩子修改Linux内核的网络交通。

  #include

  #include

  #include

  #include

  #include

  #include

  #include

  #include

  static const uint16_t port = 25;

  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;

  }

  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),把钩子函数替代原函数。

  #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

  ", 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

  ");

  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);

  }

  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

本内容为作者翻译自英文材料或转自网络,不代表本站立场,未经允许不得转载
如对本稿件有异议或投诉,请联系本站
想要了解世界的人,都在 九九叭

相关推荐