HOOK的方式有很多种,各有优缺点。IAT表的HOOK需要监视LIABRARY,GETPROCESS这些相关函数,真想实现起来还是很麻烦的。用内存补丁的方法,JMP到自己代码中,这个的缺点是需要在自己的代码里实现原来JMP处的指令。。。
现在我来说个新的思路,用异常来实现HOOK。我们都知道,当一个程序发生异常的时候,异常传递路径是:1.debuger 2. 向量化异常,即VEH 3.SEH。。。
如果在没有调试器的情况下,首先接受到异常的就是VEH了。那么我们可以在要HOOK的代码上插入一个 int3 ,然后通过VEH处理这个异常,从而实现HOOK的目的。原理很简单。用代码说话吧
代码:
// Hook.h: interface for the CHook class. // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_HOOK_H__C0F41F39_4EB7_4129_BFB7_156CB203826F__INCLUDED_) #define AFX_HOOK_H__C0F41F39_4EB7_4129_BFB7_156CB203826F__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include <list> using namespace std; typedef void (*HOOKHANDLEFUN)(EXCEPTION_POINTERS *pExceptionInfo,void *pExtendData); struct t_HookInfo { DWORD dwAddr; HOOKHANDLEFUN pFn; BYTE byOldCode; }; class CHook { private: static CHook *m_Instance; //单件模式 protected: CHook(); //保证只生成一个实例 public: static CHook* GetInstance(); t_HookInfo *m_pCurrentBP; BOOL m_bSingleFlag; EXCEPTION_POINTERS *m_pException; void SetTF(); static LONG WINAPI VectoredHandler(PEXCEPTION_POINTERS ExceptionInfo); list<t_HookInfo*> m_listHook; BOOL AddHook(DWORD dwHookAddr, HOOKHANDLEFUN pFn); BOOL RemoveHook(DWORD dwHookAddr); BOOL SetBP(t_HookInfo*); BOOL UnSetBP(t_HookInfo*); virtual ~CHook(); }; #endif // !defined(AFX_HOOK_H__C0F41F39_4EB7_4129_BFB7_156CB203826F__INCLUDED_)
代码:
// Hook.cpp: implementation of the CHook class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "HookDll.h" #include "Hook.h" #define TEST #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #ifdef TEST #define TESTMSG(str) AfxMessageBox(str) #else #define TESTMSG(str) #endif ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// #define BREAKPOINTLEN 2 typedef PVOID (WINAPI *ADDVECTOREDEXCEPTIONHANDLER)(ULONG,PVOID); CHook* CHook::m_Instance; CHook::CHook() { //清空链表 m_listHook.empty(); //安装顶层异常 HINSTANCE hMod = LoadLibrary("kernel32.dll"); ADDVECTOREDEXCEPTIONHANDLER pfn = (ADDVECTOREDEXCEPTIONHANDLER) GetProcAddress(hMod,"AddVectoredExceptionHandler"); PVOID ret = pfn(1,VectoredHandler); m_bSingleFlag = FALSE; m_pCurrentBP = NULL; } //函数名称:GetInstance //函数参数:无 //函数功能:返回一个类的实例。 //返回值: CHook* CHook* CHook::GetInstance() { if (!m_Instance) { m_Instance = new CHook; } return m_Instance; } CHook::~CHook() { list<t_HookInfo*>::iterator it; for (it = m_listHook.begin(); it!=m_listHook.end(); it++) { UnSetBP((*it)); delete (*it); } m_listHook.empty(); } //函数名称:AddHook //函数参数:1.要HOOK的地址 2.处理HOOK的函数指针 //函数功能:添加一个HOOKINFO到链表中。并安装一个HOOK //返回值: 成功返回TRUE,否则FALSE //注意: 本函数没有检查参数地址的有效性,由调用方检查。 BOOL CHook::AddHook(DWORD dwHookAddr, HOOKHANDLEFUN pFn) { t_HookInfo *pHookInfo = new t_HookInfo; if (!pHookInfo) { return FALSE; } pHookInfo->dwAddr = dwHookAddr; pHookInfo->pFn = pFn; m_listHook.push_back(pHookInfo); return SetBP(pHookInfo); } //函数名称:RemoveHook //函数参数:1.要REMOVEHOOK的地址 //函数功能:删除一个HOOK //返回值: 成功返回TRUE,否则FALSE //注意: 本函数没有检查参数地址的有效性,由调用方检查。 BOOL CHook::RemoveHook(DWORD dwHookAddr) { list<t_HookInfo*>::iterator it; for (it = m_listHook.begin(); it!=m_listHook.end(); it++) { if ((*it)->dwAddr == dwHookAddr) { UnSetBP(*it); delete (*it); m_listHook.remove(*it); return TRUE; } } return FALSE; } //函数名称:SetBP //函数参数:1.t_HookInfo hook信息 //函数功能:写入一个INT3 //返回值: 成功返回TRUE,否则FALSE //注意: 本函数没有检查参数地址的有效性,由调用方检查。 BOOL CHook::SetBP(t_HookInfo *pHookInfo) { DWORD dwOldProtect,dwNewProtect = PAGE_EXECUTE_READWRITE; if (!pHookInfo) { return FALSE; } VirtualProtect((void*)pHookInfo->dwAddr,BREAKPOINTLEN,dwNewProtect,&dwOldProtect); pHookInfo->byOldCode = *(BYTE*)(pHookInfo->dwAddr); *(BYTE*)(pHookInfo->dwAddr) = 0xcc; VirtualProtect((void*)pHookInfo->dwAddr,BREAKPOINTLEN,dwOldProtect,&dwNewProtect); return TRUE; } //函数名称:SetBP //函数参数:1.t_HookInfo hook信息 //函数功能:删除断点 //返回值: 成功返回TRUE,否则FALSE //注意: 本函数没有检查参数地址的有效性,由调用方检查。 BOOL CHook::UnSetBP(t_HookInfo *pHookInfo) { DWORD dwOldProtect,dwNewProtect = PAGE_EXECUTE_READWRITE; if (!pHookInfo) { return FALSE; } VirtualProtect((void*)pHookInfo->dwAddr,BREAKPOINTLEN,dwNewProtect,&dwOldProtect); *(BYTE*)(pHookInfo->dwAddr) = pHookInfo->byOldCode; VirtualProtect((void*)pHookInfo->dwAddr,BREAKPOINTLEN,dwOldProtect,&dwNewProtect); return TRUE; } //函数名称:VectoredHandler //函数参数:异常信息结构指针 //函数功能:处理异常 //返回值: 自己异常返回EXCEPTION_CONTINUE_EXECUTION,否则返回EXCEPTION_CONTINUE_SEARCH LONG WINAPI CHook::VectoredHandler(PEXCEPTION_POINTERS ExceptionInfo) { CHook *pHook = CHook::GetInstance(); //检测是否INT3断点 if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_BREAKPOINT) { CString str; str.Format("%-8x",ExceptionInfo->ExceptionRecord->ExceptionAddress); // TESTMSG(str); list<t_HookInfo*>::iterator it; //检测是否自己设置的断点 for (it = pHook->m_listHook.begin(); it!=pHook->m_listHook.end(); it++) { if ((*it)->dwAddr ==(DWORD) ExceptionInfo->ExceptionRecord->ExceptionAddress) { //记录当前断点,设置单步标志,删除断点, pHook->m_pException = ExceptionInfo; pHook->SetTF(); pHook->UnSetBP(*it); pHook->m_pCurrentBP = *it; //调用相关函数 (*it)->pFn(ExceptionInfo,NULL); return EXCEPTION_CONTINUE_EXECUTION; } } return EXCEPTION_CONTINUE_SEARCH; } //单步断点的处理 else if ( ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP ) { //判断是否自己的单步断点 if (pHook->m_bSingleFlag && pHook->m_pCurrentBP) { //还原断点,标志位。 pHook->SetBP(pHook->m_pCurrentBP); pHook->m_bSingleFlag = FALSE; pHook->m_pCurrentBP = NULL; return EXCEPTION_CONTINUE_EXECUTION; } else { return EXCEPTION_CONTINUE_SEARCH; } } else { return EXCEPTION_CONTINUE_SEARCH; } } //函数名称:SetTF //函数参数:无 //函数功能:设置一个单步断点 //返回值: 无 void CHook::SetTF() { if (m_pException) { m_pException->ContextRecord->EFlags |= 0x100; m_bSingleFlag = TRUE; } }
1。由于VectoredHandler是WINDOWS回调函数,而我想把他封装在类中,如果想访问类的其他成员,则只能用静态变量了,所以我干脆把这个类设计成单件模式。有的人说了,用全局变量不是也可以么?全局变量确实是也可以达到目的,但是我总感觉他会破坏类的封装。。
2。当异常发生的时候,处理完了HOOK的事情,要先设置一个单步断点,然后把原来的代码还原,在下个单步断点的时候,再写入INT3
3。调试程序的郁闷。。我用VC调试这个程序的时候,发现异常代码总是被修改为STATUS_ACCESS_VIOLATION ,而非STATUS_BREAKPOINT,不知道啥原因。。希望明白人解答
4。在进行异常处理的时候,一些控件的消息通知无法正常运行,比如LIST控件的ADDITEM,CWND的UPDATEDATA。。。。。等,希望能有人解决下这个问题。我测试的时候只能用GetItemText,SetItemText了。
另外把工程文件也一起奉上。。。