Fix native TLS hook, by calling callback only for cronet

Previously we blacklisted boringssl, but did call it for others,
including Conscrypt, where it seems that this can actually result in JNI
crashes on Android due to exception side effects. We now whitelist
cronet instead - everywhere else, the callback is skipped and we
override the result directly.
This commit is contained in:
Tim Perry
2026-08-28 16:21:21 +01:00
parent fb2e3cb162
commit 9c56acd8c8
2 changed files with 48 additions and 5 deletions
+6 -5
View File
@@ -191,11 +191,12 @@ function patchTargetLib(targetModule, targetName) {
}
pendingCheckThreads.add(threadId);
if (targetName !== 'libboringssl.dylib') {
// Cronet assumes its callback is always called, and crashes if not. iOS's BoringSSL
// meanwhile seems to use some negative checks in its callback, and rejects the
// connection independently of the return value here if it's called with a bad cert.
// End result: we *only sometimes* proactively call the callback.
if (targetName === 'libsscronet.so') {
// Cronet assumes its callback is always called, and crashes if not, so it's
// the one library where we call it up front regardless of what we decide.
// For Conscrypt (libssl) the callback returns with a pending Java exception
// and iOS's BoringSSL rejects bad certs via callback side-effects, so we must
// not call the callback in those cases.
realResult = realCallback(ssl, out_alert);
}
+42
View File
@@ -331,4 +331,46 @@ describe('Test Android unpinning', function () {
});
describe("given native TLS interception only", () => {
beforeEach(async () => {
await launchFrida([
'./test/android/tmp/config.js', // Our custom config
// Just enough to reach the proxy, plus the native TLS hook. No system certificate
// injection and no Java unpinning, so the native hook is the only reason the app
// trusts our CA at all - which is what makes this the scenario that exercises it.
'./native-connect-hook.js',
'./android/android-proxy-override.js',
'./native-tls-hook.js'
]);
});
it("should intercept TLS that the platform itself would reject", async () => {
// This ensures native TLS interception handles Conscrypt correctly, which is tricky
// as its callback has exception-throwing side effects (see #218). Note that this
// alone does cover config-pinned, context-pinned, Volley & TrustKit requests.
await testAllButtons('Success', {
exceptions: [
// Pinned above the TLS layer, in Java, so a native trust hook can't help:
// these need the Java-level unpinning scripts.
'OKHTTP PINNED REQUEST',
'APPMATTUS CT REQUEST',
'APPMATTUS+OKHTTP CT REQUEST',
// WebView does its networking in its own sandboxed process, which our scripts
// are not attached to at all:
'UNPINNED WEBVIEW REQUEST',
// HTTP/3 is blocked outright by the config, and this one doesn't fall back to
// anything we've made trusted:
'UNPINNED HTTP/3 REQUEST',
// Flutter bundles its own TLS stack & CA store, and ignores the proxy settings,
// so it connects out directly & untouched:
'FLUTTER REQUEST',
// This checks the certificate itself, by hand, at the lowest level:
'RAW CUSTOM-PINNED REQUEST'
]
});
});
});
});