对于 inline hook 这种技术我相信大家早已耳熟能详,我们往往使用 detours 或者 minhook 等框架来对函数进行挂钩。然而,hook 类成员函数却并不那么容易。
假设有这么个类:
class ClassA
{
public:
void funcA() {}
};
我们的目标是对 funcA 进行 hook。
遇到的第一个问题就是我们很难获取目标函数的地址。像 hook 框架如 minhook,都需要我们传入一个函数目标地址,这个地址类型是 void*
类型的:
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal)
但是当我们想直接对 ClassA::funcA
取地址的时候就会遇到报错:
//invalid type conversionC/C++(171)
void* funcAPtr = (void*)&ClassA::funcA;
//invalid type conversionC/C++(171)
void* funcAPtr = reinterpret_cast<void*>(&ClassA::funcA);
那么难道对于类成员函数,就不能有一个指向类成员函数的指针吗?不是的,只是它必须是指向该类成员函数的函数指针,也就是 void(ClassA::*)()
,用代码来说就是你必须得这样:
typedef void (ClassA::*PFN_FUNC_A)();
PFN_FUNC_A funcAPtr = &ClassA::funcA;
但这仍然不解决我们的问题,我们需要的是一个 void*
类型的指针而不是指向成员函数的指针,但 cpp 标准中这两者之间恰恰无法相互转换。幸好,msvc 有一个比较 hack 的方法来解决这个问题:
auto ptr = &ClassA::funcA;
void* funcAPtr = (void*&)ptr;
这实际上是未定行为但是它刚好解决了我们的问题,现在我们有了指向这个类成员函数的地址。
另外在看了 StackOverflow 的回答后我看到了另外一种更优雅的办法,适用于任何编译器:
union {
PFN_FUNC_A funcAMethodPtr;
void* funcAPtr;
} autoPtr = {&ClassA::funcA};
void* funcAPtr = autoPtr.funcAPtr;
printf("ClassA::funcA:0x%p\n", funcAPtr);
因为我们知道指针长度是相等的,通过 union 结构我们可以轻松的做数据类型转换。
现在还需要编写 stub 函数。由于成员函数的调用预定是 thiscall,但是正常来说你不能直接这样
void __thiscall stub();
在 x64 情况下所有调用约定都是直接 rcx/rdx/r8/r9 这么顺序传参,并且由调用者创建栈帧,因此我们可以直接编写 stub 函数
void stub(void* this) {}
而在 x86 下,cdecl 方式全部通过栈传递参数,而 thiscall 却需要通过 ecx 传递,所以没办法直接用 cdecl 函数来做 stub。那么有哪些其他的方式呢?最简单的就是再创建一个 class,在新 class 中定义一个相同的参数的函数来作为 stub,例如:
class StubClassA{
public:
void stubFuncA();
}
这样的方式又有点麻烦,毕竟光是获取这个 stubFuncA 的地址就需要一番操作。幸好我们还有其他选择,那就是 fastcall。
fastcall 通过 ecx/edx 传递前两个参数,并且与 thiscall 一样都是由被调用者平栈,因此通过 fastcall 我们就能获取到 this 指针了,我们可以这样写:
void __fastcall stubFuncA(void* this,void* edx,void* arg1,void* arg2, .....);
这样我们便可以通过 stub 函数来接收参数。
最后一个问题,在 stub 中我们还需要调用原始被 hook 的函数,如何通过成员函数指针来调用成员函数呢?
typedef void (ClassA::*PFN_FUNC_A)();
PFN_FUNC_A originalFuncA;
void __fastcall hookFuncA(ClassA* thisPtr){
(thisPtr->*originalFuncA)();
// or
((*thisPtr).*originalFuncA)();
}
至此,类成员函数的hook就可以实现了。
文章中出现的代码:
#include <iostream>
class ClassA
{
public:
void funcA() {}
};
class StubClassA{
public:
void stubFuncA();
};
typedef void (ClassA::*PFN_FUNC_A)();
PFN_FUNC_A originalFuncA;
void __fastcall hookFuncA(ClassA* thisPtr){
(thisPtr->*originalFuncA)();
// or
((*thisPtr).*originalFuncA)();
}
int main()
{
ClassA objA;
PFN_FUNC_A funcAMethodPtr = &ClassA::funcA;
{
auto ptr = &ClassA::funcA;
void* funcAPtr = (void*&)ptr;
printf("ClassA::funcA:0x%p\n", funcAPtr);
}
{
union {
PFN_FUNC_A funcAMethodPtr;
void* funcAPtr;
} autoPtr = {&ClassA::funcA};
void* funcAPtr = autoPtr.funcAPtr;
printf("ClassA::funcA:0x%p\n", funcAPtr);
}
}
参考:
https://isocpp.org/wiki/faq/pointers-to-members
https://stackoverflow.com/questions/8121320/get-memory-address-of-member-function