首页
社区
课程
招聘
[原创]打造史上最完整APIHOOK完整开发库
发表于: 2010-10-16 22:53 64987

[原创]打造史上最完整APIHOOK完整开发库

2010-10-16 22:53
64987

最近看见不少关于APIHook的帖子,但是大部分都只做了一个简单的描述,即使有些提供了例子的帖子,也多多少少有些执行上问题。

因此,本人根据以前写的半成品,并参照《Windows核心技术开发》的22章节,对原有代码进行重新设计,在进行详细的测试之后,基本的框架已经可以出炉了。

首先,介绍一下APIHook。进行API挂接时,所需的基本函数有【LoadLibraryA,LoadLibraryW,LoadLibraryExA,LoadLibraryExW,GetProcAddress】。
这5个API是不可缺少的,其余的API挂接都可以算作扩展,因此构架上将其分成两个以上的模块是最合适,也是最容易扩展的。

基于上述考虑,我们将实现部分分成APIHook.DLL和UserAPIHook.DLL两个部分。当然用户扩展模块还可以按照类别分成User1,User2,...UserN等多个模块,这样的话,可以在增加新的API挂接时,可以不用修改原有的DLL。

好了我们首先来介绍一下APIHook.DLL的构架。
其中包含接口和实现两个部分。
1)接口
对于这次实现的DLL,只要当前EXE的进程加载该DLL,DLL的初始化处理中,会自动扫描进程中所有的DLL模块,并且将上述5个基本函数挂接上。
原理也很简单,就是将所有的DLL的输入节都进行修改。
基于这一点,如果只是在本进程内使用的话,也不需要什么接口。因此,此处实现的接口是用来挂接其他进程的,技术上将DLL注入其他进程有很多方法,这里只实现两种,HOOK和CreateRemoteThread。

1.1)HOOK
使用系统的SetWindowsHookEx方法来实现,接口上提供以下接口:

BOOL SetupHook(DWORD dwThreadID, HMODULE hModule);
BOOL SetupHookWithHWnd(HWND hWnd, HMODULE hModule);
BOOL SetupUnhook();
BOOL CAPIHookImpl::SetupHook(BOOL bInstall, DWORD dwThreadID, HMODULE hModule)
{
    BOOL fOK = FALSE;
    HANDLE hMutex = ::CreateMutex(NULL, TRUE, TEXT("APIHook_SetWindowsHookEx_Mutex"));
    if(bInstall)
    {
        if(g_hHook == NULL)
        {
            if(hModule == NULL)
            {
                hModule = ModuleFromAddress(GetMessageProc);
            }
            g_hHook = ::SetWindowsHookEx(WH_GETMESSAGE, GetMessageProc, hModule, dwThreadID);
            fOK = g_hHook != NULL;
            if(fOK)
            {
                if(dwThreadID != 0)
                {
                    ::PostThreadMessage(dwThreadID, WM_NULL, 0 ,0);
                }
            }
        }
    }
    else
    {
        if(g_hHook != NULL)
        {
            fOK = UnhookWindowsHookEx(g_hHook);
            g_hHook = NULL;
        }
    }
    ::ReleaseMutex(hMutex);
    ::CloseHandle(hMutex);
    return fOK;
}
 
HMODULE RemoteInject(DWORD dwProcessID, PCSTR pszLibName);
HMODULE RemoteInjectWithHWnd(HWND hWnd, PCSTR pszLibName);
BOOL RemoteEject(DWORD dwProcessID, HMODULE hModule);
BOOL RemoteEjectWithHWnd(HWND hWnd, HMODULE hModule);
 
HMODULE RemoteInject(DWORD dwProcessID, PCSTR pszLibName)
{
    HMODULE hRemoteModule = NULL;
    HANDLE hProcess = NULL;
    HANDLE hThread = NULL;
    PSTR pszLibFileRemote = NULL;
    DWORD cch;
    PTHREAD_START_ROUTINE pfnThreadRoutine;
    CHAR szModuleName[MAX_PATH];
    if(pszLibName == NULL)
    {
        ::GetModuleFileNameA(g_hModule, szModuleName, sizeof(szModuleName));
        pszLibName = szModuleName;
    }
    do
    {
        //获得想要注入代码的进程的句柄
        hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);
        if (hProcess == NULL)
            break;
        //计算DLL路径名需要的字节数
        cch = 1 + strlen(szModuleName);
        //在远程线程中为路径名分配空间
        pszLibFileRemote = (PSTR)::VirtualAllocEx(hProcess, NULL, cch, MEM_COMMIT, PAGE_READWRITE);
        if (pszLibFileRemote == NULL)
            break;
        //将DLL的路径名复制到远程进程的内存空间
        if (!::WriteProcessMemory(hProcess, (PVOID)pszLibFileRemote, (PVOID)pszLibName, cch, NULL))
            break;
        //获得LoadLibraryA在Kernel32.dll中的真正地址
        pfnThreadRoutine = (PTHREAD_START_ROUTINE)::GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");
        if (pfnThreadRoutine == NULL)
            break;
        //创建远程线程,并通过远程线程调用用户的DLL文件
        hThread = ::CreateRemoteThread(hProcess, NULL, 0, pfnThreadRoutine, (LPVOID)pszLibFileRemote, 0, NULL);
        if (hThread == NULL)
            break;
        //等待远程线程终止
        ::WaitForSingleObject(hThread, INFINITE);
    }while(FALSE);
    //关闭句柄
    if (pszLibFileRemote != NULL)
    {
        ::VirtualFreeEx(hProcess,(PVOID)pszLibFileRemote, 0, MEM_RELEASE);
    }
    if (hThread != NULL)
    {
        DWORD dwExitCode;
        ::GetExitCodeThread(hThread, &dwExitCode);
        hRemoteModule = (HMODULE)dwExitCode;
        ::CloseHandle(hThread);
    }
    if (hProcess != NULL)
    {
        ::CloseHandle(hProcess);
    }
    return hRemoteModule;
}

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 7
支持
分享
最新回复 (53)
雪    币: 1163
活跃值: (137)
能力值: ( LV12,RANK:230 )
在线值:
发帖
回帖
粉丝
2
非常不错,感谢楼主分享。
2010-10-16 22:59
0
雪    币: 709
活跃值: (2420)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
3
我用mhooklib,没有版权问题,还支持64位~
2010-10-16 22:59
0
雪    币: 393
活跃值: (100)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
4
类库的设计   只做该做的事
2010-10-16 23:03
0
雪    币: 393
活跃值: (100)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
5
::CreateMutex(NULL, TRUE, TEXT("APIHook_SetWindowsHookEx_Mutex"));


这个 应该要换的
2010-10-16 23:07
0
雪    币: 83
活跃值: (43)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
6
现在我们来介绍一下其实现部分,所有的实现细节都在CAPIHookImpl类中来完成,由于大部分代码,是《Windows核心技术编程》上的例子,因此这里仅对处理方式和不同点进行说明。
我们先来看CAPIHookImpl的声明:
class CAPIHookImpl
{
public:
    CAPIHookImpl(PCSTR pszCalleeModName, PCSTR pszFuncName, PROC pfnHook);
    ~CAPIHookImpl();
 
    operator PROC(){return (m_pfnOrig);};
 
    static BOOL SetupHook(BOOL bInstall, DWORD dwThreadID = 0, HMODULE hModule = NULL);
    static HMODULE ModuleFromAddress(PVOID pv);
 
private:
    static PVOID            sm_pvMaxAppAddr;
    static CAPIHookImpl        *sm_pHead;
    CAPIHookImpl            *m_pNext;
 
    PCSTR m_pszCalleeModName;
    PCSTR m_pszFuncName;
    PROC  m_pfnOrig;
    PROC  m_pfnHook;
 
private:
    static void WINAPI ReplaceIATEntryInAllMods(PCSTR pszCalleeModName, PCSTR pszFuncName, PROC pfnCurrent, PROC pfnNew);
    static void WINAPI ReplaceIATEntryInOneMod(PCSTR pszCalleeModName, PCSTR pszFuncName, PROC pfnCurrent, PROC pfnNew, HMODULE hModCaller);
    static void WINAPI FixupNewlyLoadedModule(HMODULE hMod, DWORD dwFlags);
    static HMODULE WINAPI LoadLibraryA(PCSTR pszModulePath);
    static HMODULE WINAPI LoadLibraryW(PCWSTR pszModulePath);
    static HMODULE WINAPI LoadLibraryExA(PCSTR pszModulePath, HANDLE hFile, DWORD dwFlags);
    static HMODULE WINAPI LoadLibraryExW(PCWSTR pszModulePath, HANDLE hFile, DWORD dwFlags);
    static FARPROC WINAPI GetProcAddress(HMODULE hModule,PCSTR pszProcName);
 
private:
    static CAPIHookImpl sm_LoadLibraryA;
    static CAPIHookImpl sm_LoadLibraryW;
    static CAPIHookImpl sm_LoadLibraryExA;
    static CAPIHookImpl sm_LoadLibraryExW;
    static CAPIHookImpl sm_GetProcAddress;
};


我们从该类的声明中,可以看到定义了5各全局的静态变量,来自动挂接5个基本的操作函数。
其中sm_pHead是所有挂接API的链表的头指针,然后每一个类中,都定义指向下一个挂接类的指针,这样可以通过这两个变量就可以遍历所有的挂接函数。

实现细节:
2.1)CAPIHookImpl::CAPIHookImpl(PCSTR pszCalleeModName, PCSTR pszFuncName, PROC pfnHook)
2.1.1)取得原始的API函数指针。
2.1.2)将this链接到挂接函数链表中。
2.1.3)调用ReplaceIATEntryInAllMods内部函数,变量当前进程的所有模块,然后替换所有模块的输入节中对于本挂接函数的指针。注意APIHook.DLL本身要排除在外,否则没人干活了。
注意这里书上的代码中没有同步处理,因为其中对全局的链表头指针进行了操作,因此在多线程环境下会出问题。

2.2)CAPIHookImpl::~CAPIHookImpl()
2.2.1)调用ReplaceIATEntryInAllMods内部函数,变量当前进程的所有模块,然后还原所有模块的输入节中对于本挂接函数的指针。
2.2.2)将this从挂接函数链表中摘除。
这里我们看到处理顺序刚好和构造函数相反。
注意这里同样要考虑同步问题。

2.3)HMODULE CAPIHookImpl::ModuleFromAddress(PVOID pv)
HMODULE CAPIHookImpl::ModuleFromAddress(PVOID pv)
{
    MEMORY_BASIC_INFORMATION mbi;
    return ::VirtualQuery(pv, &mbi, sizeof(mbi)) != NULL ? (HMODULE)mbi.AllocationBase : NULL;
}

和书上一样,通过VirtualQuery函数来实现。

2.4)ReplaceIATEntryInAllMods和ReplaceIATEntryInOneMod函数
同书上一样,代码太长,不贴了。

2.5)void CAPIHookImpl::FixupNewlyLoadedModule(HMODULE hModule, DWORD dwFlags)
这个函数书上的代码有点小问题,就是没有对本身APIHook.DLL进行排除处理,这样会造成原始函数指针丢失,陷入死循环,最后堆栈溢出。
这里调试了好久才发现问题:(。

2.6)4个LoadLibrary
这里可以对书上的代码进行优化一下,即已经加载过的DLL就不用在修理了。代码如下:
HMODULE CAPIHookImpl::LoadLibraryA(PCSTR pszModulePath)
{
    HMODULE hModule = ::GetModuleHandleA(pszModulePath);
    if(hModule == NULL)
    {
        hModule = ::LoadLibraryA(pszModulePath);
        FixupNewlyLoadedModule(hModule, 0);
    }
    else
    {
        hModule = ::LoadLibraryA(pszModulePath);
    }
    return hModule;
}


2.7)FARPROC CAPIHookImpl::GetProcAddress(HMODULE hModule, PCSTR pszProcName)
这里也和书上差不多,先取得原始函数指针,然后查找挂接函数列表后返回。
注意书上的代码也没有加上同步处理,这里也已经补上。代码如下:
 
FARPROC WINAPI CAPIHookImpl::GetProcAddress(HMODULE hModule, PCSTR pszProcName)
{
    HANDLE hMutex = ::CreateMutex(NULL, FALSE, TEXT("APIHook_CAPIHook_Mutex"));
    WaitForSingleObject(hMutex, INFINITE);
    FARPROC pfn = ::GetProcAddress(hModule, pszProcName);
    if(pfn != NULL)
    {
        for (CAPIHookImpl *p = sm_pHead; (p != NULL); p = p->m_pNext)
        {
            if (pfn == p->m_pfnOrig)
            {
                pfn = p->m_pfnHook;
                break;
            }
        }
    }
    ::ReleaseMutex(hMutex);
    ::CloseHandle(hMutex);
    return (pfn);
}


写到这,回过头整理一下,其实也没有多少东西,难度也不是很大。看来也只有缺少人来整理而已。

好了,到现在基本APIHook.DLL基本上介绍差不多了,我们来说明一下,上一篇中卖的那个关子,还记得吧。
我们先来看看WindowsAPI【SetWindowsHookEx】的原型。
HHOOK SetWindowsHookEx(          int idHook,
    HOOKPROC lpfn,
    HINSTANCE hMod,
    DWORD dwThreadId
);

其中lpfn为钩子函数,hMod为钩子函数所在的模块句柄。
其实这里说的不太准确,hMod不一定非是钩子函数所在的模块句柄,只要是该句柄存在(即有效)的时候,钩子函数也有效即可。
比如说。钩子函数在DEMOA.DLL中定义,DEMOB.DLL又静态依赖DEMOA.DLL。这样我们就可以这样调用SetWindowsHookEx,如下:
::SetWindowsHookEx(WH_GETMESSAGE, GetMessageProc, hDemoB, dwThreadID);

其中GetMessageProc为DEMOA.DLL中定义的函数。
进过验证这样做完全是可行有效的,于是我们就有了在下面函数中使用hModule参数的高级应用了。
BOOL SetupHook(DWORD dwThreadID, HMODULE hModule);

因为例子中的GetMessageProc函数为APIHook.DLL中的函数,如果我们调用SetWindowsHookEx时,指定APIHook.DLL的模块句柄时,远程加载的仅仅是APIHook.DLL。远程加载仅实现5个基本挂接函数的模块是没有意义的。
所以我们在调用SetupHook时,将参数hModule指定成UserAPIHook.DLL的模块句柄,只要保证UserAPIHook.DLL静态依赖APIHook.DLL即可。
通常我们开发扩展APIHook.DLL时,会使用APIHook.DLL的导出类,因此也基本上可以保证的。
好了,谜题解开了。大家都明朗了吧。

最后,我们举一个UserAPIHook.DLL的实现样例来结束这个话题吧。
UserAPIHook.H
#ifdef USERAPIHOOK_EXPORTS
#define USERAPIHOOK_API __declspec(dllexport)
#else
#define USERAPIHOOK_API __declspec(dllimport)
#endif
 
#ifdef __cplusplus
extern "C"
{
#endif
 
USERAPIHOOK_API BOOL UserSetupHook(DWORD dwThreadID);
USERAPIHOOK_API BOOL UserSetupHookWithHWnd(HWND hWnd);
USERAPIHOOK_API BOOL UserSetupUnhook();
USERAPIHOOK_API HMODULE UserRemoteInject(DWORD dwProcessID);
USERAPIHOOK_API HMODULE UserRemoteInjectWithHWnd(HWND hWnd);
USERAPIHOOK_API BOOL UserRemoteEject(DWORD dwProcessID, HMODULE hModule);
USERAPIHOOK_API BOOL UserRemoteEjectWithHWnd(HWND hWnd, HMODULE hModule);
 
#ifdef __cplusplus
}
#endif

UserAPIHook.cpp
#define USERAPIHOOK_EXPORTS
 
#include "UserAPIHook.h"
#include "../APIHook/APIHook.h"
 
#ifdef _DEBUG
#pragma comment(lib, "../Debug/APIHook")
#else
#pragma comment(lib, "../Release/APIHook")
#endif
 
extern HMODULE g_hModule;
 
DEFINE_USERAPIHOOK4("user32.dll", int, MessageBoxA, HWND, hWnd, LPCSTR, lpText, LPCSTR, lpCaption, UINT, uType)
{
    return CallBack_MessageBoxA(hWnd, lpText, lpCaption, uType);
}
 
DEFINE_USERAPIHOOK4("user32.dll", int, MessageBoxW, HWND, hWnd, LPCWSTR, lpText, LPCWSTR, lpCaption, UINT, uType)
{
    return CallBack_MessageBoxW(hWnd, lpText, lpCaption, uType);
}
 
#ifdef __cplusplus
extern "C"
{
#endif
 
USERAPIHOOK_API BOOL UserSetupHook(DWORD dwThreadID)
{
    return ::SetupHook(dwThreadID, g_hModule);
}
 
USERAPIHOOK_API BOOL UserSetupHookWithHWnd(HWND hWnd)
{
    return ::SetupHookWithHWnd(hWnd, g_hModule);
}
 
USERAPIHOOK_API BOOL UserSetupUnhook()
{
    return ::SetupUnhook();
}
 
USERAPIHOOK_API HMODULE UserRemoteInject(DWORD dwProcessID)
{
    CHAR szModuleName[MAX_PATH];
    ::GetModuleFileNameA(g_hModule, szModuleName, sizeof(szModuleName));
    return ::RemoteInject(dwProcessID, szModuleName);
}
 
USERAPIHOOK_API HMODULE UserRemoteInjectWithHWnd(HWND hWnd)
{
    CHAR szModuleName[MAX_PATH];
    ::GetModuleFileNameA(g_hModule, szModuleName, sizeof(szModuleName));
    return ::RemoteInjectWithHWnd(hWnd, szModuleName);
}
 
USERAPIHOOK_API BOOL UserRemoteEject(DWORD dwProcessID, HMODULE hModule)
{
    return ::RemoteEject(dwProcessID, hModule);
}
 
USERAPIHOOK_API BOOL UserRemoteEjectWithHWnd(HWND hWnd, HMODULE hModule)
{
    return ::RemoteEjectWithHWnd(hWnd, hModule);
}
 
#ifdef __cplusplus
}
#endif
2010-10-16 23:54
0
雪    币: 83
活跃值: (43)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
7
HANDLE hMutex = ::CreateMutex(NULL, FALSE, TEXT("APIHook_CAPIHook_Mutex"));
WaitForSingleObject(hMutex, INFINITE);


HANDLE hMutex = ::CreateMutex(NULL, TRUE, TEXT("APIHook_CAPIHook_Mutex"));

是否等价我没有去验证。
2010-10-16 23:59
0
雪    币: 14
活跃值: (50)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
标题党啊标题党
这叫APIHOOK么。。
还最完整。。。
2010-10-17 00:09
0
雪    币: 170
活跃值: (90)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
9
贴出来看看
2010-10-17 00:11
0
雪    币: 83
活跃值: (43)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10
SDK开发包和对应源代码整理完毕。
APIHook_SDK.rar
APIHook_Src.rar
上传的附件:
2010-10-17 00:26
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
虽然没看懂
支持一下
2010-10-17 08:02
0
雪    币: 83
活跃值: (43)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
12
每个人对于APIHook的理解不同,对于完整性的理解也不同。
本人对于的理解暂时仅限于这类方法,并不涵盖所有的Hook方法,比如InlineHook之类的。
对于完整性的理解,个人认为。只要客户拿到相应的开发包之后,再参照对应的Demo样例,直接可以写出自己所需的程序来。
至少现在网上所发布的大部分所谓的开发包,在下载之后,不是编译出问题,就是链接有问题,还有一部分逻辑上不方便二次开发。
对此,本文也就着方便二次开发的原则,来做成这次开发包,并且尽可能保证其正确性和完整性。
下面所有的代码基本上都是这两天从0开始编写的,加上调试,除错和完整的测试,总共花了1-2天时间,加上上面的两个帖子也是一字一字敲进去的,由于本人打字不怎么快,光发文就花了2,3小时。
因此,请尊重别人的劳动成果,不要随便下结论,谢谢。
2010-10-17 08:02
0
雪    币: 709
活跃值: (2420)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
13
楼主要多利用搜索引擎啊,看人家的Mhook,比detours好用,还开源支持x64.

http://codefromthe70s.org/mhook22.aspx
2010-10-17 09:12
0
雪    币: 7115
活跃值: (639)
能力值: (RANK:1290 )
在线值:
发帖
回帖
粉丝
14
R0下有好用点的开源HOOK库没,推荐一个。
2010-10-17 09:37
0
雪    币: 1163
活跃值: (137)
能力值: ( LV12,RANK:230 )
在线值:
发帖
回帖
粉丝
15
一步一步来吧。
2010-10-17 09:48
0
雪    币: 83
活跃值: (43)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
16
善于利用现有资源当然是好事,不过我们本着研究精神来锻炼一下自己,不也是很好,对吧。
2010-10-17 10:16
0
雪    币: 2384
活跃值: (766)
能力值: (RANK:410 )
在线值:
发帖
回帖
粉丝
17
不管怎么说,最重要的楼主的分享精神。
2010-10-17 11:12
0
雪    币: 13
活跃值: (40)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
18
不能学习,只能膜拜
2010-10-17 11:24
0
雪    币: 370
活跃值: (15)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
19
这个好,线程安全,支持 x64
2010-10-17 11:42
0
雪    币: 131
活跃值: (98)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
20
支持楼主
更好的资源是可以使用,但也不能放弃自己开发啊。
不能什么都拿来主义啊,强烈支持楼主!~~~~~~~~~~
2010-10-17 13:36
0
雪    币: 393
活跃值: (100)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
21
对不起 我看错代码了。
2010-10-17 20:54
0
雪    币: 263
活跃值: (17)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
22
代码写的很亮 学习了
2010-10-17 23:39
0
雪    币: 284
活跃值: (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
玩命大神可以自己写一个嘛
挂个反汇编引擎
2010-10-18 08:20
0
雪    币: 4378
活跃值: (4368)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
比DETOUR好的地方就只是支持 64 没有版权问题哈...

但是DETOUR还依旧比Mhook强大... 比如双重挂钩.

Mhook 判断API地址第一字节是E9就什么也不做了

Detour 用DetourFind 可以继续HOOK  JMP后的地址做为API的头部.
2010-10-18 10:04
0
雪    币: 324
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
25
楼主的文章还是很系统的介绍了的,自己动手会有不一样的感觉,支持你
2010-10-18 11:05
0
游客
登录 | 注册 方可回帖
返回
//