mobile-ssl-pinning-bypass について
AndroidとiOSアプリのSSLピンニングを調べるモバイルセキュリティテストガイドです。実行時フック、アプリのパッチ、フレームワーク別の手法、通信傍受に失敗した場合の診断を扱います。
主な機能
- 01AndroidとiOSの回避手法
- 02FridaとObjectionのフック
- 03アプリのデコンパイルと再パッケージ
- 04Flutter、React Native、Xamarinの解説
- 05ピンニング失敗の診断
活用シーン
- 01許可されたテストでアプリのHTTPS通信を傍受
- 02複数層のピンニングがあるアプリのテスト
- 03Flutterアプリのネットワーク検証の調査
- 04プロキシ証明書の信頼問題の診断
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill mobile-ssl-pinning-bypassインストール後、Claude Code で/mobile-ssl-pinning-bypassを入力して読み込まれたか確認します。
詳細
スキル:モバイル SSL ピンニングのバイパス — エキスパート向け攻撃プレイブック
AI LOAD INSTRUCTION:モバイルプラットフォーム向けのエキスパートレベルの SSL ピンニングバイパス技術です。Android と iOS のバイパス方法(Frida、Objection、Xposed、SSL Kill Switch)、フレームワーク固有のバイパス(Flutter、React Native、Xamarin)、標準的でないピンニング実装のトラブルシューティングを扱います。ベースモデルでは、フレームワーク固有のフックポイントや多層ピンニング構成を見落とします。
0. 関連するルーティング
詳しく調べる前に、次の読み込みを検討してください。
- SSL バイパス以外の Android テスト全般については、android-pentesting-tricks
- SSL バイパス以外の iOS テスト全般については、ios-pentesting-tricks
- トラフィックを傍受した後の API レベルのテストについては、api-sec
1. SSL ピンニングの種類
| ピンニングの種類 | ピンニング対象 | 耐性 | よく使われる環境 |
|---|---|---|---|
| 証明書ピンニング | リーフ証明書そのもの(DER/PEM) | 低(証明書のローテーションで機能しなくなる) | レガシーアプリ |
| 公開鍵ピンニング | Subject Public Key Info | 中(鍵が変わらなければ証明書の更新後も有効) | 最新のアプリ |
| SPKI ハッシュピンニング | SPKI の SHA-256 | 中(公開鍵ピンニングと同じ) | OkHttp、AFNetworking |
| CA ピンニング | 中間またはルート CA 証明書 | 高(その CA が発行した証明書ならどれでも有効) | エンタープライズアプリ |
| マルチピンニング(バックアップピン) | プライマリピン+バックアップピン | 高(フォールバックピン) | HPKP 対応アプリ |
ピンニングの仕組み
TLS Handshake
│
├── Server presents certificate chain
│
├── Standard validation (system trust store)
│ └── Passes? continue : connection fails
│
└── Pin validation (app-level check)
├── Extract server cert/pubkey/SPKI hash
├── Compare against embedded pins
└── Match found? → allow : → reject connection
2. Android のバイパス方法
2.1 Frida による汎用 SSL バイパス
// Hooks TrustManager, OkHttp, Volley, Retrofit, Conscrypt
Java.perform(function() {
// ── TrustManagerImpl (Android system) ──
try {
var TMI = Java.use('com.android.org.conscrypt.TrustManagerImpl');
TMI.verifyChain.implementation = function() {
console.log('[Bypass] TrustManagerImpl.verifyChain');
return arguments[0]; // return untouched chain
};
} catch(e) {}
// ── X509TrustManager (custom implementations) ──
var TrustManager = Java.registerClass({
name: 'com.bypass.TrustManager',
implements: [Java.use('javax.net.ssl.X509TrustManager')],
methods: {
checkClientTrusted: function() {},
checkServerTrusted: function() {},
getAcceptedIssuers: function() { return []; }
}
});
var SSLContext = Java.use('javax.net.ssl.SSLContext');
SSLContext.init.overload('[Ljavax.net.ssl.KeyManager;',
'[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom')
.implementation = function(km, tm, sr) {
console.log('[Bypass] SSLContext.init');
this.init(km, [TrustManager.$new()], sr);
};
// ── OkHttp3 CertificatePinner ──
try {
var CP = Java.use('okhttp3.CertificatePinner');
CP.check.overload('java.lang.String', 'java.util.List').implementation = function() {
console.log('[Bypass] OkHttp3 CertificatePinner.check: ' + arguments[0]);
};
// check$okhttp variant (OkHttp 4.x)
try { CP['check$okhttp'].implementation = function() {}; } catch(e) {}
} catch(e) {}
// ── Retrofit / OkHttp interceptor ──
try {
var OkHttpClient = Java.use('okhttp3.OkHttpClient$Builder');
OkHttpClient.certificatePinner.implementation = function(pinner) {
console.log('[Bypass] OkHttpClient.Builder.certificatePinner');
return this; // return builder without pinner
};
} catch(e) {}
// ── Volley (HurlStack) ──
try {
var HurlStack = Java.use('com.android.volley.toolbox.HurlStack');
HurlStack.createConnection.implementation = function(url) {
console.log('[Bypass] Volley HurlStack: ' + url);
var conn = this.createConnection(url);
// Remove hostname verifier
conn.setHostnameVerifier(Java.use(
'javax.net.ssl.HttpsURLConnection').getDefaultHostnameVerifier());
return conn;
};
} catch(e) {}
// ── Conscrypt / BoringSSL (modern Android) ──
try {
var Conscrypt = Java.use('org.conscrypt.ConscryptFileDescriptorSocket');
Conscrypt.verifyCertificateChain.implementation = function() {
console.log('[Bypass] Conscrypt verifyCertificateChain');
};
} catch(e) {}
// ── Apache HttpClient (legacy) ──
try {
var AbstractVerifier = Java.use('org.apache.http.conn.ssl.AbstractVerifier');
AbstractVerifier.verify.overload('java.lang.String', '[Ljava.lang.String;',
'[Ljava.lang.String;', 'boolean').implementation = function() {
console.log('[Bypass] Apache AbstractVerifier');
};
} catch(e) {}
// ── HostnameVerifier ──
try {
var HV = Java.use('javax.net.ssl.HttpsURLConnection');
HV.setDefaultHostnameVerifier.implementation = function(v) {
console.log('[Bypass] Ignoring custom HostnameVerifier');
};
} catch(e) {}
console.log('[+] Android universal SSL bypass loaded');
});
2.2 Objection(ワンコマンド)
objection -g com.target.app explore --startup-command "android sslpinning disable"
2.3 Network Security Config(デバッグ用オーバーライド)
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
ワークフロー:APK を逆コンパイル → 設定を追加/変更 → 再パッケージ化 → 再署名 → インストール。
apktool d target.apk -o target_dir
# Edit res/xml/network_security_config.xml
# Add reference in AndroidManifest.xml if missing
apktool b target_dir -o target_patched.apk
zipalign -v 4 target_patched.apk target_aligned.apk
apksigner sign --ks my-key.keystore target_aligned.apk
adb install target_aligned.apk
2.4 Xposed/LSPosed モジュール
| モジュール | 方法 | 適用範囲 | root 権限が必要 |
|---|---|---|---|
| JustTrustMe | TrustManager と OkHttp にフック | アプリ単位 | はい(Xposed) |
| SSLUnpinning | 証明書の検証にフック | アプリ単位 | はい(LSPosed) |
| TrustMeAlready | システム全体の TrustManager をバイパス | システム全体 | はい(LSPosed) |
2.5 Magisk + システム CA のインストール
# Install proxy CA as system cert (Android 7+ requires this for system-level trust)
# Method 1: MagiskTrustUserCerts module
# Moves user CAs to /system/etc/security/cacerts/ via Magisk overlay
# Method 2: Manual (requires root)
adb push burp_ca.pem /sdcard/
adb shell
su
mount -o remount,rw /system
cp /sdcard/burp_ca.pem /system/etc/security/cacerts/9a5ba575.0 # hash-named
chmod 644 /system/etc/security/cacerts/9a5ba575.0
mount -o remount,ro /system
# Get correct hash filename:
openssl x509 -inform PEM -subject_hash_old -in burp_ca.pem | head -1
# Output: 9a5ba575 → filename is 9a5ba575.0
2.6 手動で逆コンパイル → パッチ → 再パッケージ化
# Step 1: Decompile
jadx -d decompiled/ target.apk
# Step 2: Find pinning code
grep -r "CertificatePinner\|X509TrustManager\|checkServerTrusted\|ssl" decompiled/
# Step 3: Identify pinning implementation and patch
# Use smali editing for precise control:
apktool d target.apk
# Edit smali files to NOP out pinning checks
# Look for invoke-virtual {checkServerTrusted} and replace with return-void
# Step 4: Repackage and sign
apktool b target_dir -o patched.apk
apksigner sign --ks debug.keystore patched.apk
3. iOS のバイパス方法
3.1 Frida(SecTrust フック)
// Hook core iOS SSL validation functions
var SecTrustEvaluateWithError = Module.findExportByName('Security', 'SecTrustEvaluateWithError');
Interceptor.attach(SecTrustEvaluateWithError, {
onLeave: function(retval) {
retval.replace(ptr(1));
}
});
var SecTrustEvaluate = Module.findExportByName('Security', 'SecTrustEvaluate');
Interceptor.attach(SecTrustEvaluate, {
onLeave: function(retval) {
retval.replace(ptr(0));
}
});
// Hook SSLHandshake (lower-level)
var SSLHandshake = Module.findExportByName('Security', 'SSLHandshake');
if (SSLHandshake) {
Interceptor.attach(SSLHandshake, {
onLeave: function(retval) {
if (retval.toInt32() === -9807) { // errSSLXCertChainInvalid
retval.replace(ptr(0));
}
}
});
}
// Hook NSURLSession delegate method
try {
var cls = ObjC.classes.NSURLSession;
// Hook URLSession:didReceiveChallenge:completionHandler: on delegates
ObjC.enumerateLoadedClasses({
onMatch: function(name) {
try {
var methods = ObjC.classes[name].$ownMethods;
for (var i = 0; i < methods.length; i++) {
if (methods[i].indexOf('didReceiveChallenge') !== -1 &&
methods[i].indexOf('completionHandler') !== -1) {
console.log('[SSL] Found delegate: ' + name + ' ' + methods[i]);
}
}
} catch(e) {}
},
onComplete: function() {}
});
} catch(e) {}
3.2 Objection(ワンコマンド)
objection -g com.target.app explore --startup-command "ios sslpinning disable"
3.3 SSL Kill Switch 2(脱獄用 Tweak)
# Install via Cydia/Sileo
# Package: com.nablac0d3.sslkillswitch2
# Disables SSL pinning system-wide or per-app via Settings toggle
# Hooks:
# - SecTrustEvaluate
# - SSLHandshake
# - SSLSetSessionOption
# - tls_helper_create_peer_trust
3.4 ライブラリ固有のフック
| ライブラリ | iOS のフックポイント | Frida のアプローチ |
|---|---|---|
| AFNetworking | AFSecurityPolicy.evaluateServerTrust:forDomain: | YES を返す |
| Alamofire | ServerTrustManager.evaluate(_:forHost:) | 評価をスキップ |
| TrustKit | TSKPinningValidator verifyPublicKeyPin: | 成功を返す |
| NSURLSession | URLSession:didReceiveChallenge:completionHandler: | .useCredential を指定して completionHandler を呼び出す |
3.5 手動でのバイナリパッチ
# Find pinning function in binary
strings decrypted_binary | grep -i "pin\|cert\|trust"
# Disassemble and find the validation function
# Replace comparison/branch instruction with NOP or unconditional pass
# LLDB runtime modification
lldb -n TargetApp
(lldb) breakpoint set -n "SecTrustEvaluateWithError"
(lldb) breakpoint command add 1
> thread return 1
> continue
> DONE
4. フレームワーク固有のバイパス
4.1 Flutter
Flutter は、内部で BoringSSL を使用する Dart の dart:io ライブラリを使います。Java/ObjC レイヤーへの標準的な Frida フックは機能しません。
// Flutter SSL bypass — must hook BoringSSL directly
// Find ssl_crypto_x509_session_verify_cert_chain in libflutter.so
var libflutter = Process.findModuleByName('libflutter.so'); // Android
// var libflutter = Process.findModuleByName('Flutter'); // iOS
// Hook ssl_verify_peer_cert (BoringSSL function)
// Signature varies by Flutter version — use pattern scanning
var pattern = 'FF C3 ..'; // Example pattern, varies
var matches = Memory.scan(libflutter.base, libflutter.size, pattern, {
onMatch: function(address, size) {
console.log('[Flutter] Potential verify function at: ' + address);
Interceptor.attach(address, {
onLeave: function(retval) {
retval.replace(ptr(0)); // SSL_VERIFY_OK
}
});
},
onComplete: function() {}
});
// Alternative: use reflutter tool for automated patching
// reflutter target.apk
// This patches BoringSSL in the Flutter engine directly
reflutter ツール(Flutter アプリに推奨):
pip install reflutter
reflutter target.apk
# Outputs patched APK that redirects traffic to your proxy
# Also disables SSL verification in the BoringSSL engine
4.2 React Native
React Native はプラットフォームのネットワーク機能を使用します。Android では OkHttp、iOS では NSURLSession を使用します。
| プラットフォーム | ネットワークスタック | バイパス方法 |
|---|---|---|
| Android | OkHttp3 | 標準的な OkHttp CertificatePinner フック |
| iOS | NSURLSession | 標準的な SecTrust フック |
| Android(Hermes) | 同じ OkHttp | 同じフック。ただし、Hermes JIT には追加の対応が必要な場合があります |
// React Native Android — same as OkHttp bypass
Java.perform(function() {
try {
var CP = Java.use('okhttp3.CertificatePinner');
CP.check.overload('java.lang.String', 'java.util.List').implementation = function() {};
} catch(e) { console.log('OkHttp3 not found, trying okhttp2...'); }
try {
var CP2 = Java.use('com.squareup.okhttp.CertificatePinner');
CP2.check.overload('java.lang.String', 'java.util.List').implementation = function() {};
} catch(e) {}
});
4.3 Xamarin
// Xamarin pinning typically via:
// ServicePointManager.ServerCertificateValidationCallback
// or custom HttpClientHandler
// Frida bypass for Xamarin (Mono runtime)
// Hook Mono method: System.Net.ServicePointManager.set_ServerCertificateValidationCallback
var mono_method = Module.findExportByName('libmonosgen-2.0.so',
'mono_runtime_invoke');
// More practical: hook the managed callback at CIL level
// Use Frida's Mono bridge or objection's built-in Xamarin support
// Objection has built-in Xamarin bypass:
// objection -g com.target.app explore
// > android sslpinning disable (covers Xamarin on Android)
5. 証明書の透明性と HPKP
| 技術 | 状況 | テストへの影響 |
|---|---|---|
| Certificate Transparency(CT) | 有効。ブラウザーで強制される | モバイルアプリで CT が強制されることはまれです。バイパスの障害にはなりません |
| HPKP(HTTP Public Key Pinning) | 非推奨(2018 年) | レガシーアプリでまだチェックされる場合があります。プロキシの応答からヘッダーを削除してください |
| Expect-CT ヘッダー | 非推奨(2024 年) | モバイルテストへの影響は最小限です |
| モバイルアプリでの CT | まれ | カスタム CT チェックによって強制されるのは Google アプリのみです |
6. トラブルシューティング
6.1 よくある失敗
| 症状 | 原因 | 対処法 |
|---|---|---|
| バイパススクリプトは読み込まれたが、通信が依然として失敗する | ピンニング層が複数ある | すべての層にフックします:TrustManager + OkHttp + カスタムチェック |
| 「クライアント証明書が必要です」 | 相互 TLS(mTLS) | アプリのバンドル/キーチェーンからクライアント証明書を抽出し、プロキシにインポートします |
| 接続はできるが、HTTP トラフィックがない | 非 HTTP プロトコル(MQTT、gRPC、WebSocket) | Wireshark またはプロトコル専用のプロキシを使用します |
| バイパス後にアプリがクラッシュする | 改ざん防止機能がフックを検出する | まず整合性チェックをバイパスし、その後 SSL をバイパスします |
| プロキシの CA が信頼されない | Android 7 以降のユーザー CA 制限 | CA をシステム証明書としてインストールします(Magisk モジュール) |
| Flutter アプリがフックを無視する | ネイティブ層で BoringSSL がフックされていない | reflutter またはネイティブの BoringSSL フックを使用します |
| 証明書チェーンの検証がタイムアウトする | OCSP ステープリングの不一致 | OCSP チェックを無効にするか、OCSP レスポンダーをモックします |
6.2 診断手順
# Verify proxy CA is installed correctly
# Android:
adb shell "ls /system/etc/security/cacerts/ | grep $(openssl x509 -subject_hash_old -in ca.pem | head -1)"
# iOS: Settings → General → About → Certificate Trust Settings
# Check if target app is actually using SSL (vs. plain HTTP)
# Wireshark filter: tcp.port == 443 and ip.addr == <device_ip>
# Check if Frida is hooking the right process
frida-ps -U | grep target
# Verbose Frida output for debugging hooks
frida -U -f com.target.app -l bypass.js --debug
7. SSL ピンニングのバイパス判断ツリー
Need to intercept mobile app HTTPS traffic
│
├── Platform?
│ ├── Android ↓
│ │ ├── Rooted device available?
│ │ │ ├── Yes → Frida universal bypass (§2.1) [FIRST TRY]
│ │ │ │ ├── Works? → done
│ │ │ │ └── Fails? → add Conscrypt + Volley hooks
│ │ │ ├── Still fails? → LSPosed + TrustMeAlready (§2.4)
│ │ │ └── Still fails? → install CA as system cert (§2.5)
│ │ └── No root?
│ │ ├── Debug build? → Network Security Config (§2.3)
│ │ └── Release build? → decompile + patch + repackage (§2.6)
│ │
│ └── iOS ↓
│ ├── Jailbroken device available?
│ │ ├── Yes → Objection ios sslpinning disable (§3.2) [FIRST TRY]
│ │ │ ├── Works? → done
│ │ │ └── Fails? → Frida SecTrust hooks (§3.1)
│ │ ├── Still fails? → SSL Kill Switch 2 (§3.3)
│ │ └── Still fails? → library-specific hooks (§3.4)
│ └── No jailbreak?
│ ├── Re-sign with Frida gadget → run Frida hooks
│ └── Binary patch → sideload (§3.5)
│
├── Framework-specific app?
│ ├── Flutter → reflutter tool or BoringSSL native hooks (§4.1)
│ ├── React Native → standard platform hooks (§4.2)
│ └── Xamarin → Objection or Mono runtime hooks (§4.3)
│
├── Bypass works but issues remain?
│ ├── Client cert required? → extract + import to proxy (§6.1)
│ ├── Non-HTTP protocol? → protocol-specific tooling (§6.1)
│ └── App crashes? → fix anti-tampering first (§6.1)
│
└── All methods fail?
├── Analyze traffic at network level (Wireshark/tcpdump)
├── Check for custom proprietary protocol
└── Consider iptables + transparent proxy approach
8. プロキシ設定クイックリファレンス
| プロキシツール | 最適な用途 | SSL バイパスの連携 |
|---|---|---|
| Burp Suite | HTTP の包括的な分析 | CA をデバイスにインポート |
| mitmproxy | スクリプトによる傍受 | mitmproxy --set confdir=~/.mitmproxy |
| Charles Proxy | macOS ネイティブ、簡単な設定 | CA のインストール機能を内蔵 |
| Proxyman | macOS/iOS ネイティブ | iOS デバイスを直接サポート |
| HTTP Toolkit | Android の迅速な設定 | CA と Frida バイパスを自動化 |
# Android proxy setup
adb shell settings put global http_proxy <host_ip>:8080
# Remove proxy
adb shell settings put global http_proxy :0
# iOS proxy: Settings → Wi-Fi → Configure Proxy → Manual
コメント
Markdown 対応