天天看點

硬體斷點hook

見字如面,我是東北碼農。

上文我們介紹了硬體斷點,大家可以回顧一下。本文将介紹使用硬體斷點+veh,實作硬體斷點hook。

關注後,聊天框回複“硬體斷點hook”,可以擷取本文源碼。

1、不修改原函數hook

硬體斷點這個手法,我還是分析外挂時學到的。開始做反外挂時候,隻會用xuetr、pchunter等工具檢視遊戲程序的inlinehook,分析修改了遊戲的哪些代碼,然後再防禦。然後有一天,我發現了一個外挂不按套路出牌,沒有修改任何代碼實作了透視功能,我就很納悶這個外挂是怎麼實作的呢,不改代碼我怎麼防啊?

後來經過不斷學習,才知道hook的方式有很多種,硬體斷點hook就是其中一種比較隐蔽的hook方式,可以在不修改源函數的前提實作hook,這一點比inlinehook強。

2、VEH(Vectored Exception Handling)

2.1、VEH介紹

我們先來介紹一下VEH,先放一段微軟官方描述。

參考資料:https://docs.microsoft.com/en-us/windows/win32/debug/vectored-exception-handling

Vectored exception handlers are an extension to structured exception handling. An application can register a function to watch or handle all exceptions for the application. Vectored handlers are not frame-based, therefore, you can add a handler that will be called regardless of where you are in a call frame. Vectored handlers are called in the order that they were added, after the debugger gets a first chance notification, but before the system begins unwinding the stack.

大概意思是,程式可以注冊異常回調函數連結清單。當程式觸發異常時,回調函數就會按照使用者指定的順序依次調用。

2.2、VEH 使用

向VEH鍊注冊一個異常處理函數,可以使用windows提供的API,

PVOID AddVectoredContinueHandler(
  ULONG                       First,
  PVECTORED_EXCEPTION_HANDLER Handler
);
           
  • First:是否插入VEH鍊頭部。
  • Handler:異常處理函數。

接下來看一下異常處理函數,PVECTORED_EXCEPTION_HANDLER 的定義:

LONG PvectoredExceptionHandler(
  [in] _EXCEPTION_POINTERS *ExceptionInfo
)
           

唯一的參數_EXCEPTION_POINTERS指向異常資訊,定義如下:

定義如下:

typedef struct _EXCEPTION_POINTERS {
  PEXCEPTION_RECORD ExceptionRecord;// 異常資訊記錄
  PCONTEXT          ContextRecord;// 寄存器資訊
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
           

異常處理函數的傳回值很重要,控制着異常處理的後續行為:

To return control to the point at which the exception occurred, return EXCEPTION_CONTINUE_EXECUTION (0xffffffff). To continue the handler search, return EXCEPTION_CONTINUE_SEARCH (0x0).

  • 傳回-1:異常已處理,繼續執行;
  • 傳回0:繼續調用VEH鍊的其它處理函數。

3、硬體斷點 hook 實戰代碼

介紹完硬體斷點和veh後,我們就可以實作硬體斷點hook了。我們先介紹一下思路:

  1. 設定VEH鍊異常處理函數。
  2. 設定硬體斷點,監控原函數的執行事件。
  3. 執行原函數時,會觸發異常調用我們的異常處理函數。
  4. 異常處理函數判斷,如果是原函數位址,則修改rip寄存器,跳轉到hook函數。

3.1、設定VEH鍊異常處理函數

異常處理函數,xx_hw_bp_veh的實作一會再介紹。

static bool xx_init_hwbp_hook() {
  return xx_add_veh(CALL_FIRST, xx_hw_bp_veh);
}
           

3.2、設定硬體斷點

class xx_hw_bp
{
public:
  struct bp_info {
    void* src_ = nullptr;
    void* dst_ = nullptr;
  };
public:
  void hook(void* src,void* dst,HANDLE thread,int idx)
{
    //記錄原函數與hook函數關系。
    // TODO idx check
    info_[idx].src_ = src;
    info_[idx].dst_ = dst;
    // 設定硬體斷點
    xx_set_hw_bp(thread, idx, src, RW_EXE);
  }
    // 擷取原函數與hook函數映射
  void* get_dst(void* src) {
    for (int i = 0; i < 4; ++i) {
      if (src == info_[i].src_)
      {
        return info_[i].dst_;
      }
    }
    return nullptr;
  }
  bp_info info_[4];
};

xx_hw_bp g_hwbp_;// 全局類,友善異常處理函數通路
           

先記錄一下原函數和hook函數的映射關系,再使用上文硬體斷點封裝好的xx_set_hw_bp設定硬體斷點。

3.3、異常處理函數實作

LONG NTAPI xx_hw_bp_veh(
  struct _EXCEPTION_POINTERS* ExceptionInfo)
{
  // 擷取異常位址
  void* src = ExceptionInfo->ExceptionRecord->ExceptionAddress;
  // 查找對應關系
  void* dst = g_hwbp_.get_dst(src);
  if (nullptr != dst) {
    // 修改rip 跳轉
    memcpy(&ExceptionInfo->ContextRecord->Rip, &dst, sizeof(void*));
    return EXCEPTION_CONTINUE_EXECUTION;
  }
  // 其它異常,繼續veh鍊處理
  return EXCEPTION_CONTINUE_SEARCH;
}
           

先通過異常位址,找到對應的hook函數位址。若找到,則修改Rip(指令寄存器)實作跳轉;找不到則不是我們關心的異常,交給VEH鍊繼續處理吧。

4、demo驗證

int WINAPI My_MessageBoxA(
  _In_opt_ HWND hWnd,
  _In_opt_ LPCSTR lpText,
  _In_opt_ LPCSTR lpCaption,
  _In_ UINT uType)
{
  printf("[call %s]!\n", __FUNCTION__);

  auto ori = xx_trampoline_to_func(&MessageBoxA, trampoline);
  return (*ori)(hWnd, lpText, lpCaption, uType);
}

void test_hook() {
  // 制作跳闆,隻需要複制1條指令,跳過硬體斷點即可
  xx_mem_unprotect(trampoline, 1024);
  xx_make_trampoline(&MessageBoxA, trampoline, 4);

  xx_init_hwbp_hook();
  g_hwbp_.hook(&MessageBoxA, &My_MessageBoxA,GetCurrentThread(), 0);
  ::MessageBoxA(0, "aaa", "bbb", 0);
}
           

使用時先制作跳闆,再hook。制作跳闆複用inlinehook的代碼。由于不修改代碼,是以跳闆值需要複制一條指令即可。複制的代碼少,是以也基本不用考慮重定位問題。

歡迎大家點贊、轉發、再看、留言交流~

繼續閱讀