Learn Ethical Hacking (#69) - Mobile Application Security - Android and iOS
Learn Ethical Hacking (#69) - Mobile Application Security - Android and iOS

What will I learn
- Mobile attack surface -- how mobile apps differ from web apps and why they require specialized testing;
- Android security model -- APK structure, permissions, intents, and the application sandbox;
- iOS security model -- the App Store sandbox, Keychain, and code signing;
- Static analysis -- decompiling APKs with jadx, extracting secrets, and analyzing app logic;
- Dynamic analysis -- intercepting traffic with Burp Suite, Frida instrumentation, and runtime hooking;
- Common mobile vulnerabilities -- insecure storage, certificate pinning bypass, deeplink hijacking;
- OWASP Mobile Top 10 -- the standard framework for mobile application security assessment;
- Defense: secure mobile development practices, certificate pinning, and app hardening.
Requirements
- A working modern computer running macOS, Windows or Ubuntu;
- Android Studio with an emulator or a rooted Android device;
- jadx and Frida installed;
- The ambition to learn ethical hacking and security research.
Difficulty
- Intermediate/Advanced
Curriculum (of the Learn Ethical Hacking Series):
- Learn Ethical Hacking (#1) - Why Hackers Win
- Learn Ethical Hacking (#2) - Your Hacking Lab
- Learn Ethical Hacking (#3) - How the Internet Actually Works - For Attackers
- Learn Ethical Hacking (#4) - Reconnaissance - The Art of Not Being Noticed
- Learn Ethical Hacking (#5) - Active Scanning - Mapping the Attack Surface
- Learn Ethical Hacking (#6) - The AI Slop Epidemic - Why AI-Generated Code Is a Security Disaster
- Learn Ethical Hacking (#7) - Passwords - Why Humans Are the Weakest Cipher
- Learn Ethical Hacking (#8) - Social Engineering - Hacking the Human
- Learn Ethical Hacking (#9) - Cryptography for Hackers - What Protects Data (and What Doesn't)
- Learn Ethical Hacking (#10) - The Vulnerability Lifecycle - From Discovery to Patch to Exploit
- Learn Ethical Hacking (#11) - HTTP Deep Dive - Request Smuggling and Header Injection
- Learn Ethical Hacking (#12) - SQL Injection - The Bug That Won't Die
- Learn Ethical Hacking (#13) - SQL Injection Advanced - Extracting Entire Databases
- Learn Ethical Hacking (#14) - Cross-Site Scripting (XSS) - Injecting Code Into Browsers
- Learn Ethical Hacking (#15) - XSS Advanced - Bypassing Filters and CSP
- Learn Ethical Hacking (#16) - Cross-Site Request Forgery - Making Users Attack Themselves
- Learn Ethical Hacking (#17) - Authentication Bypass - Getting In Without a Password
- Learn Ethical Hacking (#18) - Server-Side Request Forgery - Making Servers Betray Themselves
- Learn Ethical Hacking (#19) - Insecure Deserialization - Code Execution via Data
- Learn Ethical Hacking (#20) - File Upload Vulnerabilities - When Users Upload Weapons
- Learn Ethical Hacking (#21) - API Security - The New Attack Surface
- Learn Ethical Hacking (#22) - Business Logic Flaws - When the Code Works But the Logic Doesn't
- Learn Ethical Hacking (#23) - Client-Side Attacks - Beyond XSS
- Learn Ethical Hacking (#24) - Content Management Systems - Hacking WordPress and Friends
- Learn Ethical Hacking (#25) - Web Application Firewalls - Bypassing the Guards
- Learn Ethical Hacking (#26) - The Full Web Pentest - Methodology and Reporting
- Learn Ethical Hacking (#27) - Bug Bounty Hunting - Getting Paid to Hack the Web
- Learn Ethical Hacking (#28) - The AI Web Attack Surface - AI Features as Vulnerabilities
- Learn Ethical Hacking (#29) - Network Sniffing - Seeing Everything on the Wire
- Learn Ethical Hacking (#30) - Wireless Network Attacks - Breaking Wi-Fi
- Learn Ethical Hacking (#31) - Privilege Escalation - Linux
- Learn Ethical Hacking (#32) - Privilege Escalation - Windows
- Learn Ethical Hacking (#33) - Active Directory Attacks - The Crown Jewels
- Learn Ethical Hacking (#34) - Pivoting and Lateral Movement - Spreading Through Networks
- Learn Ethical Hacking (#35) - Cloud Security - AWS Attack and Defense
- Learn Ethical Hacking (#36) - Cloud Security - Azure and GCP
- Learn Ethical Hacking (#37) - Container Security - Docker and Kubernetes Attacks
- Learn Ethical Hacking (#38) - Infrastructure as Code - Securing the Automation
- Learn Ethical Hacking (#39) - Email Security - Phishing Infrastructure and Defense
- Learn Ethical Hacking (#40) - DNS Attacks - Exploiting the Internet's Foundation
- Learn Ethical Hacking (#41) - Exploitation Frameworks - Metasploit and Cobalt Strike
- Learn Ethical Hacking (#42) - Custom Exploit Development - Writing Your Own
- Learn Ethical Hacking (#43) - Exploit Development Advanced - Modern Mitigations and Bypasses
- Learn Ethical Hacking (#44) - Reverse Engineering - Understanding Binaries
- Learn Ethical Hacking (#45) - Supply Chain Attacks - Poisoning the Source
- Learn Ethical Hacking (#46) - The Human Factor - Why Security Training Fails
- Learn Ethical Hacking (#47) - Physical Security and OSINT - The Forgotten Attack Vectors
- Learn Ethical Hacking (#48) - Insider Threats - When the Call Is Coming from Inside the House
- Learn Ethical Hacking (#49) - Deepfakes and AI Deception - The New Social Engineering
- Learn Ethical Hacking (#50) - Red Team Operations - Simulating Real Attacks
- Learn Ethical Hacking (#51) - Incident Response - When Things Go Wrong
- Learn Ethical Hacking (#52) - Threat Intelligence - Knowing Your Enemy
- Learn Ethical Hacking (#53) - Security Architecture - Designing Systems That Resist Attack
- Learn Ethical Hacking (#54) - Compliance and Governance - The Business of Security
- Learn Ethical Hacking (#55) - Privacy and Data Protection - GDPR, CCPA, and Beyond
- Learn Ethical Hacking (#56) - Cryptocurrency Security - Attacking and Defending Digital Assets
- Learn Ethical Hacking (#57) - IoT and Embedded Security - Hacking the Physical World
- Learn Ethical Hacking (#58) - The AI Security Landscape - Attacking and Defending AI Systems
- Learn Ethical Hacking (#59) - Python for Pentesters - Automating Everything
- Learn Ethical Hacking (#60) - Zig for Security Tools - When Speed and Memory Matter
- Learn Ethical Hacking (#61) - Writing Custom Scanners - Beyond Off-the-Shelf
- Learn Ethical Hacking (#62) - C2 Frameworks - Building Command and Control
- Learn Ethical Hacking (#63) - Payload Generation and Evasion - Defeating Antivirus
- Learn Ethical Hacking (#64) - Fuzzing - Finding Bugs at Machine Speed
- Learn Ethical Hacking (#65) - OSINT Automation - Large-Scale Intelligence Gathering
- Learn Ethical Hacking (#66) - Reporting and Documentation - The Professional Difference
- Learn Ethical Hacking (#67) - Continuous Security - DevSecOps and Pipeline Security
- Learn Ethical Hacking (#68) - Wireless and Bluetooth Exploitation Deep Dive
- Learn Ethical Hacking (#69) - Mobile Application Security - Android and iOS (this post)
Learn Ethical Hacking (#69) - Mobile Application Security - Android and iOS
Solutions to Episode 68 Exercises
Exercise 1: Bluetooth enumeration (abbreviated).
bluetoothctl scan (60 seconds in home environment):
Discoverable devices: 7
- Samsung TV (Classic BT, A/V device)
- JBL speaker (Classic BT, Audio)
- Neighbor's iPhone (BLE, phone)
- Xiaomi Mi Band (BLE, wearable)
- Smart light bulb (BLE, Zigbee bridge)
- Unknown device (BLE, no name advertised)
- Car Bluetooth (Classic BT, hands-free)
GATT enumeration of own smart bulb:
Services: Generic Access, Device Info, custom service (FFE0)
Characteristic FFE1: read/write, NO authentication required
Writing 0x01 to FFE1 turns light ON, 0x00 turns OFF
No encryption, no challenge-response -> replay-vulnerable
The split between Classic BT and BLE in this scan tells you something fundamental about the current state of Bluetooth security. The three Classic BT devices (TV, speaker, car) all require pairing before data exchange -- you cannot just connect and start sending commands. The four BLE devices are a different story entirely. The Xiaomi Mi Band advertises openly and while it requires a paired app for most functions, the device name and advertising data (including battery level and device type) are broadcast continuously to anyone within range. The smart bulb is the real finding here -- characteristic FFE1 being readable and writable WITHOUT authentication means anyone within BLE range (typically 30-50 meters, but with a directional antenna you can push that considerably) can turn this light on and off at will. This is exactly the "Just Works" pairing problem we discussed in episode 68: the BLE device accepts connections from anyone because the manufacturer prioritized ease of setup over security. A $15 smart bulb is a minor concern. A $120 smart lock with the same architecture is a serious one.
The "Unknown device (BLE, no name advertised)" is worth investigating further in a real assessment -- some devices suppress their name in advertisements as a privacy measure, while others are simply configured without a name. Unknown BLE devices in range are always worth probing, because they could be anything from a forgotten smart sensor to a rogue device planted for data exfiltration (BLE-based keyloggers and tracking implants exist and are disturbingly cheap).
Exercise 2: KNOB attack analysis (abbreviated).
CVE-2019-9506 -- Key Negotiation of Bluetooth
Mechanism: during Bluetooth pairing, devices negotiate encryption
key length (1-16 bytes). The attacker MITMs the negotiation and
forces both sides to accept a 1-byte key. Neither device verifies
the other's requested key length.
1-byte key = 256 possible keys. Brute-force in under 1 second.
Attacker decrypts all Bluetooth traffic in real time.
Affected: ALL Bluetooth BR/EDR devices (specification flaw).
Fix: Bluetooth SIG updated spec to mandate minimum 7-byte key.
Vendors patched implementations to reject <7-byte keys.
The KNOB attack is a textbook downgrade attack -- the same pattern we analyzed in episode 9 when discussing POODLE (forcing TLS downgrade to SSL 3.0) and FREAK (forcing RSA export-grade 512-bit keys). The mechanism is identical across all three: a protocol allows negotiation of security parameters, the negotiation itself is not integrity-protected, and an attacker in the middle forces the weakest allowed setting. The Bluetooth spec allowed 1-byte key entropy because the original Bluetooth 1.0 designers in 1998 wanted backward compatibility with extremely constrained hardware. Twenty-one years later, that backward compatibility created a universal decryption vulnerability affecting every Bluetooth Classic device on the planet.
The math is worth being explicit about: 1-byte entropy = 8 bits = 256 possible keys. A modern CPU brute-forces 256 AES key candidates in microseconds. Compare that to the 7-byte minimum the Bluetooth SIG mandated after disclosure: 7 bytes = 56 bits = roughly 72 quadrillion possible keys. Even at billions of attempts per second, that takes years. The jump from 1 byte to 7 bytes is not a 7x improvement -- it is a 2^48 improvement (approximately 281 trillion times harder). This is why the minimum key length matters so much in any cryptographic negotiation, and why we keep hammering the point from episode 9: the encryption algorithm (AES in this case) is almost never the weak link. Key management, key negotiation, and key entropy are where protocols actually break.
Exercise 3: RTL-SDR 433 MHz capture (abbreviated).
Captured devices (rtl_433, 30-minute scan):
- Weather station: temperature 21.3C, humidity 65%, no encryption
- Tire pressure sensor: 2.4 bar, ID visible, no encryption
- Wireless doorbell: trigger signal, fixed code (replayable)
All 3 devices transmit in plaintext. No encryption, no
authentication. Anyone with a $25 RTL-SDR can read all data.
Three devices in 30 minutes, and every single one transmitting in the clear. The weather station is relatively harmless (knowing your neighbor's backyard temperature is not exactly a national security threat), but the tire pressure sensor is more interesting than it first appears. TPMS sensors broadcast a unique ID with each reading -- which means anyone with an RTL-SDR can track a specific vehicle by monitoring for its TPMS sensor IDs at multiple points along a route. This has been demonstrated in academic research: place receivers at intervals along a highway, correlate the TPMS IDs, and you can track a vehicle's movements without cameras or license plate readers. Just a $25 radio dongle and some Python (episode 59 ;-)).
The wireless doorbell using a fixed code is the most actionable finding. As we discussed in episode 68, a fixed code means the device responds to the exact same signal every time. Capture it once with the RTL-SDR, replay it with a HackRF, and you can ring the doorbell at will. More critically, the same fixed-code pattern is found in older garage door openers, car alarms, and wireless alarm sensors -- any device that uses a static signal without rolling codes or challenge-response is vulnerable to replay from anyone who captures a single transmission.
Episode 68 went deep on the wireless world beyond Wi-Fi -- Bluetooth Classic and BLE enumeration, BlueBorne and KNOB attacks, Zigbee and Z-Wave smart home exploitation, Software Defined Radio across the 433 MHz band and beyond, RFID protocol-level attacks with Proxmark3, and wireless IDS evasion techniques. The theme was that the radio spectrum is full of devices transmitting data in ways their manufacturers never expected anyone to intercept -- and the tools to do it are cheap, legal (for receive-only), and increasingly accessible.
Today we shift from radio frequencies to the device in your pocket. Mobile apps sit at the intersection of everything we have covered so far: they make HTTP requests (episode 11), call APIs (episode 21), store data locally, use cryptography (episode 9), and communicate over wireless networks the developer does not control. But mobile adds something fundamentally new to the attack surface: the application itself is in the attacker's hands. Unlike a web server where the attacker can only probe from the outside, a mobile APK or IPA can be downloaded, decompiled, disassembled, and analyzed at leisure. Every hardcoded secret, every obfuscated algorithm, every hidden API endpoint -- an attacker with patience and jadx can extract all of it.
Mobile Is Different
Mobile applications are NOT just web apps in a smaller window. They run in a sandboxed environment with access to sensors (camera, microphone, GPS, accelerometer, NFC), local storage, push notifications, biometric authentication, and platform-specific APIs that web applications can only dream of. They communicate over networks the developer does not control -- public WiFi, cellular connections, and Bluetooth links. They are installed on devices the developer cannot inspect or manage, running on OS versions that range from "latest security patch" to "three years behind and will never be updated."
This creates an attack surface that web applications simply do not have. With a web application, the server-side code remains on the server -- the attacker sees HTTP responses, JavaScript, and HTML, but not the backend logic. With a mobile application, the developer ships a compiled binary to the user's device, and that binary contains the application's entire client-side logic, hardcoded configuration, API endpoints, embedded certificates, and (if the developer made poor choices) API keys, OAuth client secrets, Firebase credentials, and sometimes even database passwords.
The fundamental problem is that anything shipped in a mobile binary should be considered public knowledge. Obfuscation raises the cost of analysis but does not prevent it. ProGuard on Android renames classes and methods but does not hide string constants. Bitcode on iOS adds a compilation step but the resulting binary is still reverse-engineerable with Hopper or Ghidra (which we covered in episode 44 on reverse engineering). If a secret is in the APK, the secret is compromised -- the only question is how much effort the attacker needs to extract it.
Android Security Architecture
Before attacking Android apps, you need to understand the architecture you are working within:
APK structure:
classes.dex -- compiled Java/Kotlin bytecode (Dalvik format)
AndroidManifest.xml -- permissions, activities, services, receivers
res/ -- resources (layouts, strings, images)
lib/ -- native libraries (.so files, per architecture)
assets/ -- raw files bundled with the app
META-INF/ -- APK signature (v1/v2/v3 signing schemes)
Security model:
- Each app runs in its own Linux process with a unique UID
- Each app has its own sandboxed data directory (/data/data/pkg/)
- Permissions requested at install time or runtime (API 23+)
- SELinux mandatory access control enforces per-process policies
- Verified boot chain ensures OS integrity (but rooting bypasses it)
- App signing ensures APK authenticity (but not code security)
The Linux UID separation is the foundation of Android security. Each installed app gets its own Linux user ID, which means App A cannot read App B's files, memory, or network connections -- not because of Java sandboxing, but because of Linux kernel-level permission enforcement. This is fundamentally stronger than browser-based isolation (which relies on the browser process correctly enforcing separation) because it uses the same process isolation that has protected multi-user Linux servers for decades.
Having said that, this separation only holds when the device is not rooted. A rooted Android device gives any app (or any adb shell session) root access, which means full read/write access to every app's private data directory, the ability to attach debuggers to any running process, and the ability to modify the system itself. For security testing, this is exactly what you want -- you NEED root access to inspect how apps store data, intercept encrypted traffic, and hook into running processes with Frida. For users, this is why rooting your daily-driver phone is a significant security tradeoff.
Static Analysis -- Decompiling Android Apps
Here we go -- the first step in any mobile pentest is pulling the APK and looking inside:
# Step 1: Get the APK from a device
adb shell pm list packages | grep target
adb shell pm path com.target.app
adb pull /data/app/com.target.app/base.apk
# Step 2: Decompile with jadx (Java decompiler)
jadx -d output/ base.apk
# Produces readable Java source code from DEX bytecode
# Step 3: Hunt for secrets
grep -r "api_key\|password\|secret\|token\|firebase" output/sources/
grep -r "http://\|https://" output/sources/ | grep -v "schemas.android"
# Common findings in real-world apps:
# - API keys in BuildConfig.java or strings.xml
# - Firebase database URLs (often publicly accessible)
# - Backend API endpoints with debug/admin paths exposed
# - OAuth client secrets in plain text
# - Encryption keys stored as string constants
# - AWS credentials hardcoded in source
# Step 4: Analyze AndroidManifest.xml
# Look for:
# android:debuggable="true" (debug mode in release build!)
# android:allowBackup="true" (app data extractable via ADB)
# android:usesCleartextTraffic="true" (allows HTTP, not just HTTPS)
# exported="true" on activities/services (accessible by other apps)
# Step 5: Check for native libraries
ls output/resources/lib/
# .so files need Ghidra or IDA Pro for analysis (episode 44)
The grep for secrets in decompiled source code is remarkably effective. I am consistently amazed at what developers ship in production APKs. Firebase database URLs that allow unauthenticated read/write access to the entire backend database. AWS access keys with full S3 permissions. Stripe secret keys (not publishable keys -- the actual secret keys that can create charges). Debug API endpoints like /api/admin/reset-password?email= that were meant for development and accidentically left in the release build. All of this is trivially extractable from any APK with jadx and grep -- no reverse engineering expertise required, just patience and the right search terms.
The android:allowBackup="true" flag deserves special attention. When this is enabled (and it is the DEFAULT), anyone with physical access to the device (or ADB access over USB) can extract the app's entire private data directory using adb backup. This includes SQLite databases, SharedPreferences files, and any files the app stores privately -- often including authentication tokens, cached credentials, and session cookies. The fix is one line in AndroidManifest.xml: android:allowBackup="false". One line. And yet a startling number of production apps leave the default.
Dynamic Analysis -- Traffic Interception and Runtime Hooking
Static analysis shows you the code. Dynamic analysis shows you what the code actually DOES when it runs:
# Set up Burp Suite proxy for Android traffic interception
# 1. Configure Burp to listen on all interfaces (0.0.0.0:8080)
# 2. On Android: WiFi settings -> proxy -> manual -> Burp IP:8080
# 3. Install Burp CA certificate on device (Settings -> Security)
# This intercepts ALL HTTP/HTTPS traffic from the app.
# You see every API call, every parameter, every response.
# But modern apps use CERTIFICATE PINNING to block this.
# The app embeds the server's certificate (or public key hash)
# and rejects connections to any server with a different cert --
# including your Burp proxy.
# Bypass certificate pinning with Frida:
pip install frida-tools
adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "/data/local/tmp/frida-server &"
# Universal SSL pinning bypass
frida -U -f com.target.app -l ssl-pinning-bypass.js --no-pause
# Script source: codeshare.frida.re (community-maintained bypass scripts)
Frida is the single most powerful tool in the mobile security tester's arsenal. It is a dynamic instrumentation framework -- it injects a JavaScript engine into a running process and lets you hook any function, modify return values, inspect arguments, and change application behavior at runtime. Want to bypass a login check? Hook the authentication function and force it to return true. Want to see what the app sends to its analytics server? Hook the HTTP client and log every request. Want to disable root detection? Hook the function that checks for the su binary and make it always return "not found."
# Frida script: hook a login function and bypass authentication
import frida
device = frida.get_usb_device()
pid = device.spawn(["com.target.app"])
session = device.attach(pid)
script = session.create_script("""
Java.perform(function() {
var LoginActivity = Java.use('com.target.app.LoginActivity');
LoginActivity.checkPassword.implementation = function(password) {
console.log('[*] Password checked: ' + password);
console.log('[*] Bypassing -- returning true');
return true;
};
// Also hook the root detection
var RootCheck = Java.use('com.target.app.security.RootDetector');
RootCheck.isRooted.implementation = function() {
console.log('[*] Root detection bypassed');
return false;
};
});
""")
script.load()
device.resume(pid)
This is not theoretical. Run this against a real banking app (on your own test device, with proper authorization) and watch what happens. The login function returns true regardless of the password. The root detection that was supposed to prevent the app from running on compromised devices returns false. The certificate pinning that was supposed to block traffic interception is bypassed. Three hooks, maybe 20 lines of JavaScript, and the app's entire client-side security model is defeated.
The lesson connects directly to episode 17 on authentication bypass: client-side authentication is not authentication at all. It is a UX convenience. The real authentication must happen server-side, because the client is in the attacker's hands. A mobile app that checks the password locally (comparing against a stored hash on the device) is trivially bypassed with Frida. An app that sends credentials to a server and requires a valid session token for every subsequent API call is fundamentally harder to attack -- you cannot Frida your way past a server-side check.
Common Mobile Vulnerabilities
1. Insecure Data Storage
App stores sensitive data in plaintext:
- SharedPreferences (Android) / NSUserDefaults (iOS)
- SQLite databases without encryption
- Files on external storage (world-readable on older Android)
- Application logs containing tokens or passwords
- Clipboard (copy/paste leaks to other apps on older Android)
Check: adb shell run-as com.target.app cat shared_prefs/*.xml
2. Insecure Communication
- HTTP instead of HTTPS for API calls
- No certificate pinning (MITM with proxy is trivial)
- Ignoring SSL errors in code (trustAllCertificates anti-pattern)
- Transmitting sensitive data in URL parameters (cached in logs)
3. Hardcoded Secrets
- API keys, OAuth secrets, encryption keys baked into the APK
- Firebase/AWS credentials in strings.xml or BuildConfig
- Extractable in under 5 minutes with jadx and grep
4. Deeplink Hijacking
- Android intents and iOS universal links can be intercepted
- Malicious app registers the same custom URL scheme
- Intercepts OAuth callbacks, password reset tokens, etc.
- Particularly dangerous for OAuth redirect_uri hijacking
5. Insufficient Cryptography
- ECB mode (patterns visible in ciphertext -- episode 9)
- Hardcoded IV (initialization vector reuse)
- Deprecated algorithms (MD5 for password hashing)
- Key derived from predictable source (device ID, package name)
6. WebView Vulnerabilities
- JavaScript enabled in WebView loading untrusted content
- addJavascriptInterface exposes native functions to web content
- File access enabled (can read app's private files via JS)
- This is essentially XSS (episode 14) but with native API access
Deeplink hijacking deserves extra attention because it is devastatingly effective and poorly understood by most mobile developers. Here is how it works: an Android app registers a custom URL scheme like myapp://callback for handling OAuth redirects. When the user authenticates via a browser, the OAuth provider redirects to myapp://callback?code=AUTH_CODE. The legitimate app is supposed to receive this and exchange the auth code for an access token. But Android allows MULTIPLE apps to register the same URL scheme -- if a malicious app also registers myapp://callback, Android may show a chooser dialog or (on some OS versions) route the redirect to whichever app was installed first. The malicious app receives the auth code, exchanges it for an access token, and now has full access to the victim's account. No exploit needed. No vulnerability in the target app's code. Just a design flaw in the redirect mechanism.
The fix on Android is App Links (verified HTTPS-based deep links with assetlinks.json on the server), and on iOS Universal Links rather than custom URL schemes. Both require the server to host a verification file that proves which app is authorized to handle links for that domain. This prevents a malicious app from hijacking the redirect because only the verified app can claim the domain-based link.
OWASP Mobile Top 10
M1: Improper Credential Usage -- hardcoded keys, shared secrets
M2: Inadequate Supply Chain Security -- malicious SDKs, compromised libs
M3: Insecure Authentication/Authorization -- client-side auth, weak tokens
M4: Insufficient Input/Output Validation -- mobile-specific injection
M5: Insecure Communication -- no TLS, no pinning, cleartext traffic
M6: Inadequate Privacy Controls -- data leakage, excessive permissions
M7: Insufficient Binary Protections -- no obfuscation, no tamper detection
M8: Security Misconfiguration -- debug enabled, backup allowed
M9: Insecure Data Storage -- plaintext secrets, unencrypted databases
M10: Insufficient Cryptography -- weak algorithms, hardcoded keys
Every mobile pentest should assess against this list.
Tools: MobSF automates many of these checks in a single scan.
This list maps directly to vulnerability classes we have covered across the series. M3 (Insecure Authentication) is episode 17. M4 (Insufficient Input Validation) covers SQL injection (episode 12) and XSS (episode 14) in the mobile context. M5 (Insecure Communication) connects to the MITM attacks from episodes 29 and 68. M2 (Supply Chain Security) is episode 45. The OWASP Mobile Top 10 is not a new set of vulnerabilities -- it is the same vulnerability classes we have been studying all series, repackaged for the mobile threat model where the attacker has full access to the client binary.
MobSF -- Automated Mobile Analysis
# Mobile Security Framework -- static + dynamic in one tool
docker run -it --rm -p 8000:8000 opensecurity/mobile-security-framework-mobsf
# Upload APK via web interface at localhost:8000
# MobSF automatically:
# - Decompiles the APK (like jadx)
# - Scans for hardcoded secrets (API keys, tokens, passwords)
# - Checks AndroidManifest for misconfigurations
# - Analyzes requested permissions (excessive? dangerous?)
# - Identifies known vulnerable libraries (SCA, like episode 67)
# - Checks certificate pinning implementation
# - Rates overall security posture with a score (A through F)
# For dynamic analysis:
# Connect a rooted device or Genymotion emulator
# MobSF instruments the app via Frida, monitors file system
# access, logs network traffic, captures runtime behavior
# Also supports iOS IPA analysis (static only without a device)
MobSF is the Burp Suite of mobile security -- one tool that combines quit some different analysis capabilities that would otherwise require separate tools. It runs jadx for decompilation, implements its own secret scanning rules, checks the manifest against known misconfiguration patterns, analyzes permissions against a database of abuse patterns, and produces a comprehensive report mapped to the OWASP Mobile Top 10. For a time-constrained assessment where you need to evaluate a mobile app quickly, running MobSF first gives you 80% coverage in 10 minutes. The remaining 20% (business logic flaws, complex authentication bypasses, app-specific attack chains) requires manual work with Frida, Burp Suite, and creative thinking -- the kind of analysis that separates a professional mobile pentest from an automated scan.
The dynamic analysis mode is where MobSF gets genuinely impressive. It instruments the app via Frida, launches it on an emulator, exercises the UI, and logs every file access, every network request, every inter-process communication, and every cryptographic operation. The report shows you exactly which files the app reads and writes (and whether they contain sensitive data), which domains it communicates with (and whether it uses TLS), and whether the app detects root or debugging (and how robust that detection is). All without writing a single line of Frida script yourself.
iOS Security -- A Stricter Sandbox
iOS security model is stricter than Android in several key ways:
App Store review: every app reviewed before distribution
(no sideloading without jailbreak, enterprise cert, or TestFlight)
Mandatory code signing: apps must be signed with Apple-issued cert
Keychain: hardware-backed credential storage (Secure Enclave on newer)
App Transport Security (ATS): enforces HTTPS by default
Strict sandbox: no file system access between apps
No inter-app intent system: communication only via defined APIs
Testing iOS requires:
- A jailbroken device (or provisioned dev/test device)
- Frida or objection for runtime instrumentation
- class-dump for extracting Objective-C class info
- Hopper Disassembler or Ghidra for Mach-O analysis (episode 44)
- Burp Suite with CA cert installed via MDM profile
Common iOS-specific findings:
- ATS exceptions in Info.plist disabling HTTPS enforcement
- Keychain items with kSecAttrAccessibleAlways (accessible when locked)
- Pasteboard (clipboard) leaking sensitive data between apps
- App snapshots in task switcher capturing sensitive screen content
- Biometric bypass (Touch ID / Face ID) via Frida hook
The iOS security model makes testing significantly harder than Android (which is, of course, the entire point). You cannot just download an IPA from the App Store and decompile it -- the binary is encrypted with Apple's FairPlay DRM, and you need a jailbroken device to decrypt it before analysis. The jailbreak itself is a moving target: Apple patches jailbreak vulnerabilities in every iOS update, so your test device often runs an older iOS version with a working jailbreak, which means your results may not perfectly reflect the app's behavior on the latest iOS. This is an inherent limitation of iOS security testing with no clean solution.
The Keychain with Secure Enclave hardware is iOS's answer to Android's SharedPreferences credential storage problem. The Secure Enclave is a separate processor inside the iPhone that handles cryptographic operations -- the encryption key never leaves the hardware, so even a full jailbreak cannot extract the raw key material. Developers who store credentials in the Keychain with the correct protection class (kSecAttrAccessibleWhenUnlockedThisDeviceOnly) are using one of the strongest credential storage mechanisms on any consumer device. Developers who use kSecAttrAccessibleAlways (the laziest option) defeat the entire purpose because the credential is accessible even when the device is locked -- anyone with physical access and a jailbreak can extract it.
A part from Keychain misuse, the most common iOS finding is ATS exceptions. App Transport Security requires HTTPS for all connections by default -- any HTTP request is blocked. Developers who need to reach legacy HTTP servers (or who are just trying to make their debug environment work) add exceptions to Info.plist: NSAllowsArbitraryLoads = YES disables ATS entirely. This single flag turns off the HTTPS requirement for the whole app, enabling trivial MITM interception of all traffic without even needing to bypass certificate pinning. Apple has been tightening the rules around ATS exceptions in App Store review, but many apps still ship with overly broad exceptions that should have been scoped to specific domains instead of applied globally.
Defense: Secure Mobile Development
1. Never hardcode secrets -- use server-side configuration,
Android Keystore / iOS Keychain for local secret storage
2. Certificate pinning -- pin to the server's public key (SPKI hash),
not the full certificate (survives cert rotation)
3. Encrypt local storage -- SQLCipher for databases,
EncryptedSharedPreferences on Android, Keychain on iOS
4. Obfuscate release builds -- ProGuard/R8 on Android, Swift
compiler optimizations on iOS (raises reverse engineering cost)
5. Detect rooted/jailbroken devices -- degrade functionality,
NOT a security boundary (bypassable with Frida in seconds)
6. Server-side validation on EVERY API call -- short-lived tokens,
session validation, rate limiting. This is the ONE control
the attacker cannot Frida their way past.
7. Platform attestation -- Android Play Integrity, iOS App Attest
for device integrity verification
8. Validated deep links -- App Links (Android) / Universal Links
(iOS) instead of custom URL schemes
9. Test against OWASP Mobile Top 10 before every release
10. Disable debug mode and backup in release builds
Point 6 -- server-side validation on EVERY API call -- is the single most important item on this list. All the other defenses (obfuscation, root detection, certificate pinning) are client-side controls that an attacker with Frida can bypass in minutes. Server-side validation is the one control the attacker cannot touch. Every endpoint should verify: is this token valid? Has it expired? Does this user have permission for this specific action? Is the request rate within normal bounds? If the server validates everything independently, then bypassing client-side controls only gives the attacker a slightly more convenient testing setup -- they still cannot do anything the server does not authorize. If the server trusts the client blindly... well, we covered that lesson in episode 21 on API security: never trust the client. The mobile context makes this rule even more critical because the client is literally a binary the attacker can take apart and reassemble however they want.
The broader picture: mobile security testing is becoming a core skill for any professional pentester, not a niche specialization. Organizations are building mobile-first (or mobile-only) products, and the attack surface is growing faster than the security testing capacity. If you can combine web application testing skills with mobile-specific analysis -- jadx, Frida, MobSF, certificate pinning bypass, Keychain/Keystore assessment -- you are covering a timespam of vulnerabilities that many pentesters cannot, and that translates directly into professional value.
The AI Slop Connection
AI-generated mobile code is a security disaster, and the mobile development community is adopting AI coding assistants faster than arguably any other domain. The result is predictable. AI models produce Android apps that store API keys in BuildConfig (extractable with jadx in seconds), enable android:debuggable="true" in release builds (full remote debugging access for anyone with ADB), disable certificate pinning "to fix SSL errors during development" (and leave it disabled in production), and use SharedPreferences for storing authentication tokens in plaintext (readable with one adb command on a rooted device).
The problem is structural. AI models are trained on GitHub code, and GitHub is full of Android tutorials and Stack Overflow answers that demonstrate the INSECURE way of doing things. cursor.execute(f"SELECT...") in Python is bad enough (episode 12). SharedPreferences.putString("auth_token", token) in Android is the same pattern -- storing a credential in plaintext because the example code does it that way and the developer (or the AI) does not understand the security implications.
MobSF scans of AI-generated Android apps consistently show Critical-severity findings that an experienced developer would catch: debug mode enabled, backup allowed, cleartext traffic permitted, hardcoded secrets, missing certificate pinning. The AI produced code that compiles and runs and passes functional tests -- it just happens to be insecure in half a dozen ways that are invisible until someone actually examines the APK. If you are building mobile applications with AI assistance, running MobSF against the output is not optional -- it is the minimum viable security gate, and we discussed exactly this concept of automated security gates in the DevSecOps pipeline discussion in episode 67.
Exercises
Exercise 1: Download a deliberately vulnerable Android app (DIVA -- Damn Insecure and Vulnerable App, or InsecureBankv2). Decompile it with jadx. Find: (a) hardcoded credentials or API keys, (b) insecure data storage locations (SharedPreferences with sensitive data, world-readable files), (c) exported activities that can be invoked by other apps without authentication. Document each finding with the exact file path and line number from the decompiled source. Save to ~/lab-notes/mobile-static-analysis.md.
Exercise 2: Set up Frida on an Android emulator (Genymotion or Android Studio AVD, API 28 or later). Hook into a test app (DIVA or InsecureBankv2) and: (a) bypass a login function by hooking the authentication method and forcing it to return true, (b) log all HTTP/HTTPS URLs the app connects to by hooking the HTTP client class, (c) dump the contents of SharedPreferences at runtime using Frida's Java.perform API. Document the complete Frida scripts you used for each task. Save to ~/lab-notes/frida-mobile-hooking.md.
Exercise 3: Run MobSF (via Docker) against 3 APKs: one deliberately vulnerable app (DIVA or InsecureBankv2), and 2 real-world apps from APKMirror. Compare: (a) the security scores and severity distribution across all 3 apps, (b) the most common finding category (insecure storage, insecure communication, or hardcoded secrets), (c) whether any app ships with android:debuggable="true" or android:allowBackup="true" in production, (d) whether any app has hardcoded API keys or disabled certificate pinning. Document your findings and assess which app has the weakest security posture and why. Save to ~/lab-notes/mobsf-comparison.md.