dotnet下的Hook API

/// <summary>
 /// 對CAPIHook進行了dotnet下的封裝。
 ///     其中fnHook: 爲掛鉤的函數的新地址的委託
 ///     不要傳送和掛鉤的函數不一致的委託,關於如果從程序上阻止這個行爲,暫時還沒有想到好的辦法。
 //////////////////////////////////////////////////////////////////////////////////////////////////
 /// 在dotnet中使用APIHook時發現了以下問題:
 /// 我們在c#中一般調用API是採用以下形式:
 ///                [DllImport("user32", EntryPoint = "MessageBoxA")]
 ///         public static extern int MessageBox(IntPtr hwnd, string lpText, string lpCaption, uint wType);
 /// 但是我發現
 ///       1.在我們還沒有使用APIHook來Hook MessageBox時,如果調用了MessageBox,即使以後使用APIHook來Hook了,這個MessageBox函數
 ///         仍然彈出正常的MessageBox,而不是我們希望的Hook MessageBox.
 ///       2.如果先調用APIHook進行了Hook,然後調用MessageBox,那麼會顯示hook messagebox.這時如果對APIHook類進行Unhook,再顯示
 ///         messageBox還是會進入hook messagebox的函數,並沒有回到normal messagebox.
 /// 針對這個情況,我推論出dotnet在invoke api時,一定採取了緩存機制,將地址保存起來了.
 /// 那麼它將這一地址保存在什麼地方呢?是全局中還是隻是本類中呢?
 ///              
 /// 我又進行了一次測試:
 ///     我定義了一個臨時類,該類只有一個函數就是public static extern int MessageBox,
 /// 測試結果:
 ///     1.如果調用Form的MessageBox,彈出normal messagebox.然後再使用APIHook進行hook,Form的messagebox仍然顯示
 ///       normal messagebox.但是myclass的messagebox已經顯示hooked messagebox了.
 ///     2.unhook後不改變上面的行爲.
 /// 結論:
 ///  public static extern int MessageBox(IntPtr hwnd, string lpText, string lpCaption, uint wType);
 ///    是作爲類的成員保存了起來.它具有和靜態成員類似的特性.它使用時才獲得native地址,並將這個地址保存在
 ///    本類中,它是作爲類的靜態成員存在的.
 //////////////////////////////////////////////////////////////////////////
 /// 另外對不熟悉鉤子的人來講,有一點需要說明:
 ///    本類只是通過修改IAT實現了hook api,通過本類可以截獲某個進程調用了的指定api(windows api或者動態鏈接庫中的api)
 ///    本類並沒有實現注入進程空間.如果需要使用hook api修改別的進程的行爲,需要先注入進程空間,然後使用hook api纔有效.
 /////////////////////////////////////////////////////////////////////////
 /// 使用本類可以直接用APIHook()然後調用Hook,或者使用帶參數的APIHook構造函數.
 /// unhook可以不用顯式調用,系統退出時會自動釋放.
 /// </summary>
// Hob.Toolbox.APIHook.h
#pragma once
#include "APIHook.h"
using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::Diagnostics;
namespace Hob{namespace Toolbox{namespace Classes
{
  public ref class APIHook
 {
 public:
  /// <summary>
  /// 構造函數
  /// </summary>
  APIHook()
  {
   //生成一個CAPIHook對象
   m_apiHookPtr=new CAPIHook();
  }
  APIHook(String^ CalleeModName,String^ FuncName,Delegate^ fnHook,bool fExcludeAPIHookMod)
  {
   //生成一個CAPIHook對象
   m_apiHookPtr=new CAPIHook();
   //hook
   Hook(CalleeModName,FuncName,fnHook,fExcludeAPIHookMod);
  }
  APIHook(String^ CalleeModName,String^ FuncName,Delegate^ fnHook)
  {
   //生成一個CAPIHook對象
   m_apiHookPtr=new CAPIHook();
   //hook
   Hook(CalleeModName,FuncName,fnHook,true);
  }
  /// <summary>
  /// 掛鉤某個API
  /// CalleeModName:希望掛鉤的模塊的名字
  /// FuncName:希望掛鉤的模塊中的函數的名字
  /// fnHook:掛鉤的函數的新地址  
  /// fExcludeAPIHookMod:建立掛鉤時是否包括本類所處的模塊 true:不包括 false:包括
  /// <summary>
  void Hook(String^ CalleeModName,String^ FuncName,Delegate^ fnHook,bool fExcludeAPIHookMod)
  {
   //如果已經掛鉤,必須先UnHook
   if(BeHook) UnHook();
   //開始掛鉤
   PSTR pszCalleeModName   = (PSTR)Marshal::StringToHGlobalAnsi(CalleeModName).ToPointer();
   PSTR pszFuncName   = (PSTR)Marshal::StringToHGlobalAnsi(FuncName).ToPointer();
   PROC pfnHook            =(PROC) Marshal::GetFunctionPointerForDelegate(fnHook).ToPointer();
   m_delegatefnHook     = fnHook;//save delegate防止delegate被垃圾回收
   m_fnHookType         = fnHook->GetType();
   m_apiHookPtr->Hook(pszCalleeModName,pszFuncName,pfnHook,fExcludeAPIHookMod);
  }
  void Hook(String^ CalleeModName,String^ FuncName,Delegate^ fnHook)
  {
   Hook(CalleeModName,FuncName,fnHook,true);
  }
  //撤銷掛鉤
  void UnHook()
  {
   if(!BeHook) return;//沒有hook
   m_apiHookPtr->UnHook();
  }
  //析構函數
  ~APIHook()
  {
   //釋放託管資源.....
   //調用釋放非託管資源
   this->!APIHook();
  }
  //finallize
  !APIHook()
  {
   //釋放非託管資源
   UnHook();
   delete m_apiHookPtr;
  }
  //得到原來函數api的委託
  property Delegate^ RawAPI
  {
   Delegate^ get()
   {
    try
    {
     if(!BeHook) return nullptr;
     PROC pfnOrig=(PROC)(*m_apiHookPtr);
     Delegate^ d=Marshal::GetDelegateForFunctionPointer(IntPtr((PVOID)pfnOrig),m_fnHookType);
     return d;
    }
    catch (Exception^)
    {
     return nullptr;
    }
   }
  }
  //是否被hook
  property bool BeHook
  {
   bool get()
   {
    return m_apiHookPtr->HasHook();
   }
  }
 private:
  CAPIHook* m_apiHookPtr;//CAPIHook對象
  Delegate^ m_delegatefnHook; //保存委託,避免被釋放掉,造成地址不可用
  Type^ m_fnHookType;//掛鉤的新函數的委託類型
 };
}}}
//APIHook.h
#pragma once
/// <summary>
/// CAPIHook類 本類用c++寫成,可以直接在c++下用.
/// 在此對 Jeffrey Richter 表示感謝
/// 實現應該放在cpp中,並且用#pragma unmanaged標示
/// 直接寫在頭文件會被認爲是託管的代碼,從而會發生一些異常(比如託管的異常:LoadLocker和Reentrancy)
/// 注意:  
///   最好不要使用此類掛鉤 API的LoadLibraryA LoadLibraryW LoadLibraryExA LoadLibraryExW GetProcAddress
///                             因爲本dll已經掛鉤了這幾個函數了。
///   最好不要使用多個對象掛鉤同一個API,沒有具體測試會有什麼後果。
/// </summary>
class CAPIHook
{
public:
 /// <summary>
 /// 構造函數
 /// </summary>
 CAPIHook(void);
 CAPIHook(PSTR pszCalleeModName,PSTR pszFuncName, PROC pfnHook,bool fExcludeAPIHookMod);
 //析構函數
 ~CAPIHook(void);
 //返回被hook函數原來的地址
 operator PROC();
 /// <summary>
 /// 掛鉤某個API
 /// pszCalleeModName:希望掛鉤的模塊的名字
 /// pszFuncName:希望掛鉤的模塊中的函數的名字
 /// fnHook:掛鉤的函數的新地址  
 /// fExcludeAPIHookMod:建立掛鉤時是否包括本類所處的模塊 true:不包括 false:包括
 /// <summary>
 void Hook(PSTR pszCalleeModName,PSTR pszFuncName, PROC pfnHook,bool fExcludeAPIHookMod);
 //撤銷掛鉤
 void UnHook();
 //是否掛鉤了
 bool HasHook();
private:
 bool m_hasHook;//是否已經掛鉤
 static PVOID sm_pvMaxAppAddr;// 最大私有內存空間
 static CAPIHook* sm_pHead;    //APIHook對象鏈表的頭部
 CAPIHook* m_pNext;            //本APIHook對象的下一個對象
 PCSTR m_pszCalleeModName;     //希望掛鉤的模塊的名字
 PCSTR m_pszFuncName;          // 希望掛鉤的模塊中的函數的名字
 PROC  m_pfnOrig;              // 被掛鉤的函數原來的地址
 PROC  m_pfnHook;              // 掛鉤的函數的新地址
 bool  m_fExcludeAPIHookMod;   // 建立掛鉤時是否包括本類所處的模塊 true:不包括 false:包括
private:
 //替換所有模塊中的函數地址爲新地址
 static void ReplaceIATEntryInAllMods(PCSTR pszCalleeModName,PROC pfnCurrent, PROC pfnNew, bool fExcludeAPIHookMod);
 //替換某個模塊中的函數地址爲新地址
 static void ReplaceIATEntryInOneMod(PCSTR pszCalleeModName,PROC pfnCurrent, PROC pfnNew, HMODULE hmodCaller);
 //調用真實的GetProcAddress獲得函數的原來的真正地址
 static FARPROC WINAPI GetProcAddre***aw(HMODULE hmod,LPCSTR pszProcName);
 // 得到包含指定內存地址的模塊
 static HMODULE ModuleFromAddress(PVOID pv);
private:
 #pragma region API被動態調用時的處理
 //下面處理LoadLibrary 和 GetProcAddress
 // 當我們使用這些函數動態調用API時,本類無法正確截獲
 static CAPIHook sm_LoadLibraryA;
 static CAPIHook sm_LoadLibraryW;
 static CAPIHook sm_LoadLibraryExA;
 static CAPIHook sm_LoadLibraryExW;
 static CAPIHook sm_GetProcAddress;
 //新的LoadLibrary的函數
 static void FixupNewlyLoadedModule(HMODULE hmod, DWORD dwFlags);
 //新的LoadLibraryA
 static HMODULE WINAPI LoadLibraryA(PCSTR pszModulePath);
 //新的LoadLibraryW
 static HMODULE WINAPI LoadLibraryW(PCWSTR pszModulePath);
 //新的LoadLibraryExA
 static HMODULE WINAPI LoadLibraryExA(PCSTR pszModulePath,HANDLE hFile, DWORD dwFlags);
 //新的LoadLibraryExW
 static HMODULE WINAPI LoadLibraryExW(PCWSTR pszModulePath,HANDLE hFile, DWORD dwFlags);
 //新的GetProcAddress
 static FARPROC WINAPI GetProcAddress(HMODULE hmod, PCSTR pszProcName);
 #pragma endregion
};
 
//APIHook.cpp
#include "Stdafx.h"
#include "APIHook.h"
#include "MyASSERT.h"
#include <ImageHlp.h>//由於ImageHlp.h中有部分定義和system命名空間下的混淆,應該在system前include
#pragma comment(lib, "ImageHlp")
#include <Tlhelp32.h>
#pragma unmanaged //以下爲非託管代碼
PVOID CAPIHook::sm_pvMaxAppAddr=NULL;// 最大私有內存空間
const BYTE cPushOpCode = 0x68;   //x86平臺下的PUSH的操作碼
CAPIHook* CAPIHook::sm_pHead=NULL;
//構造函數
CAPIHook::CAPIHook()
{
 m_hasHook=false;
 m_pfnOrig=NULL;
 m_pfnHook=NULL;
}
//構造函數
CAPIHook::CAPIHook(PSTR pszCalleeModName,PSTR pszFuncName, PROC pfnHook,bool fExcludeAPIHookMod)
{
 m_hasHook=false;
 m_pfnOrig=NULL;
 m_pfnHook=NULL;
 Hook(pszCalleeModName,pszFuncName,pfnHook,fExcludeAPIHookMod);
}
//析構函數
CAPIHook::~CAPIHook(void)
{
 UnHook();
}
//返回被hook函數原來的地址
CAPIHook::operator PROC()
{
 return(m_pfnOrig);
}
bool CAPIHook::HasHook()
{
 return m_hasHook;
}
/// <summary>
/// 掛鉤某個API
/// pszCalleeModName:希望掛鉤的模塊的名字
/// pszFuncName:希望掛鉤的模塊中的函數的名字
/// fnHook:掛鉤的函數的新地址  
/// fExcludeAPIHookMod:建立掛鉤時是否包括本類所處的模塊 true:不包括 false:包括
/// <summary>
void CAPIHook::Hook(PSTR pszCalleeModName,PSTR pszFuncName, PROC pfnHook,bool fExcludeAPIHookMod)
{
 //如果已經掛鉤,必須先UnHook
 if(m_hasHook) UnHook();
 if (sm_pvMaxAppAddr == 0)
 {
  //lpMaximumApplicationAddress以上的函數地址需要進行特殊處理(僅對windows98而言)
  SYSTEM_INFO si;
  GetSystemInfo(&si);
  sm_pvMaxAppAddr = si.lpMaximumApplicationAddress;
 }
 //修改 APIHook 對象的鏈表
 m_pNext  = sm_pHead;   
 sm_pHead = this;       
 //保存掛鉤的函數的信息
 m_pszCalleeModName   = pszCalleeModName;
 m_pszFuncName        = pszFuncName;
 m_pfnHook            = pfnHook;
 m_fExcludeAPIHookMod = fExcludeAPIHookMod;
 m_pfnOrig            = GetProcAddre***aw(GetModuleHandleA(m_pszCalleeModName), m_pszFuncName);
 chASSERT(m_pfnOrig != NULL);  //模塊中沒有找到對應函數
 if (m_pfnOrig > sm_pvMaxAppAddr)
 {
  // 地址在共享dll中,地址需要修正(僅對windows98而言)
  PBYTE pb = (PBYTE) m_pfnOrig;
  if (pb[0] == cPushOpCode)
  {
   // 跳過PUSH操作代碼得到真正的地址
   LPVOID pv = * (LPVOID*) &pb[1];
   m_pfnOrig = (PROC) pv;
  }
 }
 // 在當前已經加載的所有模塊中掛鉤函數
 ReplaceIATEntryInAllMods(m_pszCalleeModName, m_pfnOrig, m_pfnHook, m_fExcludeAPIHookMod);
 m_hasHook= (m_pfnOrig != NULL);//ok
}
//撤銷掛鉤
void CAPIHook::UnHook()
{
 if(!m_hasHook) return;//沒有hook
 //停止掛鉤函數,替換爲原來的函數
 ReplaceIATEntryInAllMods(m_pszCalleeModName, m_pfnHook, m_pfnOrig, m_fExcludeAPIHookMod);
 // 將對象從鏈表中移出
 CAPIHook* p = sm_pHead;
 if (p == this)
 {     // Removing the head node
  sm_pHead = p->m_pNext;
 }
 else
 {
  BOOL fFound = FALSE;
  // Walk list from head and fix pointers
  for (; !fFound && (p->m_pNext != NULL); p = p->m_pNext)
  {
   if (p->m_pNext == this)
   {
    // Make the node that points to us point to the our next node
    p->m_pNext = p->m_pNext->m_pNext;
    fFound=TRUE;
    break;
   }
  }
  chASSERT(fFound==TRUE);
 }
 m_hasHook=false;
}
//替換所有模塊中的函數地址爲新地址
void CAPIHook::ReplaceIATEntryInAllMods(PCSTR pszCalleeModName,PROC pfnCurrent, PROC pfnNew, bool fExcludeAPIHookMod)
{
 HMODULE hmodThisMod = fExcludeAPIHookMod ? ModuleFromAddress(&CAPIHook::ReplaceIATEntryInAllMods) : NULL;
 //得到當前進行的模塊列表
 HANDLE hSnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,GetCurrentProcessId());//打開
 MODULEENTRY32 me = { sizeof(me) };
 for (BOOL fOk = Module32First(hSnapshot,&me); fOk; fOk = Module32Next(hSnapshot,&me))
 {
  // NOTE: We don't hook functions in our own module
  if (me.hModule != hmodThisMod)
  {
   // Hook this function in this module
   ReplaceIATEntryInOneMod(pszCalleeModName, pfnCurrent, pfnNew, me.hModule);
  }
 }   
 CloseHandle(hSnapshot);//關閉      
}
//替換某個模塊中的函數地址爲新地址
void CAPIHook::ReplaceIATEntryInOneMod(PCSTR pszCalleeModName,PROC pfnCurrent, PROC pfnNew, HMODULE hmodCaller)
{
 //得到模塊(hmodCaller)引入片斷(import section)的地址
 ULONG ulSize;
 PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)
  ImageDirectoryEntryToData(hmodCaller, TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize);
 // 模塊(hmodCaller)沒有引入片斷(import section)
 if (pImportDesc == NULL) return;
 // 查找模塊(hmodCaller)合適的引入描述符,該描述符描述的是模塊(hmodCaller)使用被調用的模塊(pszCalleeModName)的函數信息
 for (; pImportDesc->Name; pImportDesc++)
 {
  PSTR pszModName = (PSTR) ((PBYTE) hmodCaller + pImportDesc->Name);
  if (lstrcmpiA(pszModName, pszCalleeModName) == 0)
   break;   // Found
 }
 // 模塊(hmodCaller)中沒有關於被調用的模塊(pszCalleeModName)的函數信息
 if (pImportDesc->Name == 0) return;
 // 得到調用者hmodCaller 使用被調用的模塊(pszCalleeModName) 的IAT(引入地址表)
 PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)((PBYTE) hmodCaller + pImportDesc->FirstThunk);
 //替換當前的函數地址爲需要的新地址
 for (; pThunk->u1.Function; pThunk++)
 {
  //得到需要被替換的函數地址
  PROC* ppfn = (PROC*) &pThunk->u1.Function;
  // 這是我們要找的函數嗎?
  BOOL fFound = (*ppfn == pfnCurrent);
  if (!fFound && (*ppfn > sm_pvMaxAppAddr)) //如果沒有找到,修正
  {
   //如果我們運行在win98下,地址在共享dll中,這個可能不是函數地址
   //這種情況下,指令操作使用的地址應該是正確的地址
   PBYTE pbInFunc = (PBYTE) *ppfn;
   if (pbInFunc[0] == cPushOpCode)
   {
    // 我們找到了PUSH操作,真實地址就在後面跟着
    ppfn = (PROC*) &pbInFunc[1];
    // 這是我們要找的函數嗎?
    fFound = (*ppfn == pfnCurrent);
   }
  }
  if (fFound)
  {
   // 獲得了匹配的地址,我們改寫它(IAT)
   WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew, sizeof(pfnNew), NULL);
   return;  // 成功,退出
  }
 }
 // If we get to here, the function is not in the caller's import section
}
//調用真實的GetProcAddress獲得函數的原來的真正地址
FARPROC WINAPI CAPIHook::GetProcAddre***aw(HMODULE hmod,LPCSTR pszProcName)
{
 return ::GetProcAddress(hmod,pszProcName);
}
// 得到包含指定內存地址的模塊
HMODULE CAPIHook::ModuleFromAddress(PVOID pv)
{
 MEMORY_BASIC_INFORMATION mbi;
 return( (VirtualQuery(pv, &mbi, sizeof(mbi)) != 0) ?(HMODULE) mbi.AllocationBase : NULL);
}
//以下fExcludeAPIHookMod must be true,否則下面的新函數(LoadLibraryA 等等)將會死循環。也即本模塊的IAT地址不能替換.
CAPIHook CAPIHook::sm_LoadLibraryA("Kernel32.dll", "LoadLibraryA",(PROC) CAPIHook::LoadLibraryA, true);
CAPIHook CAPIHook::sm_LoadLibraryW("Kernel32.dll", "LoadLibraryW",(PROC) CAPIHook::LoadLibraryW, true);
CAPIHook CAPIHook::sm_LoadLibraryExA("Kernel32.dll", "LoadLibraryExA",(PROC) CAPIHook::LoadLibraryExA, true);
CAPIHook CAPIHook::sm_LoadLibraryExW("Kernel32.dll", "LoadLibraryExW",(PROC) CAPIHook::LoadLibraryExW, true);
CAPIHook CAPIHook::sm_GetProcAddress("Kernel32.dll", "GetProcAddress",(PROC) CAPIHook::GetProcAddress, true);
//新的LoadLibrary的函數
void CAPIHook::FixupNewlyLoadedModule(HMODULE hmod, DWORD dwFlags)
{
 // If a new module is loaded, hook the hooked functions
 if ((hmod != NULL) && ((dwFlags & LOAD_LIBRARY_AS_DATAFILE) == 0))
 {
  for (CAPIHook* p = sm_pHead; p != NULL; p = p->m_pNext)
  {
   ReplaceIATEntryInOneMod(p->m_pszCalleeModName,p->m_pfnOrig, p->m_pfnHook, hmod);
  }
 }
}
//新的LoadLibraryA
HMODULE WINAPI CAPIHook::LoadLibraryA(PCSTR pszModulePath)
{
 HMODULE hmod = ::LoadLibraryA(pszModulePath);
 FixupNewlyLoadedModule(hmod, 0);
 return(hmod);
}
//新的LoadLibraryW
HMODULE WINAPI CAPIHook::LoadLibraryW(PCWSTR pszModulePath)
{
 HMODULE hmod = ::LoadLibraryW(pszModulePath);
 FixupNewlyLoadedModule(hmod, 0);
 return(hmod);
}
//新的LoadLibraryExA
HMODULE WINAPI CAPIHook::LoadLibraryExA(PCSTR pszModulePath,HANDLE hFile, DWORD dwFlags)
{
 HMODULE hmod = ::LoadLibraryExA(pszModulePath, hFile, dwFlags);
 FixupNewlyLoadedModule(hmod, dwFlags);
 return(hmod);
}
//新的LoadLibraryExW
HMODULE WINAPI CAPIHook::LoadLibraryExW(PCWSTR pszModulePath,HANDLE hFile, DWORD dwFlags)
{
 HMODULE hmod = ::LoadLibraryExW(pszModulePath, hFile, dwFlags);
 FixupNewlyLoadedModule(hmod, dwFlags);
 return(hmod);
}
//新的GetProcAddress
FARPROC WINAPI CAPIHook::GetProcAddress(HMODULE hmod, PCSTR pszProcName)
{
 // Get the true address of the function
 FARPROC pfn = GetProcAddre***aw(hmod, pszProcName);
 // Is it one of the functions that we want hooked?
 CAPIHook* p = sm_pHead;
 for (; (pfn != NULL) && (p != NULL); p = p->m_pNext)
 {
  if (pfn == p->m_pfnOrig)
  {
   // The address to return matches an address we want to hook
   // Return the hook function address instead
   pfn = p->m_pfnHook;
   break;
  }
 }
 return pfn;
}
#pragma managed
 
 
//MyASSERT.h
#pragma once
#pragma comment(lib, "User32.lib")
#define chDIMOF(Array) (sizeof(Array) / sizeof(Array[0]))
inline void chMB(PCSTR s)
{
 char szTMP[128];
 ::GetModuleFileNameA(NULL, szTMP, chDIMOF(szTMP));
 MessageBoxA(GetActiveWindow(), s, szTMP, MB_OK);
}
inline void chFAIL(PSTR szMsg)
{
 chMB(szMsg);
 DebugBreak();
}
// Put up an assertion failure message box.
inline void chASSERTFAIL(LPCSTR file, int line, PCSTR expr)
{
 char sz[128];
 wsprintfA(sz, "File %s, line %d : %s", file, line, expr);
 chFAIL(sz);
}
#ifdef _DEBUG
#define chASSERT(x) if (!(x)) chASSERTFAIL(__FILE__, __LINE__, #x)
#else
#define chASSERT(x)
#endif
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章