クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

x86-64 アセンブリの読み方

このレッスンのあとで: x86-64 のアセンブリの数行を読んで、何をするか言える。

55分のレッスンコマンド5件リバースエンジニアリングのラボ:gdb、radare2、strace、ltrace でプログラムを読む

アセンブリは命令を1行ずつ書く

アセンブリとは、CPU が実行する命令を、人が読める短い言葉で書いたものです。1行が1つの命令で、読み方の型は数えるほどしかありません。

この読み方が分かれば、ソースのないプログラムでも、どこで何を比べているかが見えてきます。

このレッスンのゴール

メモ

x86-64 のアセンブリの数行を読んで、何をするか言える。

重要な言葉

ことばいみれい
レジスタCPU の中にある、小さな入れ物eax
命令CPU への1つの指示add eax, 5
オペランド命令が使う値や入れ物
フラグ直前の比較の結果を残す、CPU の印
条件ジャンプフラグを見て、別の行へ飛ぶ命令jne
戻り値関数が呼んだ側に返す値。普通は rax に入る

学生が入力するセルは、このプレビューでは空白です。

レジスタは CPU の中の入れ物

計算の途中の値は、メモリでなく、レジスタに置かれます。x86-64 のレジスタは64ビットで、名前は rax のように r で始まります。

下位の32ビットだけを使うときは、eax のように e で始めて呼びます。32ビットの整数は、ほとんど e の名前で現れます。

よく見るレジスタの役目

レジスタ役目
rdi、rsi関数の1番目と2番目の引数
rax、eax計算と、関数の戻り値
rspスタックの先頭の位置
rbpいまの関数の土台の位置
rip次に実行する命令の位置

学生が入力するセルは、このプレビューでは空白です。

mov、add、cmp、jne が基本

命令は、名前と、オペランドからできています。Intel 形式では、左が行き先で、右が元です。mov eax, 5 は、eax に 5 を入れます。

cmp は2つを比べて、結果をフラグに残します。jne は、等しくなかったときだけ、別の行へ飛びます。

基本の命令と意味

命令意味
mov eax, 5eax に 5 を入れる
add eax, 3eax に 3 を足す
cmp eax, 12eax と 12 を比べ、結果をフラグに残す
jne 行き先等しくなければ、行き先へ飛ぶ
call 関数関数を呼ぶ
ret関数から戻る

学生が入力するセルは、このプレビューでは空白です。

20行足らずの小さな CPU

コード: python
# 命令を1行ずつ実行する、小さな CPU。
eax = 0
program = ["mov eax, 5", "add eax, 3", "add eax, 4"]
for line in program:
    words = line.replace(",", "").split(" ")
    op = words[0]
    number = int(words[2])
    if op == "mov":
        eax = number
    elif op == "add":
        eax = eax + number
    print(line, "のあと eax は", eax)
# 最後の行を "add eax, 10" に変えて、もう一度実行しましょう。

実物の step を読む

次の練習用プログラムには、step という関数があります。引数に 5 を足し、12 と比べて、等しければ 1、違えば 0 を返します。

ソースを作ってバイナリにし、ソースを消してから、step の命令だけを読みます。

ソースを書く

コマンド
printf '%s\n' '#include <stdio.h>' '#include <stdlib.h>' 'static int step(int a) {' '    int b = a + 5;' '    if (b == 12) return 1;' '    return 0;' '}' 'int main(int c, char **v) {' '    if (c == 2) {' '        if (step(atoi(v[1]))) { puts("OK"); return 0; }' '    }' '    puts("NG");' '    return 1;' '}' > /tmp/rev2.c

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

プログラムを作る

コマンド
gcc -O0 -no-pie -o /tmp/rev2 /tmp/rev2.c

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

ソースを消す

コマンド
rm /tmp/rev2.c

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

step の命令を読む

コマンド
objdump -d -M intel --no-show-raw-insn /tmp/rev2 --disassemble=step | grep "^ " | cut -f2

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

step を1行ずつ読む

命令意味
push rbp、mov rbp, rsp関数の始まりの決まり文句
mov DWORD PTR [rbp-0x14], edi引数 a をメモリにしまう
mov eax, DWORD PTR [rbp-0x14]a を eax に取り出す
add eax, 0x5eax に 5 を足す
mov DWORD PTR [rbp-0x4], eax結果を b にしまう
cmp DWORD PTR [rbp-0x4], 0xcb を 12 と比べる
jne違えば mov eax, 0x0 へ飛ぶ
mov eax, 0x1、jmp等しければ 1 を返す準備をして終わりへ
mov eax, 0x0、pop rbp、ret違えば 0 を返して戻る

学生が入力するセルは、このプレビューでは空白です。

書き方は2通りある

objdump は、何も指定しないと AT&T 形式で出します。そこでは、元が左で、行き先が右です。数には $ が、レジスタには % がつきます。

このモジュールは、-M intel の Intel 形式で読みます。下の Run は、同じ add と cmp を AT&T 形式で見せます。

AT&T 形式で読む

コマンド
objdump -d --no-show-raw-insn /tmp/rev2 --disassemble=step | grep -E "add|cmp" | cut -f2

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

よくある間違い

注意

cmp は、左の値を書き換えると思いがちです。実際は、引き算の答えを捨てて、フラグだけを残します。eax は変わりません。その結果を読むのは、直後の jne のような条件ジャンプです。

このラボとソースについて: リバースエンジニアリングのラボ:gdb、radare2、strace、ltrace でプログラムを読む

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。