如何确定虚函数在虚函数表中的位置2 (Windows x64版本)

之前写了一篇blog,描述 Windows 在x86 下,如何确定Virtual Function在Table中的位置。
没有写x64的情况,主要原因是x64不支持内联汇编。
我们知道MSVC编译器不会直接访问Table,而是会写一个thunk间接访问。
我们第一步要获得这个thunk的地址,用伪代码就是: &Class::vfn_name
c++认为这是一个成员函数指针,他是无法转化为void*的。(还是有办法的)
所以在上一篇blog中,我们用如下的泛型函数和内联汇编实现

template<typename src_type>
void* pointer_cast(src_type src)
{
    void* ret = NULL;
    __asm
    {
        mov eax, src
        mov ret, eax
    }
    return ret;
}

调用如下:

 void* p_thunk_fn = pointer_cast(&Class::vfn_name);

这样就通过”黑魔法“获得了这个thunk的地址。
当然,到了x64这个方法就行不通了。
后来我在咨询deepseek的时候,他偶尔提了一嘴,说可以用Union的方法
在这里插入图片描述
然后我想起来还有这个玩法,以前看到过。
这样直接通过Union就可以骗过编译器,获得thunk的地址
实现如下:

union FunctionPointer {
    void (Class::*vfn_name)(int arg);
    void* ptr;
};

FunctionPointer fp;
fp.vfn_name = &Class::vfn_name;
void* thunk = fn.ptr; //getchu~

当然,我们为了获取所有地址,需要每一个函数都写一个这样的FunctionPointer
这样要疯掉。
让deepseek给我写了一个泛型,这时候体现出ai作为工具能极大提高工作效率!

template <typename T>
union TFP;

// 特化模板,支持成员函数指针
template <typename ClassType, typename ReturnType, typename... Args>
union TFP<ReturnType(ClassType::*)(Args...)> {
    using MemberFuncPtr = ReturnType(ClassType::*)(Args...);  // 成员函数指针类型
    MemberFuncPtr memberFunc;  // 成员函数指针
    void* ptr;  // void* 指针
};

实现了泛型的FunctionPointer,TFP
接下去实现泛型的GetThunk

template <typename ClassType, typename ReturnType, typename... Args>
void* GetThunk(ReturnType(ClassType::* func)(Args...)) 
{
    TFP<decltype(func)> fp;
    fp.memberFunc = func;   
    return fp.ptr;
}

任意成员函数只需要

void* pthunk = GetThunk(&Class::vfn_name);

到这里我们获得了Thunk
接着开始反汇编x64的thunk
我们发现他的逻辑和x86是一模一样的,只是指令变为了x64的版本
下面直接贴出实现 x64 下, Windows MSVC环境如何从 thunk 获取 虚函数的 位置(Offset)

int GetMemberFnOffset(void* p_thunk_fn)
{
    int vftable_offset = -1;//虚函数表的偏移量

    auto jmp_o1_o2_o3_o4 = *(char*)p_thunk_fn;
    //第一步
    //p_thunk_fn 指向一个 近跳转, 就是当前地址 + offset
    //如下
    //jmp xti::XtTraderApi::vcall'{4}' (0926C75h)
    //E9 Offset1 Offset2 Offset3 Offset4
    //E9 A1 58 00 00
    if (jmp_o1_o2_o3_o4 != (char)0xE9)
    {
        throw std::exception("没有找到类似 jmp xti::XtTraderApi::vcall'{4}' (0926C75h) 的语句");
    }
    auto offset = *(int*)((char*)p_thunk_fn + 1);//跳转的偏移量
    auto new_address = (char*)p_thunk_fn + 5 + offset;//当前地址 + 5 个指令(jmp_o1_o2_o3_o4) + 偏移量

    //第二步
    //转到了
    //00007FF754442494 48 8B 01             mov         rax, qword ptr[rcx]
    //00007FF754442497 FF 60 08             jmp         qword ptr[rax + 8]
    /*
     48:这是 x86-64 架构中的 REX 前缀(REX prefix)。48 表示使用 64 位操作数大小(64-bit operand size)。
     8B:这是操作码(opcode),表示 MOV 指令。具体来说,8B 表示将一个值从内存移动到寄存器。
     01:这是 ModR/M 字节(ModR/M byte),用于指定操作数的寻址模式。01 的二进制形式是 00000001,具体含义如下:

     前两位 00 表示寄存器间接寻址(register indirect addressing)。
     中间三位 000 表示目标寄存器是 RAX(64 位寄存器)。
     最后三位 001 表示源操作数是 RCX 寄存器指向的内存地址。
    */
    auto mov_rax_qword_ptr_rcx = *(unsigned short*)new_address;
    if (mov_rax_qword_ptr_rcx != 0x8B48)
    {
        //抛出异常
        throw std::exception("没有找到语句mov rax, qword ptr[rcx]");
    }
    //到了最后读取 rax + x 中 x 的值
    //2个情况
    //FF 60 x1           偏移值较小
    //FF A0 x1 x2 x3 x4  偏移值较大
    auto byte_60_A0 = *((char*)new_address + 3 + 1);
    if (byte_60_A0 == 0x60)
    {
        //FF 60 X1
        vftable_offset = *((char*)new_address + 4 + 1);
    }
    else if (byte_60_A0 == (char)0xA0)
    {
        //FF A0 X1 X2 X3 X4
        vftable_offset = *(int*)((char*)new_address + 4 + 1);
    }
    else
    {
        //抛出异常
        throw std::exception("没有找到类似 jmp dword ptr [eax + x]的语句, 预期的数据不是60或者A0");
    }
    return vftable_offset;
}

最后的最后,为了操作简单,做一个Wraper

template <typename ClassType, typename ReturnType, typename... Args>
int GetMemberFnOffsetEx(ReturnType(ClassType::* func)(Args...))
{
	auto p_thunk_fn = GetThunk(func);
	return GetMemberFnOffset(p_thunk_fn);
}

这样我最终调用效果如下:

 dt[GetMemberFnOffsetEx(&XtTraderApi::destroy)] = "destroy";
 dt[GetMemberFnOffsetEx(&XtTraderApi::join)] = "join";
 dt[GetMemberFnOffsetEx(&XtTraderApi::init)] = "init";
 dt[GetMemberFnOffsetEx(&XtTraderApi::getVersion)] = "getVersion";
 dt[GetMemberFnOffsetEx(&XtTraderApi::getUserName)] = "getUserName";
 dt[GetMemberFnOffsetEx(&XtTraderApi::userLogin)] = "userLogin";

上面dt是一个map,存储着virtual function在table中的位置

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值