1

我一直在尝试使用“thunking”,这样我就可以将成员函数用于需要 C 函数的遗留 API。我正在尝试对此使用类似的解决方案。到目前为止,这是我的 thunk 结构:

struct Thunk
{
    byte mov;   // ↓
    uint value; // mov esp, 'value' <-- replace the return address with 'this' (since this thunk was called with 'call', we can replace the 'pushed' return address with 'this')

    byte call;  // ↓
    int offset; // call 'offset' <-- we want to return here for ESP alignment, so we use call instead of 'jmp'

    byte sub;   // ↓
    byte esp;   // ↓
    byte num;   // sub esp, 4 <-- pop the 'this' pointer from the stack

    //perhaps I should use 'ret' here as well/instead?
} __attribute__((packed));

下面的代码是我的一个测试,它使用了这个 thunk 结构(但它还没有工作):

#include <iostream>
#include <sys/mman.h>
#include <cstdio>

typedef unsigned char byte;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef unsigned long ulong;

#include "thunk.h"

template<typename Target, typename Source>
inline Target brute_cast(const Source s)
{
    static_assert(sizeof(Source) == sizeof(Target));

    union { Target t; Source s; } u;
    u.s = s;
    return u.t;
}

void Callback(void (*cb)(int, int))
{
    std::cout << "Calling...\n";
    cb(34, 71);
    std::cout << "Called!\n";
}

struct Test
{
    int m_x = 15;

    void Hi(int x, int y)
    {
        printf("X: %d | Y: %d | M: %d\n", x, y, m_x);
    }
};

int main(int argc, char * argv[])
{
    std::cout << "Begin Execution...\n";

    Test test;

    Thunk * thunk = static_cast<Thunk*>(mmap(nullptr, sizeof(Thunk),
        PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0));

    thunk->mov = 0xBC; // mov esp
    thunk->value = reinterpret_cast<uint>(&test);

    thunk->call = 0xE8; // call
    thunk->offset = brute_cast<uint>(&Test::Hi) - reinterpret_cast<uint>(thunk);
    thunk->offset -= 10; // Adjust the relative call

    thunk->sub = 0x83; // sub
    thunk->esp = 0xEC; // esp
    thunk->num = 0x04; // 'num'

    // Call the function
    Callback(reinterpret_cast<void (*)(int, int)>(thunk));
    std::cout << "End execution\n";
}

如果我使用该代码;Test::Hi我在函数中收到分段错误。原因很明显(一旦你分析了 GDB 中的堆栈),但我不知道如何解决这个问题。堆栈未正确对齐。

x参数包含垃圾,y参数包含指针this(参见Thunk代码)。这意味着堆栈错位了 8 个字节,但我仍然不知道为什么会这样。谁能说出为什么会这样?x并且y应该分别包含3471

注意:我知道这并不适用于所有场景(例如 MI 和 VC++ thiscall 约定),但我想看看我是否可以完成这项工作,因为我会从中受益匪浅!

编辑:显然我也知道我可以使用静态函数,但我认为这更像是一个挑战......

4

1 回答 1

2

假设您有一个独立的(非成员,或者可能是静态的)cdecl函数:

void Hi_cdecl(int x, int y)
{
    printf("X: %d | Y: %d | M: %d\n", x, y, m_x);
}

另一个函数这样调用它:

push 71
push 36
push (return-address)
call (address-of-hi)
add esp, 8 (stack cleanup)

您想用以下内容替换它:

push 71
push 36
push this
push (return-address)
call (address-of-hi)
add esp, 4 (cleanup of this from stack)
add esp, 8 (stack cleanup)

为此,您必须return-address从堆栈中读取 , push this,然后将return-address. 对于清理,将 4(而不是减去)添加到esp.

关于返回地址 - 由于 thunk 必须在被调用者返回后进行一些清理,它必须将原始返回地址存储在某个地方,并将 thunk 的清理部分的返回地址推送。那么,原始返回地址存储在哪里呢?

  • 在全局变量中 - 可能是可接受的 hack(因为您可能不需要您的解决方案是可重入的)
  • 在堆栈上 - 需要移动整个参数块(使用机器语言等效的memmove),其长度几乎是未知的

另请注意,生成的堆栈不是 16 字节对齐的;如果函数使用某些类型(需要 8 字节和 16 字节对齐的类型 - 例如 SSE 的类型;也可能 double),这可能会导致崩溃。

于 2012-07-22T17:34:01.143 回答