
chrome pwning & v8 pwning を始めるための、適切でよく構造化されたドキュメント
Chrome pwning & v8 pwning を始めるための、適切で構造化されたドキュメント
このドキュメントの整理方法
ブラウザは、今日最も使われているテクノロジーの一つです。どの既製コンピュータでも、プラグアンドプレイするだけでブラウザがインストールされているのを確認できます。そのため、攻撃者と脅威モデルの視点から見ると、攻撃者が悪意あるページを通じてブラウザを侵害できることは非常に価値があります。上記の理由から、私は Google の JavaScript エンジン、特に v8 を研究対象に選びました。
v8 プロジェクトは巨大であるため、出発点としてインタプリタ、すなわち d8 を選びました。d8 についてはすでに広範な研究が行われていますが、少なくとも 1 つのバグを見つけたいと考えています。仮に見つからなくても、v8 はブラウザエクスプロイトで使用される基本的なエクスプロイト開発戦略の入り口を提供するため、ブラウザエクスプロイトの研究を前進させることができます。
もう一つの理由は、v8 が複数のブラウザで使用されていることです。内部を見れば、このエンジンが Microsoft Edge でも使われていることが分かります。そのため、複数のバグバウンティ報奨金の獲得機会があります。基盤となる OS については、研究者は Windows と Linux を組み合わせて使うことになるでしょう。v8 のバグは wasm ページを通じたコード実行を可能にし、特定のプラットフォームに依存しないため、シェル取得に関して制限がないからです。
残念ながら、v8 のバグを悪用するとコード実行にはつながりますが、サンドボックスにより任意のコードは実行できません。つまり、レンダラーコンテキストでのコード実行は得られますが、マシン上でコードを実行することはできません。そのためには、サンドボックス用の別のエクスプロイトが必要であり、システムを攻略するにはフルチェーンが必要になります。
そこで、ブラウザハッキングを始めるために、以下の目標を定義します。
このプロジェクトの最初の段階では、Chrome アーキテクチャと、各コンポーネントが互いにどのように連携するかについての知識をできるだけ多く集める必要があります。これをよりよく理解するために、Chromium プロジェクトを複数のサブコンポーネントに分割して、すべてを分離し、適切に分析できるようにする必要があります。具体的には、以下の各コンポーネントがどのサブコンポーネントに分かれるかです。
最初のステップとして最も理にかなっているのは、Chromium アーキテクチャを理解することです。さて、私たちはブラウザを攻撃したいのですが、ブラウザを最初に起動したときには何が起こるのでしょうか? Chromium の実行ファイルをクリックすると、その実行ファイルがいくつかのプロセスを起動します。
順番と名前は次のとおりです。
最初のプロセスは content プロセスと呼ばれます。このプロセスは何をするのでしょうか?
ここまでで簡単にその役割を把握したので、もう少し深く掘り下げます。
Chromium が(少なくとも Windows では)動作する仕組みは、ファイルを DLL にコンパイルし、その後メモリに読み込むというものです。つまり、Chromium ブラウザのコアロジックは chromium.dll にあります。
これはコードによっても確認できます。

これは chrome_exe_main_win.cc から取ったもので、コード全体を読みたいのなら chromium/src/chrome/app にあります。実行を追っていくと、MakeMainDllLoader() を呼び出して DLL ローダークラスを呼び出し、その後「ローダー」を起動します。つまり chrome.dll を読み込み、必要に応じて必要なコマンドラインで再起動します。ローダーをさらに分析するには、同じディレクトリにある mail_dll_loader_win.cc というファイルのコードを理解する必要があります。ファイルの一番下までスクロールすると、MakeMainDllLoader の呼び出しがあり、それがお使いのバージョンに応じて ChromeDllLoader または ChromiumDllLoader を呼び出します。

ChromiumDllLoader は MainDllLoader から継承するクラスであることがわかります。定義から、このクラスは cmdline に渡された引数とプロセスタイプに基づいて DLL を読み込むだけであることがわかります。
Launch メソッドを分析すると、chrome.dll が起動する前に何が起こるかを理解できます。このコメント「// Launching is a matter of loading the right dll and calling the entry point. // Derived classes can add custom code in the OnBeforeLaunch callback.」から、chrome の起動は実際に作業を行う DLL のセットを読み込むことであるとわかります。次に、コマンドラインに渡された引数を取得し、さらにサンドボックスサービスを初期化します。
まず、サンドボックスの初期化を呼び出しているのがブラウザであるかどうかを確認します。次に、サンドボックスの初期化を呼び出したプロセスがクラウドプリントサービスとして呼び出されたかどうかを確認します。また、 がバイナリに渡されたかどうかも確認します。これは基本的に、サンドボックス内で実行しないようにバイナリに指示するものです。これらのいずれかが設定されているかどうかを確認し、いずれかが真であれば、それぞれのオプションを指定してサンドボックスを呼び出します。そして最後に、次の関数に到達します。
これが私たちが注目しているもので、 を呼び出すためのラッパーです。
次のように定義されるプロセスのライフステージを追跡すること```
// The phases are generic and may have meaning to the tracker.
PROCESS_PHASE_UNKNOWN = 0,
PROCESS_LAUNCHED = 1,
PROCESS_LAUNCH_FAILED = 2,
PROCESS_EXITED_CLEANLY = 10,
PROCESS_EXITED_WITH_CODE = 11,
// Add here whatever is useful for analysis.
PROCESS_SHUTDOWN_STARTED = 100,
PROCESS_MAIN_LOOP_STARTED = 101,
プロセスが終了するとすぐにログを記録し、モジュール(つまりChromiumのコンポーネント)が読み込まれたときの情報を保存します。基本的に同じ機能が、異なるスレッドやクラスに対して繰り返されます。興味があれば、chromium/src/base/debug/activity_tracker.h にあります。
その後、"Main()"が以前に呼び出されたかどうかをチェックします。ここでは、ChromeMain()が以前に呼び出されたかどうかを意味していると思います。基本的に、コンテンツプロセスが引数を渡されて呼び出されたかどうかをチェックします。もし呼び出された場合は、ブラウザプロセスが同じ引数を受け取るようにします。次に、プラットフォーム固有のチェックを行います。Windowsが見つかった場合は、CreateATLModuleIfNeededを実行して独自のハンドラーを初期化します。その後も同じようにプラットフォーム固有のチェックを行い、SetupCRTを使用して引数を渡し、プロセスを生成した後に他のプロセスと通信できるようにIPCを初期化する部分に到達します。これを行うコードは次のとおりです。詳細については触れません。後でChromeのIPCメカニズムについてさらに詳しく説明するときに戻ってきます。今のところ、これがChromeのマルチアーキテクチャプロセス間の通信を可能にするメカニズムであると理解しておいてください。IPCが何の略か知らない人のために説明すると、Inter Process Communication(プロセス間通信)の略です。
.
次に、ui::RegisterPathProviderを使用してUIの引数を設定するというよりも「転送」し、トラッカーを呼び出し、content_main_runner->Initialize(std::move(params))を使用して結果を取得し、親コンソールを作成します。これは親プロセスを作成することを意味していると思います。さらにいくつかのチェックを行い、重要な部分に到達します。それが次の画像です。
.
ここでは、IsSubprocessという関数の呼び出しが見られます。
これは基本的に、古いボイラープレートコードを避けるために、コマンドラインで受け取ったプロセスのタイプを1つの関数でチェックし、オプションが渡された場合は、次の選択肢から対応するプロセスを作成します。```
return type == switches::kGpuProcess ||
type == switches::kPpapiPluginProcess ||
type == switches::kRendererProcess ||
type == switches::kUtilityProcess || type == switches::kZygoteProcess;
そこから `content_main_runner->Run();` に到達します。これは本質的にすべての魔法を行います。その内部を見てみましょう。content_main_runner は ContentMainRunner クラスのインスタンスです(そりゃそうですよね..)。それは `content\app\content_main_runner_impl.cc` にあります。
実際にこのコードフローを理解するためには、下から上へと見ていく必要があります。というわけで、もう一度スクロールダウンすると、実際には `ContentMainRunner::Create()` が `ContentMainRunnerImpl::Create()` を呼び出していることがわかります。つまり、ContentMainRunner ではなく ContentMainRunnerImpl の定義を探す必要があるということです。その定義は、前述の同じフォルダにある `content_main_runner_impl.h` にあります。

それが ContentMainRunner から継承していることと、そのコアメソッドを見ることができます。実際にはここにはあまり多くのものはありません。その動作のほとんどは `content_main_runner_impl.cc` で上書きされているからです。
`content_main_runner_impl.cc` の run メソッドの中では、まず DCHECK を使っていくつかのチェックを行います(この関数は一体何なんだ?(```The CHECK() macro will cause an immediate crash if its condition is not met. DCHECK() is like CHECK() but is only compiled in when DCHECK_IS_ON is true (debug builds and some bot configurations, but not end-user builds).``` google docs からの引用です :) ) `is_initialized`、`content_main_params_`、`is_shutdown_` が設定されているかどうかを確認します。

その後、引数を取得し、先ほど述べたタイプを決定します。
そして、それを見つけられない場合は、`InitializeFieldTrialAndFeatureList()` `delegate_->PostFieldTrialInitialization();` を呼び出し、mojo を使ってメッセージとして投稿します。
 .
次に、UI のためにいくつかの設定を行います。
ここでも、コマンドラインで渡された内容に基づいて、`RegisterMainThreadFactories` を呼び出します。`RegisterMainThreadFactories` は `RegisterUtilityMainThreadFactory` のラッパーであり、`g_utility_main_thread_factory` を設定するだけです。名前からすると、これはメインスレッドを作成するものだと思います。メインスレッドは、他のスレッド全体のウォッチャーのような存在です。そして最後にブラウザプロセスを起動します。
私を信用しない場合のために、binja で逆アセンブルした chromium をここに示します。また、そのメモリ内解析も見ていきます。
幸運なことに、`chome.exe.pdb` があるのでデバッグシンボルを利用でき、バイナリのリバースエンジニアリングで苦労することはありません。
Windows 上の Chrome を扱うので、プログラムのメインエントリは `wWinMain` です。
グラフは次のようになります。

この関数自体は巨大なので、必要な部分だけを示します。一連の初期化の後、`MakeMainDllLoader()` を呼び出す箇所に到達します。

この関数が何をするかはすでに説明しました。では、Chrome を動的にデバッグして、これまで述べてきたことをすべて証明するにはどうすればよいでしょうか? まず windbg にロードし、`lm` を実行して実行可能ファイルの名前を探します。次に、`chrome!MakeMainDllLoader` という関数を探し、ブレークポイントを設定して実行を続けます。
その中に入ると 、`ret` に達するまで実行させ、そこから抜けると `chrome!wWinMain+0x764` に到達します。
次に、`chrome!MainDllLoader::Launch` に達するまで実行し、ステップインします。そこから `chrome!MainDllLoader::Load` にブレークポイントを設定して、chrome.dll がどのようにメモリにロードされるかを確認します。そして見ての通り、最初にロードされたものの中に chrome.dll がありました。
ここから先の解析フローは同じなので、完全なメモリ内解析は読者の演習として残しておきます。特別な目的として、コンテンツプロセスがほぼ完了し、ブラウザプロセスが開始する直前のところまで解析を行い、そこで停止します。その目的は、IPC がどのように相互通信を開始するかを示すことだけです。DLL のロード後、`call rax` 命令を探す必要があります。私がやったのは、DLL ロード後の現在の eip から 0x40 命令をリストアップすることでした。
そのアドレスには、実際に探しているもの、つまり `chrome!ChromeMain` があります。
そこから、 でブレークしたいと思います。これは基本的に `content!content::ContentMain` に飛ぶ前の大きなチェックです。そこから数命令進んで `content!content::ContentMain` に到達します。ステップインして `content!content::RunContentProcess` でブレークしたいと思います。
ステップインして、`content!content::ContentMainRunnerImpl::Run+0x430` にブレークポイントを設定します。ステップオーバーするとすぐに 、mojo IPC が開始されるのがわかります。そして、次のプロセス、つまりブラウザプロセスが、実際のブラウザ実装と他のすべてのプロセスの処理を担当していると結論づけられます。
===============================================================================================
2.
前回はコンテンツプロセスを理解したところで終わりました。今回はブラウザプロセスについて見ていきます。
これはコンテンツプロセスの後に起動されるプロセスで、Chrome が起動するプロセスのうち2番目のものです。
簡単に説明すると:
* コンテンツプロセスは初期化ルーチンと依存関係のチェックが中心であり、ブラウザプロセスはコンテンツプロセスによって起動されるため、メインプロセスと考えることができます。
* ブラウザの全生存期間にわたって生き続けます。
* すべてのプロセスの中心的なコーディネーターであり、ブラウザが利用できる最高権限レベルで動作します。
* 最高権限レベルで動作するため、他のプロセスがより高いレベルの操作を必要とする場合、その要求はブラウザプロセスによって処理されます。
* アドレスバー、ブックマーク、戻る/進む/再読み込みボタンなどの機能を制御します。これは最も特権的なプロセスであるため、他のプロセスから与えられたデータを信頼しません。
* 必要に応じて、他のプロセスのために UI、ネットワーク、ファイルシステムストレージなどの特権操作も処理します。
ブラウザプロセスが何をするかを簡単に理解したところで、さらに深く掘り下げていきましょう。
私たちの旅は `src\content\app` にある content_main_runner_impl.cc というファイルの `int ContentMainRunnerImpl::RunBrowser(MainFunctionParams main_params,bool start_minimal_browser)` 関数から始まります。そこで最初に実行される操作は `TRACE_EVENT_INSTANT0` です。 。
それは何でしょうか? その前に、トレース関数とはそもそも何なのでしょうか? https://lwn.net/Articles/379903/ にアクセスすると、次のように定義されているのがわかります。
少し抽象化すると、トレース関数とは、スタックフレーム内の特定のポイントでデータを記録する関数であるという結論に達します。これは実行関数のスタックを記録するだけでなく、最後に実行された関数のローカル変数も記録できます。トレース関数が何かを理解したところで、私たちのトレースが何をするのかを見てみましょう。
https://chromium.googlesource.com/chromium/src/base/trace_event/common/+/refs/heads/main/trace_event_common.h にアクセスすると、トレース関数は「アプリケーションのパフォーマンスとリソース使用量を追跡する」ことを唯一の目的とする関数として定義されているのがわかります。もう少し深く掘り下げて何をするのかを理解すると、それはマクロであり、次のように定義されています。
マクロの上にある短い定義には、「name と呼ばれる単一のイベントを、0、1、または2個の引数で即座に記録する」と書かれています。そして、所属するイベントのカテゴリが有効でない場合は、何も実行しません。パラメータとして、メインカテゴリに "startup"、サブシステムとして "ContentMainRunnerImpl::RunBrowser(begin)" があり、3番目のパラメータは `TRACE_EVENT_SCOPE_THREAD` です。これは `#define TRACE_EVENT_SCOPE_THREAD (static_cast<unsigned char>(2 << 2))` で、値から考えると、それぞれのインスタントイベントのIDであると思います。つまり、本質的には、RunBrowser 関数に実行が入ったという事実をトレース(ログ)しているだけです。次に、ブラウザのメインループがすでに開始されているかどうかをチェックし、すでに開始されている場合は関数を終了します。 。それからフラグを設定し、その後、コードのかなり興味深い部分に到達します。mojo_ipc メカニズムのサポートがあるかどうかを確認し、ある場合は `ShouldCreateFeatureList` を使用して、さまざまなプロセスでフィーチャーリストを作成し、それらの初期化を試み、最後に mojo フィーチャーの初期化を試みます。 。それからスレッドプールを作成します。
スレッドプールとは一体何なのでしょうか??? Wikipedia から引用すると:「コンピュータプログラムにおいて実行の並行性を達成するためのソフトウェアデザインパターン」。より適切に説明すると、「スレッドプールは、監視プログラムによって並行実行のためにタスクが割り当てられるのを待つ複数のスレッドを維持する」ものです(これも Wikipedia からの引用です)。さらにわかりやすく説明するために、工場で働く2列の作業者を想像してください。列aと列bと呼びましょう。彼らはすべてボスに監督されています。ボスを列cと呼びましょう。列bは、列aが仕事を終え、列cから通知を受けるまで待たなければ仕事ができません。列aも同様です。これがスレッドプールです。ここに小さなC言語の例もあります。
これは https://stackoverflow.com/questions/15752659/thread-pooling-in-c11 から恥ずかしながら取ってきました。次に、プラットフォーム固有の初期化を行う `PreBrowserMain();` の呼び出しがあります。その後、`BrowserTaskExecutor::Create();` を呼び出す箇所に到達します。

名前がかなり興味深いので、これが何をするのか深く掘り下げてみましょう。経験に基づく推測から、これが興味深いものである可能性に気づけます。寄り道は `content/browser/scheduler/browser_task_executor.h` の中から始まります。そこでは、`BrowserTaskExecutor` は「ブラウザプロセスの実際のタスクキューに `base::TaskTraits` をマップする」ことを目的としたクラスであることがわかります。ファイルを少し下に進むと、まず  があり、`BaseBrowserTaskExecutor` から継承しています。したがって、`BrowserTaskExecutor` を理解するには、`BaseBrowserTaskExecutor` を理解する必要があります。これは `TaskExecutor` から継承しています。幸いなことに、`BaseBrowserTaskExecutor` は `TaskExecutor` のメソッドを上書き可能なプロパティで上書きしているため、後で他のメソッド呼び出しによって上書きされることになります。興味がある人のために、`base/task/task_executor.h` にあります。調べてみると、`TaskExecutor` は「特定の `TaskTraits` 拡張IDを持つタスクを実行できる」クラスであることがわかります。 。
タスクとは何か、TaskTraits とは何か。タスクについてはすでに触れましたが、復習すると、Chrome のプロセスの1つです。では TaskTraits とは何でしょうか? `base/task/task_traits.h` にあり、次のように定義されています。「スレッドプールがより良いスケジューリング決定を行うのに役立つ、タスクに関する情報をカプセル化する」。BrowserTaskExecutor に戻ります。呼び出すメソッドは `Create()` で、その解析は次のとおりです。
まず、現在のタスクを `SingleThreadTaskRunner` から実行する必要があるかどうかを確認します。つまり、このタスクを独立したスレッドで実行する必要があるかどうかです。これは、tls へのポインタを取得することで行います。次に、UI とスレッドスケジューラを初期化します。基本的にここでは、UI に関連する将来のイベント用のスケジューラを初期化します。 。
この機能については以上です。content_main_runner_impl.cc の解析に戻ると、ここに到達します。
variations ids provider クラスのファイルを探すと、`components/variations/variations_ids_provider.h` にあります。そこにはかなり興味深いものがあります。`.mojom.h` ファイルが含まれています。その定義は `Debug/gen/components/variations/` の variations.mojom.h にあります。つまり、これは IPC メカニズムと何か関係があるということです。クラスの実際の定義を見てみると、「カスタム HTTP リクエストヘッダーで送信されるクライアント実験とメトリクス状態を維持するためのヘルパークラス」という注釈があります。その動作とソース定義を調べると、これは単に関数のマーカーとして使用されていると結論づけられます。これは「`GetClientDataHeaders()` に提供されるサインインパラメータ」をマークします。つまり、スタック実行のどこかで `GetClientDataHeaders` への呼び出しがあり、後でパラメータ付きで呼び出されることになります。次に `delegate_->PostEarlyInitialization(!!main_params.ui_task);` を実行します。これは単に、UI タスクを開始しなければならないことを mojo の IPC に投稿します。さらに初期化を行い 、`RunBrowserProcessMain` を呼び出すところに到達します。 。もし私と同じように、ここで GUI が起動するのを見られると思っていたなら、それは間違いです。master oogwgay からの引用です。
その後、さらにいくつかのチェックを行い、`BrowserMain` に到達します。
それを調べると、いくつかのトレースが行われ、その後  に到達します。ここで実際に GUI を起動します。もし私を信じないなら、動的解析に到達するまでもう少し辛抱する必要があります。次に `Run` メソッドに到達します。これが私たちが関心を持つ実際の内容です。それに続いて、ブラウザプロセスを理解するための次のポイントに進みます。しかし、ここでまた短い寄り道をして、`Initialize` メソッドがどのように作成されるかを調べてみましょう。すぐに、init メソッドの実行をトレースし、その前にヒストグラムを作成しているのがわかります。
次に `initialization_started_` フラグをチェックして、初期化段階に達したかどうかを確認します。達していない場合は、Chrome が使用するグラフィックスライブラリである skia を初期化し、このメソッドの実行にかかった秒数を後でヒストグラムで使用するために数える「タイマー」を開始し、デバッガーがプロセスにアタッチするのを待つパラメータをバイナリに渡したかどうかを確認し、最終的に `notification_service_` を開始します。これは、経験に基づく推測ですが、スレッドプールのメインウォッチャーにサービスを開始するタイミングを通知します。 。
次に、Chrome に必要なフォントを初期化し、すべてのプロセスのコーディネーターであるメインブラウザループを作成します。そして、私たちにとって興味のない3つのメソッドをスキップします。
main_loop_->CreateStartupTasks();
int result_code = main_loop_->GetResultCode();
ここから、私たちが興味があるのは `CreateStartupTasks` に入ることです。そこから `content\browser\browser_main_loop.cc` を見ると、`createstartuptask` メソッドの中にある `startup_task_runner_->RunAllTasksNow();` に注目します。`starup_task_runner_` は `StartupTaskRunner` であり、同じディレクトリの `startup_task_runner.cc` ファイル内にあります。`RunAllTasksNow` メソッドを調べると、それが行うのは単にすべてのタスクを反復処理して実行することだけであることがわかります。
==========================================================================================
動的解析の時間ですさて、レンダラーをキャッチするには、windbg でバイナリを起動する必要があります。これは、File->Open Executable を選択し、引数として --renderer-startup-dialog --no-sandbox --wait-for-debugger-children=renderer --renderer-process-limit=1 を渡すだけで実現できます。。その後、content!content::StartupTaskRunner::RunAllTasksNow+0x88 にブレークポイントを設定すると、IPC メッセージと、その後のレンダラーをキャッチできます。参考までに、一度実行した後のデバッガはこのようになっているはずです。。chrome is running in full browser mode というメッセージが表示されるはずです。そこから1から5まで数えて、さらに4回実行します。すると、次のようなものが表示されるはずです。。プロセスモニタには procmon を使うことをお勧めします。レンダラープロセスが起動するときに監視できます。その後、別のデバッガにフックします。上記の引数を使った場合は、レンダラーの PID を知らせるメッセージボックスがポップアップ表示されるはずです。。そこから base!base::RunLoop::Run にブレークポイントを設定したいと思うでしょう。残念ながら、content!content::RendererMain では何らかの理由でブレークできません。おそらく、プロセスが RendererMain 関数を終了した直後にフックし、それがスレッドによって実行されるためです。とにかく、4回実行した後の IPC は次のようになります。。これは GUI が開始されたことを示しています。そして、レンダラープロセスが起動した後の IPC は次のようになります。。その後、以下の場所にブレークポイントを設定します。
content!content::StartupTaskRunner::RunAllTasksNow+0x88 と content!content::RunOtherNamedProcessTypeMain。実行させます。
============================================================================================================
3. さて、ブラウザプロセス解析の第2部として、レンダラーを理解してデバッグできるところまで来ましたが、まだそこには進みません。私は RunBrowserProcessMain の後に解析する関数をあえて1つ残しました。厳密には RunBrowser の後ですが、覚えているかもしれませんが、RunBrowser は RunBrowserProcessMain のラッパーです。私が説明から外しておいたのは、src/content/app フォルダ内の content_main_runner_impl.cc というファイルにある、レンダラーを生成した後に呼ばれるもう1つの関数、RunOtherNamedProcessTypeMain です。このプロセスは、他のすべてのプロセスを実行する役割を担っています。。では、コードで何が起こっているのかを理解しましょう。そのプロトタイプは  のようになっており、cmdline に渡された引数、どのプロセスタイプを期待するか、そして chrome デリゲートを受け取ることがわかります。次に関数の先頭に進むと、プラットフォーム固有のチェックを行い、どのプロセスに対してイベントハンドラをインスタンス化するかを確認するマクロ定義があります。つまり、コンソールプロセス用の関数を実行するのか、ブラウザプロセス用の関数を実行するのかを確認します。。次に、渡されたプロセスを反復処理し、既知のプロセスのリストと比較して、それぞれのプロセスを実行します。。該当するプロセスが見つからない場合は、誰かが実装したカスタムプロセスです。
カスタムプロセスを実装するためのリンクはこちらです。この例は、動的解析の第2部で使用します。https://bitbucket.org/chromiumembedded/cef/wiki/Tutorial 。
=====================================================================
動的解析パート
動的解析の最初のパートは次のように始まります。まず、管理者権限の cmd.exe から次のコマンドを実行します: windbg.exe chrome.exe -G -o --renderer-startup-dialog --no-sandbox --wait-for-debugger-children=renderer --renderer-process-limit=1 --allow-pre-commit-input --allow-sandbox-debugging 。その後、.childdbg 1 を設定して、新しく生成された子プロセスをデバッグできるようにします。要するに、上記のコマンドの目的は「すべての子プロセスにアタッチしていることを確認する」ことです。マルチプロセスデバッグについて正しい方向に導いてくれた @spoofyroot と @_coreDump に感謝します。.childdbg 1 を設定した後の状態は次のようになります。。次に何が起こるかを他の方法ではキャプチャできなかったので、ビデオを作りました。次の展開はこのビデオで説明します。https://streamable.com/9t4iof 。基本的には、content!content::StartupTaskRunner::RunAllTasksNow+0x88 を最後に実行した後、IPC 関連の処理を扱う新しいプロセスが生成されるので、content!content::RunContentProcess にブレークポイントを設定し続けて、ヒットするまで待つ必要があります。これが私が「経験に基づく試行錯誤」と呼ぶものです:)) 。
=====================================================================
4. レンダラー解析
!免責事項
レンダラーコードの解析を行うにあたり、レンダラー内で何が正しく行われているかを理解するために、blink コードにも少し深く踏み込みます。
このコースのこの部分を書いているときに、このプロセスが何をするのか、そもそもなぜ調べるのかについて簡単に説明し忘れていたことに気づきました。ご存知のように、これは Chromium が起動する3つ目のプロセスで、レンダラーと呼ばれます。しかし、なぜ「レンダラー」と呼ばれるのでしょうか。その名前の由来は、Web サイト上で見えるすべてのものをレンダリング(描画)するのが仕事だからです。基本的に、Web サイトにアクセスしたときにテーブルがテーブルとして見えるのはこのためですし、CSS によってテキストの一部をカスタマイズできるのもこのためです。あるいは、JS が黒魔術*を実行できるのもこのためです。これを解析するもう1つの重要な理由は、バグの大部分がここから発生するからです。HTML、CSS、JS、その他のコンポーネントのいずれについても、bugs.chromium.org の Chromium では blink の下に表示されるでしょう。
* レンダラープロセスは複数存在します。ブラウザが現在開いているタブごとに、完全に独立したプロセスが1つあります。
* このプロセスは、実際の Web サイトタブ内のすべてを制御します。
* 2018年以降、iframe はアップグレードされ、すべての iframe がタブを持つことができるようになりました。そのため、iframe の各タブには個別のレンダラープロセスがあります。これは Site-Isolation と呼ばれます。
* その役割は、Web サイトを解析し、Web サイトの内部にあるもの(テーブル、画像など)を画面に描画し、JavaScript を実行することです。
* サンドボックス化されています。
* 中核では Blink と呼ばれるレンダリングエンジンを使用します。
* chrome://、devtools://、chrome-error:// などの URL スキームを作成し、処理します。
* レンダラープロセスに対して、実際にすべての解析と重い処理を行う Blink エンジンを初期化します。
前回はブラウザプロセス解析を終えたところでした。そして今、みんなが待ち望んでいたレンダラー解析のパートに進む時が来ました。そうそう、Chromium で遊んでいたときにクラッシュさせることができて、次のスタックトレースが得られました。。これに基づくと、旅の始まりは `\content\renderer\renderer_main.cc` にある content::RendererMain です。そう、出発点は RendererMain です。まず、それがどのように定義されているか、そしてその内部を少し分析してみましょう。。MainFunctionParams 型のパラメータを受け取っていることがわかります。つまり、この関数はバイナリに渡された引数を受け取るということです。次に、RendererMain の呼び出しに到達したことを知るためのトレースポイントを追加し、パラメータの値をデリファレンスして command_line に保存します。次に、プラットフォーム固有のアーキテクチャをチェックするいくつかのマクロがありますが、これは無視できます。、kTimeZoneForTesting に渡された値(テストに使用するタイムゾーン)をチェックし、次に icu と呼ばれる新しいクラスとデータ型に出会います。

さて、その定義のソースを探すと、https://unicode-org.github.io/icu-docs にたどり着きます。ファイルの先頭にある include ディレクティブを見ると、サードパーティライブラリであることがわかります。つまり、これは Unicode の国際化コンポーネントを扱うサードパーティライブラリであり、Unicode サポートのためにこれを使用していることがわかります。では、その関数は何をするのでしょうか。https://unicode-org.github.io/icu-docs を見ると、 のようになっており、パラメータとして設定した値に基づいてデフォルトのタイムゾーンを設定します。次に、skia ライブラリを初期化し、--renderer-startup-dialog を処理します。
。次に、新しいクラス RendererMainPlatformDelegate に出会います。
これは何をするのでしょうか。まず、これはプラットフォーム固有の抽象クラスであることを明示する必要があります。私たちのケースでは、/content/renderer 内の renderer_main_platform_delegate_win.cc というファイルにあります。次のような形です。
。つまり、これはヘルパー関数であり、--no-sandbox を渡した場合にサンドボックスを有効にし、必要なアクションを実行するだけです。それだけです。次に、スレッドの名前を CrRendererMain に設定します。。次に、また新しいクラス RenderThread に出会います。
繰り返しになりますが、それが何をするのかほとんど知らないので、少し探ってみましょう。RendererThread がどのようなものかを理解する旅は、content/public/renderer/render_thread.h から始まります。render_thread.h ファイルの中を見ると、次のように定義されています。。そのファイル全体の中で私たちが興味があるのは IsMainThread です。これは rendere_thread.cc ファイルで定義されており、次のようなものです。(このメカニズムについての長い説明を追加すること)。さらに renderer コードの解析を進めると、待ちに待った瞬間、ついに blink コードを見ることができます。その最初のものは、ライブラリを初期化するものです。。裏側では、関数は次のようになります。。ここで自然に浮かぶ疑問は、これらのクラスは一体何なのか、ということです。WTF とは何か、Platform とは何か。幸いなことに、blink のドキュメントがこれらが何であるかを教えてくれています。
https://docs.google.com/document/d/1aitSOucL0VHZa9Z2vbRJSyAIsAz24kX8LFByQ5xQnUg/edit 。「Directory structure and dependencies」を見ると、platform はジオメトリとグラフィックスを支援するクラスであると推測できます。次に WTF と Partitions クラスについて。Blink のドキュメントは WTF についても述べています: WTF は Web Template Framework に由来し、「コンテナ、文字列ライブラリ、参照カウント機構、ファンクタ、スレッドプリミティブなど、さまざまな基本機能を提供する Blink のベースライブラリ」という意味で、STL ライブラリの「ラッパー」のようなものです。(Blink ドキュメントからの引用(https://chromium.googlesource.com/chromium/src/+/refs/heads/main/third_party/blink/renderer/platform/wtf/README.md))。blink についての詳細を追記予定。
では、renderer_main.cc の解析を続けると、また何をするのかわからないものが出てきます。それは  です。詳細は後日追記予定。次に  を呼び出します。次に、コンパイル時にプラグインを有効にしたかどうかを確認し、有効にした場合はプラグインをロードします。
。その後、さらにいくつかのチェックを行いますが、説明は省略します。説明がかなり長くなり、この段階では必要ないからです。TL;DR で言うと、RenderProcess の初期化前にサンドボックスを有効にするかどうかのチェックです。そして、ついに  に到達します。(残りの関数の詳細は後日追記予定)。これはレンダラー解析の終わりではありませんが、実際のレンダラー解析の開始点と考えることができます。なぜなら、後で見るように、ここで最も興味深いことのほとんどが起こるからです。つまり、これをレンダラーのコードと見なすことができます。私たちが興味があるのは RenderThreadImpl です。これは  のようなものです。そのコードの中で私たちが興味があるのは Init() 関数で、次のようなものです:
、しかし、それはもっと長いです。:) 残念ながら1枚の画像に収めることはできないので、その冒頭をキャプチャしました。それ以外に、InitializeWebKit() から先に起こることは、あまり興味がありません。そのほとんどは GPU プロセスとの IPC 通信であり、今は対象外だからです。幸運なことに、その関数の中に、私たちが興味がある関数の1つ、正確には InitializeWebKit() も見つけました。これもまた次のようなものです。。InitializeWebKit の内部で何が起こっているのかをよりよく理解するために、renderer_main.cc の説明という目的から少し脱線します。理解するのは大変だとは思いますが、この混乱を整理してみましょう。InitializeWebKit() は、このプロセスに渡された引数を取得することから始まります。次に、コンパイル時に -dENABLE_VTUNE_JIT_INTERFACE を有効にしたかどうかを確認し、有効にした場合は、cmdline に渡された enable-vtune-support オプションを確認します。それは何でしょうか?Google で検索すると、https://www.intel.com/content/www/us/en/develop/documentation/vtune-help/top.html へのリンクが見つかり、そのページには「シリアルおよびマルチスレッドアプリケーション向けのパフォーマンス解析ツール」と書かれています。つまり、要するに Chrome のパフォーマンスを向上させるものです。次に blink を初期化します。。blink の初期化プロセスを理解するために、簡単に説明します。詳細は次の章で blink を深く見ていくときに説明します。ここでまた見知らぬクラス、RendererBlinkPlatformImpl に出会います。これは次のようなものです。
。また、これは
content/renderer/renderer_blink_platform_impl.cc にあります。名前から、プラットフォームに基づいて実装された抽象クラスであると結論づけることができ、また、 から継承していることもわかります。RendererBlinkPlatformImpl が実際に何をするのかを見ていくと、プラットフォームベースのチェックを行い、そのチェックの結果に基づいてフラグを立てることがわかります。。次に、TLS へのポインタを取得して、現在のスレッドが RendererThread であるかどうかを確認します。
blah blah blah(クラスについての内容をさらに追加するかどうか検討中)。そして、私たち全員が待っていたかもしれないものにたどり着きます。それは V8 コードの最初の部分です。それが  です。これは何をするのでしょうか。まず、ドキュメントから、v8::isolate は V8 エンジンのインスタンスであることがわかります。つまり、ヒープマネージャ、ガベージコレクタなどを含む V8 ランタイムの独立したコピーですが、スクリプトを実行するには十分ではありません。blink::MainThreadIsolate() は次のように定義されています。
。これはファイル blink/renderer/platform/bindings/v8_per_isolate_data.cc にあり、さらに V8PerIsolateData::MainThreadIsolate() は次のようになります。
。V8PerIsolateData は、ご想像のとおり、これもまたクラスであり、bindings/core/v8/V8PerIsolateData.h にあります。おおよそ次のようなものです。。blah blah blah(詳細追記予定)。次に、kDisableThreadedCompositing(フラグが実際にどのように見えるかは後日追記予定)をコマンドラインに渡したかどうかを確認します。。渡していない場合は、コンポジタスレッドを開始します。それは何でしょうか?https://frontendmasters.com/courses/web-performance/the-compositor-thread/ から引用すると、その「唯一の仕事はビットマップを描画し、ビットマップを取得して GPU に送り、画面に表示すること」であるスレッドです。次に、スキームと呼ばれるものを登録します。。基本的に、ソースを表示するときに URL の前に何かが付いているのを覚えていますか?例えば「view-source:website」のようなものです。ええ、それは実際にはレンダラーによって処理されます。そして、これらのスキームは他にもあります。登録とはどういう意味でしょうか?まだ確信はありませんが、「ユーザーが来てこの処理を行ったときに、これを処理してほしい」というような意味だと思います。とにかく、次のような感じです。。詳細を追記予定。では、最後のコードの断片も詳しく説明するために追加します。
. blah blah blah そして最後に、renderer_main の最終部分に到達しました。詳細で更新予定。

=====================================================================
動的解析パートさて、これを動的にデバッグするために、私と同じくらい初心者である場合は、cmd.exe から windbg を次のように実行したくなるでしょう: windbg.exe chrome.exe -G -o --renderer-startup-dialog --no-sandbox --wait-for-debugger-children=renderer --renderer-process-limit=1 --allow-pre-commit-input --allow-sandbox-debugging --time-zone-for-testing="US/Pacific"、そして生成された子プロセスをデバッグできるように .childdbg 1 を設定します。そこから bp content!content::RendererMain を設定し、5回か6回実行させます。(動画の4を参照するように変更した方がより明確です)その後、 に到達します。







--no-sandbox
chrome_main次に、それが何をするのかを見てみましょう。chromium.dll を Binja で開きます。
https://blogs.igalia.com/jaragunde/files/2019/03/chrome-init-sequence.png のこの画像に基づくと、Binja で何を探すべきかおおよそ見当がつきます。
Binja でのグラフの見え方は次のとおりです。

より良く追跡するには、ChromeMain と呼ばれる関数を探してください。これが Chrome のメイン(つまりコア)ロジックです。その中に、chrome.dll が起動時に実行される際の前述のフェーズが見られます。
ここでは、最初に sub_180001420、sub_180017020、sub_180017020 などの関数を呼び出しています。これらは Chrome がどのようにインストール/コンパイルされたかに関する詳細を確認します。ソースコードを使ってそれらの属性を追跡できます。最初の関数 sub_180001420 は UmaHistogramEnumeration に対応し、次に sub_180017020 は InitializeFromPrimaryModule で、3 番目で最後の sub_180017020 は chrome_main_delegate です。
chrome_main_delegate を繰り返し言っていますが、その目的を定義していませんでした。ChromeMainDelegate は ContentMainDelegate から継承するクラスで、主に起動関連の関数とプロセス呼び出し処理を提供します。必要に応じて、カスタムの ContentMainDelegate インターフェースを実装して Content モジュールのデフォルト動作を変更し、Chromium でカスタムの ChromeMainDelegate クラスを使って起動プロセスの動作をカスタマイズすることもできます。

内部には、いくつかのデータ領域に対して XOR を実行し、それをいくつかのレジストリと連結して Chrome の起動に関するオプションを取得する関数呼び出しがあります。

また、関数 sub_180001510 は 2 つのコールバックを持つスコープ付きポインタ参照を作成していることに注意してください。それが行っていることはあまり重要ではありませんが、BindStateBase が何かを学ぶのは興味深いです。これは Chrome のベースコードで頻繁に出会うでしょう。
さて、最後の 3 行が何をするのか疑問に思うかもしれません。正確には
です。まず、最初の行は基本的に Closures 用の std::unique_ptr<> を作成します。クロージャとは何だって?! Mozilla Developer から引用すると、「クロージャは、関数をその周囲の状態(レキシカル環境)への参照と一緒に束ねた(閉じ込めた)組み合わせです。言い換えると、クロージャは内部関数から外部関数のスコープにアクセスできるようにします。JavaScript では、クロージャは関数が作成されるたびに、関数の作成時に作成されます。」実際には、外側の関数内にあり、外側の関数の変数にアクセスする関数のことです。例:
。
つまり、クロージャが確実に実行されるようにするのです。そして、他の 2 行は Chrome がクラッシュしたときのダンプの特定の動作を設定するだけです。crashes.InstallDetails::Get().VersionMismatch() patchuie aici
さらに進むと、実行時にバージョンを確認し、一致しない場合はクラッシュします。そしてコマンドライン解析に到達します。
sub_184171800 の中に入ると、それほど大きくないことがわかります。
まず、現在のプロセスに渡された引数を取得し、次に StringPiece を取る関数を呼び出します。StringPiece は基本的に std::string のクラスラッパーですが、もう少しスマートです。その関数は基本的に、バイナリがヘッドレスで呼び出されたかどうかを確認し、実行されたバイナリの名前が chrome であるかを比較し、USE_HEADLESS_CHROME が設定されているかどうかを確認してから、次のステップである content main に進みます。
提供された引数に基づいて、content main の仕事は対応するシェルを起動することです。
シェルに引数を渡さない場合、実行フローは次に進みます。
それが何をするのかを理解するには、そのソースを確認する必要があります。/src/content/app/content_main.cc にあります。
一番下までスクロールすると、ContentMain 関数の定義があります。そこでは 2 つの関数を呼び出していることがわかります。1 つは ContentMainRunner を初期化するもので、このクラスはブラウザの IPC、SQL、ネットなどの作成という文脈での「プロセス生成」をすべて処理します。もう 1 つは、基本的にサブプロセスのタイプを確認して ContentMainRunner クラスに渡す関数です。
ここで一歩下がって、コードフローを理解しましょう。まず RunContentProcess を調べます。ContentMainRunner はかなり複雑だからです。最初のステップとして、GlobalActivityTracker を作成します。なんてことだ!!! あなたの推測どおり、Google はあなたを追跡しています :))) いや、冗談です。Google 訴えないでください。真面目な話、これは各スレッドを追跡するスレッドトラッカーであり、デバッグを改善するためのいくつかの便利なことを行います。例えば:
これは本質的に、各スレッドの識別子として一意の整数が使用されることを意味し、プロセスが壊れた原因をよりよく理解できるようにします。
例:```
kTypeIdActivityTracker = 0x5D7381AF + 4, // SHA1(ActivityTracker) v4
kTypeIdUserDataRecord = 0x615EDDD7 + 3, // SHA1(UserDataRecord) v3
kTypeIdGlobalLogMessage = 0x4CF434F9 + 1, // SHA1(GlobalLogMessage) v1
kTypeIdProcessDataRecord = kTypeIdUserDataRecord + 0x100,