3つのプラットフォームについて、ネイティブ側の接続ポイントはすべて特定できていました。デバイス登録がどこで始まるか、セキュリティSDKがどの層でリクエストを横取りするか、ネイティブのインターセプターが送信パケットをどう書き換えるか、RegisterNativesでJNIがランタイムへ動的登録するメソッドは何か。Fridaスクリプトもアタッチ済みで、ログは流れ続け、各フックも安定して刺さる。ところがコマンドカタログを開き、この3プラットフォームで呼び出せるネイティブアプリのエンドポイントを数えた結果は、0件でした。
同じプロジェクト内でも、別のプラットフォームには11件あります。実機で検証済みで、すでにコードへ移植され、正式なコマンドとして動作しています。
差を生んだのはフックの技術ではありません。フックはすべて刺さり、接続関係もきれいに図示できています。それでも実用化には、見落とされがちな基準があるのです。フックが刺さることと、機能として使えることの間には、証拠の階層があります。この記事では、その線引きの方法、未完成のエンドポイントを出すより「まだ使えない」と判断するほうが安い理由、そしてその過程でモデルをどう使うべきかを整理します。
フック成功は、実用的な機能の完成ではない
アプリをリバースする人は、「フックが刺さった」ことをゴールのように扱いがちです。対象メソッドにスクリプトがアタッチされ、ログには引数、戻り値、コールスタックが出る。「中に入れた」という感覚は確かにありますが、それが誤解を招きます。
一方、コマンドカタログが求めるのは「中に入れた」ことではありません。通常の入力を与えたとき、後段で利用できる、正しい構造を持つ空でないペイロードを安定して返せるか。必要なのはそれです。この2つには大きな隔たりがあります。
よくあるのはこういう状況です。フックはすべて刺さり、接続も追えている。ログではデバイス登録リクエストが送られ、セキュリティSDKが値を計算し、ネイティブのインターセプターが署名ヘッダーを付与する様子まで確認できる。すべて正しく見えます。ところが実機で実行すると、デバイス登録はゼロ値のデバイスIDを返す。あるいは詳細エンドポイントのボディが空で返る。経路は開いていても、データが空なのです。
その時点で手元にあるのは、特定バージョンのアプリ内部動作を観測できるプローブ群です。呼び出し可能なエンドポイントではありません。前者を後者として出荷すれば、後から使う人全員のために地雷を埋めることになります。
11件と0件を分けたもの
2つのプラットフォーム群を並べると、必要な基準がはっきり見えてきます。
比較対象プラットフォーム(動画コミュニティアプリ) | 主要コンテンツアプリ3件 | |
|---|---|---|
フック経路 | 特定済み・実機検証済み | すべて特定済み、全フック成功 |
実際のインストール状態 | ゼロでないデバイスIDを取得 | デバイスIDがゼロ / 実機プロファイル不足 |
空でないレスポンス | 構造化された詳細データ | 詳細が空 / ボディが空 |
コマンドカタログ登録数 | 11 | 0 |
比較対象プラットフォームの11件は、単に「フックの出来がよかった」わけではありません。正式なPythonコマンドへ移す前に、次節で説明する4つの証拠基準をすべて通過し、構造化された検証証拠も一緒に残しています。
3つのプラットフォームで何もしていなかったわけではありません。セキュリティSDKのインターセプト層、ネイティブのリクエストインターセプター、JNIが動的登録する一連のメソッドはすべて把握し、Fridaフックも残しています。ただし実際のインストール状態を通過できず、デバイス登録でゼロでないIDを取れない限り、その後の出力はすべて空になります。そのためネイティブアプリのコマンド数は正直に0のままです。現在使えるのは、これとは完全に別系統のWebまたはブラウザ経由のトランスポートです。
より大きな集計では、このモバイル機能群は合計32件で、そのうち23件は実装まで到達しました。残る9件は「実機デバイスプロファイル待ち」で止まっており、早まってカタログへ入れてはいません。この9件は失敗ではなく、規律です。「経路は理解したが、証拠が足りない」ものがどれだけあるかを正確に示しています。
カタログ登録に必要な4つの証拠基準
比較を分解すると、アプリの機能がコマンドカタログへ入るには4つの条件を満たす必要があります。どれか1つでも欠ければ、登録しません。
1. 実際のインストール状態。 リクエストは、上流側が認識するゼロでないインストールIDから送られなければなりません。エミュレータ上や壊れたプロファイルでフックが刺さっても、それでは不十分です。デバイス登録がゼロ値IDを返すのは、このゲートを通れていない状態です。ここを越えられなければ、経路をどれだけ完全に把握しても出力は空のままです。3プラットフォームはそろってここで止まりました。
2. 空でないレスポンス。 接続できることと、データがあることは別です。リクエストは送信され、ステータスコードは200、それでもボディは空。この「成功したように見える空レスポンス」は、例外の有無しか見ないチェックをすり抜けるため、エラーより危険です。必要なのは、後段へそのまま渡せる、構造化され完全で空でないデータです。
3. 完全なエラー分類。 成熟した機能は、失敗時に一律のエラーを投げるのではなく、なぜ失敗したのかを示せなければなりません。ページがない、未ログイン、ランタイム未準備、レスポンスが空というケースは、まったく異なる失敗です。ひとまとめにせず、区別できるエラーコードにする必要があります。「失敗した」としか言えないコマンドは、呼び出し側がリトライすべきか、再認証すべきか、スキップすべきかを判断できないため、カタログ登録の段階ではありません。
4. 閉じた再現可能なテスト。 たまたま1回成功しただけでは、機能とは言えません。同じ入力と同じフローを繰り返し実行でき、その成功を保存済みの構造化検証証拠として固定できる必要があります。一度は動き、次回は空になるなら、その経路を制御できているのではなく、その時だけランタイムの状態が偶然噛み合ったにすぎません。
4つすべてを満たして初めて、コマンドカタログへ登録します。どれか1つでも未達なら、それはエンドポイントではなく、まだ調査資産です。この言葉の違いこそ、この記事の核心です。
Fridaログが膨らんだら、モデル階層で分業する
4つの基準のうち、どこで経路が止まっているかを判断する材料は、Fridaが出力するトレースです。そしてFridaのトレースはすぐに膨れ上がります。数十のメソッドへアタッチして1本のフローを最後まで実行すれば、数万行から数十万行になるのは普通です。その大半はポリフィル、ハートビート、無関係な業務モジュールといったノイズです。
これを丸ごと1つのモデルへ渡し、「なぜこのフックはデータを取れなかったのか」と聞くと、返ってくるのは大雑把な推測になりがちです。コンテキストが大きいほど、関係のない呼び出し区間をつなぎ合わせる可能性も高まります。正しいやり方は、トレースを分割し、各セグメントを得意分野に応じて異なるモデル階層へ渡すことです。
ここは「一番強いモデルを全部に使う」場面ではなく、階層を組み合わせる典型例です。必要な仕事は4種類あり、モデルに求める能力もまったく異なります。
Fridaログ処理の工程 | 必要な能力 | 選択 | model id |
|---|---|---|---|
呼び出しチェーン全体のトレースを一度に読む | 長いコンテキストでチェーン全体を読めること | Kimi K3 |
|
数万行を分類する(デバイス登録 / ネットワーク / 暗号 / ノイズ) | 低コストで、数千回の高並列呼び出しに耐えること | Claude Sonnet 5 |
|
4つの基準のどこで経路が止まったかを判定する | 強い推論力があり、明確に判断できること | Claude Opus 5 |
|
2つのトレースの分岐点を比較し、レスポンスが空の理由を説明する | 中程度の推論と帰属分析。特定行に基づいて説明できること | GPT-5.6 Sol |
|
特に重要なのは分類工程です。数万行の中からノイズを除外し、デバイス登録、暗号、ネットワークに属する行だけを残す作業は、純粋な力仕事であり、量が多すぎて手作業には向きません。「単純な判断を非常に高頻度で繰り返す」この処理こそ、低コスト階層の担当です。これを推論階層で回すのは、そのままコストの無駄になります。ログを数百行のラベル付き要約まで圧縮してから推論階層に渡し、「どの基準で止まっているか」を判断させれば、コストと精度の両方が噛み合います。
この分業の効果は、1回試せばわかります。
1本のフローからトレース区間を切り出します。規模は数千行から数万行です。
まず
claude-sonnet-5でチャンクごとにラベル付けし、ノイズを除き、デバイス登録・暗号・ネットワークの行を残します。ラベル付き要約を
claude-opus-5に渡し、「4つの証拠基準のうち現在どこで止まっているか」と「根拠となる行」を出力させます。比較対象として、同じ生トレースを1つのモデルへ丸ごと渡し、同じ質問をします。
見るべき点は1つです。大雑把な推測を返すのか、それとも具体的な基準と具体的な行を示すのか。この差が選定基準になります。
フックはバージョン依存の観測プローブであり、署名器ではない
3プラットフォームのフックを残しながら、コマンドカタログには入れない理由はここにあります。フックはプローブであって署名器ではなく、両者の性質は根本的に違います。
フックは、たとえば32.xのような特定ビルドのアプリに紐付きます。依存するシンボル、オフセット、メソッドレイアウトはすべてそのバージョン固有です。上流が新バージョンを出せば、それらはずれてフックは即座に壊れます。本質的に消耗品であり、バージョン依存です。答えられるのは「このバージョンは今、内部で何をしているか」という観測の問いです。まさにインストルメンテーションツールの用途であり、Fridaのドキュメントでも、Interceptorは実行時の呼び出しを観測・書き換えする手段として説明されています。関数の呼ばれ方や引数は確認できますが、それ自体が「入力から署名を計算する」成果物ではありません。
エンドポイントカタログに載せる署名器は、その逆です。安定し、再現可能で、単体で動き、CIに組み込めなければなりません。「入力を渡せば正しい署名を計算する」という、再利用可能な機能の問いに答えるものです。フックによって署名アルゴリズムの骨格が明確に見えたとしても、それはまだ algorithm-fingerprinting の段階です。単体で動く署名器にするには、純化と差分検証のフローがまだ丸ごと残っています。
これは純化ラダーの順序も説明します。機能はまず純粋なPythonによる直接接続を目指し、次にローカルNode/V8上で最小の署名断片を動かす方式へ後退し、それも難しい場合にのみ受動的なブラウザブリッジを受け入れます。フックはまだそのラダーにすら乗っていません。「実装」ではなく、その手前の「調査」にあるものです。バージョン依存の観測プローブを署名器として登録するのは、完成していない「調査」に「実装」の看板を掛けるようなものです。
調査資産を腐らせずに残す方法
フックをエンドポイントカタログに入れないことは、捨てることではありません。そこには経路の知識、サンプル、検証証拠といった、実際に工数をかけて得た調査資産があります。削除すれば純粋な損失です。大切なのは適切に保管することです。そうしなければ、2か月後には自分自身でさえ、どこまで進んでいたのかわからなくなります。
アプリの調査資産には、少なくとも次の4点を記録します。
トランスポート種別。 経路がネイティブプロトコル、ローカルNode/V8署名、受動的ブラウザブリッジのどれで動くか。後でどこまで純化できるかを決めます。
証拠階層。 4つの基準のうち、いくつを通過したか。「経路を特定済み」なのか、「ゼロでないインストール状態は得たがレスポンスは空」なのか、「空ではないが再現できない」のか。次に引き継ぐ人が、実用化までの障害を正確に把握できます。
アプリバージョン / ビルド。 フックがどのバージョンに紐付くか。これがなければ上流の更新後、実装ミスなのかビルド変更なのかを判別できません。
サンプル要約。 この実行時の入力と出力がどのようなものだったかを、マスキング済みのコピーとして残します。調査を再開するときの最速の足場になります。
この4点を記録しておけば、コマンド数0で止まった経路も、先へ進められる資産になります。ただの期限切れログの山にはなりません。同時に、最悪の2つの扱いを防げます。フックを消し、調査そのものがなかったことにすること。そして、無理にカタログへ入れ、使えるふりをすることです。特に後者のコストは大きい。「呼び出せそうに見えるが、実際には空を返す」エンドポイントは、カタログを信頼した下流の呼び出し元すべてにコストを拡散します。連携を書き、リトライも書き、空データで一度痛い目を見れば、最終的にはカタログ全体を信頼しなくなります。一方、「調査資産、コマンド数0」と正直に空白を記すコストは、READMEの1行を一度書くだけです。
空白よりも、空の殻のほうが高くつきます。この領域ほど、その事実が明確に出る場所はありません。
ログ分割の面倒を、1つのキーでなくす
第4節のログ分割に戻りましょう。トレース全体を読む長コンテキスト、大量ラベル付け用の低コスト階層、分類用の推論階層、帰属分析用の中程度の推論階層。複数ベンダーの4階層を使うと、SDKも4つ、認証方式も4つ、エラー形式も4つになります。4クライアントをまたいでFridaログを分類しようとすると、多くの人はコストを計算して割に合わないと判断し、結局は数十万行のトレースを1つのモデルで処理することになります。費用を燃やすか、処理しきれないかのどちらかです。
AIReiterは、この摩擦を取り除きます。キーは1つ、インターフェースはOpenAI互換で1つ。4階層はすべてその裏側にあり、リクエストボディのmodelフィールドを変えるだけで切り替えられます。
# ログをラベル付け: 低コスト階層で、数千回を高並列に実行
curl https://aireiter.com/api/v1/chat/completions \
-H "Authorization: Bearer $AIREITER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-5",
"messages": [{"role": "user", "content": "<トレースの1チャンク + ラベル付け指示>"}]
}'
# 基準を分類: 推論階層へ切り替え、他はそのまま
# "model": "claude-opus-5"
# 呼び出しチェーン全体を読む: 長コンテキスト階層
# "model": "kimi-k3"
# レスポンス差を帰属分析:
# "model": "gpt-5.6-sol"
すでにOpenAI SDKを使っているなら、base_urlをhttps://aireiter.com/api/v1へ向けるだけで、ほかは変更不要です。Anthropic SDKでは、同じキーでPOST /api/v1/messagesを呼びます。
この記事で扱っている処理は、コスト配分が偏っています。そして割引は、その偏った部分にちょうど当たります。1本の経路でもトレースは数万行から数十万行あり、ラベル付けはチャンク単位で走り、1プラットフォームあたり数百回から数千回のclaude-sonnet-5呼び出しになります。ここがコストの大半です。トレース全体を読む長コンテキスト呼び出しも、入力あたり数十万トークンで、もう1つの大きな割合を占めます。分類と帰属分析は、単価は高くても呼び出し数は少数です。Claudeの30%オフは、最も高くつくラベル付けと基準分類に直結します。GPTの半額は帰属分析階層に適用されます。長コンテキストの読解は、同じキーから呼び出せるKimi K3で実行できます。
登録なしで試す:トレースの一部を
claude-sonnet-5へ渡してラベル付けし、続けてclaude-opus-5に分類させます。導入を決める前に、どの基準で止まっているのかを直接示せるか確認してください。
まとめ
アプリのリバースエンジニアリングでフックが刺さることは、「このバージョンが内部で何をしているかを観測できる」という能力を意味します。一方、エンドポイントカタログが必要とするのは、「入力を渡せば、空でない正しいデータを確実に返せる」という呼び出し能力です。その間には、実際のインストール状態、空でないレスポンス、エラー分類、再現可能なテストという4つの証拠基準があります。
3プラットフォームで経路を完全に特定し、すべてのフックが刺さっていても、コマンド数が0なのは失敗ではありません。実際のインストール状態を通過できないなら、正直に0で止める。フックは調査資産として保存し、デバイスプロファイルを得られた時点で先へ進めればよいのです。
このフローにおけるモデルの役割は明確です。数十万行のトレースを分割し、ラベル付けし、分類し、帰属分析することで、「この経路はどこで止まっているか」をログ読解に費やす午後から数分へ圧縮してくれます。ただし最終的に「基準を通過したか」を決めるのは、モデルではありません。差分テストにおいて「仮説は正しいか」を判断するのと同じく、設定した4つの基準と、繰り返し成立する証拠が決めます。リバースエンジニアリングのワークフロー全体でモデルを適切な位置に置く方法は、4段階の概要で説明しています。