アセンブラ入門(弊研究室の某課題について考える一日目)
はじめに
弊研究室の某課題について考えるの一日目の記事です。
新しく入ってくる方たちになにとぞ覚えていただきたいのがBoFとかPwnable系の知識だと思うのでそのための入門編のアセンブラ入門です。
なのでx86, x64系の説明になります。
お品書き
アセンブラについて
計算機は機械語(0と1のコード)を実行するがそれを人間にわかりやすくした言語です。大学3年生ならそこらへんはわかってるよねってことで割愛
(注) アセンブラを人に説明する際に
アセンブラを進捗報告で取り扱う際は何のアセンブラなのかを明確にしましょう。アーキテクチャが何なのか、記法は何を使用しているのかを必ず示しましょう。
記法について
x86系のアセンブラにはIntel記法とAT&T記法があります。この記事では基本的にintel記法で説明していきます。
違いはこんな感じ
Intel記法
<命令> <命令先> <命令元>
AT&T記法
<命令> <命令元> <命令先>
レジスタ
CPUにはデータを操作するための変数のようなレジスタと呼ばれるデータを格納する場所がいくつか用意されています。レジスタはCPUの中にあらかじめ決められた個数だけ用意されていて名前とその用途も決まっています。
後のアセンブラを読んでみようでわからなくなったらここに戻ってきてください。
であとで気づいたんですが32bitでコンパイルしたのでこの記事のレジスタはeがrになってることがあります。ごめんね。
レジスタの名前 | 用途 |
---|---|
EAX | 算術計算、返り値の格納 |
EBX | 算術計算に使用される |
ECX | ループ等の回数を数えるのに使用される |
EDX | 算術計算、データI/Oに使用される |
ESP | スタックのトップを指し示す |
EBP | スタックのベースを指し示す |
ESI | 書き込み先アドレスを指し示す |
EDI | 読み込み先アドレスを指し示す |
EIP | 次に実行するアドレスが入ります |
アセンブラの形式を知る
アセンブラには記述方式があります。まずは下を見てください
最初に関数のアドレスが存在して、関数が始まるとインデントが下がり、関数の終わりまでそのインデントで記述されています。
次の行からが関数の内容で、左からその命令のアドレス、機械語のバイナリ、アセンブラのニーモニックと並びます。
16進の機械語は人間にはわからないので、人間にも多少わかりやすくしたものをニーモニックと呼びます。アセンブラとニーモニックはこんな関係です。
アセンブラと書いてあってもニーモニックのことを指している場合もあるので、「ニーモニック」という単語があるんだという認識でいいと思います。
アセンブラ命令を覚える
簡単なアセンブラコード(とC言語のコード)を見て覚えましょう。
objdumpコマンドを使うことでアセンブラを見ることができます。
$ objdump -d ./a.out
//C言語ソース #include<stdio.h> int main(void){ int a = 1; int b = 2; int c = a + b; printf("%d\n", c); return 0; } //アセンブラのmain部 000000000000064a <main>: 64a: 55 push rbp 64b: 48 89 e5 mov rbp,rsp 64e: 48 83 ec 10 sub rsp,0x10 652: c7 45 f4 01 00 00 00 mov DWORD PTR [rbp-0xc],0x1 659: c7 45 f8 02 00 00 00 mov DWORD PTR [rbp-0x8],0x2 660: 8b 55 f4 mov edx,DWORD PTR [rbp-0xc] 663: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] 666: 01 d0 add eax,edx 668: 89 45 fc mov DWORD PTR [rbp-0x4],eax 66b: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 66e: 89 c6 mov esi,eax 670: 48 8d 3d 9d 00 00 00 lea rdi,[rip+0x9d] # 714 <_IO_stdin_used+0x4> 677: b8 00 00 00 00 mov eax,0x0 67c: e8 af fe ff ff call 530 <printf@plt> 681: b8 00 00 00 00 mov eax,0x0 686: c9 leave 687: c3 ret 688: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0] 68f: 00
C言語のコードとアセンブラのコード
mov
mov eax ebx
modはsourceをdistにコピーします。mov eax ebx
ならebxの値をeaxにコピーします。
add
mov eax, 0x5 mov ebx, 0x3 add eax, ebx
add
は名前の通り加算します。add eax ebx
の時、eax
の値とebx
の値を足した結果をeax
に格納します。
eax
にebx
の値を足すという感じです。引き算も同じ動作になります
sub
sub
は引き算です。足し算と同じですね。
mov eax, 0x5 mov ebx, 0x3 sub eax, ebx
push
push ebp
push
はスタックに値をpushします。そのままです
pop
pop ebp
ついでにpop
です。スタックの値をebp
に格納します。
push、popは明日の記事で重要な要素になってきます
DWORD PTR
これが多分初心者が戸惑うものだと思います。DWORD PTRは対象の値(レジスタ)を何バイトとして扱うかということを意味します。
mov DWORD PTR [rbp-0x8],0x2
という文がありますが、rbp
のアドレスから8引いた値からDWORD(4バイト)分の場所に0x2をコピーしています。
メモリに値を格納する際、そのメモリアドレスの何バイト分に値を格納するのかがわかりません。ゆえにDWORD PTRで指定しているのです。
そういったものはDWORDの他にもあります。
- DWORD : 4バイト
- WORD : 2バイト
- BYTE : 1バイト
WORD PTR <addr>
ならaddr
から2バイト分ってことになります。
他の命令について
大体読み方分かったと思うので、わからない命令が出てきたらその都度調べればなんとかなるでしょう
ココとか
アセンブラを読んでみよう
さてでは先ほどのアセンブラを読んでみましょう。下の図と合わせてみるとわかりやすいはず
最初の3行は関数の時に必ずある処理なので次の回で話します。
次の2行ではどうやらメモリに0x1
と0x2
をmov
命令を使ってコピーしています。どうやらint a = 1; int b = 2;
っぽいです。
次の4行の3行目にはadd命令があります。そうみてみるとその前の2行ではa
とb
の値をレジスタにコピーして、4行目で加算命令をした後にメモリにmov
命令を書き込んでいるのでint c = a + b;
ですね。
次の塊ではprintf関数を読んでいます。なのでちょっと面倒です(次回詳しく話すところです)、呼んでるなぁってことが分かればいいので飛ばそうと思いましたが説明しますね。飛ばしたい人は飛ばしてどうぞ
最後にreturn命令ですね。ねアセンブラ意外と読めるでしょ。
気になる人向けのprintfのブロック
66eアドレスでmov esi, eax
とありますが、その前の行でeax
にはc
の値が入っています。なのでesi
レジスタにはprintfの第二引数を入れておきます。
670アドレスでmov rdi, [rip+0x9d]
とありますがprintfの第一引数はrdi
レジスタに入ります。[rip+0x9d]
に"%d\n"
があるってことですね。
これでprintfを呼ぶことによってc
の値が表示されるってことになります。
関数を呼ぶときは引数を規則に従ってレジスタに叩き入れます。参照
レジスタ | 引数 |
---|---|
edi | 第一引数 |
esi | 第二引数 |
edx | 第三引数 |
ecx | 第四引数 |
最後に
この記事読んでアセンブラってそんなに難しそうじゃないなと感じていただければ幸い、
明日は関数の仕組み系統について書きます。力尽きなければ
参考文献
- 熱血アセンブラ入門
- 楽しいバイナリの歩き方
- セキュリティコンテストプログラミングブック
- Binary Hacks
- HACKING 美しき策謀