「リバースエンジニアリングバイブル」勉強メモ#1
リバースエンジニアリングのためだけのアセンブラ
2014/10/08 : CTF
CTFの勉強の第2弾として、「リバースエンジニアリングバイブル」を読み始めたので、章ごとに読書メモとして残しておきたいと思います。
本書を読んで勉強している人の参考になれば幸いです。
実行環境は、Windows7(64bit版)です。
本書のサポートページはこちらになります。
http://www.impressjapan.jp/books/1113101030
はじめに
リバースエンジニアリングとは、ソースコードを逆追跡することを言う。
リバースエンジニアリングでできること
[1]侵入テスト、弱点発見
[2]セキュリティコードの開発
[3]バグ修正
[4]新技術の研究と学習
セキュリティ業界では、プログラミングのスキルが最も基本的に備えるべき資質であると同時に重要な基礎技術である。
リバースエンジニアリングをする前に、C/C++ のコード作成能力、さまざまなライブラリをコントールするスキル、さらに、Win32 API とシステムプログラミングのための基礎知識が十分備わっていないとならない。
リバースエンジニアリングも、結局は決まった文法の中で基本的なルールに従えば解決できるものに過ぎない。
PE ヘッダーは、リバースエンジニアリングを勉強している人を最も悩ますものである。
本書はリバースエンジニアリングを初めて勉強する人、または、ある程度知っている人を対象にする。
1つの目標を達成するまで曲がらずに進むためには、一遍に育たない竹と同じく、勉強の途中に節を作りながら一歩一歩進んでいくことだ。
そうすれば、後で驚くほど成長した自分を発見するはずである。能力が伸びないと自分を責めたり、焦ったりしてはならない。
第1部 リバースエンジニアリングの基本
1章 リバースエンジニアリングのためだけのアセンブラ
リバーサーとして初めの一歩を踏み出す人々に必要なのは、大量のアセンブラ文法ではなく、リバースエンジニアリングをするためのツールとしてのアセンブラである。
[アセンブラの命令形式]
x86 CPU の基本構造である IA-32 を基本プラットフォームとして説明する。
事実上ほとんどの PC は Intel CPU を使用しており( AMD の場合も、実際にはほとんどのコードが Intel プロセッサと互換性がある)、この時代にアセンブラを学習するということは、IA-32 を身に付けることと同じである。
IA-32 の基本的な形:命令(オペコード:opcode)+引数(オペランド:operand)
(オペランドが2つの場合)すべてのオペランドは最初のもの(左)が目的地(destination:デスティネーション)、後ろのもの(右)が出発地(source:ソース)である。
アセンブラは「命令1個+引数1つまたは2つ」の構造で1行のコードが成り立つ。3以上のものはほとんどない。
[レジスタに複雑な説明は必要ない]
レジスタは( CPU が使用する)変数に過ぎない。
レジスタは、EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP の合計8つである。
EAX
算術計算と戻り値の保存の役割をする。
最も多く使われる変数。
Accumulator の略。
EDX
EAX と役割は同じだが、戻り値の保存には使用されない。
Data の略。
ECX
ループを実行するときに(減算)カウントする役割をする。
カウンターが必要ないときは変数として使用してもよい。
Count の略。
EBX
EAX、EDX、ECX では足りない場合に使用されるレジスタ。
ESI、EDI
ESI と EDI もスタートアドレスと目的地アドレスであり、ESI からメモリの内容を読み込んで EDI にコピーすると考えれば簡単である。
ソース(Source:スタートアドレス)
デスティネーション(Destination:目的地アドレス)
31 0
|←───────EAX───────→|
┌────┬────┬────┬────┐
│ │ │ AH │ AL │
└────┴────┴────┴────┘
|←─ AX ─→|
15 0
EAX : 32bit( 4Byte )のレジスタ
AX : 16bit( 2Byte )のレジスタ
AH : 8bit( 1Byte )のレジスタ
AL : 8bit( 1Byte )のレジスタ
EAX に 0x78563412 が入ると、
AX : 0x3412
AH : 0x34
AL : 0x12
と入ることになる。
コラム:リトルエンディアン
バイトの保存順序をエンディアン(endian)と呼ぶ。
DWORD( 4 Byte) の 0x12345678 の値を、
12 34 56 78 と格納するのが「ビッグエンディアン」
78 56 34 12 と格納するのが「リトルエンディアン」(インテルの CPU が採用している方式)
1バイトずつ右から表現するものがリトルエンディアン、通常の順番で表現するものがビッグエンディアン。
リバースエンジニアリングをするとき、ほとんどの2バイトまたは4バイトの値はリトルエンディアンを使用することを念頭に置いて、バイナリを解釈する習慣を身に付ける。
アセンブラでの演算はレジスタを使用するものであるため、メモリ同士の演算はできない。
コラム:__declspec(naked)
( C 言語の)関数のエントリポイントでは、プログラマが作成したコードの最初の行が登場するのではなく、コンパイラが独自に作成したスタックを確保する処理のコードが登場する。
naked はそれを防止するためのもので、naked を使用すると、それ以降、この関数の中では補助的なコードを全く使用しないことを指定することになり、コンパイラはこの関数内にどんな独自コードも生成しない。
さらに戻り値さえもコンパイラが作ってくれない。
[暗記する必要のないアセンブラ命令]
熟知すべきオペコードは構造別に区分して覚えるとよい。
必須命令だけを頭の中に入れて、残りはリバースエンジニアリングの実践のときにそのアセンブラの構文が出てくるまでは、頑張って覚えなくてもよい。
PUSH, POP
MOV
LEA
MOV が値を取得するものであるのに対し、LEA はアドレスを取得する。
LEA は取得するソースオペランドがアドレスだという意味で、だいたい[]で囲まれている。
ADD
SUB
INT
MS-DOS 時代には、アプリケーションレベルですぐに割り込みを発生させることができたので、INT でさまざまな応用が可能だったが、現在の 32 ビットの時代ではアプリケーションレベルでの割り込みには限界があり、ring0 レベルまで下がっていかない限り、ほとんど使われない命令になっている。
リバースエンジニアリングをしていて最も多く出会うものは INT 3 命令(オペコード 0xCC:DebugBreak())くらい。
CALL
INC, DEC
AND, OR, XOR
XOR は、デスティネーションとソースを同じオペランドで処理できる。
[例] XOR EAX,EAX → EAX が0になる。
NOP
CMP, JMP
[リバースエンジニアリングに必要なスタック]
リバースエンジニアリングのためにスタックについて知っておくべき知識
1.関数の呼び出し時にパラメータが入る方向
2.リターンアドレス
3.ローカル変数の使用
スタックは LIFO( Last In First Out ) の性質によりマイナスの方向に進むので、特定の値を引くということは、その分だけスタックを使用するということになる。
「sub esp,50h」であれば、50h の容量のローカル変数を使用することになる。
[関数の呼び出し/リターンアドレス]
関数の引数は LIFO の順序でスタックに入れるため、実際のソースコードで呼び出される順序とは逆になる。
パラメータを push で入れるので、これらの値にアクセスするためには、EBP からのオフセットを足していく方法で計算しなければならない。
引数が DWORD 型(4Byte)の場合、
ebp+0x04 この関数の処理が終わって復帰するときに使用するリターンアドレス
ebp+0x08 最初の引数
ebp+0x0C 2番目の引数
ebp+0x10 3番目の引数
記述に際しては、細心の注意をしたつもりですが、間違いやご指摘がありましたら、こちらからお知らせいただけると幸いです。
←「リバースエンジニアリングバイブル」勉強メモ#2
←「たのしいバイナリの歩き方」勉強メモ#4
« 戻る