プロフィール

髭山髭人(ひげひと)

自分の書いた記事が、一人でも誰かの役に立てれば...
活動信条の一つとして「貴方のメモは、誰かのヒント」というのがあります。

このサイトについて

本家HP(packetroom.net)から切り離した いわゆる技術メモ用のブログで、無料レンタルサーバーにて運用しています。広告表示はその義務なのでご容赦。
XREA さんには長年お世話になっています

サ終した Qrunch (クランチ) と呼ばれる技術ブログサービスに掲載していた記事も含まれます

chrome.dll の横幅制限箇所を探るまでのノウハウメモ

概要とかいきさつとか

過去「GoogleChromeの横幅に制限があって、最小500pxまでしか縮められない!」
というイライラを解消したいが為だけに、ゼロ知識からアセンブリをかじってバイナリを弄って制限を撤廃する記事を書きました。

Chromeの横幅最小制限をバイナリ書き換えで撤廃させる

この記事は、まぁざっくりそこに至るまでの探り方とかを示したものです。

日々バージョンはあがっているので、時々それまでの方法が使えなくなったりします。 ここに書いてあることも、いつかそのうち探り方として通用しなくなる日が来るとは思うのですが、まぁそれはそれで。

用意するもの

下準備

1. x64dbg に chrome.dll を突っ込む

まず Chrome.dllx64dbg にドラッグアンドドロップ。 (※ Chrome.exeではない)
但し、どこかに退避コピーさせたchrome.dllではダメ。 必ず、インストールされたフォルダの中のものを突っ込む事
( 依存関係とか上手く呼び出せず、きちんと中身を掘れないっぽいので )

2. chrome.dll で停止させる

x64dbgchrome.dll を突っ込むと 読み込み処理が何かの折、都度停止される。
この時、タイトルバーで探っているアセンブリ名が表示されるので、注視しながら 実行(F9キー)を押して進めていく。
これを、探るアセンブリの表記が chrome.dll になるまで続ける。

設定にもよるが、恐らく最初から chrome.dll 表記とはならず、 多数の dll 表示遍歴を経て、最後に chrome.dll の箇所になる筈
(多分30回以上は実行しないと辿り着かない)

しかもそのくせ、進めすぎると肝心の chrome.dll をすぐに飛び越えて終了してしまうので注意が必要

なので、設定の「Events」タブで DLL EntryDLL Load のチェックを外しておけば
この面倒さが幾らか省かれ、数手でchrome.dllにたどり着けるはずなのでオススメ。

文字列解析

x64dbg の CPU タブ内で 右クリックメニューから 検索 > モジュール内を検索 > 参照文字列 を指定 するとタブが 「リファレンス」 に切り替わり、chrome.dll 内の文字列と判断した場所をリストアップしてくれる

ちなみに執筆時の chrome.dll では、約24万件ほどリストアップされた。
( 数十秒ほど待たされると思います )

画像は 参照文字列 の検索処理前のものです。

足掛かりとなる参照文字列とソースコードを見比べ、付近を特定する

少し話が変わりますが 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" をダブルクリックして当該箇所へ飛ぶ

上記画像にも載せているが、ここでたどり着く場所は

における、

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 - モジュール解析について
    予め x65dbgchrome.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;
}

と表現されているのかは分からないんですけど。

兎も角、「ecx0x1F4 が入っており、それが 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 C90F 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進数における 0x154340 を示します。
書き換える時に 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 で処理を探ると、
ちゃんと命令が書き換わり、 ecx0x154 を代入する処理になっている事が確認できました。
やったぜ😎

あ、ちなみに

あのクソデカ画像を舐めまわすように見ていると、アセンブリ内に

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 よりは縮むものの 途中で値が置き換わっているようで、狙った領域以下にはなりませんでした。 ざんねん。