C++で機械語を実行(バッファーコピー, memcpy)

C++言語を使用している際に、直接アセンブリで記述した機械語(=マシン語)を実行します。

はじめに

本例は、バッファーコピーを行うmemcpyと同じ動作を行う機械語を示します。以前のarques.hatenablog.comstrcpy差し替えを変更します。

C++コード

C++で記述したソースコード機械語を取り込みます。プログラムは以前のものと同じ構造です。

#include <iostream>
#include <windows.h>

using namespace std;

// alloc memory and copy code to it
void* allocAndCopyIt(unsigned char code[], size_t size)
{
    void* mem = VirtualAlloc(
                    NULL, size,
                    MEM_COMMIT | MEM_RESERVE,
                    PAGE_EXECUTE_READWRITE);

    if (!mem)
        throw "VirtualAlloc failed.";

    memcpy(mem, code, size);

    return mem;
}

// free memory
void freeMem(void* mem)
{
    if (!VirtualFree(mem, 0, MEM_RELEASE))
        throw "VirtualFree failed";
}

int main(void)
{
    unsigned char code[] = {
        0x48, 0x56,         // push     rsi
        0x57,               // push     rdi
        0x48, 0x8B, 0xF2,   // mov      rsi,rdx
        0x48, 0x8B, 0xF9,   // mov      rdi,rcx
        0x49, 0x8B, 0xC8,   // mov      rcx,r8
        0xFC,               // cld
        0xF3, 0xA4,         // rep movs byte ptr [rdi],byte ptr [rsi]
        0x5F,               // pop      rdi
        0x5E,               // pop      rsi
        0xC3                // ret
    };

    try
    {
        void* mem = allocAndCopyIt(code, sizeof(code));
        void (*func)(char*, char*, size_t)
                    = (void (*)(char*, char*, size_t))mem;

        char src[] = "This is a pen.", dst[256];
        func(dst, src, sizeof(src) + 1);

        cout << "in:  \"" << src << "\"" << endl;
        cout << "out: \"" << dst << "\"" << endl;

        freeMem(mem);
    }
    catch (const char* str)
    {
        cerr << str << endl;
        return -1;
    }
    return 0;
}

allocAndCopyIt関数

以前説明したものと同じです、処理についてはソースコード、あるいは以前の説明を参照してください。

freeMem関数

以前説明したものと同じです、処理についてはソースコード、あるいは以前の説明を参照してください。

main関数

本関数の処理を順次説明します。
unsigned char code[]が機械語を保持するunsigned charの配列です。

allocAndCopyIt関数を呼び出し、必要な実行可能メモリーを割り当て、そこへ機械語をコピーします。返却値はvoid*ですので、関数のシグネチャへ合わせたいためポインタの代入を行います。これによってfuncを通常の関数と同じように呼び出すことができます。
func関数に2つのchar*と長さを与え、バッファーの内容を比較します。

実行

機械語で作成した関数を呼び出し、結果を表示します。

 実行結果

C:\>cl /O2 /EHsc memcpy.cpp
...
C:\>.\memcpy
in: "This is a pen."
out: "This is a pen."


機械語

unsigned char code[]が機械語の定義を行っている部分です。これを実行可能なメモリーへ転送し、C++から直接制御を渡します。この例では、以下のアセンブリ言語で記述したソースコード機械語化したものです。

include ksamd64.inc

_TEXT   segment

        public asmCode
asmCode proc    frame
        ; prolog
        rex_push_reg rsi        ; push rsi
        push_reg rdi            ; push rdi
        .endprolog ;


        mov     rsi, rdx        ; source address
        mov     rdi, rcx        ; destination address
        mov     rcx, r8         ; length

        cld
        rep movsb               ; move it


        ; epilog
        pop rdi                 ; pop rdi
        pop rsi                 ; pop rsi

        ret
asmCode endp

_TEXT   ends
        end

機械語への変換はml64コマンドとdumpbin コマンドを使うと簡単に得られます。得られた16進数の値を以下に示します。コメントを参照すると何を行っているか分かるでしょう。

unsigned char code[] = {
    0x48, 0x56,         // push     rsi
    0x57,               // push     rdi
    0x48, 0x8B, 0xF2,   // mov      rsi,rdx
    0x48, 0x8B, 0xF9,   // mov      rdi,rcx
    0x49, 0x8B, 0xC8,   // mov      rcx,r8
    0xFC,               // cld
    0xF3, 0xA4,         // rep movs byte ptr [rdi],byte ptr [rsi]
    0x5F,               // pop      rdi
    0x5E,               // pop      rsi
    0xC3                // ret
};