VMT(虚函数表)读取异常:注入DLL后地址异常求助
VMT Hook 注入DLL后获取虚表地址异常问题分析
问题场景
我正在学习VMT Hook技术,遇到了一个问题:
- 在可执行程序内部直接执行代码时,能正确输出虚函数地址
- 将DLL注入到自己编写的可执行程序后,手动计算虚表地址并访问时,输出了异常地址
程序内正常执行的代码
Obj* obj = new Obj; std::uintptr_t* vmt = *(std::uintptr_t**)(obj); printf("0x%llx\n", vmt[0]);
DLL注入后的输出结果
Hello in first function, 0x000001CCDA662210 ??_7Obj@@6B@ -> 0x7ff678043008 vmt[0]: 0x244c894838ec8348
DLL注入后的代码
std::uintptr_t address = ( ( std::uintptr_t )GetModuleHandleA( NULL ) + 0x3008 ); printf( "??_7Obj@@6B@ -> 0x%llx\n", address ); std::uintptr_t* vmt = *(std::uintptr_t**)( address ); printf( "vmt[0]: 0x%llx\n", vmt[ 0 ] );
IDA反编译的虚表信息
.rdata:0000000140003008 ; const Obj::`vftable' .rdata:0000000140003008 ??_7Obj@@6B@ dq offset sub_140007390 ; DATA XREF: sub_140007130+1D↓o .rdata:0000000140003010 dq offset sub_1400073C0 .rdata:0000000140003018 dq offset sub_1400073E0 .rdata:0000000140003020 dq offset sub_140007400 .rdata:0000000140003028 align 10h
问题原因
你犯了一个核心错误:??_7Obj@@6B@本身就是虚表(vftable)的起始地址,而非指向虚表的指针。
从IDA的输出能看到,??_7Obj@@6B@直接存储的是第一个虚函数sub_140007390的地址(dq offset sub_140007390),说明虚表本身就是一个函数指针数组。
对比程序内的正确逻辑:std::uintptr_t* vmt = *(std::uintptr_t**)(obj);
这里是从对象实例的前8字节(64位环境)取出指向虚表的指针,这个指针才是虚表数组的起始地址。
但你在DLL中的代码多做了一次解引用:std::uintptr_t* vmt = *(std::uintptr_t**)( address );
此时address已经是虚表的起始地址,你却把它当成了指向虚表的指针去解引用,相当于把虚表第一个函数的地址当作指针访问,自然得到的是垃圾数据。
解决方法
直接将address作为虚表数组的起始地址即可,无需额外解引用:
修改后的DLL代码:
std::uintptr_t address = (std::uintptr_t)GetModuleHandleA(NULL) + 0x3008; printf("??_7Obj@@6B@ -> 0x%llx\n", address); // 直接将address作为虚表指针,跳过不必要的解引用 std::uintptr_t* vmt = (std::uintptr_t*)address; printf("vmt[0]: 0x%llx\n", vmt[0]);
修改后,vmt[0]会输出和程序内一致的0x000001CCDA662210(对应IDA中的sub_140007390地址)。
额外提示:如果要更可靠地获取虚表,建议通过对象实例来获取,而非手动计算偏移——虽然用GetModuleHandle获取了基址,但程序编译节偏移变化时,手动指定的0x3008可能失效。若必须用静态偏移,务必确认基址+偏移确实指向虚表起始地址,且不要重复解引用。
内容的提问来源于stack exchange,提问作者reiizl
相关产品推荐
相关产品推荐

