東方紅魔郷AI

@aki33524
自己紹介
• 大阪大学工学部電子情報学科一回
• バイナリ読んだり
– x86限定

• SECCON CTF 2013本戦
– 観光勢
– !!!竹田氏!!!
目標
• 紅魔郷Exのノーショットノーボムノーミスクリア
する
– 回避特化

– スコアタックなどアイテムの処理は組み込んでい
ない
処理の介入
• 全体的な処理の把握
– 気合

• 毎フレーム呼ばれる関数を見つけ出してAIの
関数に書き換える
– Dllinjectionを用いる
STGのフローチャート
キー入力
の評価
描画

自機死亡
処理

敵弾移動
処理

自機移動
処理
敵移動処
理
STGのフローチャート
• 正確にはキー入力処理の後にシーン評価
(e.g タイトルシーン、ゲームシーン)
– 先の図はゲームシーン時の処理

• キー入力処理はシーンに依らず必ず行われ
る
– これを乗っ取る
Dllinjection
• Dllをターゲットのプロセスに読み込ませて介
入
– Attach/Detachの処理でメモリアドレスの書き換え
など

• CreateRemoteThreadを用いる方法
– 参考 O’REILLY アナライジングマルウェア
Dllinjection(読み込ませる側)
VirtualAllocEx()で対象プロセス内にメモリ確保
WriteProcessMemory()で確保したメモリに対象
Dllのファイルパスを書き込む
GetModuleHandle()で自プロセスがロードしてい
るkernel32.dllのハンドル取得

GetProcAddress()でLoadLibraryA()の関数アドレ
ス解決(ASLRが無効でなければならない)
CreateRemoteThread()でLoadLibraryA()を実行し
DLLの読み込み完了
Dllinjection(読み込ませる側)
• ASLRが有効であれば先の方法は用いることが出
来ない
– 自プロセスと対象プロセスのLoadLibraryA()のアドレ
スが必ずしも一致しない
– IMAGE_DLLCHARACTERISTICS_DINAMIC_BASEをoffに

• PEheader内のIAT(Import Address Table)から解
決することが出来る
• LoadLibraryA()と同様にFreeLibrary()でDllの
detachを実装出来る
Dllinjection(Dll側)
• DllMainの引数として呼び出された理由
(reasonForCall)が渡される
– DLL_PROCESS_ATTACH/DLL_PROCESS_DETACH
Dllinjection(Dll側)
• キーの評価関数(以下getKeyState())の上書
き
– Attach
• ラッパー関数(以下wrapper())を宣言してgetKeyState()
をwapper()で上書き、内部でgetKeyState()を呼び出す

– Detach
• 関数アドレスの復帰

• 書き換えが少ない(関数アドレスの4byteの
み)
Dllinjection(Dll側)
• InlineAsmは__declspec(naked)な関数に分け
る
– O2オプションで死ぬ
AIの手順
• 敵、敵弾、自機の解析
• nフレーム後の敵、敵弾の位置予測
• 頑張って避ける
敵、敵弾の解析
• 敵弾には速度ベクトルが入っている
– 基本的には不変なので誤差なく予測できる

• 敵、レーザーには速度ベクトルが入っていな
い
– しかもランダム性が高い
– メモリ上の解析からは限界がある
nフレーム後の予測
• 真面目にやるには敵弾を動かす関数を読むしか
無い??
– 解析コストがヤバイ

• 知りたいのは関数の結果であって処理ではない
– 敵、敵弾の更新をする関数(以下
refreshEnemyState())を見つけ出して実行するといい
– 変更されたデータを復帰する必要がある
nフレーム後の予測
• refreshEnemyState()はthiscallらしい
nフレーム後の予測
• 敵弾等のデータはグローバル変数として宣言
されていた
– PEheaderからグローバル変数の領域を知れる
– memcpyでまるごと退避、refreshEnemyState()を
実行、最後に復帰
nフレーム後の予測
nフレーム後の予測
• 自機狙い弾(自機の変位で速度情報が変化
する弾)の予測は出来ない
– 処理系から外す
– 自機の位置を変えてシミュレーション ステータス
が変わった弾を除く

• 弾の回避は予測が完全であることを前提
弾の回避
弾の回避
弾の回避
弾の回避
弾の回避
弾の回避
• フレームの先のほうから現在に向かってDP
的に更新するだけ
• 計算量はO(F E)
– F:フレーム数
– E:区間の最大数(弾の最大数)
弾の回避
弾の回避
弾の回避
• 一般にbool値のDPは効率が悪いことが多い
(らしい)
• 中心からの距離の二乗をその点の基本コスト
とする
– 被覆された点はコストINFとする
– 次のフレームの移動出来る点がすべてINFであれ
ばその点のコストはINF
– 次のフレームで移動出来る点の中で最もコストの
小さい点のコストを基本コストに加算して更新
弾の回避
弾の回避
弾の回避
弾の回避
弾の回避
弾の回避
• 縦横の移動しか考えない
– 初期化 O(F ^ 3)
– 通常弾(回転のない矩形)の被覆 O(F E H W)
• H, W : 弾の高さ、幅
• E : 弾の最大数

– レーザーの被覆 O(L F ^ 3)
• L : レーザーの最大数

– 被覆の更新O(F ^ 3)

• 総計算量はO(L F ^ 3)的
• ナナメ移動を考慮するとO((E+L) F ^ 5)
– 弾の被覆を独立して考えるのが難しい
弾の回避
• 1フレームに掛けられる時間は1/60sec
– 10^7 〜 10^8手数

• およそ60F先の計算なら処理落ちしないはず
– 実際は30fpsほどしか出なかった memcpy周りが
重い?
弾の回避
• 実演!!!
弾の回避
• ほとんど死なない
• 「そして誰もいなくなるか」以外は回避出来る
弾の回避
• 「そして誰もいなくなるか」の難しさ
– 弾が自機狙いではなく、弾の発生位置が自機狙
い
– 弾が大量
– 速度が遅い(弾抜けが出来ない)
弾の回避
• 1Fだけ全探索する
– 1F先の場面は自機狙いも含めて誤差がない
– 完全ではないがナナメ対応が出来る!!!
– 単純に17倍の処理時間が掛かる
– 30Fの先読み

• おおよそ20 〜 30F先読みで紅魔郷Exはすべ
て回避出来るらしい
弾の回避
• 死ななくなった
• 【AIリプレイ】紅魔郷Exノーショットノーボムノー
ミスクリア1/2
– http://www.nicovideo.jp/watch/sm23043946
質問

東方紅魔郷AI