bypassing-mobile-pinning について
認可されたモバイルアプリテストで、TLS プロキシに通信が表示されない原因を診断し、対処するスキルです。CA の信頼、プロキシ設定、相互 TLS などを切り分け、Android と iOS のスタックに応じた確認やフックを案内します。
主な機能
- 01プロキシに通信が表示されない原因の診断
- 02Android と iOS の TLS スタック特定
- 03スタックに応じたフック位置の選択
- 04相互 TLS とプロキシ迂回の確認
- 05リクエスト変更による傍受の検証
活用シーン
- 01モバイルアプリの TLS ハンドシェイク失敗の調査
- 02認可されたテストでアプリの API リクエストを確認
- 03ピンニングと他のネットワーク問題の切り分け
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trilwu/ secskills --skill bypassing-mobile-pinningインストール後、Claude Code で/bypassing-mobile-pinningを入力して読み込まれたか確認します。
詳細
モバイルのピンニングを回避する
ここで時間を無駄にする原因の大半は、そもそも存在しないピンニングを回避しようとすることです。プロキシに何も表示されない原因は少なくとも6つあり、それぞれ対処法が異なります。そのうちの1つはピンニングではなく、ユーザー CA を信頼しない Android のデフォルト設定であり、もう1つはアプリが独自の TLS スタックを持ち、プロキシを経由していないケースです。回避スクリプトを試す前に、原因を診断してください。
テストが許可されているアプリに対してのみ使用してください。
使用する場面
- Burp/mitmproxy/Charles にモバイルアプリからの TLS ハンドシェイク失敗が表示される
- アプリが明らかにネットワークに接続しているのに、プロキシには何も表示されない
- プロキシを設定すると、アプリに「ネットワークエラー」、「安全でない接続」などが表示される
- 対象にピンニングがあると把握しており、そのスタックに適したフックが必要である
使用しない場面
- Flutter の対象 —
reversing-flutter-appsを使用してください。Flutter でも同じような失敗が起きますが、原因と対処法はエンジンに特有です - モバイルアセスメント全般 —
testing-mobile-applicationsを使用してください - トラフィックが見えるようになった後のバックエンド —
testing-apisを使用してください - ネイティブ
.soの詳細なリバース —analyzing-binariesを使用してください
まず原因を診断する
何かをインストールする前に、これを実行してください。症状から原因を特定できます。
| 症状 | 考えられる原因 | 対処法 |
|---|---|---|
プロキシに CONNECT が表示され、その後 TLS アラート unknown_ca/bad_certificate が表示される | CA が信頼されていない | システム CA としてインストールするか、Network Security Config にパッチを適用する |
| プロキシに CONNECT が表示され、その後一部のドメインに限ってアラートが表示される | 対象ホストに限定された実際のピンニング | ピンニングのチェックにフックを仕掛ける |
| プロキシにまったく何も表示されないのに、アプリは動作する | アプリがシステムプロキシを無視している | ネットワーク層でトラフィックを強制的に経由させる(Flutter、gRPC、一部の SDK) |
| プロキシにハンドシェイクが表示されるが、サーバーがクライアントを拒否する | 相互 TLS — アプリがクライアント証明書を提示している | クライアント証明書を抽出する。これはピンニングではありません |
| エミュレーターでは動作するが、実機では失敗する(またはその逆) | TLS ではなく、ルート/エミュレーター検出 | 別の問題です — 検出のセクションを参照してください |
| ログイン後にのみ失敗する | API ホストにのみ適用されたピンニング | フックを仕掛けてから、再度確認する |
# What does the app actually declare?
apktool d target.apk -o out
cat out/res/xml/network_security_config.xml 2>/dev/null
rg -n 'networkSecurityConfig|usesCleartextTraffic' out/AndroidManifest.xml
# Which TLS stack is in play?
rg -l 'okhttp3|CertificatePinner|TrustKit|AFNetworking|Alamofire' out/ 2>/dev/null | head
unzip -l target.apk | rg 'libssl|libcrypto|libconscrypt|libflutter|libil2cpp|libmonodroid'
ピンニングを疑う前に、Network Security Config を確認してください。 Android 7 以降では、アプリはユーザーがインストールした CA をデフォルトで信頼しません。ピンニングのコードがまったくないアプリでも、通信の傍受に失敗することがあります。対処法は2つあります。
# A. Install your CA as a SYSTEM certificate (no app modification — preferred)
# Emulator:
emulator -avd <name> -writable-system
adb root && adb remount
openssl x509 -inform PEM -subject_hash_old -in burp.pem | head -1 # → e.g. 9a5ba580
cp burp.pem 9a5ba580.0 && adb push 9a5ba580.0 /system/etc/security/cacerts/
adb shell chmod 644 /system/etc/security/cacerts/9a5ba580.0
# Rooted device: use a Magisk module so the change survives reboot
# B. Patch the config and repack (when you cannot get a system CA)
# Add to network_security_config.xml:
# <base-config><trust-anchors>
# <certificates src="system"/><certificates src="user"/>
# </trust-anchors></base-config>
apktool b out -o patched.apk && apksigner sign --ks debug.keystore patched.apk
方法 A のほうが適しています。アプリに一切変更を加えないため、整合性チェック、署名チェック、Play Integrity に影響しません。デバイスにシステム CA を導入できない場合に限り、方法 B を選んでください。
Android:スタック別のフック
スタックを特定してからフックします。汎用スクリプトが機能しないと、実際には対象レイヤーを間違えているだけなのに、「バイパスに失敗した」と見えてしまいます。
# Start here — covers the common Java-layer stacks in one shot
objection -g com.target.app explore
# then: android sslpinning disable
# Or a maintained universal script
frida -U -f com.target.app -l android-ssl-bypass.js --no-pause
| スタック | 識別子 | フックポイント |
|---|---|---|
| OkHttp 3/4 | okhttp3.CertificatePinner | CertificatePinner.check() — 正常に戻るようにします |
| HttpURLConnection | javax.net.ssl.* | カスタム X509TrustManager.checkServerTrusted() |
| Conscrypt | libconscrypt_jni.so | Platform.checkServerTrusted、またはネイティブレイヤー |
| Apache HttpClient | org.apache.http | SSLSocketFactory の検証処理 |
| WebView | onReceivedSslError | handler.proceed() を強制します |
| ネイティブ / BoringSSL | libssl.so、カスタム .so | SSL_CTX_set_custom_verify、ssl_verify_peer_cert |
| Xamarin | libmonodroid.so、libxamarin* | Mono レベルの ServicePointManager / HttpClientHandler |
| Unity | libil2cpp.so | UnityWebRequest の証明書ハンドラー、または BestHTTP の検証処理 |
| gRPC | libgrpc.so | チャネルの認証情報。多くの場合、プロキシも無視します |
ネイティブのピンニングでは、汎用スクリプトは機能しなくなります。 objection が成功を報告しているのに通信が失敗する場合、チェックは .so 内にあります。BoringSSL の検証エントリーポイントはエクスポートされていないため、スクリプトはパターンスキャンでそれらを特定しますが、パターンはバージョンごとに異なります。対象を確認してください。
# Is a native TLS stack even present?
unzip -j target.apk 'lib/arm64-v8a/*' -d libs && rg -l 'SSL_|BoringSSL|s2n' libs/
# Trace the native calls to see which library terminates TLS
frida-trace -U -f com.target.app -i 'SSL_*' -i '*verify*'
Unity、Xamarin、Flutter のビルドでは、まずシンボルを復元します — reversing-unity-il2cpp、reversing-flutter-apps — そのうえで、手当たり次第にパターンスキャンするのではなく、名前が判明した関数をフックします。
iOS
# Frida-based, covers NSURLSession and the common libraries
objection -g com.target.app explore
# then: ios sslpinning disable
# Jailbroken device, no Frida
# SSL Kill Switch 3 — system-wide, survives app updates
| ライブラリ | フックポイント |
|---|---|
| NSURLSession(デフォルト) | URLSession:didReceiveChallenge: → サーバーのトラストを使った .useCredential |
| TrustKit | TSKPinningValidator の結果 |
| AFNetworking | AFSecurityPolicy.evaluateServerTrust: |
| Alamofire | ServerTrustManager / ServerTrustEvaluating |
| ネイティブ/BoringSSL | Android と同様 — 検証コールバックをフックします |
ジェイルブレイクされていないデバイスの場合:Frida ガジェットを使って IPA を再パッケージ化し(objection patchipa)、再署名してサイドロードします。強化されたアプリでは、整合性チェックやアテステーションチェックに引っかかる可能性があります。
それでも失敗する場合
バイパススクリプトをさらに試すのではなく、以下を順番に確認してください。
- アプリがプロキシを使っていません。 一部のスタックはシステムのプロキシ設定をまったく無視します。
iptablesによる透過的なリダイレクト、または VPN/tun ベースのプロキシで通信を強制します。adb shell su -c 'iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to <proxy>:8080' - 証明書の透明性(CT)の強制。 一部の SDK は SCT を要求しますが、使用している CA にはありません。CT ライブラリを探し、CT 検証処理を個別にフックします。
- 相互 TLS。 サーバーがクライアント証明書を要求しています。アプリバンドルから
.p12またはキーストアを抽出し、そのパスワード(コードやstringsに含まれていることがよくあります)を復元して、プロキシのクライアント証明書設定に読み込みます。これはピンニングとは別の仕組みであり、ピンニングのバイパスでは対処できません。 - ルート化/ジェイルブレイク、または Frida の検出。 アプリが計装を検出して処理を失敗させると、TLS の問題のように見えます。
frida文字列のスキャン、ポート 27042 のプローブ、/proc/self/mapsの検査、または Play Integrity を確認してください。まず検出をバイパスしてから、ピンニングのバイパスを再試行します。 - 通信が HTTP ではありません。 WebSockets、gRPC、MQTT、または TLS 上のカスタムプロトコルです。HTTP プロキシには表示されないため、ソケットレイヤー、またはプロトコル対応プロキシでキャプチャします。
- 特定のホストにだけピンニングが適用されています。 分析用通信は成功するのに、API 通信は失敗します。プロキシに表示されるホストを比較して確認してください。
実際に成功したことを確認する
アプリが単にエラーを出さなくなっただけではなく、リクエストをエンドツーエンドで確認し、変更できることをもって、バイパスが成功したと判断します。
1. Proxy shows the API host, not just analytics and CDN traffic
2. Request and response bodies are readable
3. A modified request produces a different server response
4. Authenticated flows still work through the proxy
ステップ 1 だけが成功する場合、実際の API 通信は別の経路を通っており、フォールバック経路を見ているだけかもしれません。
却下すべき誤った理屈
- 「バイパスに失敗した。アプリは強力なピンニングを使っているに違いない。」 原因は6つあり、そのうちの1つがピンニングです。症状の表から原因を特定してください。
- 「CAをインストールしたので、TLSは動作するはずです。」 ユーザー証明書としてインストールした場合、Android 7以降では、証明書の使用を明示的に許可していないアプリでは動作しません。
- 「objectionではピンニングが無効になったと表示された。」 objectionが対応しているJavaレイヤーのチェックは無効になります。ネイティブのピンニングには影響しません。
- 「プロキシにトラフィックがないので、アプリはオフラインだ。」 それは、アプリがあなたのプロキシを使っていないことを意味します。
- 「APKにパッチを当てればいい。」 再パッケージ化すると署名が無効になり、整合性チェックに引っかかります。システムCAとランタイムフックを優先し、パッチは最後の手段にしてください。
- 「ピンニングをバイパスできたので、評価は完了だ。」 ピンニングのバイパスは準備であり、指摘事項ではありません。ピンニングは多層防御の一環であり、ピンニングがないことは重大度の低い注記です。実際の指摘事項は、その先にあるAPIにあります。
参考資料
testing-mobile-applications— これによって実施できる、より広範な評価reversing-flutter-apps— Flutter独自のTLSスタックとプロキシの挙動reversing-unity-il2cpp、reversing-react-native-apps— フレームワーク固有のスタックにフックする前のシンボル復元testing-apis— トラフィックを確認できるようになった後に、実際の指摘事項が見つかる場所- objection、Frida、SSL Kill Switch 3、apktool、apksigner、Magisk