トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS

解説/FEBuilderGBA/ポインタの自動計算機能_JP

Last-modified: 2018-01-13 (土) 05:45:14 (6d)

ポインタの自動計算機能 FEBuilderGBAを利用したROM解析(hacker用)


FEBuilderGBAはGBAFEを簡単に改造するためのツールですが、
本格的にコードを解析したい方を手助けすることもできます。

今回は、FE8の関数アドレスをFE6の関数アドレスに自動的に変換したりすることができる、ポインタの自動計算機能を紹介します。

ポインタの自動計算機能

FEBuilderGBAには、関数やデータアドレスの移植機能が搭載されています。
この機能を使うと、FE8Jの関数アドレスから、FE7Uの関数アドレスを求めることができます。

例えば、LZ77圧縮されたデータを解凍する関数を探してみましょう。
先人たちの解析により、私たちは、この関数の場所が以下であることを知っています。
FEBuiderGBAに自動的にやらせてみましょう。

FE8
08013008	LZ77圧縮されているデータの解凍AutoCopyOrDecompressImageToVRAM	void	r0:圧縮データ	r1:解凍場所	{J}
08012f50	LZ77圧縮されているデータの解凍AutoCopyOrDecompressImageToVRAM	void	r0:圧縮データ	r1:解凍場所	{U}

FE7
08013688	LZ77圧縮されているデータの解凍AutoCopyOrDecompressImageToVRAM	void	r0:圧縮データ	r1:解凍場所	{J}
08013168	LZ77圧縮されているデータの解凍AutoCopyOrDecompressImageToVRAM	void	r0:圧縮データ	r1:解凍場所	{U}

FE6
08013ca4	LZ77圧縮されているデータの解凍AutoCopyOrDecompressImageToVRAM	void	r0:圧縮データ	r1:解凍場所	{J}


まずは、小手調べということで、FE8JのLZ77解凍関数 08013008 から、 FE8UのLZ77解凍関数を求めてみましょう。

StepA01

まずは、FE8JのROMを開きます。

StepA02

メニューから ツール->ポインタ計算ツール を選択してください。
https://i.imgur.com/BGvcg4I.jpg

StepA03

ポインタ計算ツールが起動します。
https://i.imgur.com/F42b4fi.jpg

StepA04

アドレスの部分に、 FE8JのLZ77解凍関数 08013008 を入れます。
https://i.imgur.com/Tkx9MZe.jpg

StepA05

次に、"別ROM読込"ボタンをクリックして、FE8UのROMを開きます。
https://i.imgur.com/cK1ryMk.jpg

StepA06

すると、自動的に比較が行われ・・・・
https://i.imgur.com/Z4LbBb8.jpg

StepA07

08012F50 という数字が表示されました。
この数字は、FE8UのLZ77解凍関数 08012F50 の値と同一です。
https://i.imgur.com/3JkYdFf.jpg

魔法?

いったいどうやって実現しているのでしょうか?
これはバイナリ比較を行うことで実現しています。
その関数と似たような処理は似たようなアセンブリコードを持っているはずだという仮説から作られています。

もちろん、たまに間違うこともあるのですが、かなりの精度で正解を言い当ててくれます。
(当然ですが、バイナリ比較には、MAPファイルを利用していません。)

再度検索したい場合は、アドレスの項目でエンターキーを押してください。

FE8JからFE6の関数を見つける

それでは、今度は、FE6の同様の関数を見つけてみましょう。

StepB01

"別ROM読込"ボタンを押して、FE6のROMを読みこみます。
すると、自動的に比較が行われ・・・・
https://i.imgur.com/55CluzB.jpg

StepB02

おや、今度はうまくいきませんでした。
0xFFFFFFFF が表示されています。見つからなかったようです。
https://i.imgur.com/voJj0jV.jpg
これがこのツールの限界なのでしょうか?
そんなことはありません。

自動追跡システムのレベルを上げればよいのです。
ディフォルトでは、正確さを重視しています。
FE8J->FE8Uのような同世代のROMならば、これでもいいのですが、FE8J->FE6のような世代を超える場合は、バイナリが多少違います。
そのため、多少の正確性を犠牲にして、自動追尾レベルを上げる必要があります。
自動追尾レベルを上げると、曖昧でもマッチするようになります。

StepB03

とりあえず、早速やってみましょう。
今回は、最大の追跡レベル7にしてしみましょう。
https://i.imgur.com/P9h21Rm.jpg


追跡レベルを変更したあとは、再検索をするために、アドレスのテキストボックスでエンターを押してください。
https://i.imgur.com/RGffwTx.jpg

StepB04

アドレスのテキストボックスでエンターキーを押すと、解析が開始されます。
今回は、追跡レベルを上げたので、比較に少し時間がかかります。
https://i.imgur.com/wMgVEYh.jpg

StepB05

今回は、08013CA4という数字が表示されました。
この数字は、FE6のLZ77解凍関数の数字と一致しています。

やりました。自動的に、FE8Jの関数から、FE6の関数のアドレスを見つけることができました。
https://i.imgur.com/3kbgn7b.jpg

データ追尾機能

ROMのデータを自動的に追跡できることがわかりました。
それでは、RAMのデータはどうでしょうか?

FE8
0202BCEC	ステージの領域	{J}
0202BCF0	ステージの領域	{U}


今までは追跡でませんでしたが、新しく参照値から検索機能が実装されました。
早速やってみましょう。

FE8Jのステージの領域 0202BCEC から、FE8Uのステージの領域を検索してみましょう。

StepC01

アドレスのテキストボックスに、0202BCECと入力します。
そして、"別ROM読込"でFE8UのROMを読みこみます。
https://i.imgur.com/Spv9eYV.jpg

StepC02

すると、自動で検索が行われ・・・・
https://i.imgur.com/7MYsqSr.jpg

StepC03

0202BCF0という数字が表示されました。
これは、FE8Uのステージの領域の数字と一致しています。
RAM領域も検索することできてしまいました。
https://i.imgur.com/1qa3zjh.jpg

魔法?

これはLDR参照を利用した技です。
アルゴリズムは以下の通りです。

1. 元ROMで指定された数字をLDR参照している部分を見つける
↓
2. そこから関数のプロローグまでさかのぼる。(検索する余地を増やすためです)
↓
3. さかのぼったバイト数を記録する.
↓
4. その関数が、相手ROMにあるかどうかを調べる
↓
5. 相手ROMに関数があった場合、そのアドレスに、先ほどさかのぼったバイト数をアドレス加算する。
↓
6. 加算した場所がLDR参照かどうかを見る
↓
7. LDR参照だった場合、その値を採用する.


これもまた、処理が似ているならソースコードも似ているだろうという仮説から由来するアルゴリズムです。
LDR値はRAMポインタですから、検索することができませんが、
それを呼び出しているコードはROMデータですから、検索することができます。
そして、コードは処理が同じならたいてい似ているものです。

検索オプション

https://i.imgur.com/XrwRvpr.jpg
検索オプションを変更すると、手動でもっと細かい比較をすることができます。
自動追跡では誤判定をさけるために、ある程度のところまでしか追跡しません。
もっと深いレベルで追跡したい場合は、手動で設定を変えることができます。

比較サイズ

比較するバイナリサイズを指定します。
当然ですが、小さい方がマッチしやすいです。ただ、小さすぎると誤判定の可能性がより高まります。
なるべく長い比較サイズを利用することが大切です。

内容

探したいものが、ASM関数なのか、データなのかを設定します。
これは次のパターンマッチに利用されます。

比較方法

完全一致の場合、 memcmp のように完全にマッチしたデータのみが検出されます。
パターンマッチの場合、 一部に?ワイルドカードを利用したマッチを行います。

パターンマッチデータは、"内容"で設定したもので変わります。
データの場合、0x08000000 - 0x0A000000 までのポインタと思われる部分がワイルドカードに置き換われます。
ポインタはROMが違えば違うので、ここを無視して比較することで、マッチする可能性を高めます。

ASMの場合は、LDR参照やBL呼び出しがワイルドカードに置き換わります。
ROMによって、LDR直値のデータや、BL呼び出しは違いますし、中には、構造体のオフセット値が違う場合があります。
そのため、これらをワイルドカードに変換してマッチさせます。

スライドして追加検索

指定したバイトをジャンプし無視して検索します。
コンパイラはアライメント調整のため、NOPを埋め込んだり、バージョンによってプロローグで生成される push時にpushされるレジスタの数が違ったりします。
これらを無視して検索します。

自動追跡システム

これらの検索オプションを手でいちいち試すのは面倒なので、自動で勝手にオプションを調整して検索してくれるモードです。
本来は手動で検索オプションを調整して、厳格な検査からスタートして、それでも見つからなければ曖昧な比較としていっていたのですが、面倒になったため開発されました。
これを利用することで、エンターキー押すだけで自動的に比較してくれます。


警告システム

新しく追加した機能です。
ディフォルトは、真ん中のレベルの"参照あれば警告を無視する"です。
間違ったマッチを避けるために、あまりに元ROMと異なるアドレスとマッチしたり、ゼロがたくさんある領域とマッチしたときは警告を表示します。
自動追跡システムで検索したときに、この警告が出たマッチを、成功とみなすかどうかの設定です。
マッチ成功とみなせば、それ以降の検索をしません。

警告をエラーにする警告があれば、マッチしなかったものとします。
参照あれば警告を無視する警告があったとしても、そのアドレスへの参照が取れれば、マッチしたとみなします。
警告を無視する警告が出たとしても、マッチしたとみなします。

一括処理バッチ

まとめてアドレスの変換をやりたい場合、手でいちいちコピペするのは面倒です。
そこで、tsv形式とかで張り付けると、関数アドレスぽいところを自動で検出して、アドレスを求めてくれる機能を作りました。
これが、一括処理バッチ です。

StepD01

tsv形式とかでデータを作り、コピペしてボタンを押すだけです。
https://i.imgur.com/QnLn125.jpg

StepD02

https://i.imgur.com/R8n5rmd.jpg

StepD03

https://i.imgur.com/PHnNVS3.jpg

080D4E34	MPlayContinue	{J}
080D4E50	MPlayFadeOut	{J}
080D4E70	m4aSoundInit	{J}
080D4EE8	m4aSoundMain	{J}
080D4EF4	m4aSongNumStart	{J}
↓↓↓↓変換結果↓↓↓↓
080D4E34(FFFFFFFF->FFFFFFFF,FFFFFFFF->080D013C)	MPlayContinue	{J}
080D4E50(FFFFFFFF->FFFFFFFF,FFFFFFFF->080D0158)	MPlayFadeOut	{J}
080D4E70(FFFFFFFF->FFFFFFFF,FFFFFFFF->080D0178)	m4aSoundInit	{J}
080D4EE8(FFFFFFFF->FFFFFFFF,0029B4C8->080D01F0)	m4aSoundMain	{J}
080D4EF4(FFFFFFFF->FFFFFFFF,FFFFFFFF->080D01FC)	m4aSongNumStart	{J}

アドレスの種類判定

アドレスマッチとは関係ない機能です。
あなたがdebuggerでROM追いかけていると、よくわからない 0x085C5528 等のアドレスにであったとします。
いったいこれは何のデータなのか?
疑問に思うときがあります。

そんなとき、いちいち資料を探すのは面倒です。

アドレスの欄に、あなたが調べたいアドレスを入れて、"アドレスの種類判別"ボタンを押してください。
FEBuilderGBAが知っている領域であれば、その領域の名前が表示されます。

https://i.imgur.com/cLePP7x.jpg


この名前は、repoint用機能のデバッグ用に持っているデータなので、簡素なものですが、ヒントにはなります。
ちなみに、0x085C5528は、何かと問い合わると、MenuDef4だという答えを得られます。

0x085C5528は、デバッグメニューの一部の領域のアドレスでした。
上から4番目のメニューなので MenuDef4と表示されました。
https://i.imgur.com/SdX2ywz.jpg