workstation · Genymotion · Corellium · jailbroken iPhone · proxy · Frida
The shape of the lab
The first real mobile assessment teaches you the same lesson everyone learns the hard way: the bottleneck is never the app, it is the lab. You burn a day flashing a phone that bricks, fighting an emulator that will not root, or staring at an empty Burp because the device was never actually pointed at the proxy. Build the lab once, deliberately, and the actual testing becomes the easy part.
The shape is always the same. A workstation holds the toolchain and never moves. Around it sits a small fleet of targets — rooted Android and jailbroken iOS, virtual where possible, physical where necessary — and every one of them routes its traffic back through the workstation's proxy. Targets are disposable; you snapshot them, break them, and re-flash them. The workstation is the one thing you keep clean.
What goes on the workstation
Group your tools by the analysis they do, because that is how the previous architecture articles framed the work: static, dynamic, network. You do not need all of it on day one, but you want the trio represented.
| Job | Tools | Why |
|---|---|---|
| Static | jadx, apktool, MobSF, otool/class-dump (iOS), Ghidra | Read the binary without running it |
| Dynamic | Frida, objection, frida-tools | Hook and manipulate the live app |
| Network | Burp Suite, mitmproxy | Intercept and tamper traffic |
| Device glue | adb + platform-tools (Android), libimobiledevice (iOS), scrcpy | Talk to and drive the devices |
docker run -p 8000:8000 opensecurity/mobile-security-framework-mobsf and you have the automated static+dynamic first pass from the threat-model article up in a minute.Android targets — virtual first, hardware when you must
Android is the easy platform to build for, because almost everything can be virtual and rooted with no special hardware. You want two tiers: a fast disposable emulator/Genymotion image for the bulk of the work, and one physical rooted phone for the cases that need real silicon.
Emulator and Genymotion — your default
For day-to-day testing, a virtual device wins: it is fast, you can snapshot it before you break something, and you can re-create it in seconds. Two good options:
| Option | What it is | When to reach for it |
|---|---|---|
| Android Studio AVD | Free, official; use a "Google APIs" image (not "Play Store") so adb can get root | Quick start, integrates with the SDK you already need |
| Genymotion | Faster x86 images, easy rooted devices, ARM-translation for ARM-only apps, cloud option | When the stock emulator is too slow or you need ARM compatibility |
# Stock emulator: pick a Google APIs (not Play) image, then take adb root: emulator -avd Pixel_API_34 -writable-system adb root && adb remount # writable system → install CA into system store # Confirm you have root on the device: adb shell whoami # → root adb shell getprop ro.build.version.release
adb root, and that matters more than it sounds: on Android 7+, most apps only trust CAs in the system store, and you can only write there with root + a writable system. Always pick the Google APIs (non-Play) image for testing, or you will fight the proxy CA forever. Genymotion images are rooted by default and sidestep this entirely.A physical rooted phone — for the hardware-only cases
Keep one real Android phone (a Pixel is the path of least resistance for Magisk) for the things an emulator cannot do honestly: hardware-backed Keystore behaviour, real sensors, biometric flows, and how an app reacts to Play Integrity / SafetyNet attestation, which often behaves differently on emulated hardware. Root it with Magisk so you get systemless root plus the ability to hide it (Zygisk/DenyList) when you need to test the app's own root-detection.
iOS targets — Corellium, a real iPhone, or both
iOS is where the lab gets expensive and opinionated, for exactly the reasons the iOS architecture article laid out: you cannot run modified or instrumented code on a stock device, so you need a jailbroken one, and jailbreaks are version-locked and increasingly scarce on current hardware. There are two routes, and serious teams run both.
Corellium — virtual jailbroken iOS
Corellium is the closest thing to a cheat code in mobile testing: it virtualises real ARM iOS devices in the browser, and the devices come already jailbroken, on a range of iOS versions you choose. No physical jailbreak to chase, no waiting for a tool to drop for the version you need, and you can spin up a fresh device per engagement and throw it away. It is a paid, enterprise-priced product, but for a team doing regular iOS work it removes the single biggest source of lab pain.
A physical jailbroken iPhone — for what virtualisation cannot do
A real jailbroken iPhone still earns its place: some hardware behaviours, certain Secure Enclave / biometric flows, and the odd anti-tampering check are most faithfully tested on physical silicon. The catch is the one from the architecture article — the jailbreak must exist for the exact iOS version and device you have, and Apple closes the bugs that jailbreaks rely on. Practically this means keeping an older device on an older iOS that a known jailbreak supports.
| Type | Survives reboot? | Notes |
|---|---|---|
| Jailbreak type | Survives reboot? | Notes |
| Untethered | Yes, fully | Rare on modern versions; the gold standard |
| Semi-tethered | Boots, but re-run an app to re-jailbreak | Common today (e.g. checkra1n-style on older A-chips, palera1n) |
| Tethered | No — needs a computer every boot | Annoying for a lab device |
Once jailbroken, you install a package manager (Sileo/Cydia), add the Frida repo, and the device behaves much like a rooted Android phone for testing purposes — you can read containers, run frida-server, and inject. The tooling on top is nearly identical across both platforms, which is the whole point of building the fleet this way.
# On a jailbroken iPhone, add the Frida apt repo, then: apt install re.frida.server # via Sileo/Cydia → frida-server on the device # From the workstation, it now looks just like Android to Frida: frida-ps -U # list processes over USB frida-ios-dump -u root -P alpine "Target" # pull the decrypted IPA
The intercepting proxy (the part everyone fights)
Now the part that eats more lab hours than anything else: getting an intercepting proxy to actually show you decrypted traffic. It fails in four predictable stages, and knowing the stage you are in saves the day you would otherwise lose.
Stage one, nothing in Burp: the device was never told to use the proxy. Set the device's Wi-Fi proxy to your workstation's IP and the proxy port (8080 for Burp by default). Stage two, TLS errors: traffic now reaches the proxy, but the proxy presents its own certificate and the device does not trust it. Stage three, install the proxy CA and the handshake succeeds — you read plaintext. Stage four, pinning: the app rejects even a trusted CA because it only accepts a specific certificate, which you defeat at runtime.
Installing the CA — and the Android-7 trap
# Export Burp's CA (Proxy → Options → Import/Export CA) as DER, convert to PEM: openssl x509 -inform DER -in cacert.der -out burp.pem # Android 7+: apps trust only the SYSTEM store by default. With root + writable system: HASH=$(openssl x509 -inform PEM -subject_hash_old -in burp.pem | head -1) adb push burp.pem /sdcard/ adb shell su -c "cp /sdcard/burp.pem /system/etc/security/cacerts/${HASH}.0" adb shell su -c "chmod 644 /system/etc/security/cacerts/${HASH}.0" # (or just let objection/Magisk modules place a system cert for you) # iOS: install the .pem via the device, then TRUST it: # Settings → General → About → Certificate Trust Settings → enable the cert
/system/etc/security/cacerts/. This one trap is responsible for most "my proxy doesn't work" hours — check the store first.Bypassing certificate pinning
When even a trusted system CA does not produce traffic, the app is pinning — it ships the expected certificate/key and rejects anything else. You do not break the crypto; you neuter the check at runtime with the dynamic tooling from the next section.
# objection's one-liner (covers the common pinning implementations): objection -g com.target.app explore -s "android sslpinning disable" objection -g com.target.app explore -s "ios sslpinning disable" # Or a Frida pinning-bypass script (e.g. the well-known universal scripts): frida -U -f com.target.app -l frida-multiple-unpinning.js --no-pause
Wiring up Frida
Frida is the engine behind most of the dynamic work — pinning bypass, root-detection bypass, dumping decrypted iOS binaries, reading keychains, tracing methods. It is worth understanding what it actually does, because once it clicks, half the lab's "magic" stops being magic.
You run frida-server on the device (it needs root/jailbreak — injecting into another process is exactly what the sandbox forbids), connect to it from the workstation over USB, and Frida injects an agent — a small JavaScript runtime — directly into the target app's address space. Your script now runs inside the app, with full reach over its memory and functions. Interceptor.attach() then hooks a function so calls detour through your JS, where you read arguments and rewrite return values live.
# Get the matching frida-server onto the device and run it: # Android: push the right-arch binary, chmod +x, run as root adb push frida-server-android-arm64 /data/local/tmp/fs adb shell "su -c '/data/local/tmp/fs &'" # iOS: apt install re.frida.server (jailbroken), it autostarts # Confirm the host sees it: frida-ps -U # processes over USB # Spawn the app under instrumentation with a script: frida -U -f com.target.app -l hook.js --no-pause
// hook.js — kill a boolean root check by rewriting its return value. // This is the exact pattern objection's bypasses automate. Java.perform(function () { var Sec = Java.use('com.target.app.security.RootCheck'); Sec.isDeviceRooted.implementation = function () { console.log('[*] isDeviceRooted() called -> forcing false'); return false; // app now believes the device is clean }; });
Cheatsheet + proxy triage
Lab build cheatsheet
| Step / command | What it gives you |
|---|---|
emulator -avd NAME -writable-system + adb root | Rooted Android emulator (Google APIs image) |
| Genymotion rooted image | Faster Android target, rooted by default |
| Magisk on a Pixel | Physical rooted Android, hideable root |
| Corellium device | Virtual jailbroken iOS, any version, snapshot-able |
| palera1n / version-matched jailbreak | Physical jailbroken iPhone |
CA → /system/etc/security/cacerts/HASH.0 | Trusted proxy CA on Android 7+ (the trap) |
objection -g APP explore -s "... sslpinning disable" | Bypass certificate pinning |
frida -U -f APP -l hook.js --no-pause | Spawn + instrument the app |
| MobSF in Docker | Automated static+dynamic first pass |
"My proxy shows nothing" — fast triage
| Symptom | Likely cause | Fix |
|---|---|---|
| Burp totally empty | Device proxy not set | Set Wi-Fi proxy → workstation IP:8080 |
| App shows TLS / connection errors | Proxy CA not trusted | Install + trust the CA |
| CA installed, still TLS errors (Android) | CA is in the user store, not system | Move it to /system/etc/security/cacerts (root) |
| Trusted CA, but no HTTPS traffic | App is pinning | objection/Frida pinning bypass |
| Some traffic missing entirely | Non-HTTP protocol / hardcoded IP | Use transparent mode / inspect with tcpdump |
Closing thoughts
Three things to carry forward.
Targets are disposable; the workstation is not. Snapshot your devices, break them freely, and keep all state and tooling on the one machine you never re-flash. Virtual-first (emulator/Genymotion, Corellium) for speed and reset-ability, physical only for the hardware truths virtualisation cannot tell.
iOS cost is a jailbreak problem, and Corellium is the way around it. Everything in the iOS architecture article — encrypted binaries, page-level signing, AMFI — means you need a device where signing enforcement is off. Chasing version-matched physical jailbreaks is the old pain; virtual jailbroken iOS removes it, at a price that pays off if you touch iOS regularly.
Most lab pain is the proxy CA and pinning — and both have a known fix. Put the CA in the system store on Android 7+, trust it on iOS, and bypass pinning at runtime with objection or Frida. Learn the four-stage failure ladder once and you stop losing days to an empty Burp window. With the fleet wired and traffic readable, the rest of the Mobile track — actually attacking storage, IPC, crypto, and the API behind the app — is just work you can finally do.