无对应头文件时,如何正确调用Windows C++ DLL中的类成员函数?
手动调用无导出C++类的DLL成员函数方案
核心前提:搞懂C++名称修饰与调用约定
你从dumpbin拿到的是C++名称修饰后的函数名,针对x64平台(__ptr64),MSVC的调用约定是统一的,但类成员函数的调用必须明确:
- 所有非虚成员函数(包括构造、析构)都需要传递
this指针,x64下this固定通过RCX寄存器传递。 - 构造函数的本质是在你提供的内存块上初始化对象(包括设置虚表指针、成员变量),本身没有返回值。
第一步:搞定对象大小与内存布局
1. 获取Client对象的精确大小
没有头文件的情况下,两种靠谱方法:
- 调试演示程序:用x64dbg附加到演示程序,找到
Client对象实例,查看它的内存占用(比如从对象起始地址到最后一个成员变量的偏移+大小)。 - 逆向DLL:在DLL里找构造函数的实现,看它初始化的内存范围,或者直接找
sizeof(Client)的硬编码数值。
2. 解析虚表(针对虚函数)
因为~Client()是虚析构,对象的第一个8字节(x64)是虚表指针。你可以:
- 在调试器里查看演示程序中
Client对象的虚表,记录每个虚函数的地址,后续直接通过虚表指针调用对应函数。
第二步:调用构造函数
以public: __cdecl SomeComapny::Cds::Api::Client::Client(void) __ptr64为例:
- 先分配一块内存,大小就是第一步拿到的
sizeof(Client),比如用malloc或VirtualAlloc。 - 把这块内存的地址放到RCX寄存器(作为
this指针)。 - 调用构造函数的地址:构造函数会直接在你提供的内存上完成对象初始化,包括设置虚表指针、初始化成员变量。
- 你之前觉得构造函数有返回值,大概率是调试时的误解——C++构造函数本身没有返回值,可能是混淆了工厂函数的逻辑。
第三步:调用普通成员函数
以IsConnectedToServer(void)const __ptr64为例:
- 把
Client对象的指针放到RCX寄存器(this指针)。 - 调用该函数的地址,返回值会存在RAX寄存器里(bool类型返回0或非0)。
- 带参数的成员函数比如
ControlArmMeasurementStream(...),参数按x64调用约定依次放到RDX、R8、R9寄存器,剩下的参数压栈即可。
第四步:处理虚函数
以虚析构public: virtual __cdecl SomeComapny::Cds::Api::Client::~Client(void) __ptr64为例:
- 从
Client对象的第一个8字节取出虚表指针:void** vtable = *(void**)client_ptr。 - 虚析构在虚表中的位置:MSVC下通常是第一个条目,你可以通过调试演示程序确认。
- 调用虚表中的函数:把
client_ptr放到RCX,然后调用vtable[0](假设是虚析构的位置)。- 注意:虚析构必须通过虚表调用,不能直接调用dumpbin里的析构函数地址(那只是非虚的基础部分)。
实操技巧
- 跟踪演示程序的调用流程:用x64dbg看演示程序怎么创建
Client对象、调用成员函数,记录每一步的寄存器操作和内存变化,直接抄逻辑就行。 - 写汇编包装:C++没法直接控制寄存器,你需要用MSVC的内联汇编(
__asm)或者单独写汇编文件,处理this指针和参数的传递。比如调用无参构造的汇编片段:mov rcx, [client_memory_ptr] ; 将对象内存地址放到RCX call [constructor_address] ; 调用构造函数地址 - 逆向自定义类型:对于
eCDSReturnCode、sCDSArmMeasurementStreamConfig这类类型,从DLL的函数实现里推断它们的大小和内存布局(比如看函数怎么访问参数的内存偏移)。
内容的提问来源于stack exchange,提问作者Fake Name
相关产品推荐
相关产品推荐

