动态调用成员方法c ++

时间:2016-04-04 16:17:41

标签: c++ dynamic linker getprocaddress

我知道这已经讨论了几次,但我的情况有点不同。

我有第三方dll导出一些类。不幸的是,头文件不可用。 仍然可以调用导出的函数。但是,我无法绕过正确的'这个'指针(在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方法内部。但是这个指针不好。虽然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,则返回预期值。

我做错了吗? 提前谢谢。

1 个答案:

答案 0 :(得分:0)

好的,我发现了一个解决方案,因为我已经使用了类似的方法,但它的工作方式略有不同。

解决方案是通过定义假类并通过指针调用成员方法来欺骗编译器,但假装它是指向已知(到编译器)类的指针。

也许,没关系,但我知道ThirdPartyNamespace :: ThirdPartyClass有虚函数,所以我也用虚函数声明了假类。

class FakeCall
{
private:
    FakeCall(){}
    virtual ~FakeCall(){}
};

其余的就像在初始代码中除了一次小事,​​而不是调用ptr-> * _ pFnGetId(其中ptr是指向未知的指针,前向声明的类ThirdPartyNamespace :: ThirdPartyClass),我假装我在调用成员方法我的FakeCall课程:

    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 

它完美无缺!

一些观察结果:

  1. 我最初认为的成员方法指针只不过是一个普通的函数指针。
  2. Microsoft编译器(至少VS2008)如果为未定义的类调用成员方法(即只是名称的前向声明),则会发疯。