基礎用語 環境依存型変動アドレス
アドレス変動の仕組み
例えば、PCゲームのパラメータ等のアドレスが、プロセスメモリ上に『***.exe』といった解析対象アプリケーションのEXEファイルがロードされたエリアであるモジュールエリア内にあるならば、そのアドレスは実行環境等に依存しない固定アドレスとなります。これは、EXEファイルのロード処理は実行環境に影響を受けず、また、モジュールエリア内のデータ格納用セクションには、基本的にプログラム側で使用することやサイズ上限が『確定している』データを格納するためです。32ビットアプリケーションのプロセスでは、メインモジュール(ロードされたEXEファイル)のプロセスメモリ上のアドレスは0x00400000となり、64ビットアプリケーションならば、メインモジュールのプロセスメモリ上のアドレスは0x0001'40000000となります。ただし、Windows
Vista以降では、EXEファイルをランダムなアドレスにロードする機能ASLRが実装されており、必ずしも上記の固定アドレスになるとは限りません。プログラム解析におけるASLRへの対処、すなわちASLR無効化によるメインモジュールの固定アドレス化については、当ソフトウェアに同梱しているPEエディタ『UMPE』の解説を参照してください。 一方、解析対象アプリケーションが起動後に自分で動的に確保したメモリブロックおよび、そのメモリブロックに格納されたパラメータ等のアドレスは固定とはなりません。これは動的に行うメモリ確保では、状況に応じて確保されるメモリブロックのアドレスが変化するためです。 動的なメモリブロック確保について、ヒープ(プログラムが実行中に動的にメモリを割り当てるためにOSが用意している、あるいはアプリケーションが自分でHeapCreate関数を使用して作成したメモリ領域)から動的に必要なサイズのメモリブロックを確保するAPI関数には、HeapAlloc関数があります。他にはGlobalAlloc関数やLocalAlloc関数も使用可能ですが、速度面等の理由により現在では推奨されていません。ちなみに、32ビットアプリケーションにより、WindowsNT系OS上でGlobalAlloc関数を使用して確保されたメモリブロックのアドレスは、Windows OSにおけるメインモジュールの開始アドレス0x00400000よりも低いアドレスになります。 また、プログラミング手法として大きなメモリブロックを確保する場合には、Windows OSのバージョンに関わらずパフォーマンス面で適しているVirtualAlloc関数の使用が推奨されています。VirtualAlloc関数はヒープからの確保ではなく、簡単に言えばプロセスの仮想アドレス空間内のページ(システムがメモリ管理に用いるメモリ単位で、x86コンピュータでのサイズは0x1000バイト)に使用可能属性を設定して割り当てるAPI関数です。なお、1つのアプリケーションだが実際には2つ以上のプロセスが同時に実行されるケースでは、CreateFileMapping関数で第1引数に定数INVALID_HANDLE_VALUE(0xFFFFFFFF)を指定して作成したファイルマッピングオブジェクトを、プロセス間の共有メモリとして使用することもあります。 これらのメモリ確保用API関数を用いて確保されたメモリブロック上のアドレスは、基本的にOSその他実行環境によって変化する環境依存型変動アドレスとなります。使用OSが同じ場合にプロセスヒープ(OSが各プロセス用に作成する既定のヒープで、スタックとは別物)から確保されたメモリブロックのアドレスが同一になるケースもありますが、確実とはいえません。なお、解析対象アプリケーションのプログラマがメモリ確保の設定やタイミング等を変化するように設計すれば、実行環境に依存するアドレス変動に加え、アプリケーション起動ごとのアドレス変動も生じさせることが可能です。また、起動ごとのアドレス変動は、常駐ソフト等の環境要因によっても起こりえます。おおむね、大きなメモリブロックの確保が必要になるPCゲームに加え、各種ゲーム機のエミュレータでもパラメータ等のアドレスは環境依存型変動アドレスになるといえます。 さらに、パラメータ等の管理を解析対象アプリケーション専用のDLLがそのモジュールエリア内で行う場合、このようなDLLがプロセスメモリ上にロードされるアドレスは、実行環境によって必ずしもWindows OSにおける32ビットアプリケーションでの既定のDLLロード先アドレスである0x10000000とはならないため、この場合も環境依存型変動アドレスとなります。このような解析対象アプリケーション専用のDLLは、解析対象アプリケーションがLoadLibrary関数を使用して動的にロードすることもできますが、『***.exe』といったメインモジュールからリンク(実行ファイルの設定で実行に必須のDLLとして指定)することで、アプリケーションの起動時に自動的にプロセスメモリ上にロードされるようにすることもできます。また、プログラムの処理としては、プロセスメモリ上で特定のDLLがロードされたアドレスを取得するのは容易です。 アドレス変動に関しては、解析対象アプリケーションが、データを格納するための必要最小限のサイズのメモリブロックを確保し、必要に応じてより大きなサイズのメモリブロックを確保してデータを格納し直すことがアドレス変動の原因となるケースもあります。このようなケースではHeapReAlloc関数かメモリブロック再確保用の自作関数を用い、格納するデータの増加に応じてより大きなサイズのメモリブロックを確保し、元のメモリブロックの内容を新しく確保したメモリブロックにコピーしたうえで元のメモリブロックを解放します。 HeapReAlloc関数はメモリ確保対象ヒープ内の使用状況や再確保するメモリブロックのサイズに応じて、元のメモリブロックを拡張あるいは、別途メモリブロックを確保してそこに元のメモリブロックの内容をコピーします。元のメモリブロックを拡張する場合はアドレス変動は生じません。 環境依存型変動アドレスは、プログラム側でそのアドレスにアクセスする以上、対象アドレス(群)の基準となるアドレスをポインタ(アドレスを指定するために特定アドレスに格納された値を使うやり方)として格納する必要があります。また、プログラム側では確保したメモリブロックが不要になった時に解放するため指定するパラメータとして、確保したメモリブロックの先頭アドレスを保持しなければなりません。そのため、データを格納するために確保したメモリブロックの先頭アドレスを格納するポインタは、基本的にEXEモジュールのモジュールエリア内の固定アドレスあるいは、DLLモジュールのモジュールエリア内でDLLロード先アドレスからみて相対値が固定のアドレスに配置することになりえます。なお、ヒープから確保されたメモリブロックの先頭アドレスはページ境界(0x1000単位)上にはならず、0x00831E90といった半端なアドレスとなります。一方、VirtualAlloc関数で割り当てられたページの先頭アドレスは64KB境界(0x10000単位)上となります。 |
アプローチ
これら環境依存型変動アドレスの解析においては、パラメータ等のアドレスにデバッガで読み書きまたは書き込みブレークポイントを設定してブレークさせ、ブレーク箇所周辺の逆アセンブルコードリストを読解してプログラムの処理上どのようにアドレスを管理しアクセスしているかを把握するというアプローチが基本となります。この際、逆アセンブルコードリスト上で処理をさかのぼって観察し、特にパラメータ等のアドレスを指定するためにレジスタに格納される値の出所に注目して下さい。また、パラメータ等のアドレスが含まれるメモリブロックの、API関数による確保処理とその後のデータ格納処理を追いかけていくというアプローチもあります。 システム情報表示ツールなどが表示する、解析対象プロセスに属するヒープの一覧をアプローチの参考にする場合は、そのヒープ一覧はあくまである瞬間でのスナップショットであり永続的な情報ではないこと、各ヒープ内の確保済みメモリブロックにはプロセスのメインモジュールだけではなくシステムDLL他別のモジュールが確保したものも含まれること及び、基本的にヒープ内のメモリブロックのアドレスは環境に依存する変動アドレスであることに十分注意して下さい。 環境依存型変動アドレスを改造コードで指定する際には、アドレスの直接指定ができないため、ポインタを利用することになります。解析対象アプリケーション側が複数のポインタを連ねてデータのアドレスを管理しアクセスしているケースでは、改造コードは多重ポインタ対応のものを使用することで対処します。 また、多重ポインタ解析の手間を省くために、解析対象アプリケーションの改造対象パラメータ等操作処理箇所をデバッガで特定後、その処理箇所でパラサイトルーチンへ繋がるようコードを書き替えて、パラサイトルーチンの処理として固定アドレスにポインタを自作する方法もあります。この自作ポインタは、解析対象アプリケーションのプロセスメモリ上で、メインモジュール(EXEモジュール)のモジュールエリア内にある未使用領域に配置します。適切に利用可能な未使用領域が無ければ、当ソフトウェア同梱のPEエディタ「UMPE」を使って、EXEファイルにセクションを追加することで、起動後EXEモジュールのモジュールエリア内に新しい未使用領域を作成できます。 ポインタに関する解析結果の公開にあたっては、予期せぬアドレス変動要因を考慮して、公開前にOSを再起動させて解析対象アプリケーションも再起動させたうえで、ポインタを用いた改造コード等の動作を再確認されることをお奨めします。 ●パラサイトルーチン作成機能による多重ポインタへのアプローチ例 1.解析対象のEXEファイルあるいはレジストリを書き換えてASLRを無効化する(ASLR無効化の手順) 2.検索でパラメータのアドレスを特定する(このアドレスは解析対象の起動ごとに変動する) 3.デバッガで、パラメータのアドレスへ読み書きを行うプログラムコードの位置を特定する 4.上記3のプログラムコードにパラサイトルーチンを適用し、パラメータのアドレスを数値として、EXEモジュール内の読み書き可能な未使用アドレスに書き込ませる 5.上手順で書き込んだアドレスを新しいポインタとして、その格納値であるパラメータのアドレスを得る 6.得られたパラメータのアドレスの格納値を書き換える 7.上記4から6のプロセスメモリ書き換えを、改造コードあるいは自作プラグインを使って一括で実行する これで、13重ポインタといった多重ポインタで、解析対象の起動ごとにパラメータのアドレスが変動しても、簡単に書き換えることが可能になります。自作プラグインでEXEモジュールの先頭アドレスを得るならば上記ASLRの無効化は不要です。 以下は同梱 UsaTest2_x64.exe での、ポインタ作成による環境依存型変動アドレス対応を、改造コード化する例です。UsaTest2_x64.exe は、パラメータを格納する環境依存型変動アドレスを多重ポインタで管理してはいませんが、ポインタ作成の練習に使用可能なため用いています。 ▲パラサイトルーチン作成例 ;バッファ(ジャンプ先)のアドレスはバッファアドレスのリストで「ダンプ画面の選択アドレス」を使って指定する ;テンプレート出力の「基本パラサイトルーチン」を使用 ;ジャンプ元:00014000299C 先:00014016AF00 ;ジャンプ元は環境依存型変動アドレス用の「パラメータ増加」ボタン押し下げ処理箇所 PUSHFQ mov rax, rcx add rax, 2000 mov [000140215FF0], rax ;単純ポインタをEXEモジュール内に作成 POPFQ ;オリジナルのコード(JMPで上書き分)を復元 ;">"で目印設定 >ADD DWORD PTR [RCX+00002000], 64 ;ジャンプ元に戻る JMP 0001400029A3 ▲上記パラサイトルーチンを改造コード化(パラサイトルーチンのプレビューを右クリックから) ;PUSHFQ 0001'4016AF00-9C ;MOV RAX, RCX 0001'4016AF01-4889C8 ;ADD RAX, 2000 0001'4016AF04-480500200000 ;MOV [000140215FF0], RAX 0001'4016AF0A-488905DFB00A00 ;POPFQ 0001'4016AF11-9D ;ADD DWORD PTR [RCX+00002000], 64 0001'4016AF12-83810020000064 ;JMP 0001400029A3 0001'4016AF19-E9857AE9FF ;JMP命令で上書き分 0001'4000299C-E95F8516009090 ;上書き分の書き戻し用 ;ADD DWORD PTR [RCX+00002000], 64 ;0001'4000299C-83810020000064 ▲作成したポインタを使ってパラメータ書き換え ;上の改造コード実行後に、環境依存型変動アドレス用の「パラメータ増加」ボタンを押すとポインタが作成される ;ポインタ作成後に下の改造コードが使用可能になる ;*000140215FF0>0-E703 ;<参考>表示により判明しているポインタのアドレスとオフセットを使用した、本来の改造コード ;*0001'40214050>2000-E703 |
構造体
PCゲームにおけるキャラクター情報等、メモリブロックを確保して複雑なデータを格納する場合は、そのデータは色々な要素を格納できる構造体(複数のデータを一つに集めたデータ構造)の形式をとるケースが多いといえます。構造体の中にさらに別の構造体を格納することも可能です。基本的に、構造体を使用する場合のプログラムの処理としては、複数のポインタを連携させた効率的なデータ管理・アクセス手法が使われます。 <参考>構造体を用いたデータ格納例 以下は構造体の要素を大幅に省略して簡略化したモデルです。さらに、プロセスメモリ上へのデータの格納方法はプログラマのコーディングによって変化するため、以下はあくまで1つの例に過ぎないと考えて下さい。 [構造体のヘッダ] 正義の味方のキャラ数(0x2バイト) 悪の組織のキャラ数(0x2バイト) ---他の色々なデータは省略--- [正義の味方] キャラ1名前(0x10バイト) HP(0x4バイト) MP(0x4バイト) 所持金(0x4バイト) 所持アイテム数(0x2バイト) 所持アイテム1レベル(0x2バイト) 所持アイテム2レベル(0x2バイト) ---その他の要素が続く--- キャラ2名前 HP MP 所持金 所持アイテム数 所持アイテム1レベル 所持アイテム2レベル ---その他の要素が続く--- 以下キャラ3以降に続く [悪の組織の一員] キャラ1名前 HP MP 所持金 所持アイテム数 所持アイテム1レベル 所持アイテム2レベル ---その他の要素が続く--- 以下キャラ2以降に続く解析対象アプリケーションが、ソースコードのメンテナンス性向上等を目的として構造体の形式でデータを格納している場合、プログラム上でのデータの管理とアクセスは、アドレスの直接指定ではなくポインタを使用することで処理の効率化を図ります。上の例で各キャラクターのデータのサイズが0x100ならば、例えば正義の味方キャラ2の所持金データは、構造体の先頭アドレスである「正義の味方のキャラ数」のアドレスからみて、+(0x2+0x2) +(0x100) +(0x10+0x4+0x4)のアドレスに格納されています。ここで、正義の味方データの先頭アドレスとなる「正義の味方キャラ1名前」のアドレスがポインタとして別の場所に格納されていれば、「正義の味方キャラ2の所持金」のアドレスはプログラム上の処理では「ポインタの格納値+0x100*1+0x18」として効率的に処理され、同様に他のキャラの各要素へアクセスすることも容易になります。 逆アセンブルコードリスト上でのこのようなポインタに関するアドレスの演算処理は、「LEA EDX,[ECX*4+00425180] 」といった形で特にLEA命令やレジスタを使って処理を高速化することが可能であり、また、対象アドレス格納値の取得や変更にはMOV命令で「MOV EAX, DWORD PTR[EBX+ECX*4+1470]」、「MOV DWORD PTR[EBX+ECX*2+1470], EAX」といった効率的な処理を行うことも可能です。 構造体のデータにアクセスするためのポインタは構造体に含めることも、全く別のメモリエリアに配置することもできます。また、上の例のように構造体でデータを管理する場合は、一般的に各ポインタを連携させて使用するため、プログラムの処理の効率上、使用する複数のポインタを特定のメモリエリアに固めて格納していることもあります。 |