C++类成员函数指针无法直接获取真实内存地址,因其可能包含虚表索引、偏移量或调整标志;可通过联合体解析字节、内联汇编读指令、vtable手动索引或memcpy提取原始字节等方式逆向分析。

如果您尝试在C++中获取类成员函数指针的实际内存入口地址,会发现其行为与普通函数指针截然不同——由于虚函数表、this指针调整、多重继承等机制,成员函数指针可能包含多个字段。以下是几种可验证的底层指针技巧实现方式:
一、通过联合体(union)逐字节解析成员函数指针结构
成员函数指针在多数编译器(如MSVC、GCC)中被实现为多字节结构,可通过联合体将其映射为整数数组,从而提取原始字节数据。
1、定义一个与目标平台成员函数指针大小一致的联合体,包含成员函数指针类型和对应长度的size_t数组。
2、将目标成员函数指针赋值给联合体的成员函数指针字段。
立即学习“C++免费学习笔记(深入)”;
3、读取联合体中size_t数组的首元素或全部元素,该值不等于真实代码地址,可能为偏移、索引或标志位组合。
4、在x86_64 Linux GCC环境下,非虚成员函数指针通常表现为单个8字节值,而含虚函数调用的指针可能显示为0x1或0xffffffffffffffff等特殊标记。
二、借助汇编内联强制转换为void*并检查指令流
通过内联汇编跳过类型系统限制,将成员函数指针强制解释为可执行地址,并读取其起始位置的机器码,以确认是否指向有效函数入口。
1、声明成员函数指针变量,并确保其绑定到具体类实例的非虚、静态绑定函数。
2、使用reinterpret_cast<uintptr_t></uintptr_t>转换为整数,再转为void*,此操作在标准C++中未定义,仅限调试与逆向分析场景。
3、在GCC中插入asm volatile ("" ::: "rax")防止编译器优化掉该地址读取。
4、用hexdump或调试器在运行时查看该地址处的前16字节指令,若首字节为0x55(push %rbp)或0x48(rex.W前缀),则大概率命中真实函数入口。
三、利用虚函数表(vtable)手动索引定位虚函数地址
对于虚成员函数,其实际地址存储在对象的虚函数表中,可通过对象首地址偏移获取vtable指针,再按虚函数声明顺序索引。
1、创建类实例,并确认其首个成员为虚函数表指针(通常位于对象内存布局起始处)。
2、用reinterpret_cast<uintptr_t>(obj_ptr)[0]</uintptr_t>获取vtable首地址。
3、根据虚函数在类中声明顺序(从0开始计数),读取vtable_ptr[index]所存地址,该地址即为虚函数的真实入口,可安全reinterpret_cast为void*。
4、注意:多重继承下子对象偏移可能导致vtable指针不在对象起始位置,需结合clang -cc1 -fdump-record-layouts输出确认布局。
四、使用std::memcpy逐字节提取并跨ABI比对
规避编译器对成员函数指针的类型保护,通过内存拷贝将其原始字节序列导出为字节数组,便于跨平台/编译器分析其编码规律。
1、声明std::array<char sizeof> raw_bytes;</char>用于接收原始数据。
2、调用std::memcpy(raw_bytes.data(), &mfptr, sizeof(mfptr));完成无符号字节复制。
3、逐字节打印raw_bytes内容,在MSVC x64下,非虚函数常呈现为[addr_lo, addr_hi, 0x00, 0x00, ...];GCC则可能填充0xcc或0x00。
4、对比同一函数在不同编译器生成的字节序列,若第0–7字节全为零,则表明该指针指向虚函数且需查vtable。


















