🎯 カスタマイズ可能で検出回避型のクラウドブラウザ。自社開発のChromiumを搭載し、ウェブクローラーAIエージェント向けに設計されています。👉今すぐ試す
ブログに戻ります

ゴーストワイヤー: ブラウザJavaScriptのためのフリーダのようなランタイム計測

Michael Lee
Michael Lee

Expert Network Defense Engineer

30-Jul-2026

要約:

  • Ghostwireは、ブラウザのJavaScript用のランタイム計測ツールで、Fridaと同じカテゴリーに属します。 両者はソースを読み取って推測するのではなく、ライブ関数境界を観察します。ターゲットは異なります:Fridaはネイティブおよびモバイルプロセスを計測し、GhostwireはChrome内のJavaScriptを計測します。
  • 輸送手段は、OSパイプを介したChrome DevToolsプロトコルです。 GhostwireはChromeを--remote-debugging-pipeオプションで起動するため、接続するためのデバッグポートはなく、コアのためにインストールする必要のあるサードパーティの依存関係もありません。
  • フックはデバッガーブレークポイントであり、ラッパーではありません。 ライブ関数オブジェクトは決して置き換えられないため、fn.toString()は元のソースを返し続けます。
  • 興味深い部分はオラクルです。 実際の(入力 → 出力)ペアをキャプチャし、再実装の候補を書き、Ghostwireに具体的な反例で間違いを証明させます。
  • ライブ実行によって妥当な候補が拒否されました。 12のキャプチャされたペアが正しい関数を12/12検証し、1つの+1が欠けたバージョンは0/13に一致し、正確な失敗入力を生成しました。
  • Ghostwireは現在のところローカルChromeツールです。 リモートWebSocket CDPエンドポイントには接続しないため、現在Scrapeless Scraping Browserセッションを操作することはできません。
  • 認可された場所でのみ使用してください。 通常のブラウザデータ収集用の無料Scrapelessアカウントから始めましょう、これはランタイム分析とは異なる作業です。

難読化されたJavaScriptを読むことは仮説を与えます。それを実行することは事実を与えます。Ghostwireは最初のものを第二のものに変えるために存在します:関数が実行時に実際に返すものを観察し、その基礎的な真実に対して再実装をチェックし、あなたが間違っていたすべての入力を報告します。

Ghostwireとは

GhostwireはChrome内で実行されるJavaScript用のPython計測ライブラリです。Chrome DevToolsプロトコルを介して接続し、ライブ関数オブジェクトにフックを設定し、引数と返り値を記録し、候補の実装をキャプチャされた動作と比較します。

ここで使用されているバージョンは0.1.0、アップストリームコミットはa3fe4ef1e27804ab1e976e28dfa1801ee53aabf8です。Python 3.9以上が必要で、MITライセンスが適用され、プロトコルはWebSocketクライアントライブラリではなくOSパイプを介して実行されるため、コアにはサードパーティの依存関係はありません。

このガイドでは、合成されたローカルページを通して説明します。ランタイム計測は、他のセキュリティツールと同じ認可カテゴリーに属します:あなたが所有するシステムや明示的にテストする許可のあるシステムを分析してください。

GhostwireとFrida:類似性のある場所

Ghostwireを「ブラウザJavaScript用のFrida」と呼ぶことは便利な短縮表現であり、実装よりもメソッドについて正確です。

懸念事項 Frida Ghostwire 0.1.0
主なターゲット ネイティブプロセス、モバイルアプリ、デスクトップバイナリ Chromeで実行されるJavaScript
接続メカニズム ターゲットプロセスにエージェントを注入 CDPを介してデバッガーセッションを接続
フックメカニズム ネイティブ関数アドレスのインターセプター ライブ関数オブジェクトでのDebugger.setBreakpointOnFunctionCall
スクリプティングサーフェス プロセス内のJavaScriptエージェント 外部からプロトコルを駆動するPython
コア依存関係 Fridaランタイムとバインディング なし;OSパイプ経由のCDP

FridaのJavaScript APIは、調査中のプロセス内でエージェントを実行します。Ghostwireはページにコードを挿入することはありません。この違いは意図的です:ライブ関数オブジェクトへのブレークポイントはオブジェクトを変更しないため、ページ自身のfn.toString()チェックでは元のソースが見えます。

類似性のあるのは作業モデルです。両者は静的な読み取りをライブ観察に置き換え、両者とも認可された分析のためのもので、プロダクションデータ収集には使用されません。

必要条件

  • Python 3.9以上
  • ローカルにインストールされたGoogle ChromeまたはChromium
  • ピン留めされたコミットのGhostwireリポジトリのチェックアウト
  • あなたが所有するか、分析する権限のあるターゲットのみ

インストール

コアにはインストールするパッケージがないため、ピン留めされたチェックアウトが全体のセットアップです:

bash Copy
git clone https://github.com/sofianeelhor/ghostwire.git
cd ghostwire
git checkout a3fe4ef1e27804ab1e976e28dfa1801ee53aabf8

何かを実行する前にpyproject.tomlを読みます。version = "0.1.0"requires-python = ">=3.9"、空のdependenciesリストが宣言されており、mcp>=1.0はMCPサーバー用のオプションとしてのみ存在します。

合成ターゲット

このフィクスチャはローカルホストから提供され、故意に単純な値を計算します。認証情報、アンチボットロジック、サードパーティのコードは含まれていません:

python Copy
PAGE = b"""<!doctype html><html><body><script>
function transform(name, value) {
  return btoa(name + ':' + (value * 7 + 1));
}
window.transform = transform;
let i = 0;
setInterval(function () {
  window.lastResult = transform('sample' + i, 10 + i);
  i += 1;
}, 120);
</script></body></html>"""

タイマーが重要です。Ghostwireは呼び出しが行われる際にキャプチャを行うため、繰り返し実施される境界がインタラクションなしにコーパスを生成します。

アタッチとフック

ghostwire.attach()はChromeを起動し、デフォルトのプローブを接続し、Inspectorを返します:

python Copy
import ghostwire

with ghostwire.attach("http://localhost:8000/", headless=True) as gw:
    gw.wait(1.0)
    gw.hook("window.transform", capture_returns=True, label="transform")
    gw.wait(1.5)
    corpus = gw.corpus("transform")
    print("captured pairs:", len(corpus))

capture_returns=Trueは、引数と共に戻り値を記録し、これによりコーパスが真実の基準として利用可能になります。labelは後で検証するためにそれらのペアをグループ化します。

普通のページデータを収集する準備はできましたか? 無料のScrapelessアカウントを開設してください 。その仕事にはマネージドブラウザを使用してください。

現実に対する候補の検証

あなたは再実装を提供し、Ghostwireはキャプチャされたコーパスに対して、実際の関数が見えない孤立したページでそれを実行します:

python Copy
good = "(name,value)=>btoa(name+':'+(value*7+1))"
wrong = "(name,value)=>btoa(name+':'+(value*7))"

good_result = gw.verify("window.transform", good, label="transform")
wrong_result = gw.verify("window.transform", wrong, label="transform")

2番目の候補は現実的な失敗です:構文的には問題ありませんが、意味的には1つずれています。ミニファイドソースを読むモデルはこの種の誤りを定期的に生み出し、出力が依然として有効なbase64のように見えるため、カジュアルなレビューを生き残ります。

完全な実行可能スクリプト

検証に使用されたコンテナには2つの調整が必要で、どちらもGhostwireの欠陥ではなく環境的なものでした:Chromeはrootとして実行する際に--no-sandboxを必要とし、最初のHTTPナビゲーションは標準の20秒CDPの締切を超えるほど遅くなります。

python Copy
import base64
import http.server
import socketserver
import sys
import threading
from pathlib import Path

HERE = Path(__file__).resolve().parent
UPSTREAM = next(
    p / "upstream" for p in (HERE, *HERE.parents)
    if (p / "upstream" / "ghostwire").is_dir()
)
sys.path.insert(0, str(UPSTREAM))
from ghostwire import Browser, Engine, Tracer
from ghostwire.api import Inspector
from ghostwire.crypto import CryptoLogger
from ghostwire.dataflow import DataflowTracer
from ghostwire.objects import LiveObjects
from ghostwire.oracle import Oracle
from ghostwire.origin import OriginTracer
from ghostwire.probes import NetLog, ScriptWatcher


def attach_local(url, extra_flags):
    """ghostwire.attach()と同様の配線で、コンテナ安全なChromeフラグを使用します。"""
    browser = Browser(headless=True, extra_flags=extra_flags)
    browser.cdp.default_timeout = 90.0
    engine = Engine(browser=browser)
    scripts, net, tracer = ScriptWatcher(), NetLog(), Tracer()
    for probe in (scripts, net, tracer):
        engine.add_probe(probe)
    oracle = Oracle(engine, tracer)
    engine.start()
    engine.navigate(url)
    return Inspector(
        engine, scripts, net, tracer, oracle,
        OriginTracer(engine), DataflowTracer(engine),
        LiveObjects(engine), CryptoLogger(engine),
    )


PAGE = b"""<!doctype html><html><body><script>
function transform(name, value) {
  return btoa(name + ':' + (value * 7 + 1));
}
window.transform = transform;
let i = 0;
setInterval(function () {
  window.lastResult = transform('sample' + i, 10 + i);
  i += 1;
}, 120);
</script></body></html>"""


class Handler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-Type", "text/html")
        self.send_header("Content-Length", str(len(PAGE)))
        self.end_headers()
        self.wfile.write(PAGE)

    def log_message(self, *args):
        pass


class QuietServer(socketserver.TCPServer):
    allow_reuse_address = True

    def handle_error(self, request, client_address):
        pass


server = QuietServer(("127.0.0.1", 0), Handler)
port = server.server_address[1]
threading.Thread(target=server.serve_forever, daemon=True).start()

gw = attach_local(
    f"http://localhost:{port}/",
    ["--no-sandbox", "--disable-dev-shm-usage"],
)
try:
    gw.wait(1.0)
    gw.hook("window.transform", capture_returns=True, label="transform")
    gw.wait(1.5)

    corpus = gw.corpus("transform")
    good = "(name,value)=>btoa(name+':'+(value*7+1))"
間違い = "(name,value)=>btoa(name+':'+(value*7))"
    正しい結果 = gw.verify("window.transform", good, label="transform")
    間違った結果 = gw.verify("window.transform", wrong, label="transform")
    フレッシュ = [["alpha", 1], ["beta", 2], ["", 0]]
    フレッシュ結果 = gw.verify("window.transform", good, fresh_inputs=フレッシュ)

    不一致 = 間違った結果["mismatches"][0]
    期待されるデコード結果 = base64.b64decode(不一致["expected"]).decode()
    得られたデコード結果 = base64.b64decode(不一致["got"]).decode()

    assert len(corpus) >= 5
    assert 正しい結果["verified"] is True
    assert 間違った結果["verified"] is False
    assert フレッシュ結果["verified"] is True

    print("ghostwire バージョン: 0.1.0")
    print("キャプチャしたペア:", len(corpus))
    print("正しい候補:", 正しい結果["verified"],
          "テスト済み:", 正しい結果["tested"], "一致した:", 正しい結果["matched"])
    print("間違った候補:", 間違った結果["verified"],
          "テスト済み:", 間違った結果["tested"], "一致した:", 間違った結果["matched"])
    print("反例の入力:", 不一致["input"])
    print("反例の期待値:", 期待されるデコード結果)
    print("反例の結果:", 得られたデコード結果)
    print("フレッシュ入力の検証:", フレッシュ結果["verified"])
    print("フレッシュ入力のテスト:", フレッシュ結果["tested"])
finally:
    gw.close()
    server.shutdown()

## 実行の結果

```text
ghostwire バージョン: 0.1.0
キャプチャしたペア: 12
正しい候補: True テスト済み: 12 一致した: 12
間違った候補: False テスト済み: 13 一致した: 0
反例の入力: ['sample8', 18]
反例の期待値: sample8:127
反例の結果: sample8:126
フレッシュ入力の検証: True
フレッシュ入力のテスト: 3

間違った候補は 0/13 一致 しました。これは、欠落した +1 がすべての結果をシフトさせるため、コーパスがどこでも拒否するためです。より微妙なバグは部分一致を生成し、コーパスがそれを浮き彫りにするのが得意な信号になります。

反例は具体的です: 入力 ['sample8', 18]sample8:127 を生成すべきであり、なぜなら 18 * 7 + 1 = 127 であり、候補は sample8:126 を生成しました。それはブール値の失敗ではなく、デコードされた、実行可能な差異です。

フレッシュ入力チェックは以前にコーパスがなかった3つの入力を検証しました。Ghostwireは観察されたことがない値のためにライブ関数を呼び出し、タイマーが生成したことのないエッジケースをカバーしています。

誠実な限界: Ghostwire と スクレイピングブラウザ

Ghostwireは現在、Scrapeless Scraping Browserセッションを運転できず、その理由はアーキテクチャ的であって設定の問題ではありません。

Browser.__init__--remote-debugging-pipe=JSON でローカルChromeプロセスを起動し、2つのファイルディスクリプタを子に複製し、それらを PipeConnection に渡します。すべてのプロトコルメッセージはそれらのディスクリプタを通過します。スクレイピングブラウザは代わりにリモートWebSocketエンドポイントを公開し、Ghostwireにはそれと対話するWebSocketトランスポートがありません。

Engine(browser=...) はブラウザのようなオブジェクトを受け入れ、これは本物の拡張シームです — この記事のランナーはそれを使ってコンテナーセーフなChromeフラグを渡します。しかし、それをリモートエンドポイントに到達させるには、PipeConnection に互換性のあるWebSocketトランスポートおよびライフサイクルの変更が必要で、Browser.close() はローカルプロセスを所有し、それを終了させることを想定しています。その作業は上流には存在しないため、ブリッジをサポートされた経路ではなく設計のスケッチとして扱うべきです。

実用的な分割はクリーンです。認可された実行時分析にはGhostwireをローカルで使用してください。スケールでのブラウザベースのデータ収集にはスクレイピングブラウザを使用してください。これは、Chrome DevTools Protocol概要 がカバーしているものです。

トラブルシューティング

Chromeがすぐに終了し、パイプが壊れる

cdp.py からの BrokenPipeError は、Chromeが起動中に死んだことを意味します。コンテナ内でrootとして実行することが通常の原因です; --no-sandbox--disable-dev-shm-usageextra_flags 経由で渡してください。

CDPError: timeout: Page.navigate

ナビゲーションが接続の締切を超えました。Engine を作成する前に browser.cdp.default_timeout を上げてください。ここで使用されているコンテナ内では、127.0.0.1 への最初のナビゲーションは約15秒かかり、localhost は約0.1秒で解決されたため、ホスト名自体が結果を変えました。

フックが何もキャプチャしない

フックが設定されたときに関数が定義されていませんでした。ページの初期化を待ち、最初に typeof window.yourFunction を評価して表現が解決されることを確認してください。

検証が通過したがコーパスが小さい

ペアの数が非常に少ないことは、非常に少ないことを証明します。境界をもっと行使するか、観察されたトラフィックが決して到達しないエッジをカバーする fresh_inputs を提供してください。

結論

関数フックは簡単な部分です。あなたの作業方法を変える部分はオラクルであり、それはあなたの再実装を記録された動作と照らし合わせ、壊れたところでの入力を返します。この実行での入力は ['sample8', 18] であり、候補は +1 を一つ落としたために失敗しました。

範囲を正直に保ちましょう。Ghostwireは、あなたが分析する権限を持つシステムのためのローカルChrome研究ツールであり、現在、リモートブラウザエンドポイントとは通信できません。

FAQ

Q: Ghostwireは実際にブラウザ用のFridaですか?

同じ役割を果たします—静的な読み取りではなくライブランタイム計測—が、実装は異なります。Fridaはネイティブプロセスにエージェントを注入しますが、GhostwireはChromeにデバッガセッションを接続し、ページにコードを配置することはありません。

Q: GhostwireはなぜWebSocketの代わりにパイプを使用するのですか?

--remote-debugging-pipeは、ページのJavaScriptが発見できるデバッグポートを開くことを避け、コアにWebSocket依存性が不要になります。

Q: 関数をフックすることでページが見る内容は変わりますか?

通常の検出チェックに対しては変わりません。フックはライブオブジェクトに対してDebugger.setBreakpointOnFunctionCallで設定されるため、オブジェクトはラップされず、fn.toString()は元のソースを返します。

Q: 検証オラクルは実際に何を証明しますか?

あなたの候補がテストされたすべての入力で本物の関数と一致したことを証明します。それはカバレッジによって限られた証拠であり、同等性の証明ではなく、12対の結果が通過した場合、それは広範なエッジケースのカバレッジを持つ場合よりも意味が少なくなります。

Q: GhostwireはScrapeless Scraping Browserに接続できますか?

いいえ。Ghostwire 0.1.0はローカルのChromeをOSパイプを介して起動して所有しますが、Scraping BrowserはリモートWebSocket CDPエンドポイントを公開します。それらを接続するにはWebSocketトランスポートとリモートライフサイクル処理が必要ですが、上流の提供はありません。

Q: これは任意のウェブサイトに対して実行する安全性がありますか?

いいえ。ランタイム計測は、所有しているシステムまたはテストするための書面による承認を受けたシステムに制限するべきです。ターゲットの利用規約とロボット指令を確認し、第三者のコードを分析する前に法的アドバイスを得てください。

Scrapelessでは、適用される法律、規制、およびWebサイトのプライバシーポリシーを厳密に遵守しながら、公開されているデータのみにアクセスします。 このブログのコンテンツは、デモンストレーションのみを目的としており、違法または侵害の活動は含まれません。 このブログまたはサードパーティのリンクからの情報の使用に対するすべての責任を保証せず、放棄します。 スクレイピング活動に従事する前に、法律顧問に相談し、ターゲットウェブサイトの利用規約を確認するか、必要な許可を取得してください。

最も人気のある記事

カタログ