基礎用語 逆アセンブルコードリスト
概要
デバッガ「OllyDbg」で表示されたり、逆アセンブラ「PeRdr」で出力される、プログラムのコードに「逆アセンブル」と呼ばれる操作を行った結果として、人間がプログラムの処理の流れを視認できるようにしたものを、「逆アセンブルコードリスト」と呼びます。 OllyDbgが表示しているのは、対象アプリケーションの起動に伴いプロセスメモリ上に読み込まれた、対象アプリケーションのEXEファイルに含まれるプログラムのコードを、プロセスメモリから読み込んで逆アセンブルしたものです。一方、「PeRdr」は実行ファイルを実行されないままファイルからプログラムのコードを読み込んで逆アセンブルしています。 デバッガを用いたプログラム解析においては、逆アセンブルコードリストの読解は必須となります。そのため、逆アセンブルコードリストの構成要素である「レジスタ」、「ニーモニック」、「(アセンブリ言語の)命令」、「API関数」等を理解する必要があります。 デバッガを用いたプログラム解析を始める前に、ヘキサエディタ(バイナリエディタ)やプロセスメモリエディタといった基本ツールの操作を理解しておくと、デバッガを用いたプログラム解析がよりスムーズに理解できるようになります。そのため、これからプログラム解析を始めようという方には、いきなりデバッガから入るのではなく、あらかじめプロセスメモリエディタの操作に慣れておくことを強くお勧めします。 以下では、32ビットアプリケーションの逆アセンブルコードリストの読解を始めるための端緒とすべく、必要最低限の基本事項を簡潔に解説します。あくまでプログラム解析という視点での解説であり、CPUアーキテクチャ全般の解説やプログラミングを目的とする解説ではありません。なお、64ビットアプリケーションの解析についてはこの質問と回答を参照して下さい。 以下は逆アセンブルコードリストが読解しやすい、C/C++言語で開発されたPCゲームの逆アセンブル例です。すでに一定のシェアを持つC#言語で開発された.NET対応アプリケーションでは以下のような逆アセンブルは行えず、専用の逆コンパイラ等を使用して解析することになります。また、C++言語で開発されたゲームやアプリケーションでも、プログラムのコードに圧縮や暗号化を施すことで逆アセンブルを困難にしているケースもあります。 これら基本事項の詳細については、当ソフトウェア公式サイトから以下のリンクにて資料や参考となる書籍を紹介していますので、入手・参照されることを強くお勧めします。特に、アセンブリ言語の命令の一覧とその詳細な解説を含む「IA-32 インテル・アーキテクチャ・ソフトウェア・デベロッパーズ・マニュアル」および、API関数の一覧とその詳細な解説を含むMicrosoftの「Technical documentation (旧Microsoft Docs、旧MSDN Library)」といったネット上の資料や「Windows SDK」は、逆アセンブルコードリストの読解を含むプログラム解析に大いに役立ちます。また、ネット上での検索も積極的に活用することをお勧めします。 逆アセンブルコードリスト読解等プログラム解析に関する各種Q&A 逆アセンブルコードリスト読解等プログラム解析に役立つ各種資料 プログラム解析に役立つ参考書籍 逆アセンブルコードリストの基本的な読解ができるようになったら、次はデバッガが持つ「ブレークポイント機能」、「ステップ実行機能」、「レジスタ・プロセスメモリ編集機能」、「参照API関数と同呼び出し箇所の一覧表示機能」および「参照文字列一覧表示機能」と、デバッグ時の逆アセンブルコードリストとの関わりを理解されることをお勧めします。これは各種チュートリアルや実際の解析を通しての試行錯誤が、理解する上での近道になると考えられます。 なお、当ソフトウェア『うさみみハリケーン』には、x86/x64対応、すなわち32ビットCPUと64ビットCPUの両方のモードに対応した、簡易アセンブラ兼逆アセンブラ「ロケットねこみみ」(NekoAsm.exeとNekoAsm64.exe)を同梱しています。このツールでは特に、ニーモニック(後述)とバイト列の相互変換を簡単に行うことが可能ですので、アセンブリ言語およびプログラム解析を学ぶ過程で活用されることをお勧めします。 |
逆アセンブルコードリスト出力例
●OllyDbgでCPUウィンドウに表示 0040158F > 6A 10 PUSH 10 ; /Key = VK_SHIFT 00401591 . E8 86010000 CALL <JMP.&USER32.GetAsyncKeyState> ; \GetAsyncKeyState 00401596 . 80FC 80 CMP AH,80 00401599 75 55 JNZ SHORT UsaTest2.004015F0 0040159B B8 01000000 MOV EAX,1 004015A0 8305 34124000 64 ADD DWORD PTR DS:[401234],64 004015A7 813D 34124000 E8030000 CMP DWORD PTR DS:[401234],3E8 004015B1 7E 1D JLE SHORT UsaTest2.004015D0 004015B3 C705 34124000 E8030000 MOV DWORD PTR DS:[401234],3E8 ●PeRdrでファイルに出力 0040158F 6A10 push 10h * Reference to USER32.GetAsyncKeyState | 00401591 E886010000 call 0040171Ch 00401596 80FC80 cmp ah,80h 00401599 7407 jz 004015A2h 0040159B B800000000 mov eax,00000000h 004015A0 8B00 mov eax,[eax] |
逆アセンブルコードの構成要素
●アドレス 上記の例では「40158F」といった16進数の値。これは、実行されるコードのプロセスメモリ上での番地を指し、32ビットアプリケーションでは4バイトの値となる。EXEファイル等の実行ファイルがプログラムとして実行される際には、まず実行ファイルがシステムにより用意された当該プロセスのためのプロセスメモリ上にロードされ、その実行ファイルに含まれるコードがプロセスメモリ上で実行される。基本的に、EXEファイルがロードされるプロセスメモリ上のアドレスは0x400000が起点となる(注:Windows Vista以降では、EXEモジュールをランダムなアドレスにロードする機能ASLRが実装されています。プログラム解析におけるASLRへの対処(無効化)については、当ソフトウェアに同梱しているPEエディタ『UMPE』の解説を参照してください)。また、実行ファイルに付属する、Kernel32.DLL等のシステムファイルではないDLLファイルは基本的にアドレス0x10000000にロードされるが、複数のDLLが使用される場合は別のアドレスにもロードされる。この配置換えをリロケーションと呼ぶ。実行ファイルのおおまかな構造としては、PEヘッダと呼ばれる実行ファイルの基本設定を格納するデータブロックが先頭にあり、その後にセクションと呼ばれる、コードやデータ等の格納内容に応じて区切られたデータブロックが並んでいる。基本的に、実行されるコードが含まれるコードセクションが最初のセクションとなる。 ●バイナリデータ 上記の例では「6A 10」といった16進数で表示されるデータ。これは、プロセスメモリ上にある実行されるコードの実体で、CPUは0か1で指定された2進数のデータ(バイナリデータ)として読み込む。CPUはこのバイナリデータをコードとして直接解釈して対応する処理を実行するため、このコード対応バイナリデータはマシン語(機械語)と呼ばれる。プログラムの処理とは、このマシン語で指定された処理をCPUが1つ1つ順番に実行していくことを意味する。なお、バイナリデータを16進数で表示しているのは人間にとっての視認性や可読性向上のため。 ●ニーモニック 上記の例では「PUSH 10」といった、ニーモニックと呼ばれる英数字文字列。これはマシン語の内容を人間が理解できるよう分かりやすく翻訳したものでアセンブリ言語と呼ばれる。本来アセンブリ言語はマシン語と1対1で対応する処理をニーモニックの形式で記述して実行ファイルを作成(アセンブル)する開発言語であるため、マシン語からアセンブリ言語に翻訳する処理を「逆アセンブル」という。なお、アセンブリ言語で記述されたコードをアセンブルするソフトウェアをアセンブラと呼ぶ。 ●オペコード ニーモニックの最初にある「PUSH」、「MOV」、「CMP」および「J**」といった英字文字列はCPUが行う処理の種類を指すもので「(アセンブリ言語の)命令」と呼ぶ。ニーモニックの構成要素としてはオペコードと呼ばれる。 ●オペランド ニーモニックの中で、演算等、命令の処理対象となる数値やレジスタ(後述)をオペランドと呼ぶ。例として、ニーモニック「MOV EAX,1」においてはEAXレジスタが第1オペランド、数値1が第2オペランドと呼ばれる。オペランドが大括弧[ ]で指定されている場合は、[ ]の中のアドレスに格納された値を意味する。「DWORD PTR [402010]」や単に「[402010]」ならば、アドレス0x402010に格納されたDWORDの値となる。対象となる値のサイズがDWORD以外ならば、「BYTE PTR」や「WORD PTR」で指定する。 ●レジスタ オペランドとして使用される、CPUの内部にある一時記憶用メモリ。一般的な命令に使用される汎用レジスタや、浮動小数点数を用いた演算に使われるものおよび、マシン語レベルでのアプリケーションのデバッグ専用のもの等がある。32ビットCPUにおける汎用レジスタは、EAX、EBX、ECX、EDX、ESI、EDIがあり、他には特殊な用途に用いられるESP、EBP、EIP、EFLAGSがある。32ビットCPUにおける汎用レジスタのサイズは4バイト(32ビット)となるが、汎用レジスタE*Xは、4バイトのうちの2バイトや1バイトを処理対象として使用することもできる(EAXレジスタの下位2バイト=AX、AXの上位1バイト=AH、下位1バイト=AL)。レジスタ名先頭の「E」は16ビットCPUのレジスタとの対比で32ビットへ拡張(Extend)されたことを意味する。 E*Xレジスタは特に演算やポインタ(アドレスを指定するためにレジスタや特定アドレスに格納された値を使うやり方)およびカウンタ等を主用途とする。E*Xレジスタにはそれぞれ本来の用途(EAX:各種演算、EBX:ポインタ、ECX:カウンタ、EDX:データ一時記憶およびEAXとの連携による乗除算)が定められているがその用途に制限される訳ではない。ESIとEDIレジスタは汎用的な使用も可能だが、本来はデータ転送命令等でデータの転送元アドレス(SI:SourceIndex)や転送先アドレス(DI:DestinationIndex)が格納されるもの。ESP(SP:StackPointer)は現在のスタック(後述)のアドレスが格納される。EBP(BP:BasePointer)は基本的にスタック上のデータに対するポインタとして使用される。EIP(IP:InstructionPointer)は、次に実行される命令のアドレスが格納され、命令が実行されるたびに内容が更新される。 ●フラグ 演算命令や比較命令の実行後、演算結果がゼロか否かや、比較結果での大きいか否かなどを意味するフラグがビット単位の1か0でEFLAGSレジスタに格納される。EFLAGSレジスタにはゼロか否かなど特定条件に対応するビットがあらかじめ指定してあり、条件に合致したらフラグをセット(該当ビットに1を設定)、条件に合致しないならばフラグをクリア(該当ビットに0を設定)する。このフラグの状況を元にジャンプ命令を使ってプログラムの処理を条件分岐させることが可能になる。EFLAGSレジスタ内のこのようなフラグをステータス・フラグと呼ぶ。 ●数値 逆アセンブルコードリスト上では、アドレスやオペランドとして使用される数値は16進数で表示される。なお、一般的に16進数であることを明示する場合は数値の後に「h」を付加するか、数値の前に「0x」を付加することで行う。 ●スタック プログラムが一時的にデータを格納するメモリ領域。スタック内で新たにデータを格納するアドレスは常にESPレジスタに格納されており、プログラム側でアドレスを直接指定する必要がない。スタックにデータを格納する際はPUSH命令を用い、スタック内では、アドレス0x10FFF8にデータを格納したら次はアドレス0x10FFF4というように、アドレスの高い方から低い方へ向かって、まるで積み木を積むようにデータが格納されるため、これをスタックに積むという。スタックからデータを取り出す際にはPOP命令が用いられ、ESPレジスタのスタックポインタを用いるスタックのデータ格納方式により、後に積まれたデータが先に取り出されることになる。PUSH/POP命令実行時には自動的にESPレジスタの格納値が修正される。 また、汎用レジスタの内容をスタックに格納/取り出しするPUSHAD/POPAD命令や、EFLAGSレジスタの内容をスタックに格納/取り出しするPUSHFD/POPFD命令もある。 ●API関数 上記の例では「USER32.GetAsyncKeyState」。API関数は、アプリケーションがOSの機能を容易に使用することを可能にする、API(Application Programming Interface)の1要素。この例では、WindowsOSのシステムDLLであるUser32.dllが提供する、特定のキーが押されているか否かを判断する機能を持つGetAsyncKeyState関数を呼び出している。API関数の呼び出しにあたり、関数の処理を実行するために必要なパラメータ(引数:ひきすう)を指定する場合は、PUSH命令で数値そのものあるいはデータが格納されたアドレス等をスタックに積むことで指定する。上記の例では10hがShiftキーを意味する。 また、API関数の多くは、関数の処理が実行されると、関数の処理結果や、関数が成功したか否か等を意味する値(戻り値)がEAXレジスタに格納される。なお、NT系OSが内部的に用いる基本的に非公開のAPI関数を「ネイティブAPI」と呼ぶ。ネイティブAPIはユーザー側(ユーザーモード)とシステム側(カーネルモード)の間で処理の橋渡しを行う役目を持つ。 API関数はそれぞれが特定の機能を持つため、プログラム解析において、逆アセンブルコードリストを読解する手がかりとなることが少なくない。個々のAPI関数の機能や、どのようなケースにどのようなAPI関数が使われるかは、ネット上に有用な資料が多々あるため、必要に応じて検索されることをお勧めする。 ●<参考> プログラム解析において頻出する基本的な命令
●<参考> 主なステータス・フラグ
|