1

C関数を期待するレガシーAPIにメンバー関数を使用できるように、「サンキング」を使用しようとしています。これと同様のソリューションを使用しようとしています。これはこれまでの私のサンク構造です:

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));

次のコードは、このサンク構造を使用する私のテストです (ただし、まだ機能していません)。

#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含む必要が34あり71ます。

注: これがすべてのシナリオ (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、スタックから を読み取り、 をプッシュしてからthis、 をプッシュする必要がありreturn-addressます。そしてクリーンアップのために、 に 4 を加算します (減算ではありません) esp

戻りアドレスについて - サンクは呼び出し先が戻った後に何らかのクリーンアップを行う必要があるため、元の戻りアドレスをどこかに保存し、サンクのクリーンアップ部分の戻りアドレスをプッシュする必要があります。では、元の返品先住所はどこに保存すればよいのでしょうか?

  • グローバル変数内 - 許容できるハックかもしれません (おそらく、ソリューションを再入可能にする必要はないため)
  • スタック上 - パラメーターのブロック全体を移動する必要があり (機械語で に相当するものを使用memmove)、その長さはほとんど不明です

また、結果として得られるスタックは 16 バイトでアラインされていないことに注意してください。これは、関数が特定の型 (8 バイトと 16 バイトのアラインメントを必要とするもの - たとえば SSE のもの; また多分 double) を使用する場合にクラッシュを引き起こす可能性があります。

于 2012-07-22T17:34:01.143 に答える