コンパイラ出力から学ぶアセンブリ言語

Twitterアセンブリ言語の勉強の仕方が分からないという投稿を見かけたので...
11年ぐらい前の学習方法を振り返ります。

デバッガによるx86プログラム解析入門

デバッガによるx86プログラム解析入門

最初の最初の基礎固めはこちらの書籍からでした。
ただ書籍をさらっと読んだだけでは正直なところちんぷんかんぷんでしたね。

そこで試してみたのが自分で簡単なプログラムを書いてコンパイルした出力からアセンブリ言語を読み取り比較するというものでした。

当時通っていた高専ではプログラミングの講義でBorland C++ Compiler 5.5とBCpadというエディタを活用していました。

そのため、BC++の出力された実行ファイルをOllyDbgというデバッガ上で動作させて、C言語で記述されたコードとアセンブリ言語の出力を比較したりデバッガでステップ実行させて動作を確認するなどしていました。今思えば、BC++コンパイラの出力は比較的シンプル(だと思っているの)で学習に適していたのでは…!

まずはデバッガでメイン関数まで到達するチュートリアルです。

#include <stdio.h>

int main()
{
    printf("hello, world\n");
    return 0;
}

以下はBC++コンパイラで進めていきますが、お好みのコンパイラでかまいません。おなじみのC言語ハローワールドサンプルをコンパイルして、生成された実行ファイルをOllyDbgに放り込み、ステップオーバー実行(F8)で進んでいきます。

f:id:i53:20180623155404p:plain

00407C8B : CALL DWORD PTR DS:[ESI+18] の命令を実行した直後に、Hello,world! が出力されましたね。ここがメイン関数呼び出しになっています。

f:id:i53:20180623160937p:plain

Restart(Ctrl + F2)して00407C8B: CALL DWORD PTR DS:[ESI+18] までステップオーバー実行したら今度は、ステップイン実行(F7)してメイン関数の中に潜っていきます。

00401150  /. 55             PUSH EBP
00401151  |. 8BEC           MOV EBP,ESP
00401153  |. 68 28A14000    PUSH sample.0040A128      ; /Arg1 = 0040A128 ASCII "Hello, world!"
00401158  |. E8 0B270000    CALL sample.00403868      ; \sample.00403868
0040115D  |. 59             POP ECX
0040115E  |. 33C0           XOR EAX,EAX
00401160  |. 5D             POP EBP
00401161  \. C3             RETN

Hello Worldサンプルのメイン関数の実態はたったのこれだけです。
ここで何が行われているのかは今回は無視しておきます。
大事なのは、メイン関数の中身がここで確認できているということだけ。

#include <stdio.h>

int main()
{
    int i;
    for (i = 1; i <= 100; i++) {
        if (i % 3 == 0 && i % 5 == 0) {
            printf("FizzBuzz\n");
        } else if (i % 3 == 0) {
            printf("Fizz\n");
        } else if (i % 5 == 0) {
            printf("Buzz\n");
        } else {
            printf("%d\n", i);
        }
    }
    return 0;
}

お次はFizzBuzzサンプルをBC++でコンパイルし、生成された実行ファイルをOllyDbgに放り込み、ステップオーバー実行(F8)で進んでいきます。

00401150  /. 55             PUSH EBP
00401151  |. 8BEC           MOV EBP,ESP
00401153  |. 53             PUSH EBX
00401154  |. 56             PUSH ESI
00401155  |. BE 28A14000    MOV ESI,sample.0040A128                           ;  ASCII "FizzBuzz"
0040115A  |. BB 01000000    MOV EBX,1
0040115F  |> 8BC3           /MOV EAX,EBX
00401161  |. B9 03000000    |MOV ECX,3
00401166  |. 99             |CDQ
00401167  |. F7F9           |IDIV ECX
00401169  |. 85D2           |TEST EDX,EDX
0040116B  |. 75 17          |JNZ SHORT sample.00401184
0040116D  |. 8BC3           |MOV EAX,EBX
0040116F  |. B9 05000000    |MOV ECX,5
00401174  |. 99             |CDQ
00401175  |. F7F9           |IDIV ECX
00401177  |. 85D2           |TEST EDX,EDX
00401179  |. 75 09          |JNZ SHORT sample.00401184
0040117B  |. 56             |PUSH ESI                                         ; /Arg1
0040117C  |. E8 57270000    |CALL sample.004038D8                             ; \sample.004038D8
00401181  |. 59             |POP ECX
00401182  |. EB 41          |JMP SHORT sample.004011C5
00401184  |> 8BC3           |MOV EAX,EBX
00401186  |. B9 03000000    |MOV ECX,3
0040118B  |. 99             |CDQ
0040118C  |. F7F9           |IDIV ECX
0040118E  |. 85D2           |TEST EDX,EDX
00401190  |. 75 0C          |JNZ SHORT sample.0040119E
00401192  |. 8D46 0A        |LEA EAX,DWORD PTR DS:[ESI+A]                     ;  ASCII "Fizz"
00401195  |. 50             |PUSH EAX                                         ; /Arg1
00401196  |. E8 3D270000    |CALL sample.004038D8                             ; \sample.004038D8
0040119B  |. 59             |POP ECX
0040119C  |. EB 27          |JMP SHORT sample.004011C5
0040119E  |> 8BC3           |MOV EAX,EBX
004011A0  |. B9 05000000    |MOV ECX,5
004011A5  |. 99             |CDQ
004011A6  |. F7F9           |IDIV ECX
004011A8  |. 85D2           |TEST EDX,EDX
004011AA  |. 75 0C          |JNZ SHORT sample.004011B8
004011AC  |. 8D46 10        |LEA EAX,DWORD PTR DS:[ESI+10]                    ;  ASCII "Buzz"
004011AF  |. 50             |PUSH EAX                                         ; /Arg1
004011B0  |. E8 23270000    |CALL sample.004038D8                             ; \sample.004038D8
004011B5  |. 59             |POP ECX
004011B6  |. EB 0D          |JMP SHORT sample.004011C5
004011B8  |> 53             |PUSH EBX                                         ; /Arg2
004011B9  |. 8D56 16        |LEA EDX,DWORD PTR DS:[ESI+16]                    ;  ASCII "%d"
004011BC  |. 52             |PUSH EDX                                         ; |Arg1
004011BD  |. E8 16270000    |CALL sample.004038D8                             ; \sample.004038D8
004011C2  |. 83C4 08        |ADD ESP,8
004011C5  |> 43             |INC EBX
004011C6  |. 83FB 64        |CMP EBX,64
004011C9  |.^7E 94          \JLE SHORT sample.0040115F
004011CB  |. 33C0           XOR EAX,EAX
004011CD  |. 5E             POP ESI
004011CE  |. 5B             POP EBX
004011CF  |. 5D             POP EBP
004011D0  \. C3             RETN

メイン関数の中身を抜き出してきました。
細かな解説はしませんが、この中からでも学び取れることは非常に多いです。

  • forループに該当する箇所はどこでしょうか?
    • 変数i を担っているのはEBXレジスタです。
    • EBXレジスタをインクリメントした後、CMP命令で0x64と比較して、その後JLE(Jump if less or equal)命令で分岐しています。
  • 除算の結果が割り切れているかどうかどのように判定していますか?
    • IDIV(符号付き除算)命令で、除数・被除数・商や剰余はどこに格納されていますか?
    • TEXT EDX, EDX という命令はなにを判定しているのでしょうか?

f:id:i53:20180623223009p:plain
デバッガ上で動作させているので、この後なにが起こるのかをステップ実行で進めて確認することもできるのでオススメです。
慣れたらフラグやスタックの動きも確認しましょう。

gcc.godbolt.org
ちなみに、こんな便利なものもあります。

最近ではゲームハッキングなどの邪な動機付けに限らず、マルウェア静的解析(リバースエンジニアリング)、セキュリティ教育や競技(CTF)のためにアセンブリ言語を学習したいと考える学生も増えてきているのかなと思います。

お好みのコンパイラコンパイルして逆アセンブルして、ステップ実行で処理を追ってみて、分析してみて...少しでも楽しいなあ、面白いなと思えるのであれば、あなたには(変態の)素質があります...w

命令セットや頻出パターンに慣れたら次のステップへ移行するのは容易かと思います。
ぜひぜひお試しください。