前回の記事
任意のDLLをexeに挿入することができた。 popush.hatenablog.com
変更箇所の検出
exeをIDA Proで開いて処理を変更したい部分を見つけたとする。例として下記はhoi4.exeのversionである。
実行され、メモリ上に展開されたexeからこれを変更しようとしたとき、その位置(アドレス)を見つける必要がある。 これを担うのがbyte_pattern(下記の二つのファイル)である(見つけるだけでbypassできるわけではないことに注意)。
EU4dll/byte_pattern.cpp at win32 · matanki-saito/EU4dll · GitHub
EU4dll/byte_pattern.h at win32 · matanki-saito/EU4dll · GitHub
コメントを見ると下記からフォークされたようだ。
byte_pattern
パターンを見つけるときには下記のように書く。
byte_pattern::temp_instance().find_pattern("48 79 64 72 61 20 76 31 2E 37 2E 31 00")
EU4dll/version.cpp at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub
byte_pattern::temp_instance()でシングルトンインスタンスを取得する。初期化はスレッドセーフになるが(参考1)、以降の処理はアトミックになるわけではない。並列に実行しないほうが良いとおもう。
EU4dll/byte_pattern.cpp at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub
exe(PEフォーマット)の解析
byte_patternはコンストラクタでexeの開始アドレス(HMODULE/HINSTANCE)を取得(参考2)して、
EU4dll/byte_pattern.cpp at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub EU4dll/byte_pattern.cpp at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub
その後の処理で使いやすいようにmemory_pointerでwrapしている。
EU4dll/byte_pattern.h at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub
これにより指定のアドレスから任意のフォーマットを取り出すことができるようになる。
EU4dll/byte_pattern.h at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub
メモリ上に展開されたexeをPEフォーマット(参考3)として解析するための処理は下記。
EU4dll/byte_pattern.cpp at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub
*PEフォーマットを説明するのに良い図だったので拝借した。
まず最初にIMAGE_DOS_HEADERを読み込み(青)、そこにあるオフセット値を使ってIMAGE_NT_HEADERS(黄色と桃色)を読み込む。
EU4dll/byte_pattern.cpp at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub
win32と64でIMAGE_DOS_HEADERには変化がないが、IMAGE_NT_HEADERSには変化がある。マクロで切り替わるので気にしなくてよい。
任意のセクションの開始アドレスはセクションヘッダ配列(ピンク色)にある。セクションヘッダ全体の開始アドレスはIMAGE_NT_HEADERS(黄色と桃色)とディレクトリ配列(水色)の下から始まる。セクションは固定長なので、インデックスをもとにオフセットを加算する。下記のlambda関数で算出できる。
EU4dll/byte_pattern.cpp at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub
すべてのセクションヘッダから、コードに関係するもの(.text)と定数に関係するもの(.rdata)を抜き出している。対象のセグメントヘッダがあれば、セグメントへのRVAとバイト長をrange変数に保存する。
EU4dll/byte_pattern.cpp at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub
これらはただのラベルなので、IDAで属性を見て追加するセグメントを決めるほうが安全だ。
もしsegmentが1つもなければコード全体が対象となる。
EU4dll/byte_pattern.cpp at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub
find_pattern
find_patternは最初にパターンをバイト列に成型する。文字列を空白で分割した後(xx ?x ? xx -> [xx,?x,?,xx])、
EU4dll/byte_pattern.cpp at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub
文字をASCIIとして解釈していくのだが、?があった場合はすべてにマッチさせるためのマスク設定を同時に作成している。([xx,?x,?,xx] → [(bb,0xFF),(bb,0x0F),(0,0),(bb,0xFF)])
EU4dll/byte_pattern.cpp at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub
次にパターンに対して下記の事前処理をしている。これはパターンの検出にボイヤー-ムーア法(参考4)を使用しているためで、不一致文字規則のためのテーブル作成になる。
EU4dll/byte_pattern.cpp at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub
最後に実際に下記でマッチ処理を行っている。使われている規則は不一致文字規則だけで、一致サフィックス規則などは使われていないが、今のところ実用上は問題ない。
EU4dll/byte_pattern.cpp at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub
アクセス違反が出た場合、特にエラーハンドリングなく次の処理へと進む(参考5)が、ログを入れたほうがよい。
EU4dll/byte_pattern.cpp at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub
実例
HO4で実際に試してみると、検索対象のパターンが検出できてアドレスも取得できていることがわかる