pop↑ push↓

☆ (ゝω・)v

byte pattern search メモ

前回の記事

任意のDLLをexeに挿入することができた。 popush.hatenablog.com

変更箇所の検出

exeをIDA Proで開いて処理を変更したい部分を見つけたとする。例として下記はhoi4.exeのversionである。 f:id:saito-matanki:20190825174824p:plain

実行され、メモリ上に展開されたexeからこれを変更しようとしたとき、その位置(アドレス)を見つける必要がある。 これを担うのがbyte_pattern(下記の二つのファイル)である(見つけるだけでbypassできるわけではないことに注意)。

EU4dll/byte_pattern.cpp at win32 · matanki-saito/EU4dll · GitHub

EU4dll/byte_pattern.h at win32 · matanki-saito/EU4dll · GitHub

コメントを見ると下記からフォークされたようだ。

github.com

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フォーマットを説明するのに良い図だったので拝借した。

f:id:saito-matanki:20190825183336p:plain

まず最初にIMAGE_DOS_HEADERを読み込み(青)、そこにあるオフセット値を使ってIMAGE_NT_HEADERS(黄色と桃色)を読み込む。

EU4dll/byte_pattern.cpp at cebec8e139b69d3fe0b4e4c461ffb541f36960e9 · matanki-saito/EU4dll · GitHub

win32と64でIMAGE_DOS_HEADERには変化がないが、IMAGE_NT_HEADERSには変化がある。マクロで切り替わるので気にしなくてよい。

f:id:saito-matanki:20190825184800p:plain

任意のセクションの開始アドレスはセクションヘッダ配列(ピンク色)にある。セクションヘッダ全体の開始アドレスは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で属性を見て追加するセグメントを決めるほうが安全だ。 f:id:saito-matanki:20190825191402p:plain

もし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で実際に試してみると、検索対象のパターンが検出できてアドレスも取得できていることがわかる

f:id:saito-matanki:20190825193710p:plain

参考

  1. ブロックスコープを持つstatic変数初期化のスレッドセーフ化 - cpprefjp C++日本語リファレンス
  2. GetModuleHandleA function (libloaderapi.h) | Microsoft Docs
  3. PEファイルフォーマットについて - Qiita
  4. ボイヤー-ムーア文字列検索アルゴリズム - Wikipedia
  5. VC++構造化例外メモ(Hishidama's VC++2005 SEH Memo)