SSL Pinning Bypass Technology - Essential for Data Collection & Security Testing
When doing App data collection and functional safety testing, the system root certificate of Charles/Fiddler is obviously installed, and the Wi‑Fi proxy is also configured, but the clear text HTTPS packet is still not captured**? Don't panic - 90% or more of them encounter SSL Pinning (SSL certificate locking) this security wall.
This article will help you quickly understand the principles and use Frida to achieve universal Java layer bypass covering mainstream scenarios. It comes with one-click Python injection script, making it extremely easy to get started.
1. Quickly understand the principle of SSL Pinning
Review of normal HTTPS process
- The client (App/browser) initiates an HTTPS connection request
- The server returns its own public key certificate + certificate chain
- The client uses the "trust root certificate library" pre-installed in the operating system/browser to verify the validity of the certificate chain (authenticity, validity period, etc.)
The client hardcodes/built-in a set of certificates/public key hashes/certificate fingerprints that only trust the App server, completely skipping/enhancing the verification of the system root certificate library: either only recognizes the built-in ones; or both built-in and system roots pass, but the built-in ones are given priority.
In this way, even if you install the proxy certificate into the system, the App will directly disconnect because "the proxy certificate is not in the built-in trust list", and the traffic will naturally not be able to capture the clear text.
2. Frida universal Java layer bypass solution
Frida is a cross-platform dynamic instrumentation tool that can directly hook the key verification functions of the Java layer when the App is running, forcing the Pinning logic to fail - no need to decompile or change the package signature, or restart the device for multiple debugging.
The following is the Frida JS script that has been streamlined and adapted to the three mainstream scenarios of OkHttp full series/system SSLContext/WebView:
Core Frida bypass script
Java.perform(function() {
console.log("[+] 🌐 通用SSL Pinning Bypass已激活");
// --------------------------
// 1. 覆盖OkHttp全系列(国内90%+App用它做网络)
// --------------------------
const okHttpCheckClasses = [
'okhttp3.CertificatePinner',
'okhttp3.internal.tls.CertificatePinnerChainCleaner',
'com.squareup.okhttp.CertificatePinner' // 老版本OkHttp兼容
];
okHttpCheckClasses.forEach(className => {
try {
const clazz = Java.use(className);
// 自动获取类的所有check/checkChain重载,避免遗漏
const methods = clazz.class.getDeclaredMethods();
methods.forEach(method => {
const methodName = method.getName();
if (methodName.startsWith('check') && clazz[methodName]) {
clazz[methodName].overload.apply(
clazz[methodName],
Array.from({length: method.getParameterCount()}, () => '*')
).implementation = function() {
console.log(`[+] ✅ OkHttp ${className}.${methodName} 绕过成功`);
return; // 直接返回,不做任何验证
};
}
});
} catch(e) {
// 类不存在直接跳过,不影响其他模块
}
});
// --------------------------
// 2. 替换系统X509TrustManager(所有Java原生网络的通用兜底)
// --------------------------
try {
const X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
const SSLContext = Java.use('javax.net.ssl.SSLContext');
// 注册「全信任」的自定义TrustManager
const TrustAllManager = Java.registerClass({
name: 'com.universal.trustall',
implements: [X509TrustManager],
methods: {
checkClientTrusted: function() {}, // 客户端证书验证直接跳过
checkServerTrusted: function() {}, // 服务端证书验证直接跳过
getAcceptedIssuers: function() { return []; } // 返回空数组就行
}
});
const trustAllArray = [TrustAllManager.$new()];
// Hook SSLContext.init的所有重载,强制替换TrustManager
SSLContext.init.overloads.forEach(overload => {
overload.implementation = function(keyManagers, trustManagers, secureRandom) {
this.init(keyManagers, trustAllArray, secureRandom);
};
});
console.log("[+] ✅ 系统X509TrustManager已替换为全信任");
} catch(e) {
// 兜底方案异常不影响其他绕过
}
// --------------------------
// 3. 绕过WebView SSL错误(内嵌H5场景的HTTPS抓包)
// --------------------------
try {
const WebViewClient = Java.use('android.webkit.WebViewClient');
WebViewClient.onReceivedSslError.implementation = function(view, handler, error) {
console.log(`[+] ✅ WebView SSL错误已忽略,错误类型: ${error.getPrimaryError()}`);
handler.proceed(); // 强制继续加载
};
} catch(e) {
// 无WebView直接跳过
}
});
The above script can be used alonefrida/frida-toolsThe command line can also be used, but in order to reduce input, I packaged a Python tool that automatically connects USB devices, supports spawn startup/attach modes, and outputs script logs in real time:
import frida
import sys
import time
from typing import Tuple, Optional
# 直接把核心绕过脚本嵌入,无需单独创建JS文件
BYPASS_SCRIPT = """
Java.perform(function() {
console.log("[+] 🌐 通用SSL Pinning Bypass已激活");
const okHttpCheckClasses = [
'okhttp3.CertificatePinner',
'okhttp3.internal.tls.CertificatePinnerChainCleaner',
'com.squareup.okhttp.CertificatePinner'
];
okHttpCheckClasses.forEach(className => {
try {
const clazz = Java.use(className);
const methods = clazz.class.getDeclaredMethods();
methods.forEach(method => {
const methodName = method.getName();
if (methodName.startsWith('check') && clazz[methodName]) {
clazz[methodName].overload.apply(
clazz[methodName],
Array.from({length: method.getParameterCount()}, () => '*')
).implementation = function() {
console.log(`[+] ✅ OkHttp ${className}.${methodName} 绕过成功`);
return;
};
}
});
} catch(e) {}
});
try {
const X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
const SSLContext = Java.use('javax.net.ssl.SSLContext');
const TrustAllManager = Java.registerClass({
name: 'com.universal.trustall',
implements: [X509TrustManager],
methods: {
checkClientTrusted: function() {},
checkServerTrusted: function() {},
getAcceptedIssuers: function() { return []; }
}
});
const trustAllArray = [TrustAllManager.$new()];
SSLContext.init.overloads.forEach(overload => {
overload.implementation = function(keyManagers, trustManagers, secureRandom) {
this.init(keyManagers, trustAllArray, secureRandom);
};
});
console.log("[+] ✅ 系统X509TrustManager已替换为全信任");
} catch(e) {}
try {
const WebViewClient = Java.use('android.webkit.WebViewClient');
WebViewClient.onReceivedSslError.implementation = function(view, handler, error) {
console.log(`[+] ✅ WebView SSL错误已忽略,错误类型: ${error.getPrimaryError()}`);
handler.proceed();
};
} catch(e) {}
});
"""
def bypass_ssl_pinning(app_package: str) -> Tuple[Optional[frida.core.Session], Optional[frida.core.Script]]:
"""
一键绕过Android App的SSL Pinning
:param app_package: 目标应用的完整包名(如 com.tencent.mm)
:return: (session, script) 成功返回Frida对象对,失败返回(None, None)
"""
# --------------------------
# 1. 获取USB设备
# --------------------------
try:
device = frida.get_usb_device(timeout=5)
print(f"✅ 连接到设备: {device.name}")
except Exception as e:
print(f"❌ 未找到USB设备,请检查ADB/Frida Server是否运行: {str(e).strip()}")
return None, None
# --------------------------
# 2. 连接App(优先spawn启动更稳定)
# --------------------------
session: Optional[frida.core.Session] = None
spawned_pid: Optional[int] = None
try:
print(f"⚠️ 正在尝试spawn启动App: {app_package}...")
spawned_pid = device.spawn([app_package])
session = device.attach(spawned_pid)
except frida.ProcessNotFoundError:
print(f"❌ 找不到包名对应的App,请检查是否正确: {app_package}")
return None, None
except frida.NotSupportedError:
print(f"⚠️ 设备不支持spawn(部分Android 14+定制系统会禁用),尝试附加到已运行的App...")
try:
session = device.attach(app_package)
except Exception as e:
print(f"❌ 附加失败,请确认App已在前台运行: {str(e).strip()}")
return None, None
except Exception as e:
print(f"❌ 连接App时发生未知错误: {str(e).strip()}")
return None, None
# --------------------------
# 3. 注入绕过脚本
# --------------------------
script: Optional[frida.core.Script] = None
try:
script = session.create_script(BYPASS_SCRIPT)
# 实时接收并输出Frida脚本的日志
def on_message(message, data):
if message['type'] == 'send':
print(f"📜 [Frida] {message['payload']}")
script.on('message', on_message)
script.load()
print("✅ SSL Pinning绕过脚本注入成功")
# 如果是spawn启动的,必须resume才能让App继续运行
if spawned_pid is not None:
device.resume(spawned_pid)
print("✅ App已恢复运行")
except Exception as e:
print(f"❌ 脚本注入失败,请检查Frida Server与电脑端Frida版本是否严格匹配: {str(e).strip()}")
if session:
session.detach()
return None, None
return session, script
if __name__ == "__main__":
# 检查命令行参数
if len(sys.argv) != 2:
print("⚠️ 使用方法: python ssl_bypass.py <目标应用完整包名>")
print("💡 示例: python ssl_bypass.py com.tencent.mm")
sys.exit(1)
target_pkg = sys.argv[1]
session_obj, script_obj = bypass_ssl_pinning(target_pkg)
if session_obj and script_obj:
print("\n🎉 绕过成功!现在可以用Fiddler/Charles/Burp Suite抓明文HTTPS包了")
print("⏳ 按 Ctrl+C 退出并清理Frida资源...")
try:
# 保持脚本运行,直到用户手动中断
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\n🛑 正在清理资源...")
script_obj.unload()
session_obj.detach()
print("✅ 清理完成,已退出")
else:
sys.exit(1)
3. Necessary preparations before use (must read)
Hardware/Software Requirements
-
Android Device/Emulator
✅ Recommended: MuMu Player 12 (ROOT, comes with ADB, compatible with Windows/Mac, small memory footprint)
✅ Real machine: Must be ROOT + turn on "USB debugging" + allow "USB installation" (optional)
❌ Note: Android 7.0+ system no longer trusts the "User Certificate Store" by default, you need to manually move the root certificate of the packet capture tool to the "System Certificate Store" (requires ROOT or the help ofMagisk+TrustMeAlreadyplug-in)
-
Frida Toolchain
-
PC version:pip install frida frida-tools
-
Device side:
-
Open Frida GitHub Releases
-
Download the version that is strictly consistent with the computer version of Frida** and matches the device/simulator architecture.frida-server-xxx-android-xxx(Such as MuMu Player 12 64-bit downloadfrida-server-16.5.4-android-x86_64)
-
Rename tofrida-server, use ADB to push to/data/local/tmp/and run:
adb push frida-server /data/local/tmp/
adb shell
su
chmod 755 /data/local/tmp/frida-server
/data/local/tmp/frida-server &
- Packet capture tool
- Local proxy has been configured (such as Charles default port 8888)
- The packet capture tool root certificate has been installed into the system certificate store of the Android device
4. Quick troubleshooting of common problems
5. Disclaimer
This article is only used for legal App function testing, security audits of own applications, and explicitly authorized data collection scenarios**. Please do not use it for illegal intrusion, stealing other people's private data, disrupting normal network services, etc. Otherwise, all consequences will be borne by the user and has nothing to do with the author of this article.