アドレッシングモード
アドレッシング
オペレータの動作対象であるオペランドには、いくつかのアクセス方法があります。 このアクセス方法をアドレッシング(addressing)といいます。アドレッシングモード
アドレッシングの組み合わせをアドレッシングモード(addressing mode)と いいます。組み合わせることができないアドレッシングもあります。 x86の機械語ではメモリからメモリへの転送はできません。$ cat addressing.s
~(略)~
mov $message, %ebx ← 即値,レジスタ
mov $0x6c6c6f44, %eax ← 即値,レジスタ
mov %eax, (message) ← レジスタ,直接メモリ
mov $0x616d2061, %eax ← 即値,レジスタ
mov %eax, (message + 4) ← レジスタ,直接オフセット
mov $0x6d20656b, %eax ← 即値,レジスタ
mov %ebx, %edi ← レジスタ,レジスタ
add $8, %edi ← 即値,レジスタ
mov %eax, (%edi) ← レジスタ,レジスタ間接
mov $0x6f682065, %eax ← 即値,レジスタ
mov $12, %edi ← 即値,レジスタ
mov %eax, (%ebx, %edi, 1) ← レジスタ,ベースインデックス
mov $0x21616c6c, %eax ← 即値,レジスタ
mov %eax, 4(%ebx, %edi, 1) ← レジスタ,ベースインデックス
~(略)~
C言語
C言語
C言語(C programming language)は、1972年にデニス・リッチー(Dennis MacAlistair Ritchie)氏が中心になって開発された、プログラミング言語です。アセンブリ言語よりもわかりやすい文法でプログラムを記述できるため、汎用的なプログラミング言語として、現在でも多く使われています。C++やJava言語などの多くのプログラミング言語は、C言語の文法を踏襲しています。
コンパイル
C言語はコンパイル型言語です。C言語の文法に則って記述したソースファイルを、 Cコンパイラでコンパイル(compile)することで機械語のオブジェクトファイルに 変換して、ライブラリファイルとリンクすることで実行形式ファイルを作成します。 Cコンパイラとしては、gccやclangがよく使われます。関数
C言語のプログラムでは関数(function)を定義します。関数が他の関数を呼び出 すことで、プログラムは動作します。あらかじめ作成されているライブラリ関数を 利用することもできます。プログラムはmain関数から開始されます。引数と戻り値
関数は入力を受け取り、なんらかの処理を行って、結果を返します。入力のことを 引数やパラメータ、結果のことを戻り値や返り値といいます。引数は0個以上、 戻り値は0個か1個です。C言語のプログラム
C言語のプログラムフリーフォーマットなので、空白や改行を適当に挿入して、 読みやすく記述することができます。ソースファイルの拡張子には .c を用います。$ cat hello.c
#include <stdio.h> ← printfライブラリ関数を使用するために必要
int main() ← main関数から始まる
{
printf(“hello, world¥n”); ← printfライブラリ関数は標準出力に出力する
return 0; ← プログラムの終了ステータスは0
}
コンパイル
コンパイルオプションに –S を指定すると、アセンブリ言語のソースファイルを生成 します。アセンブリ言語のソースファイルの拡張子は .s です。$ gcc –S hello.c
$ ls –l hello.s
-rw-rw-r-- 1 user user 655 5月 1 02:17 hello.s
コンパイルオプションに –c を指定すると、コンパイルのみ実行して、オブジェクトファイルを生成します。オブジェクトファイルの拡張子は .o です
$ gcc –c hello.c
$ ls –l hello.o
-rw-rw-r-- 1 user user 1072 5月 1 02:23 hello.o
リンク
オブジェクトファイルをライブラリファイルとリンクすることで、実行形式ファイル を作成します。コンパイルオプションに –S や –c を指定しないと、コンパイルした 後にリンクまで実行されます。-o オプションで実行形式ファイルの名前を指定します。 ※ 実行形式ファイルの名前を指定しないとデフォルトでa.outになります。$ gcc –o hello hello.c
$ ls –l hello
-rwxrwxr-x 1 user user 7348 5月 1 02:50 hello
$ ./hello
hello, world
C言語プログラムの解析
ソースコード
バイナリ解析では、ソースコードを利用できることはほとんどありません。バイナリ 解析することによってソースコードを推測します。$ cat parameter.c ← 通常はソースファイルは存在しないが参考のために
#include <stdio.h>
char *msgfmt = "result is %d.¥n";
int func(int a, int b) ← func関数はaとbの2つの引数を受け取る
{
return a + b; ← 引数aとbを加算した値を返す
}
int main()
{
int x = 1; ← ローカル変数xに初期値1を代入
int y = 2; ← ローカル変数yに初期値2を代入
int c; ← ローカル変数cを宣言
c = func(x, y); ← 引数に1と2を設定してfunc関数を呼び出す
printf(msgfmt, c); ← func関数の戻り値を格納した変数cを表示する
return 0;
}
シンボル
関数名や変数名は、実行形式ファイルのシンボルテーブルに、シンボル(symbol) として記録されています。シンボルはgdbでデバッグをする時に、アドレスや値の代 わりに指定することができます。※ シンボルはプログラムの実行には不要なので、通常は削除されています。
$ LANG=C readelf –h parameter
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x8048310 ← _startの値と同じ
Start of program headers: 52 (bytes into file)
~(略)~
デバッグの開始
gdbを起動したら、main関数にブレークポイントを設定してrunコマンドで実行しま す。start(sta)コマンドを使用すると、main関数に一時的なブレークポイントが設定さ
れて実行されます。
$ gdb –q parameter
Reading symbols from parameter...(no debugging symbols found)...done.
(gdb) sta
Temporary breakpoint 1 at 0x8048426 ← 一時的ブレークポイントが設定される
Starting program: /home/user/parameter ← 実行(run)される
Temporary breakpoint 1, 0x08048426 in main () ← 一時的ブレークポイントで停止する
(gdb) i b
No breakpoints or watchpoints. ← ブレークポイントは1つもない
逆アセンブル
disassemble(disas)コマンドで逆アセンブルすると、どこで停止しているかが分か ります。ステップ実行、レジスタやメモリ内容の確認や変更、任意のアドレスへジャ ンプなどすることで、解析を進めていきます。(gdb) disas
Dump of assembler code for function main:
0x08048418 <+0>: lea 0x4(%esp),%ecx ← シンボルmainのアドレス
0x0804841c <+4>: and $0xfffffff0,%esp
0x0804841f <+7>: pushl -0x4(%ecx)
0x08048422 <+10>: push %ebp
0x08048423 <+11>: mov %esp,%ebp
0x08048425 <+13>: push %ecx
=> 0x08048426 <+14>: sub $0x14,%esp ← このブレークポイントで停止中
0x08048429 <+17>: movl $0x1,-0x14(%ebp)
0x08048430 <+24>: movl $0x2,-0x10(%ebp)
0x08048437 <+31>: pushl -0x10(%ebp)
0x0804843a <+34>: pushl -0x14(%ebp)
0x0804843d <+37>: call 0x804840b <func> ← call命令でfunc関数を呼び出す
~(略)~
関数とスタック
スタック
スタック(stack)は、プログラムが実行中に一時的に利用するデータが保持され るメモリ領域です。スタックは、LIFO(Last In First Out)構造になっていて、 後から追加したデータが先に取り出されます。データを追加することをプッシュ (push)、データを取り出すことをポップ(pop)といいます。スタックの先頭アドレスは、スタックポインタレジスタに保持されます。x86では、スタックポインタレジスタはespレジスタです。
push命令を実行するとespが減算されて、pop命令を実行するとespが加算されます。
関数のルール
関数呼び出し
関数を実行することを、関数呼び出しといいます。x86ではcall命令が使用されます。ローカル変数
ローカル変数(local variable)は、関数の内部で利用する変数です。ローカル変数 はスタック領域に領域が確保されます。引数
関数の引数は、右側から左側の順でスタックにプッシュされます。 関数の呼び出し規約(calling conversion)であるcdecl(C declaration)では、 スタックを経由して引数を渡す方法が決められています。戻り値
関数の戻り値は、eaxレジスタにセットされます。戻り値は値やメモリのアドレス などです。関数の終了は、C言語ではreturn命令、x86ではret命令が使用されます。スタックフレーム
関数が呼び出されると、スタック領域にスタックフレーム(stack frame)が生 成されます。スタックフレームのことを単にフレームともいいます。 フレームには関数への引数、関数のローカル変数、関数の実行アドレスなどの 情報が含まれます フレームは呼び出された関数ごとに1つずつ作成されて、関数の処理が完了すると 消去されます。 プログラムが開始した時点では、main関数のフレームのみ存在します。このフレ ームを初期フレームや最上位のフレームといいます フレームの中でも処理対象になっているフレームを、最下位のフレームといいます。 最下位のフレームは、存在する全てのフレームの中で最も直近に生成されたものです。 gdbは最下位のフレームに0、最下位のフレームを生成したフレームに1のように、 連番を設定することで、関数呼び出しの連鎖が追跡できるようにしています。バックトレース
バックトレース(backtrace)は、 プログラムが現在停止している箇所に、 どのようにして到達したかを示す要約情報です。 backtrace(bt)コマンドを実行すると、1フレームの情報が1行に表示されます。 現在実行中のフレーム(番号0のフレーム)を先頭に、それを呼び出したフレーム (番号1のフレーム)を次行に、 以降、 同様にスタックをさかのぼって、 フレーム情報が表示されます。(gdb) b func ← func関数にブレークポイントを設定
Breakpoint 1 at 0x804840e
(gdb) r
Starting program: /home/user/parameter
Breakpoint 2, 0x0804840e in func () ← ブレークポイントのfunc関数で停止
(gdb) bt ← バックトレースを表示
#0 0x0804840e in func () ← 最下位のフレーム
#1 0x08048442 in main ()
ベースポインタ
実行中の関数のフレームアドレスを、フレームポインタ(frame pointer)と いいます。x86ではebpがフレームポインタを保持するレジスタです。 ebpのことを、ベースポインタ(base pointer)ともいいます。 関数が呼び出されると、ebpレジスタの値はスタックにプッシュされます。そして、 ebpレジスタにはスタックポインタレジスタであるespの値がコピーされます。 関数の終了時にはebpレジスタの値はポップされて、関数の呼び出し時の状態に 復元されます。関数の入口と出口
関数の入口では、ebpがスタックにプッシュされて、espの値がコピーされます。 関数の出口では、ebpの値がスタックからポップされて、呼び出された時点での 値に復元されます。 ``` (gdb) disas func Dump of assembler code for function func: 0x0804840b <+0>: push %ebp 0x0804840c <+1>: mov %esp,%ebp ← espの値をebpにコピー 0x0804840e <+3>: mov 0x8(%ebp),%edx 0x08048411 <+6>: mov 0xc(%ebp),%eax 0x08048414 <+9>: add %edx,%eax 0x08048416 <+11>: pop %ebp 0x08048417 <+12>: ret ```ローカル変数
ローカル変数は、スタック上に確保されます。(gdb) disas main
~(略)~
0x08048425 <+13>: push %ecx
=> 0x08048426 <+14>: sub $0x14,%esp ← スタック領域を確保
0x08048429 <+17>: movl $0x1,-0x14(%ebp) ← ローカル変数xの初期化
0x08048430 <+24>: movl $0x2,-0x10(%ebp) ← ローカル変数yの初期
関数引数の設定
cdeclでは関数の引数はスタックに、右から左の順でプッシュされます。(gdb) disas main
~(略)~
0x08048437 <+31>: pushl -0x10(%ebp) ← 引数yの値をプッシュ
0x0804843a <+34>: pushl -0x14(%ebp) ← 引数xの値をプッシュ
関数呼び出し
call命令は、ret命令で戻るアドレスをスタックにプッシュして呼び出す関数にジャン プします。(gdb) disas main
~(略)~
0x0804843d <+37>: call 0x804840b <func> ← func関数を呼び出す
0x08048442 <+42>: add $0x8,%esp ← func関数から戻ると実行される
引数の取り出し
引数はスタックにプッシュされているので、ebpレジスタを基準に相対的なアドレス を使用して取り出します。(gdb) disas func
~(略)~
0x0804840b <+0>: push %ebp
0x0804840c <+1>: mov %esp,%ebp ← espの値をebpにコピー
0x0804840e <+3>: mov 0x8(%ebp),%edx ← 引数aの値をedxにコピー
0x08048411 <+6>: mov 0xc(%ebp),%eax ← 引数bの値をeaxにコピー
関数の戻り値
関数の戻り値は、eaxレジスタに設定されます。
(gdb) disas func
~(略)~
0x08048414 <+9>: add %edx,%eax ← eax+edxの結果がeaxに格納されている
0x08048416 <+11>: pop %ebp ← ebpを関数呼び出し時点の値に戻す
0x08048417 <+12>: ret ← スタックに保存されているアドレスへジャンプ
バイナリエディタ
バイナリエディタ
解析対象の実行形式ファイルは、通常はソースコードがないので、修正をして
コンパイルし直すことが不可能です。
バイナリディタ(binary editor)は、バイナリファイルの内容を直接編集できる
ツールです。
バイナリエディタとしては、hexcurseやhexeditが使われます。
viエディタも、-b オプションを指定してバイナリモードで起動することで、
バイナリエディタとして利用することができます。