引数付きのメンバ関数の場合

今回は、実際に動くコードを紹介します。

const void *pvVF = VirtualFree;

__declspec( naked ) void RestoreESP
    ( void *pvCode, unsigned long espval, unsigned long retaddr )
{
    __asm
    {
        push 0x00008000
        push 0x00000000
        push dword ptr[ esp + 8 ]
        call pvVF ; VirtualFree( pvCode, 0, MEM_RELEASE );

        mov eax, dword ptr[ esp + 8 ] ; EAX = retaddr
        mov esp, dword ptr[ esp + 4 ] ; ESP = espval
        jmp eax                       ; jump to retaddr
    }
}

const unsigned char codetmpl[] =
{
    0x68, 0x00, 0x00, 0x00, 0x00, // push imm32( EIP value to return )
    0x68, 0x00, 0x00, 0x00, 0x00, // push imm32( ESP value before calling )
    0x68, 0x00, 0x00, 0x00, 0x00, // push imm32( address of code )
    0xb8, 0x00, 0x00, 0x00, 0x00, // mov  EAX, imm32( address of RestoreESP )
    0xff, 0xe0,                   // jmp  EAX ( jump to RestoreESP )
    0xcd, 0x0d                    // int  0Dh ( guard instruction )
};

void *__stdcall WriteCode( unsigned long espval, unsigned long retaddr )
{
    unsigned char *pvCode = ( unsigned char* ) 
        VirtualAlloc( NULL, 24, MEM_COMMIT, PAGE_EXECUTE_READWRITE );

    memcpy( pvCode, codetmpl, sizeof( codetmpl ) );

    memcpy( pvCode + 1, &retaddr, sizeof( retaddr ) );
    memcpy( pvCode + 6, &espval, sizeof( espval ) );
    memcpy( pvCode + 11, &pvCode, sizeof( pvCode ) );

    unsigned long restoringProcAddr = ( unsigned long ) RestoreESP;
    memcpy( pvCode + 16, &restoringProcAddr, sizeof( restoringProcAddr ) );

    return pvCode;
}

__declspec( naked ) void Invoke( Delegate *pDelegate, ... )
{
    __asm
    {
        lea  edx, [ esp + 4 ] ; EDX = [ ESP value before calling ]
        push dword ptr[ esp ] ; [ EIP value to return ]
        push edx              ; [ ESP value before calling ]
        call WriteCode        ; EAX = pvCode;

        mov edx, dword ptr[ esp + 4 ] ; EDX = pDelegate
        add esp, 8
        mov ecx, dword ptr[ edx ]     ; ECX = pDelegate->obj
        push eax                      ; RA  = pvCode
        jmp dword ptr[ edx + 4 ]      ; pDelegate->funct()
    }
}

新しく WriteCode という関数を定義しました。これは仮想メモリ上に実行可能なコードを生成し、その先頭アドレスを返す関数です。Invoke では、WriteCode を使って得られたアドレスを、メンバ関数のリターン アドレスとして設定します。こうすれば、メンバ関数の実行が終了すると「コード」に制御が移り、さらに RestoreESP に移行します。RestoreESP では、まずコードのために確保された仮想メモリを解放し、続いて ESP レジスタの値を復元し、大元の関数( Invoke を呼び出した関数)に戻ります。

この Invoke は引数の数が決まっている場合に使用できます。引数が可変個の場合、this ポインタは ECX レジスタではなく、スタックを使って渡すことになっているため、少し書き換える必要があります。

__declspec( naked ) void Invoke( Delegate *pDelegate, ... )
{
    __asm
    {
        lea  edx, [ esp + 4 ] ; EDX = [ ESP value before calling ]
        push dword ptr[ esp ] ; [ EIP value to return ]
        push edx              ; [ ESP value before calling ]
        call WriteCode        ; EAX = pvCode;

        mov edx, dword ptr[ esp + 4 ] ; EDX = pDelegate
        add esp, 8
        push dword ptr[ edx ]
        push eax                      ; RA  = pvCode
        jmp dword ptr[ edx + 4 ]      ; pDelegate->funct()
    }
}

書き換えるといっても一行だけです(一行だけで済むようにしたのです)。通常の Invoke を PascalInvoke とし、このような Invoke を CInvoke として、両方定義しておいてもよいでしょう。詳しい使い方についてはコード サンプルを参照してください。

ようやく!と、感嘆符を入れたくなるほど面倒なことを説明してきたつもりです。今回の企画について、「こうすればできます」ということだけを述べればよいのであれば、最初の回と今回の記事だけで済んだのですが、なぜこんな複雑奇怪なコードになるのか不思議に思っただろうと思います。そこで私は今まで数回に渡り、「単純にはいかないのだ」ということを伝えようとしてきました。伝わったでしょうか?

コード サンプルにあるプログラムでは単一のメンバ関数だけを呼び出す機能に留めていますが、これを複数個登録して連続的に呼び出せるようにすれば、いわゆるマルチキャスト デリゲートとなります。最後にまた繰り返しますが、これはあくまで実験、または遊びのつもりで作った危険なコードであり、現実のプログラムで真似をすることのないようお願いします。[EOP]

[ コード サンプルはこちら ]