动态调用成员方法c++
Dynamic calling member method c++
我知道这已经讨论过几次了,但我的情况有点不同。
我有一个导出一些 类 的第三方 dll。不幸的是,头文件不可用。
仍然可以调用导出的函数。但是我无法绕过传递正确的 'this' 指针(在 RCX 寄存器中传递)。
首先我使用dumpbin /exports 提取函数名(名称已更改,因为第三方库和函数名是保密的)。
4873 1308 0018B380 ?GetId@ThirdPartyClass@ThirdPartyNamespace@@QEBAJXZ = ??GetId@ThirdPartyClass@ThirdPartyNamespace@@QEBAJXZ (public: long __cdecl ThirdPartyNamespace::ThirdPartyClass::GetId(void)const )
现在,API 允许我注册接收指向 ThirdPartyNamespace::ThirdPartyClass 指针的回调(只有 ThirdPartyClass 的前向声明)。
这是我尝试调用 ThirdPartyNamespace::ThirdPartyClass::GetId():
的方式
long (ThirdPartyNamespace::ThirdPartyClass::*_pFnGetId)() const;
HMODULE hModule = GetModuleHandle("ThirdPartyDLL.dll");
*(FARPROC*)&_pFnGetId= GetProcAddress(hModule, "?GetId@ThirdPartyClass@ThirdPartyNamespace@@QEBAJXZ");
long id = (ptr->*_pFnGetId)();
一切看起来都很好(即,如果我介入 - 我确实进入了 ThirdPartyClass::GetId 方法。但是 this 指针不好。虽然 ptr 很好,但如果在调试器中,我手动将 rcx 更改为 ptr - 它工作正常。但是由于某种原因编译器没有传递 ptr。这里是反汇编:
long id = (ptr->*_pFnGetId)();
000000005C882362 movsxd rax,dword ptr [rdi+30h]
000000005C882366 test eax,eax
000000005C882368 jne MyClass::MyCallback+223h (05C882373h)
000000005C88236A movsxd rcx,dword ptr [rdi+28h]
000000005C88236E add rcx,rsi
000000005C882371 jmp MyClass::MyCallback+240h (05C882390h)
000000005C882373 movsxd r8,dword ptr [rdi+2Ch]
000000005C882377 mov rcx,rax
000000005C88237A mov rax,qword ptr [r8+rsi]
000000005C88237E movsxd rdx,dword ptr [rax+rcx]
000000005C882382 movsxd rcx,dword ptr [rdi+28h]
000000005C882386 lea rax,[r8+rdx]
000000005C88238A add rcx,rax
000000005C88238D add rcx,rsi
000000005C882390 call qword ptr [rdi+20h]
000000005C882393 mov ebp,eax
在执行这些命令之前,rsi 包含指向 ThirdPartyClass 对象的指针(即 ptr),但不是直接将其传递给 rcx,而是对其进行了一些运算,结果该指针完全错误。
一些我不明白为什么编译器这样做的痕迹,因为它最终调用了非虚拟函数 ThirdPartyClass::GetId():
000000005C88237A mov rax,qword ptr [r8+rsi]
R8 0000000000000000
RSI 000000004C691AA0 // good pointer to ThirdPartyClass object
RAX 0000000008E87728 // this gets pointer to virtual functions table of ThirdPartyClass
000000005C88237E movsxd rdx,dword ptr [rax+rcx]
RAX 0000000008E87728
RCX FFFFFFFFFFFFFFFF
RDX FFFFFFFFC0F3C600
000000005C882382 movsxd rcx,dword ptr [rdi+28h]
RCX 0000000000000000
RDI 000000005C9BE690
000000005C882386 lea rax,[r8+rdx]
RAX FFFFFFFFC0F3C600
RDX FFFFFFFFC0F3C600
R8 0000000000000000
000000005C88238A add rcx,rax
RAX FFFFFFFFC0F3C600
RCX FFFFFFFFC0F3C600
000000005C88238D add rcx,rsi
RCX 000000000D5CE0A0
RSI 000000004C691AA0
000000005C882390 call qword ptr [rdi+20h]
在我看来,它应该像
一样简单
long id = (ptr->*_pFnGetId)();
mov rcx,rsi
call qword ptr [rdi+20h]
mov ebp,eax
如果我在调用 qword ptr [rdi+20h] 之前设置 rcx 等于 rsi,它 returns 我的期望值。
我做错了什么吗?
提前致谢。
好的,我偶然找到了一个解决方案(因为我已经使用了类似的方法并且它在略有不同的情况下工作。
解决方案是通过定义一个假 class 并通过指针调用成员方法来欺骗编译器,但假装它是一个指向已知(对编译器)的指针 class.
也许,没关系,但我知道ThirdPartyNamespace::ThirdPartyClass有虚函数,所以我也用虚函数声明假class。
class FakeCall
{
private:
FakeCall(){}
virtual ~FakeCall(){}
};
其余部分与初始代码中的一样,除了一个小东西,而不是调用 ptr->*_pFnGetId(其中 ptr 是指向未知的指针,前向声明 class ThirdPartyNamespace::ThirdPartyClass),我假装我在我的 FakeCall class:
中调用成员方法
FakeCall * fake = (FakeCall*)ptr;
long sico = (fake->*_pFnGetId)();
反汇编看起来完全符合预期:
long sico = (fake->*_pFnGetSico)();
000000005A612096 mov rcx,rax
000000005A612099 call qword ptr [r12+20h]
000000005A61209E mov esi,eax
而且效果很好!
一些观察:
- 成员方法指针,我一开始以为,无非是一个普通的函数指针。
- 如果调用未定义的成员方法,Microsoft 编译器(至少 VS2008)会变得疯狂 class(即仅向前声明名称)。
我知道这已经讨论过几次了,但我的情况有点不同。
我有一个导出一些 类 的第三方 dll。不幸的是,头文件不可用。 仍然可以调用导出的函数。但是我无法绕过传递正确的 'this' 指针(在 RCX 寄存器中传递)。
首先我使用dumpbin /exports 提取函数名(名称已更改,因为第三方库和函数名是保密的)。
4873 1308 0018B380 ?GetId@ThirdPartyClass@ThirdPartyNamespace@@QEBAJXZ = ??GetId@ThirdPartyClass@ThirdPartyNamespace@@QEBAJXZ (public: long __cdecl ThirdPartyNamespace::ThirdPartyClass::GetId(void)const )
现在,API 允许我注册接收指向 ThirdPartyNamespace::ThirdPartyClass 指针的回调(只有 ThirdPartyClass 的前向声明)。
这是我尝试调用 ThirdPartyNamespace::ThirdPartyClass::GetId():
的方式long (ThirdPartyNamespace::ThirdPartyClass::*_pFnGetId)() const;
HMODULE hModule = GetModuleHandle("ThirdPartyDLL.dll");
*(FARPROC*)&_pFnGetId= GetProcAddress(hModule, "?GetId@ThirdPartyClass@ThirdPartyNamespace@@QEBAJXZ");
long id = (ptr->*_pFnGetId)();
一切看起来都很好(即,如果我介入 - 我确实进入了 ThirdPartyClass::GetId 方法。但是 this 指针不好。虽然 ptr 很好,但如果在调试器中,我手动将 rcx 更改为 ptr - 它工作正常。但是由于某种原因编译器没有传递 ptr。这里是反汇编:
long id = (ptr->*_pFnGetId)();
000000005C882362 movsxd rax,dword ptr [rdi+30h]
000000005C882366 test eax,eax
000000005C882368 jne MyClass::MyCallback+223h (05C882373h)
000000005C88236A movsxd rcx,dword ptr [rdi+28h]
000000005C88236E add rcx,rsi
000000005C882371 jmp MyClass::MyCallback+240h (05C882390h)
000000005C882373 movsxd r8,dword ptr [rdi+2Ch]
000000005C882377 mov rcx,rax
000000005C88237A mov rax,qword ptr [r8+rsi]
000000005C88237E movsxd rdx,dword ptr [rax+rcx]
000000005C882382 movsxd rcx,dword ptr [rdi+28h]
000000005C882386 lea rax,[r8+rdx]
000000005C88238A add rcx,rax
000000005C88238D add rcx,rsi
000000005C882390 call qword ptr [rdi+20h]
000000005C882393 mov ebp,eax
在执行这些命令之前,rsi 包含指向 ThirdPartyClass 对象的指针(即 ptr),但不是直接将其传递给 rcx,而是对其进行了一些运算,结果该指针完全错误。
一些我不明白为什么编译器这样做的痕迹,因为它最终调用了非虚拟函数 ThirdPartyClass::GetId():
000000005C88237A mov rax,qword ptr [r8+rsi]
R8 0000000000000000
RSI 000000004C691AA0 // good pointer to ThirdPartyClass object
RAX 0000000008E87728 // this gets pointer to virtual functions table of ThirdPartyClass
000000005C88237E movsxd rdx,dword ptr [rax+rcx]
RAX 0000000008E87728
RCX FFFFFFFFFFFFFFFF
RDX FFFFFFFFC0F3C600
000000005C882382 movsxd rcx,dword ptr [rdi+28h]
RCX 0000000000000000
RDI 000000005C9BE690
000000005C882386 lea rax,[r8+rdx]
RAX FFFFFFFFC0F3C600
RDX FFFFFFFFC0F3C600
R8 0000000000000000
000000005C88238A add rcx,rax
RAX FFFFFFFFC0F3C600
RCX FFFFFFFFC0F3C600
000000005C88238D add rcx,rsi
RCX 000000000D5CE0A0
RSI 000000004C691AA0
000000005C882390 call qword ptr [rdi+20h]
在我看来,它应该像
一样简单long id = (ptr->*_pFnGetId)();
mov rcx,rsi
call qword ptr [rdi+20h]
mov ebp,eax
如果我在调用 qword ptr [rdi+20h] 之前设置 rcx 等于 rsi,它 returns 我的期望值。
我做错了什么吗? 提前致谢。
好的,我偶然找到了一个解决方案(因为我已经使用了类似的方法并且它在略有不同的情况下工作。
解决方案是通过定义一个假 class 并通过指针调用成员方法来欺骗编译器,但假装它是一个指向已知(对编译器)的指针 class.
也许,没关系,但我知道ThirdPartyNamespace::ThirdPartyClass有虚函数,所以我也用虚函数声明假class。
class FakeCall
{
private:
FakeCall(){}
virtual ~FakeCall(){}
};
其余部分与初始代码中的一样,除了一个小东西,而不是调用 ptr->*_pFnGetId(其中 ptr 是指向未知的指针,前向声明 class ThirdPartyNamespace::ThirdPartyClass),我假装我在我的 FakeCall class:
中调用成员方法 FakeCall * fake = (FakeCall*)ptr;
long sico = (fake->*_pFnGetId)();
反汇编看起来完全符合预期:
long sico = (fake->*_pFnGetSico)();
000000005A612096 mov rcx,rax
000000005A612099 call qword ptr [r12+20h]
000000005A61209E mov esi,eax
而且效果很好!
一些观察:
- 成员方法指针,我一开始以为,无非是一个普通的函数指针。
- 如果调用未定义的成员方法,Microsoft 编译器(至少 VS2008)会变得疯狂 class(即仅向前声明名称)。