C++で機械語を実行(文字列比較, strcmp)

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

はじめに

本例は、文字列比較を行うstrcmpと同じ動作を行う機械語を示します。直前の
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
        0xFC,                   // cld
        0x8A, 0x06,             // mov  al,byte ptr [rsi]
        0x84, 0x07,             // test byte ptr [rdi],al
        0x74, 0x1D,             // je   02D
        0xA6,                   // cmps byte ptr [rsi],byte ptr [rdi]
        0x74, 0xF7,             // je   00A
        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,       // test eax,80h
        0x00, 0x00,
        0x75, 0x0E,             // jne  032
        0x48, 0xC7, 0xC0, 0x01, // mov  rax,1
        0x00, 0x00, 0x00,
        0xEB, 0x0C,             // jmp  039
        0x48, 0x33, 0xC0,       // xor  rax,rax
        0xEB, 0x07,             // jmp  039
        0x48, 0xC7, 0xC0, 0xFF, // mov  rax,-1
        0xFF, 0xFF, 0xFF,
        0x5F,                   // pop  rdi
        0x5E,                   // pop  rsi
        0xC3                    // ret
    };

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

        char src[] = "This is a pen.", dst[256];
        strcpy(dst, src);

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

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

        src[10] = '0';
        cout << "src: \"" << src << "\"" << endl;
        cout << "dst: \"" << dst << "\"" << endl;
        cout << "   asm: " << func(dst, src) << endl;   // asm
        cout << "   C++: " << strcmp(dst, src) << 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を通常の関数と同じように呼び出すことができます。
func関数に2つの文字列を与え、その値を比較した結果を得ます。

実行

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

 実行結果

C:\>cl /O2 /EHsc strcmp.cpp
...
C:\>.\strcmp
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        ; rsi = rcx str0
        mov     rdi, rdx        ; rdi = rdx str1

        cld
loop1:
        mov     al, [rsi]       ; load [rsi]
        test    al, [rdi]       ; al or byte [rdi] == null
        jz      equend          ;   if equal, goto equend

        cmpsb                   ; compare [rdi], [rsi] and then inc rdi, rsi
        je      loop1           ; if equal, goto loop1

        dec     rsi             ; if not equal
        dec     rdi             ;  dec rdi
strend:                         ; set ret valiue
        mov     al, [rsi]       ; byte [rdi]をalに代入
        sub     al, [rdi]
        test    eax, 80h
        jnz     minus
        mov     rax,1           ; set ret valiue = 1
        jmp     procend

equend:
        xor     rax, rax
        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
    0xFC,                   // cld
    0x8A, 0x06,             // mov  al,byte ptr [rsi]
    0x84, 0x07,             // test byte ptr [rdi],al
    0x74, 0x1D,             // je   02D
    0xA6,                   // cmps byte ptr [rsi],byte ptr [rdi]
    0x74, 0xF7,             // je   00A
    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,       // test eax,80h
    0x00, 0x00,
    0x75, 0x0E,             // jne  032
    0x48, 0xC7, 0xC0, 0x01, // mov  rax,1
    0x00, 0x00, 0x00,
    0xEB, 0x0C,             // jmp  039
    0x48, 0x33, 0xC0,       // xor  rax,rax
    0xEB, 0x07,             // jmp  039
    0x48, 0xC7, 0xC0, 0xFF, // mov  rax,-1
    0xFF, 0xFF, 0xFF,
    0x5F,                   // pop  rdi
    0x5E,                   // pop  rsi
    0xC3                    // ret
};