7

私の質問は少し奇妙に思えるかもしれませんが、これで終わりです。私は C++ でプログラムを動的に作成しようとしています (主に楽しみのためですが、プログラム上の理由でもあります)。思ったほど難しくはありません。これを行うには、次のように実行時にアセンブリを使用する必要があります。

byte * buffer = new byte[5];
*buffer = '0xE9'; // Code for 'jmp'
*(uint*)(buffer + 1) = 'address destination'; // Address to jump to

1 つのプラットフォームとコンパイラのみをターゲットにしているため、これは思ったよりもはるかに簡単です。Linux 32 ビットの GCC (および1 つの呼び出し規約 cdecl)。そのため、トリガーからの呼び出しをリダイレクトする動的アセンブリ関数を作成しようとしているので、クラスメソッドをコールバックとして使用できます (C API ライブラリでも (もちろん cdecl を使用))。これは、ポインターとネイティブ型 (char、int、short など) をサポートするためだけに必要です。

ANYTHING MyRedirect(ANY AMOUNT ARGUMENTS)
{
    return MyClassFunc('this', ANY AMOUNT ARGUMENTS);
}

上記の関数は、純粋なアセンブリ (C++ のメモリ内) で作成したい関数です。関数は非常に単純なので、その ASM も単純です (引数によって異なります)。

55                      push   %ebp
89 e5                   mov    %esp,%ebp
83 ec 04                sub    $0x4,%esp
8b 45 08                mov    0x8(%ebp),%eax
89 04 24                mov    %eax,(%esp)
e8 00 00 00 00          call   <address>
c9                      leave
c3                      ret  

そのため、私のプログラムでは、ASM パターン ジェネレーターを作成しました (ASM は特によく知らないので、パターンを検索します)。この関数は、関数が必要とする引数の量を指定することにより、アセンブリ コード (バイト単位、上記の正確なケース、つまり、リダイレクトして戻る関数) を生成できます。これは私の C++ コードの抜粋です。

std::vector<byte> detourFunc(10 + stackSize, 0x90); // Base is 10 bytes + argument size

// This becomes 'push %ebp; move %esp, %ebp'
detourFunc.push_back(0x55);     // push %ebp
detourFunc.push_back(0x89);     // mov
detourFunc.push_back(0xE5);     // %esp, %ebp

// Check for arguments
if(stackSize != 0)
{
    detourFunc.push_back(0x83);     // sub
    detourFunc.push_back(0xEC);     // %esp
    detourFunc.push_back(stackSize);    // stack size required

    // If there are arguments, we want to push them
    // in the opposite direction (cdecl convention)
    for(int i = (argumentCount - 1); i >= 0; i--)
    {
        // This is what I'm trying to implement
        // ...
    }

    // Check if we need to add 'this'
    if(m_callbackClassPtr)
    {

    }
}

// This is our call operator
detourFunc.push_back(0xE8);     // call

// All nop, this will be replaced by an address
detourFunc.push_back(0x90);     // nop
detourFunc.push_back(0x90);     // nop
detourFunc.push_back(0x90);     // nop
detourFunc.push_back(0x90);     // nop

if(stackSize == 0)
{
    // In case of no arguments, just 'pop'
    detourFunc.push_back(0x5D); // pop %ebp
}

else 
{
    // Use 'leave' if we have arguments
    detourFunc.push_back(0xC9); // leave    
}

// Return function
detourFunc.push_back(0xC3);     // ret

ゼロを指定すると、stackSizeこれが出力になります。

55                      push   %ebp
89 e5                   mov    %esp,%ebp
e8 90 90 90 90          call   <address>
5d                      pop    %ebp
c3                      ret   

ご覧のとおり、これは完全に有効な 32 ビット ASM であり、引数がなく、「this」ポインターが必要ない場合、「MyRedirect」として機能します。問題は、「リダイレクト」関数が受け取るように指定した引数の量に応じて、ASM コードを生成する部分を実装したいということです。私の小さなC++プログラムでこれを成功させました(パターンをクラックしました)。

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char * argv[])
{
    int val = atoi(argv[1]);

    printf("\tpush %%ebp\n");
    printf("\tmov %%esp,%%ebp\n");

    if(val == 0)
    {
        printf("\tcall <address>\n");
        printf("\tpop %%ebp\n");
    }

    else
    {
        printf("\tsub $0x%x,%%esp\n", val * sizeof(int));

        for(int i = val; i > 0; i--)
        {
            printf("\tmov 0x%x(%%ebp),%%eax\n", i * sizeof(int) + sizeof(int));
            printf("\tmov %%eax,0x%x(%%esp)\n", i * sizeof(int) - sizeof(int));
        }

        printf("\tcall <address>\n");
        printf("\tleave\n");
    }

    printf("\tret\n");
    return 0;
}

この関数は、「objdump」によって生成された ASM コードとまったく同じパターンを出力します。私の質問は次のとおりです。上記のようなリダイレクト関数のみが必要な場合、引数に関係なく、Linux 32ビットのみである場合、または知っておく必要がある落とし穴がある場合、これはすべての場合に有効ですか? 例えば; 生成された ASM は「shorts」または「chars」で異なりますか、それともこれは機能しますか (私は整数でのみテストしました)、また「void」を返す関数を呼び出すと (ASM にどのように影響しますか)?

私はすべてを少しあいまいに説明したかもしれないので、誤解の代わりに質問してください:)

注: 代替案を知りたくありません。現在の実装を楽しんでおり、非常に興味深い実装だと思います。この件に関するご支援をいただければ幸いです。

編集: 興味がある場合は、上記の C++ コードのダンプをいくつか示します:リンク

4

1 に答える 1

1

Dan が示唆するように、メモリを実行可能としてマークする必要があります。使用できるコードをいくつか書きました。(GNU/Linux および Windows で動作します。) ARM、x86-64、またはその他のプラットフォームをサポートするつもりがない場合、(実行可能部分が追加された) コードに問題はないと思います。 「常に機能する」必要があります。(もちろん、他のすべてが適切に機能していると仮定します。)

#include <sys/mman.h>

...

n = <size of code buffer>;
p = mmap(0, n, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANON|MAP_PRIVATE, 0, 0);

「魚」はasmjitを使用することを提案しました。私はそれに同意しなければなりません。あなたの方法よりも移植性があります。しかし、あなたは代替案には興味がないと言いました。

あなたは「サンキング」(一種の)と呼ばれるものに興味があるかもしれません。基本的に、「C コールバックを C++ メソッドに置き換える」ことを試みます。これは実際には非常に便利ですが、実際にはアプリケーションにとって適切な設計ではありません。

それが役立つことを願っています。

于 2012-06-16T09:14:26.277 に答える