// SSL_VERIFY_NONE, on a client, verifies the server certificate but does not // make errors fatal. The result may be checked with |SSL_get_verify_result|. On // a server it does not request a client certificate. This is the default. #define SSL_VERIFY_NONE 0x00
// SSL_VERIFY_PEER, on a client, makes server certificate errors fatal. On a // server it requests a client certificate and makes errors fatal. However, // anonymous clients are still allowed. See // |SSL_VERIFY_FAIL_IF_NO_PEER_CERT|. #define SSL_VERIFY_PEER 0x01
// SSL_VERIFY_FAIL_IF_NO_PEER_CERT configures a server to reject connections if // the client declines to send a certificate. This flag must be used together // with |SSL_VERIFY_PEER|, otherwise it won't work. #define SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02
voidSSL_CTX_set_custom_verify( SSL_CTX *ctx, int mode, enumssl_verify_result_t (*callback)(SSL *ssl, uint8_t *out_alert));
/* * TikTok / Douyin Cronet SSL-Pinning bypass — minimal reference. * * Target verified: TikTok com.zhiliaoapp.musically 45.7.1 (arm64-v8a). * IDA (libsscronet.so v45.7.1): SSL_CTX_set_custom_verify and SSL_CTX_set_quic_method * are import thunks -> the real functions live in libttboringssl.so. So we hook the * EXPORT by name (Strategy A): resolve it in the app's BoringSSL, capture arg[2] * (the verify callback) and flip its result 1 (ssl_verify_invalid) -> 0 (ssl_verify_ok). * * MANDATORY method (see rev-cronet-ssl skill): * - observe-and-nudge, NEVER replace the callback. Let the real verifier run, then in * onLeave flip ONLY 1 -> 0. Leave 2 (ssl_verify_retry, the async signal) untouched. * - QUIC registers a second custom_verify on the same SSL_CTX (the one that also calls * SSL_CTX_set_quic_method). Its callback is an async state machine returning 2; flipping * it desyncs state and crashes a media thread. We remember QUIC ctxs via * SSL_CTX_set_quic_method and SKIP installing our hook for those ctxs. * * Run at spawn (the SSL_CTX is built early; attaching late misses it): * frida -U -f com.zhiliaoapp.musically -l tiktok_ssl_bypass.js * frida -U -f com.ss.android.ugc.aweme -l tiktok_ssl_bypass.js # Douyin * * Success signal: "hit | caller=libsscronet.so ..." then "force verify 1 -> 0". * QUIC/UDP video traffic stays native and won't show in an HTTP/TCP proxy — expected. */ 'use strict';
varCONFIG = { // App's own BoringSSL only. NEVER add the system libssl.so: it resolves the symbol but // no Cronet traffic flows through it, so you'd hook it and see zero callback hits. modules: [ 'libttboringssl.so', // ByteDance BoringSSL (real home of the symbols) 'libsscronet.so'// Cronet (PLT thunks; kept as a fallback resolver) ], pollMs: 200, pollTimeoutMs: 60000, verbose: true };
functiontryHookModule(moduleName) { // Register the QUIC watcher first so ctxs are known before custom_verify fires. watchQuicMethod(moduleName); returnhookExport(moduleName); }
functionscanLoaded() { var any = false; CONFIG.modules.forEach(function (m) { if (findModuleByName(m) !== null) { if (tryHookModule(m)) any = true; } }); return any; }
functionwatchDlopen() { ['android_dlopen_ext', 'dlopen'].forEach(function (loaderName) { var loader = null; try { loader = Module.getExportByName(null, loaderName); } catch (_) {} if (loader === null) { try { loader = Module.findExportByName(null, loaderName); } catch (_) {} } if (loader === null) return; Interceptor.attach(loader, { onEnter: function (args) { this.target = null; var p = args[0]; if (!p || p.isNull()) return; var path = p.readCString(); if (path === null) return; for (var i = 0; i < CONFIG.modules.length; i++) { if (path.indexOf(CONFIG.modules[i]) >= 0) { this.target = CONFIG.modules[i]; break; } } }, onLeave: function () { if (this.target) { log('loaded ' + this.target); tryHookModule(this.target); } } }); }); }
// Poll until the app's BoringSSL is hooked. Keep polling for libttboringssl.so specifically; // do not accept the system libssl.so (it resolves the symbol but carries no Cronet traffic). functionstartPolling() { if (scanLoaded() && installed['libttboringssl.so']) return; var elapsed = 0; var timer = setInterval(function () { elapsed += CONFIG.pollMs; scanLoaded(); if (installed['libttboringssl.so']) { clearInterval(timer); return; } if (elapsed >= CONFIG.pollTimeoutMs) { clearInterval(timer); log('gave up after ' + (CONFIG.pollTimeoutMs / 1000) + 's; no BoringSSL hooked. ' + 'Confirm the app uses libttboringssl.so / libsscronet.so and run at spawn (-f).'); } }, CONFIG.pollMs); }