Android SSL Pinning Bypass โ Complete Troubleshooting Guide
During mobile penetration testing engagements, one of the most common challenges is bypassing SSL pinning to intercept HTTPS traffic. This guide documents the common issues, errors, and solutions I've encountered across multiple Android mobile assessments.
1. Android Emulator Setup Issues
1.1 ADB Root Access Denied
Error:
adbd cannot run as root in production builds
This happens when using a Google Play system image instead of a Google APIs image. Google Play images are production builds and do not allow root access.
Solution:
- Open Android Studio โ SDK Manager โ SDK Platforms โ Show Package Details
- Download an image labeled "Google APIs" (NOT "Google Play System Image")
- Create a new AVD using this image
Command line alternative:
sdkmanager "system-images;android-30;google_apis;x86_64"
avdmanager create avd -n pentest -k "system-images;android-30;google_apis;x86_64"
2. Frida Server Setup
2.1 Uploading Frida Server to Emulator
First, check the emulator architecture:
adb shell getprop ro.product.cpu.abi
Download the matching frida-server from GitHub releases. Make sure the server version matches your local Frida version:
# Check your frida version
frida --version
# Download matching server (example for x86_64)
wget https://github.com/frida/frida/releases/download/16.7.19/frida-server-16.7.19-android-x86_64.xz
xz -d frida-server-16.7.19-android-x86_64.xz
Push to the device and set permissions:
adb root
adb push frida-server-16.7.19-android-x86_64 /data/local/tmp/frida-server
adb shell chmod 755 /data/local/tmp/frida-server
adb shell /data/local/tmp/frida-server &
Verify it's running:
frida-ps -U
2.2 SELinux Policy Error
Error:
Unable to load SELinux policy from the kernel: Failed to open file
"/sys/fs/selinux/policy": Permission denied
SELinux is blocking Frida server execution. Disable enforcement before running frida-server:
adb root
adb shell setenforce 0
adb shell /data/local/tmp/frida-server &
frida-ps -U
3. APK Patching Issues
3.1 Extracting APK from Emulator
# Find APK path
adb shell pm path com.target.app
# Pull the APK
adb pull /data/app/com.target.app-xxxx/base.apk ./target.apk
For split APKs (multiple paths):
adb shell pm path com.target.app | while read -r line; do
adb pull $(echo $line | cut -d: -f2)
done
3.2 Invalid Filename Characters ($)
Error:
W: res/drawable/$avd_hide_password__0.xml: Invalid file name:
must contain only [a-z0-9_.]
Apktool cannot handle filenames containing the $ character.
Solution:
# Decompile
apktool d merged.apk -o source
# Rename files with $ to _
cd source
find . -name '*\$*' -exec bash -c 'mv "$0" "${0//\$/_}"' {} \;
# Fix references in XML files
grep -rl '\$' res/ | xargs sed -i 's/\$/_/g'
3.3 Corrupted PNG Files
Error:
W: libpng error: IDAT: invalid bit length repeat
W: ERROR: Failure processing PNG image /path/to/image.png
W: libpng error: bad adaptive filter value
W: libpng error: IDAT: CRC error
PNG files became corrupted during decompilation or were optimized in a way apktool cannot process. Skip apktool entirely and use apk-mitm which modifies the APK directly:
npm install -g apk-mitm
apk-mitm merged.apk
3.4 String Formatting Errors
Error:
W: /path/strings.xml:371: error: Multiple substitutions specified
in non-positional format; did you mean to add the formatted="false" attribute?
Add formatted="false" to the problematic string, or use apk-mitm to bypass this issue entirely.
3.5 Merging Split APKs
# Using APKEditor
java -jar APKEditor.jar m -i ./split_apks/ -o merged.apk
4. SSL Pinning Bypass Methods
Method 1: Frida + Objection (Fastest)
The quickest approach when Frida is already running on the device:
objection -g com.target.app explore
# Then run inside objection:
android sslpinning disable
Method 2: Universal Frida Script
Use the community-maintained unpinning script from Frida Codeshare:
frida -U --codeshare akabe1/frida-multiple-unpinning -f com.target.app
Method 3: Custom Bypass Script
Save this as bypass.js for apps with custom pinning implementations:
Java.perform(function() {
// Custom TrustManager that accepts all certificates
var TrustManager = Java.registerClass({
name: 'dev.asd.test.TrustManager',
implements: [Java.use('javax.net.ssl.X509TrustManager')],
methods: {
checkClientTrusted: function(chain, authType) {},
checkServerTrusted: function(chain, authType) {},
getAcceptedIssuers: function() { return []; }
}
});
var TrustManagers = [TrustManager.$new()];
var SSLContext = Java.use('javax.net.ssl.SSLContext');
var SSLContext_init = SSLContext.init.overload(
'[Ljavax.net.ssl.KeyManager;',
'[Ljavax.net.ssl.TrustManager;',
'java.security.SecureRandom'
);
SSLContext_init.implementation = function(km, tm, sr) {
SSLContext_init.call(this, km, TrustManagers, sr);
};
// OkHttp CertificatePinner bypass
try {
var CertificatePinner = Java.use('okhttp3.CertificatePinner');
CertificatePinner.check.overload(
'java.lang.String', 'java.util.List'
).implementation = function() {};
CertificatePinner.check.overload(
'java.lang.String', 'java.security.cert.Certificate'
).implementation = function() {};
} catch(e) {
console.log("[*] OkHttp CertificatePinner not found");
}
});
Run with:
frida -U -f com.target.app -l bypass.js
Method 4: APK Patching with apk-mitm
When Frida is not an option (e.g. Frida detection), patch the APK directly:
npm install -g apk-mitm
apk-mitm merged.apk
# Uninstall original and install patched
adb uninstall com.target.app
adb install merged-patched.apk
Method 5: Manual network_security_config.xml Patch
After decompiling with apktool d merged.apk -o source/, edit res/xml/network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system"/>
<certificates src="user"/>
</trust-anchors>
</base-config>
</network-security-config>
Then rebuild and sign:
# Rebuild
apktool b source -o patched.apk
# Create keystore
keytool -genkey -v -keystore test.keystore -alias test \
-keyalg RSA -keysize 2048 -validity 10000 \
-storepass password -keypass password -dname "CN=test"
# Sign
apksigner sign --ks test.keystore --ks-pass pass:password patched.apk
# Install
adb uninstall com.target.app
adb install patched.apk
5. Quick Reference: Error โ Solution
| Error | Solution |
|---|---|
adbd cannot run as root |
Use Google APIs image, not Google Play |
SELinux policy permission denied |
adb shell setenforce 0 |
Invalid filename ($) |
Rename $ to _ in filenames |
libpng errors / corrupted PNG |
Use apk-mitm instead of apktool |
Multiple substitutions error |
Add formatted="false" or use apk-mitm |
| APK on emulator, not local | adb shell pm path <pkg> then adb pull |
aapt: Could not extract resource |
Install aapt: apt install aapt |
6. Required Tools
| Tool | Installation |
|---|---|
| Frida | pip install frida-tools |
| Objection | pip install objection |
| apk-mitm | npm install -g apk-mitm |
| apktool | apt install apktool |
| apksigner | Part of Android SDK build-tools |
| Android SDK | With Google APIs images |
| Burp Suite | For traffic interception |
7. Workflow Summary
The key takeaway is to start simple โ Objection and Frida scripts handle most apps. Only fall back to APK patching when runtime approaches fail, typically due to Frida detection or root detection mechanisms.
Always document your bypass methodology. It helps the client understand the risk and shows that their pinning implementation needs improvement.