C++で機械語を実行(バッファー比較, memcmp)

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

はじめに

本例は、バッファー比較を行うmemcmpと同じ動作を行う機械語を示します。以前の
arques.hatenablog.comを変更します。

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, 0xF1,       // mov         rsi,rcx
        0x48, 0x8B, 0xFA,       // mov         rdi,rdx
        0x49, 0x8B, 0xC8,       // mov         rcx,r8
        0xFC,                   // cld
        0xF3, 0xA6,             // repe cmps   byte ptr [rsi],byte ptr [rdi]
        0x75, 0x05,             // jne         0000000000000016
        0x48, 0x33, 0xC0,       // xor         rax,rax
        0xEB, 0x21,             // jmp         0000000000000037
        0x48, 0xFF, 0xCE,       // dec         rsi
        0x48, 0xFF, 0xCF,       // dec         rdi
        0x8A, 0x06,             // mov         al,byte ptr [rsi]
        0x2A, 0x07,             // sub         al,byte ptr [rdi]
        0xA9, 0x80, 0x00, 0x00, // test        eax,80h
        0x00, 0x75, 0x09,       // jne         0000000000000030
        0x48, 0xC7, 0xC0, 0x01, // mov         rax,1
        0x00, 0x00, 0x00,
        0xEB, 0x07,             // jmp         0000000000000037
        0x48, 0xC7, 0xC0, 0xFF, // mov         rax,0FFFFFFFFFFFFFFFFh
        0xFF, 0xFF, 0xFF,
        0x5F,                   // pop         rdi
        0x5E,                   // pop         rsi
        0xC3                    // ret
    };

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

        char src[] = "This is a pen.", dst[256];
        const size_t length = strlen(src) + 1;
        strcpy(dst, src);

        cout << "src: \"" << src << "\"" << endl;
        cout << "dst: \"" << dst << "\"" << endl;
        cout << "   asm: " << func(dst, src, length) << endl;   // asm
        cout << "   C++: " << memcmp(dst, src, length) << endl; // C++

        src[10] = 'z';
        cout << "src: \"" << src << "\"" << endl;
        cout << "dst: \"" << dst << "\"" << endl;
        cout << "   asm: " << func(dst, src, length) << endl;   // asm
        cout << "   C++: " << memcmp(dst, src, length) << endl; // C++

        src[10] = '0';
        cout << "src: \"" << src << "\"" << endl;
        cout << "dst: \"" << dst << "\"" << endl;
        cout << "   asm: " << func(dst, src, length) << endl;   // asm
        cout << "   C++: " << memcmp(dst, src, length) << endl; // C++

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

allocAndCopyIt関数

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

freeMem関数

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

main関数

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

allocAndCopyIt関数を呼び出し、必要な実行可能メモリーを割り当て、そこへ機械語をコピーします。返却値はvoid*ですので、関数のシグネチャへ合わせたいためポインタの代入を行います。これによってfuncを通常の関数と同じように呼び出すことができます。
char srcと同じ値をchar dst[]に設定します。そして、この二つのバッファアドレスと、比較するバイト数を引数にしてfunc関数を呼び出します。比較結果はint型で呼び出し元へ返されます。

実行

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

 実行結果

C:\>cl /O2 /EHsc memcmp.cpp
...
C:\>.\memcmp
src: "This is a pen."
dst: "This is a pen."
asm: 0
C++: 0
src: "This is a zen."
dst: "This is a pen."
asm: -1
C++: -1
src: "This is a 0en."
dst: "This is a pen."
asm: 1
C++: 1



機械語

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, rcx        ; str0 address
        mov     rdi, rdx        ; str1 address
        mov     rcx, r8         ; length

        cld
        repe    cmpsb
        jne     notequ          ; if not equal, goto notequ

        xor     rax, rax        ; equal all
        jmp     procend         ; set ret valiue = 0

notequ:                         ; memcmpと互換を持たせる処理
        dec     rsi
        dec     rdi
        mov     al, [rsi]
        sub     al, [rdi]
        test    eax, 80h
        jnz     minus
        mov     rax,1           ; set ret valiue = 1
        jmp     procend

minus:
        mov     rax, -1         ; set ret valiue = -1
procend:

        ; 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, 0xF1,       // mov         rsi,rcx
    0x48, 0x8B, 0xFA,       // mov         rdi,rdx
    0x49, 0x8B, 0xC8,       // mov         rcx,r8
    0xFC,                   // cld
    0xF3, 0xA6,             // repe cmps   byte ptr [rsi],byte ptr [rdi]
    0x75, 0x05,             // jne         0000000000000016
    0x48, 0x33, 0xC0,       // xor         rax,rax
    0xEB, 0x21,             // jmp         0000000000000037
    0x48, 0xFF, 0xCE,       // dec         rsi
    0x48, 0xFF, 0xCF,       // dec         rdi
    0x8A, 0x06,             // mov         al,byte ptr [rsi]
    0x2A, 0x07,             // sub         al,byte ptr [rdi]
    0xA9, 0x80, 0x00, 0x00, // test        eax,80h
    0x00, 0x75, 0x09,       // jne         0000000000000030
    0x48, 0xC7, 0xC0, 0x01, // mov         rax,1
    0x00, 0x00, 0x00,
    0xEB, 0x07,             // jmp         0000000000000037
    0x48, 0xC7, 0xC0, 0xFF, // mov         rax,0FFFFFFFFFFFFFFFFh
    0xFF, 0xFF, 0xFF,
    0x5F,                   // pop         rdi
    0x5E,                   // pop         rsi
    0xC3                    // ret
};