ハンド (逆) アセンブルのための x86 ニーモニックの覚え方

バイナリ列を見て x86 のコードかな〜とニヨニヨできる人に、x86 のコードであること、だけじゃなく実際のコード列も読めるようになってほしい!そんな願いから、今回は hex dump のバイト列を見つめてハンド逆アセンブルできるようになるための、効率良い覚え方を紹介します。
今回は、32-bit x86 について解説するよ。

まとめて覚えておきたい 8 つの命令、add, sub, adc, sbb, and, or, xor, cmp (00h〜3Dh)

これらの命令は近い所に配置されていて、しかも命令のルールがほとんど同じです。
つまり、ほとんど同じように覚えることができるのです。opcode map 上では次の領域が相当します。

0 1 2 3 4 5 6 7 8 9 A B C D E F
0 ←add ←or
1 ←adc ←sbb
2 ←and ←sub
3 ←xor ←cmp
4

並びの覚え方ですが… xor と cmp に関してはこのまま覚えましょう。その他については…

  • 00h〜 と 08h〜: ビットや値を (足す) 感じの操作 (add と or)
  • 10h〜 と 18h〜: キャリーを使った加減算 (adc と sbb)
  • 20h〜 と 28h〜: ビットや値を (引く) 感じの操作 (and と sub)
    • ここでは sub が後に来ていることに注意。

さて、ここでは add 命令 (00h〜05h) について詳しく見てみましょう。これが分かれば、他の 7 つの命令もおのずと理解できるはずです。

00h, 02h: レジスタやメモリに対する操作 (バイト)
命令フォーマット:
[00h|02h] [Mod R/M] ([SIB]) ([Address Offset])

00h と 02h の違いはデータがどちらに流れるかというものです。

00h: add reg/mem ← reg
02h: add reg/mem → reg

x86 の基本的な (Mod R/M を使う) 命令では、メモリオペランドは片方につけることしかできません。そのため、メモリをソースとして扱うかデスティネーションとして扱うかでオペコードが違うということになってきます。
ちなみに、レジスタ同士の場合 00h, 02h のどちらを使っても良いので、次の 2 つのバイト列は同じ操作を意味します。

00 c8: add al ← cl (add al, cl)
02 c1: add cl → al (add al, cl)

Mod R/M バイトは全部暗記しておくと読むのが楽になるけど、シェルコードの多くを読むだけであれば、Mod R/M バイトが C0h〜FFh ならレジスタ同士の操作 (SIB バイトやオフセットはつかない)、とだけ覚えておくと扱いやすいです。

01h, 03h: レジスタやメモリに対する操作 (ワード)

00h, 02h と基本は似ています。ただし、基本的にはワード (32-bit x86 の通常モードでは 32-bit) を扱います。ただし、プレフィックス 66h がオペコードの前についている場合、16-bit 操作になります。(16-bit モードの場合には、66h プレフィックスで 32-bit 操作になる)

命令フォーマット:
[01h|03h] [Mod R/M] ([SIB]) ([Address Offset])

もちろん、メモリ操作の方向も似ています。

01h: add reg/mem ← reg
03h: add reg/mem → reg

というわけで、レジスタ同士の演算では 01h, 03h で同じ操作の別バイト列が作れます。

01 c8: add eax ← ecx (add eax, ecx)
03 c1: add ecx → eax (add eax, ecx)
04h, 05h: eax レジスタと定数に対する操作
命令フォーマット:
[04h] [定数 (1-byte)]
[05h] [定数 (4-byte または 2-byte)]

これは、eax レジスタ (あるいは al レジスタ) と定数とで操作を行うための命令です。05h の方はプレフィックス 66h で 16-bit 操作になります。

04 0a:          add al,  10       (0x0a)
66 05 e8 03:    add ax,  1000     (0x03e8)
05 40 42 0f 00: add eax, 1000000  (0x000f4240)

もっと大きな命令グループ (レジスタ)

x86レジスタは 3 ビットの数値で表現されます。その数値が命令の 1 バイト目に含まれる命令グループを紹介しましょう。

AL eAX 0
CL eCX 1
DL eDX 2
BL eBX 3
AH eSP 4
CH eBP 5
DH eSI 6
BH eDI 7
0 1 2 3 4 5 6 7 8 9 A B C D E F
4
5
9
B
  • 40h-47h: INC reg
  • 48h-4Fh: DEC reg
    • INC と逆にレジスタから 1 を引く命令です。
  • 50h-57h: PUSH reg
    • ワードレジスタをスタックにプッシュする命令です。
  • 58h-5Fh: POP reg
    • PUSH と逆に、ワードレジスタをスタックからポップした値で置き換える命令です。
  • 90h-97h: XCHG eAX, reg
    • AX もしくは EAX レジスタと、指定のワードレジスタの内容を置換する命令です。90h (XCHG eAX, eAX) は NOP としても有名ですね。
  • B0h-B7h: MOV reg8, imm8
    • 指定のレジスタに定数を入れる際に用いられます。
  • B8h-BFh: MOV reg, imm
    • これも B0h-B7h と同じく頻繁に用いられます。ワード (16/32 ビット) の定数をレジスタに代入するというところだけが異なります。ちなみに 32-bit では関係ありませんが、64-bit ではほとんどの命令で 64-bit 即値を使うことができません。しかし、64-bit サイズの REX プレフィックスをつけたこの命令は 64-bit 即値をレジスタに即代入することができます。

条件分岐 (70h-7Fh)

0 1 2 3 4 5 6 7 8 9 A B C D E F
7

これには簡単な覚え方は…なさそうです。ただひとつだけ覚えておきたいのは、ある条件とその否定はかならず隣り合っていることです。つまり、short 条件分岐の 1 バイト目、最下位ビットを反転すると、条件が反対になるということです。

72 xx:  JE short XX
 ↑
 ↓
73 xx:  JNE short XX

…記事の書き方が雑になってないか?

気のせいではないです、今日中に公開することを急いだ結果がこれだよ! そのうちちゃんと書き足すです。

ハンド (逆) アセンブルを補助するための PDF リスト!

今回はこれを簡単に実現するために、手軽に印刷して参照できる PDF を作ってみました。
http://dl.dropbox.com/u/2476414/TechResources/x86_opcodemap_1_a4.pdf
http://dl.dropbox.com/u/2476414/TechResources/x86_opcodemap_1_b4.pdf
余白の大きい B4 版をオススメします。手元の紙に手軽に印刷できるよう一応 A4 版も作りましたが…ぶっちゃけ見づらいです。

続きは何書く?

Mod R/M バイトについてほとんど何も解説せずに記事を書き終えてしまった (しかもオペコード表だけだと分からない) ので、次回このシリーズを書くとしたら、 Mod R/M バイトと SIB バイトについて、あと Mod R/M を覚えないと書けない単項 (Unary) 命令についても解説してみようと思います。