10: CheatEngineとGhidraでゲームを解析する
概要
一般に実行ファイルを解析する際、デバッガに加えて、機械語 => アセンブリ (ディスアセンブル)
や 機械語 => Cみたいなコード (デコンパイル)
といった事をしてくれるツール
も使って解析する (こういうビルド済み実行ファイルを逆アセンブル等して解析する事をReverseEngineering(RE)と言う)。前回、ダメージを受ける処理を無効化したが、自分が敵にダメージを与える命令も同じ命令であったため、 敵にダメージを与えられなくなってしまった。そこで、プレイヤーと敵を区別したい訳だが、その命令周辺のアセンブリだけを見てもどこに何の情報が入っているのかよくわからない(もっと言えばアセンブリで読みたくない)。
よって、今回はGhidraというツールを使い、前回特定したダメージを受ける命令の周辺のコードの意味を理解することを目的にやっていく。
より具体的には、プレイヤーや敵の構造体 と その構造体のメンバ を解析していく。
この記事は前回の続きです。
ダメージを受けるコードのデコンパイルの整理
前回、 ダメージを受ける処理はGameLogic.Actor::Damage+E5
またの表現で GameLogic+0x20C5
にある事が分かったので、まずはGameLogic.dllをGhidraで読み込む。GameLogic.dllは、
PwnAdventure3/Binaries/Win32/GameLogic.dll
にある。また、Ghidraはここからダウンロードできる。 Ghidraを起動した後、新規プロジェクトを作成し(自分は
for-demo
という名前で作成)、下図のようにGameLogic.dllと、それと依存関係にあるDLLをインポートする。
OKを押すと、依存関係のあるDLLと共にインポートされ、サマリー情報が表示されるので、OK押した後、実際にGameLogic.dllをダブルクリックする。
すると、GameLogic.dll has not been analyzed. Would you like to analyze it now?
と出てくるので、Yes > Analyze
で解析させましょう。
このAnalyzeは実行ファイルによっては結構時間がかかりますが、これに関しては数分で終わるはずです。それでは早速解析していきます。
GameLogic.dllを読み込んで、Analyzeが無事終了したら、以下のような画面になっているかと思いますが、下図の赤く囲った
0x10000000
が、
このGhidra上でのGameLogic.dllのベースアドレスとなっています。
Ghidraの設定によってこの値が違ったりするかもしれないですが、ベースアドレスはとりあえず一番上にスクロールした時の一番上のアドレスと思っておけば良いです。ダメージを受ける処理へのオフセットは、
0x20C5
だったので、ベースアドレスと足して、0x100020C5
まで行ってみましょう
(上のツールバーから、Navigation > Go To ...
を選んでアドレスを入力すれば簡単に飛べます)。すると、まさに前回見たダメージを受ける処理がある事がわかります。 右側のウィンドウのデコンパイルウィンドウの方が分かりやすいので右側を見ていきます。上図でハイライトされてる部分がダメージを受けるコードなので、 ざっくりと
体力 = 体力 - ダメージ
というコードだと言う風にわかります。それなので、param_3
はダメージの値だとわかるので、
この変数名を右クリック > Rename Variable
で damage
とかの名前に変えておきましょう。続いて、
this->m_states
の方は体力のはずですが、ここは名前的に体力という感じがしません。このような場合、デコンパイル結果が間違っている可能性があります。これは別にGhidra自体にバグがあるとかではなく、そもそもデコンパイルというものが 間違いありきでコードを復元しているというのが理由です。なぜかと言うと、プログラムをコンパイルする時、実行ファイルには変数名とかの情報は残らないですし、構造体のサイズとかも どこかに書いてあるわけではないので、自動でGhidraが推測しないといけないからです。Reverse Engineeringでは、こういう小さなデコンパイルの間違いを 修正していくというのも大事な作業の一つになります。
this->m_state
となっているという事は、this
は構造体という事です。そして、この関数の第一引数のデコンパイル結果を見れば、
Actor *this
となっているので、Actor
という型の構造体である事がわかります。構造体のメンバとかを編集できる構造体エディタを開くために、
this->m_state
のm_state
を右クリックして、Edit Data Type
を押しましょう。上図の構造体エディタによると、
m_state
メンバの一個前のメンバにm_health
という明らかこっちだよなというメンバがあります。
this->m_state
となっているという事は、オフセットを見るとthis+48
を指しているという事ですが、this+48
が4バイト手前のm_health
を指すようにするためには、下図のように構造体の最初の方に余計に4バイト追加すれば良いことがわかります。
これで、+ボタンの左側にある 保存ボタン を押すると、以下のようにデコンパイルが切り替わります。
こうすることで、正しいデコンパイル結果になり、ちゃんとダメージを減らすコードなんだなというのがわかります。また、ちょっと下を見れば、体力が0になると(
this->m_health<1
)、スポーンの処理みたいなのがある事がわかります。
this+0x78
とあるので、どうやらActor構造体のオフセット120(0x78)
にスポーン関連の処理を行うコードのアドレスが入っているそうですが、
ここは関係ないのでこのままにしておきます。
ディスアセンブルとデコンパイルとの比較
基本解析はデコンパイルを見ればいいのですが、少しだけ重要な点があるのでディスアセンブルの方も見てみます。ダメージを受ける命令は、
- デコンパイル:
this->m_health = this->m_health - damage
- ディスアセンブル:
SUB dword ptr [EDI+0x30], EAX
0x30 (48)
を見てわかる通り、EDI
がデコンパイルで言うthis
、
すなわち、Actor構造体のインスタンスのアドレスを持っていることがわかります。Ghidraで解析中にこれを知る事に特に意味はありません。しかし、CheatEngineではアセンブラしか見れないわけですが、ダメージを受けるこの処理をCheatEngineで見た際、
[EDI+0x30]
のような表記を見たら、
EDI
はおそらく体力とかをメンバに持つプレイヤーの構造体とかなんだろうなと予想が付くようにして置く事はとても大事になります。
プレイヤーと敵を識別する情報の解析
Actor構造体の全てのメンバは分かっていないですが、現状見えているメンバで、どの情報がプレイヤーと敵の識別に使えそうかを見てみます。この中だと、
m_blueprintName
に名前みたいなのが入ってそうなので、使えそうだとわかります。しかし、実際にじゃあプレイヤーだと
m_blueprintName
にどんな値が入り、敵だとm_blueprintName
にどんな値が入るのかは、
実際にゲームをやっている時にここを覗かないとわかりません(Ghidraはプログラムを実行しているわけではない)。よって、ここでCheatEngineの出番です。どうやってこの値を覗くかは、以下のようにしていきます。
- ダメージを受ける命令にブレークポイントを打っておく
- ゲーム上で敵からダメージを受けると上記のブレークポイントで止まる
- ブレークポイントで止まっていると、その時点でのレジスタの値が見れるので、
EDI
レジスタの値をメモする m_blueprintName
のオフセットはGhidraの構造体エディタを見れば、24 (0x18)
であることがわかるので、メモしたEDI
のアドレスに0x18
を足す- 上記で求めたアドレスをCheatEngineで覗き、値を確認する
EDI
レジスタの値を読み取る。
最後に、この0x62045300 + 0x18 = 0x62045318
を、CheatEngineのアドレスリストに追加して、中の値を見る。
この時点で気づくのは、Ghidraの構造体エディタではm_blueprintName
はActor構造体のアドレスからオフセット24 (0x18)
の位置にあるとなっていたが、実際にそこに入っていた値はer...
と書いてある。なんとなく
Player
のer
っぽいので、4バイト引いたアドレスにしてみると、
となり、オフセットは0x18
ではなく、0x14
が正しい事がわかる!ここで思い返すと、先程自分でActor構造体の一番上に4バイト追加したが、これをしなかったら
m_blueprintName
の方のオフセットは正しかったわけなので、
4バイトを追加するべきだったのは、構造体の先頭ではなく、m_blueprintName
とm_health
の間であった事がわかる(ここでは単純にm_blueprintName
のサイズを4バイト大きくする)。
ここまでで、Actor構造体+0x14
すなわち、EDI+0x14
の位置に、プレイヤーならPlayer
という文字列がある事がわかった。また、今度は同じ手順で敵に攻撃を当ててみると、このようにちゃんと別の値が入っていることがわかる。
よって、Actor構造体のオフセット
0x14
にある、m_blueprintName
メンバの値が"Player"
かどうかで敵と自分を区別でき、
そのActor構造体は、ダメージを受ける命令が実行されるタイミングにおいて、edi
にアドレスが入っていることがわかった。