C++ Thunk技术

一. 使用回调函数时遇到的问题

在使用Win32 API(如CreateThread等)时我们经常需要传入回调函数,这个回调函数不能是类的成员函数,只能是友元函数或静态函数。为了在回调函数中访问某个类的成员变量或函数,我们通常不得不通过某种方式将该类的This指针传入到回调函数中,从而实现在回调函数中访问类的公有的成员变量或函数。

如果有一种方式可以实现将类的成员函数作为回调函数来使用,那么就可以很完美的解决上面的问题。本篇介绍的C++ Thunk技术就是来解决这个问题的。

二. Thunk实现原理

在了解Thunk实现原理之前,需要从汇编角度知道函数的调用过程,可以参考:
从汇编的角度分析函数调用过程(1)
从汇编的角度分析函数调用过程(2)

Thunk技术的大致步骤:
<1>. 在指定的内存区域(可以使用VirtualAlloc函数来分配)写入模拟函数调用的汇编指令:

// 该行用于存储函数的返回地址,因为在进入调用函数后,栈顶存储的就是函数的返回地址,所以可以直接使用[esp]。
push dword ptr [esp] 

// 将pThis指针放入到函数的第一个参数位置(注意参数入栈是从右往左入栈的,而上面一行代码又压入了函数返回地址,所以是[esp+4])
mov  dword ptr [esp+4], pThis 

// 直接使用jmp跳转到指定的相对地址(留意相对地址的计算方式)
jmp XXXXXXX

<2>. 通过Windows提供的FlushInstructionCache函数来更新指令缓存
<3>. 最后再将指定的内存区域的起始地址作为函数指针来使用。

三. 代码

下面的代码有如下限制(不足):
1. 只支持x86平台,如果需要支持x64平台,可以在该代码的基础上进行修改。可以参考http://www.cnblogs.com/fangkm/archive/2009/05/25/1488727.html
2. 只适用于__stdcall调用约定。

将Thunk功能的实现放入到Thunk类中,代码如下:

Thunk.h

#ifndef _THUNK_H_
#define _THUNK_H_
#pragma once

// TODO: support x64 platform.
//

#define __u8 unsigned char
#define __u16 unsigned short
#define __u32 unsigned long
#define __u64 unsigned long long

class Thunk
{
public:
    Thunk();
    ~Thunk();

    void* Stdcall(void* pThis, __u32 MemberFxnAddr);

    template 
    static __u32 GetMemberFxnAddr(T MemberFxnName) {
        union
        {
            T From;
            __u32 To;
        }union_cast;

        union_cast.From = MemberFxnName;
        return union_cast.To;
    }

private:
#pragma pack (push, 1)

    typedef struct _BYTECODE_STDCALL {
        __u8 Push[3];  // push dword ptr[esp] ;
        __u32 Move;    // mov dword ptr [esp + 4], ?? ?? ?? ?? ;
        __u32 This;    // this pointer
        __u8 Jmp;      // 0xE9
        __u32 Adrr;    // relative jmp
    }BYTECODE_STDCALL, *PBYTECODE_STDCALL;

#pragma pack (pop)

    BYTECODE_STDCALL  m_BytecodeStdcall;
    Thunk* pthis_;
};

#endif // ! _THUNK_H_

Thunk.cpp

#include "Thunk.h"
#include 

Thunk::Thunk()
{
    pthis_ = (Thunk*)VirtualAlloc(NULL, sizeof(Thunk), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
}

Thunk::~Thunk()
{
    if (NULL == pthis_)
        return;

    VirtualFree(pthis_, 0, MEM_RELEASE);
}

void* Thunk::Stdcall(void* pThis, __u32 MemberFxnAddr)
{
    // FF 34 24                      push dword ptr [esp]
    pthis_->m_BytecodeStdcall.Push[0] = 0xFF;
    pthis_->m_BytecodeStdcall.Push[1] = 0x34;
    pthis_->m_BytecodeStdcall.Push[2] = 0x24;

    // C7 44 24 04 ?? ?? ?? ??       mov  dword ptr [esp+4], pThis
    pthis_->m_BytecodeStdcall.Move = 0x042444C7;
    pthis_->m_BytecodeStdcall.This = (__u32)pThis; // 4个字节

    // E9 ?? ?? ?? ??                jmp to target addr
    pthis_->m_BytecodeStdcall.Jmp = 0xE9;
    pthis_->m_BytecodeStdcall.Adrr = MemberFxnAddr - (__u32)(&(pthis_->m_BytecodeStdcall)) - sizeof(BYTECODE_STDCALL);

    FlushInstructionCache(GetCurrentProcess(), &(pthis_->m_BytecodeStdcall), sizeof(BYTECODE_STDCALL));

    return &(pthis_->m_BytecodeStdcall);
}

如何使用Thunk类

_beginthreadex函数为例,来介绍如何使用Thunk类。

#include  
#include 
#include 
#include "thunk.h"

using namespace  std;


class Test {
public:
    Test() {

    }

    bool CreateNewThread() {
        // 重点
        void* func = thunk_.Stdcall(this, Thunk::GetMemberFxnAddr(&Test::ThreadProc));

        HANDLE h = (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)func, NULL, 0, NULL);

        return h != NULL;
    }

private:
    unsigned int __stdcall ThreadProc(void *arg) {
        printf("ThreadProc\n");

        return 0;
    }

private:
    Thunk thunk_; // 重点
};

int main() {
    Test t;
    t.CreateNewThread();

    getchar();
    return 0;
}

你可能感兴趣的:(thunk,回调函数,☆,C/C++,C/C++基础)