chrome.dll 改造メモ - 横幅制限箇所を探るまでのノウハウ
用意
-
適当なバイナリエディタ
-
GoogleChrome または Chromium
-
および、それらの公開されているソースコード
下準備
1. x64dbg に chrome.dll を突っ込む
まず Chrome.dll
を x64dbg
にドラッグアンドドロップ。 (※ Chrome.exeではない)
但し、どこかに退避コピーさせたchrome.dll
ではダメ。 必ず、インストールされたフォルダの中のものを突っ込む事
( 依存関係とか上手く呼び出せず、きちんと中身を掘れないっぽいので )
2. chrome.dll で停止させる
x64dbg
に chrome.dll
を突っ込むと 読み込み処理が何かの折、都度停止される。
この時、タイトルバーで探っているアセンブリ名が表示されるので、注視しながら 実行(F9キー)を押して進めていく。
これを、探るアセンブリの表記が chrome.dll
になるまで続ける。
設定にもよるが、恐らく最初から chrome.dll
表記とはならず、
多数の dll 表示遍歴を経て、最後に chrome.dll
の箇所になる筈
(多分30回以上は実行しないと辿り着かない)
しかもそのくせ、進めすぎると肝心の chrome.dll
をすぐに飛び越えて終了してしまうので注意が必要
なので、設定の「Events」タブで DLL Entry
と DLL Load
のチェックを外しておけば
この面倒さが幾らか省かれ、数手でchrome.dll
にたどり着けるはずなのでオススメ。
文字列解析
x64dbg
の CPU タブ内で 右クリックメニューから 検索 > モジュール内を検索 > 参照文字列 を指定
するとタブが 「リファレンス」 に切り替わり、chrome.dll
内の文字列と判断した場所をリストアップしてくれる
ちなみに執筆時の chrome.dll
では、約24万件ほどリストアップされた。
( 数十秒ほど待たされると思います )
画像は 参照文字列 の検索処理前のものです。
足掛かりとなる参照文字列とソースコードを見比べ、付近を特定する
- Chromium - https://source.chromium.org/chromium/chromium/src/+/master:chrome/browser/ui/views/frame/browser_view_layout.cc;l=169?q=BrowserViewLayout::GetMinimumSize&ss=chromium
- GoogleChrome - https://chromium.googlesource.com/chromium/src/+/master/chrome/browser/ui/views/frame/browser_view_layout.cc#169
少し話が変わりますが gfx::Size BrowserViewLayout::GetMinimumSize()
というメソッドがあり、これがウィンドウサイズの許容最小値を返す処理に相当。
最終目標は この処理をアセンブリ上で特定し、そのバイナリを弄る感じとなります。
GetMinimumSize()
処理内に、こんな感じの箇所がある
const bool has_tabstrip =
delegate_->SupportsWindowFeature(Browser::FEATURE_TABSTRIP);
const bool has_toolbar =
delegate_->SupportsWindowFeature(Browser::FEATURE_TOOLBAR);
const bool has_location_bar =
delegate_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR);
const bool has_bookmarks_bar =
bookmark_bar_ && bookmark_bar_->GetVisible() &&
delegate_->SupportsWindowFeature(Browser::FEATURE_BOOKMARKBAR);
ここで引数として使われている各種定数は、それぞれ
Browser::FEATURE_TABSTRIP // 2
Browser::FEATURE_TOOLBAR // 4
Browser::FEATURE_LOCATIONBAR // 8
Browser::FEATURE_BOOKMARKBAR // 10
となっており、これは後々詳しく触れますが、アセンブリを覗くとGetMinimumSize()
該当箇所付近にこの定数が含まれているので、このGetMinimumSize()
関数自体 ないし その付近を探る目印・判断材料としてかなり有用となりました。
...話を戻します。
x64dbg
の検索機能から「文字列参照」を用いて chrome.dll
を解析。
暫くの後、一覧がリストアップされるので、更にそこから "BrowserView"
のキーワードで絞り込みを掛ける
"BrowserView::Layout"
"BrowserViewLayout::Layout"
"BrowserViewLayout::LayoutTabStripRegion"
"BrowserViewLayout::LayoutWebUITabStrip"
"BrowserViewLayout::LayoutToolbar"
"BrowserViewLayout::LayoutBookmarkAndInfoBars"
"BrowserViewLayout::LayoutDownloadShelf"
"BrowserViewLayout::LayoutContentsContainerView"
すると、↑ この辺りが候補に出てくるはずなので、"BrowserViewLayout::Layout"
をダブルクリックして当該箇所へ飛ぶ
上記画像にも載せているが、ここでたどり着く場所は
- https://chromium.googlesource.com/chromium/src/+/master/chrome/browser/ui/views/frame/browser_view_layout.cc#296
- https://source.chromium.org/chromium/chromium/src/+/master:chrome/browser/ui/views/frame/browser_view_layout.cc;l=292?q=BrowserViewLayout::Layout&ss=chromium
における、
void BrowserViewLayout::Layout(views::View* browser_view) {
TRACE_EVENT0("ui", "BrowserViewLayout::Layout"); // ← ※ ここで文字列が与えられている
vertical_layout_rect_ = browser_view->GetLocalBounds();
int top_inset = delegate_->GetTopInsetInBrowserView();
int top = LayoutTabStripRegion(top_inset);
(..以下略...)
の決め打ち文字列が含まれている箇所に相当する。
( いや~、コード内に文字列仕込んでくれて助かりました💦 これがなかったらもっと探すのに苦労してたと思います )
本来のターゲットである BrowserViewLayout::GetMinimumSize()
は、
ソースコード上でいえば BrowserViewLayout::Layout()
定義の幾らか上に位置しているので、同様にアセンブリでも上の方を暫くスクロールで探ってみる。
-
閑話 1 - モジュール解析について
予めx65dbg
でchrome.dll
の「モジュール解析」処理をしておくと、ざっくり 兼 過信は禁物という前提はありながらも、関数のスコープっぽい範囲を黒い縦の枠線?で表示してくれるので、予測を立てやすくなってオススメ。但し微妙なデメリット?もあって 「モジュール解析」を行うと、そのアセンブリ情報を
x64dbg
がデーターベースとして書き出す仕様があるみたい。chrome.dll
は 150MB以上あるので、これを解析すると 結果的に相当な量のデータベースが出力されるうえ、x64dbg の終了時 または 再立ち上げ→chrome.dll 読み込み時 に、このデータベースをRead/Writeする激重処理が都度発生する -
閑話 2 - 次回の為に位置を記録しておこう
x64dbg
にはブックマーク機能(しおり)があり、これを使う事でアセンブリの任意箇所へジャンプできます
右クリックメニューからでも、ショートカットCtrl+D
からでも 設定or解除 が可能です
設定は書き出されますが、上で述べた「モジュール解析」と違い、しおりの操作だけではクソ重ファイルは読み書きされないので気軽に使えます。
調べたい箇所をマークしておくには非常に有効な手段 なので是非👍但し、しおりは開始するアセンブリに左右されるようなので、同じ
chrome.dll
を対象にしたブックマークであっても、
chrome.dll から始めたのか chrome.exe から始めたのか で、設定が共有されないぽいのでご注意。
最初はバイナリ値を直接メモっておく方が良い かも知れません。例えば、この画像の箇所であればそのまま
48 8D 15 EF DB 88 03(次の行)4C 8D 25 38 7B 1F 03
あたりをメモっておき、次回は「検索」→「モジュール内検索」→「パターン」機能を使って、 膨大なバイナリの中から目的の場所を見つけてすぐ移動できるようになるかと。
閑話休題..
"BrowserViewLayout::Layout"
文字列がヒットした箇所を囲む関数ブロックから、更に1つ上の関数とされるブロックにて、
先程示した定数 2
, 4
, 8
, 10
がそれぞれ含まれる、それっぽい個所を発見した。
この定数が含まれる場所(関数ブロック)が、弄りたい場所 GetMinimumSize()
に相当していると特定(仮定)
ブレークポイントを張り、判断材料を増やす
※ ここからは「chrome.dll」ではなく 「chrome.exe」から x64dbg
経由で実行 させていきます。
x64dbg
のデバッガを一度停止させ、代わりに新しく chrome.exe
を放り投げて都度F9キーで実行...
先程特定した chrome.dll
内部のブロック箇所にブレークポイントを幾つか張っておき、そのまま実行(F9キー)を続けて、立ち上げ済みのChrome(またはChromium)本体ウィンドウのふちをドラッグしてサイズを弄ろうとした場合、数あるイベントの停止に加え 今張った任意のブレークポイントにも必ず引っかかる様なら、高確率で確信が持てる。
後はこの該当した関数ブロックを、右クリックメニューの「逆コンパイル」→「ファンクション」にて解析
すると Snowman
というタブに切り替わり、アセンブリよりもう少しソースコードに近い関数のような式?を展開してくれる。
逆コンパイルで式を展開
先程述べた当該箇所を関数ブロックに見立てたもの(画像の灰色で選択した長い部分)を、「ファンクション」機能で解析したもの。
長いけど、それぞれ アセンブリ・逆コンパイル後のファンクション・ソースコード を、1枚のクソデカ画像にまとめておきます
あと先程書きましたが、ファンクション解析の機能自体が、必ず正しくブロックスコープの範囲を炙り出してくれるとは限らないため、ここの逆コンパイル式と構成が酷似しているかどうか だけを判断材料とするのは危険 ...とは、一応記しておきます。
上記式内にある、
struct s0* fun_7ffc53137750(struct s1* rcx, struct s0* rdx)
...以下略...
から始まるブロック・スコープが、ソースコードでいう所の
gfx::Size BrowserViewLayout::GetMinimumSize(const views::View* host) const {
...以下略...
のブロック始まりと同義と見てよい。
ちなみに、実際のコード中で int
を返す処理は、こういった逆コンパイル式の結果でも同じく int
を返す関数で表示される模様。
今回は struct s0*
を返し、
これは今張り付けた 逆コンパイルクソ長コード内における最上部の、
struct s0 {
int32_t f0;
int32_t f4;
};
を示す。 つまり 「struct s0
を返す」≒ 「2つの int
から成る構造体を返す」...ということで、
gfx::Size
(=つまり高さと幅を持つサイズ型)を返す GetMinimumSize()
の特徴とも似ている。
まぁ結果的にそうだった訳なんだけれども、こういった「似ている特徴」というのも、自分にとっては大きな判断材料の一つとなりました。
レジスタを覗きつつ、値と処理の流れを追う
x64dbg
でブレークポイントを貼った箇所や(停止設定に応じた)イベント等で止まると、右側の項目にて「現在扱っているデータ的な物」(?) が可視化されます。 多分、64bitCPUなら RAX
とか RBX
とかの表記になってる筈。
これは レジスタ
と呼ばれるもので、CPU内部の一時的データ格納場所みたいなものなのだとか。
実際にこの中を覗いていきます。
「当該箇所の処理(データの流れなど)を1ステップずつ進めれば、どこかで横幅最小制限値の実数 500
が、やり取りの中に現れるんじゃね?」という読みからなる理屈・動機です。
ステップ実行で追っていると RCX
という領域に 00000000000001F4
の値が入っているタイミングを確認。
ecx
というものは、このRCX
の下位32ビット(4バイト)を表すそうな。
画像を載せるので、雰囲気で伝わればと思います。
それでまぁ、
test ecx,ecx
cmovs ecx,edx
の箇所なんだけれども、ぶっちゃけ素人の自分は細かい仕組みや処理が理解できてないんですが、
雑に言えば「判定と代入」を司っている箇所っぽいです。
何故、逆コンパイル式で
if (ecx39 < 0) {
ecx39 = 0;
}
と表現されているのかは分からないんですけど。
兎も角、「ecx
に 0x1F4
が入っており、それが r15
の構造体に渡されて返却されている」のは確認できました。
それに伴い r15
に渡す値を何らかの形で誤魔化せばOK! というのも分かった次第。
バイナリを書き換え、命令を改竄する
r15_3->f0 = ecx39;
最後に返却する構造体に ecx39
を渡しているので、ここに直接最小幅値を代入出来れば良かったんですが、
なにぶんソースコードを書き換える訳ではなくバイナリなので
mov dword ptr ds:[r15],ecx
が示す処理 = 41:890F
の3バイト分では出来ないと判断。
なので、色々考えた挙句
この直前、ecx39
に触れている最後の箇所、つまり...
test ecx,ecx
cmovs ecx,edx
を示す 85 C9
と 0F 48 CA
の5バイト分を使って、 ecx39
に任意の実数を代入させる処理を仕込む事にしました。
-
編集前
31 D2 xor edx,edx 85 C9 test ecx,ecx ←ここを弄る 0F 48 CA cmovs ecx,edx ←ここを弄る 85 C0 test eax,eax
-
編集後
31 D2 xor edx,edx B9 54010000 mov ecx,154 ←バイナリを書き換える事で、2行分を1行処理(実数の代入)にすり替える 85 C0 test eax,eax
B9 xxxxxxxx
とすると、4バイト分の実数を ecx
へ代入する命令になるようです
ちなみに B8 xxxxxxxx
だと edx
への代入になるのだとか。
16進数における 0x154
は 340
を示します。
書き換える時に 00000154
ではなく 54010000
なのは、まぁそういう決まり事だと思ってください。
これで結果的に ecx39
の中身が決め打ちの実数 340
となり、
BrowserViewLayout::GetMinimumSize()
が返す gfx::Size
の幅情報が必ず 340
で確定します。
今まではどうやっても
ユーザー「ウィンドウ幅縮めた~い」
A「B氏、許可できる最小幅っていくつだっけ?」
B「500pxですね」
A「おk、あ~ごめんね? ユーザー氏、500px未満には出来ないのよ」
という流れでしたが、B氏の処理を書き換える事により
ユーザー「ウィンドウ幅縮めた~い」
A「B氏、許可できる最小幅っていくつだっけ?」
B「340pxですね」
A「おk、じゃあ 340px までは縮めていいよ!」
という結果が得られます。
後は最後に、他と被らない程度に付近のバイナリ値を含めて当該箇所をメモっておき、
書き換え後の値と併せて公開すれば、バイナリ書き換えレシピの完成です😋
- 例
8B 0B 31 D2 85 C9 0F 48 CA 85 C0 0F 48 C2
↓8B 0B 31 D2 B9 54 01 00 00 85 C0 0F 48 C2
chrome.dll
のバイナリ書き換え後、再び x64dbg
で処理を探ると、
ちゃんと命令が書き換わり、 ecx
へ 0x154
を代入する処理になっている事が確認できました。
やったぜ😎
あ、ちなみに
あのクソデカ画像を舐めまわすように見ていると、アセンブリ内に
je chrome.xxxxxxxxxxxx
mov rax,1000001F4
jmp chrome.xxxxxxxxxxxx
という箇所があります。
ここを
48 B8 F4 01 00 00 01 00
↓48 B8 54 01 00 00 01 00
こんな感じで書き換えればOKじゃね?という考えもあったのですが、
幅は確かに 500px よりは縮むものの 途中で値が置き換わっているようで、狙った領域以下にはなりませんでした。 ざんねん。