Setting Up a Mobile Pentest Lab

Mobile testing lives or dies on the lab. You need a workstation that holds the toolchain, Android targets that are rooted and disposable (an emulator or Genymotion, plus one real phone), iOS targets that are jailbroken (Corellium in the browser or a physical iPhone), and the proxy plumbing that lets you read TLS. Here is how to assemble that fleet, the trade-offs between virtual and physical, and how to get interception and Frida working without the usual day of fighting certificates.

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.

JobToolsWhy
Staticjadx, apktool, MobSF, otool/class-dump (iOS), GhidraRead the binary without running it
DynamicFrida, objection, frida-toolsHook and manipulate the live app
NetworkBurp Suite, mitmproxyIntercept and tamper traffic
Device glueadb + platform-tools (Android), libimobiledevice (iOS), scrcpyTalk to and drive the devices
💡 Run MobSF in Docker rather than installing it natively — it has a heavy dependency tree and a containerised instance is trivial to reset. 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:

OptionWhat it isWhen to reach for it
Android Studio AVDFree, official; use a "Google APIs" image (not "Play Store") so adb can get rootQuick start, integrates with the SDK you already need
GenymotionFaster x86 images, easy rooted devices, ARM-translation for ARM-only apps, cloud optionWhen the stock emulator is too slow or you need ARM compatibility
shell
# 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
A "Play Store" emulator image will not give you 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.

The emulator-vs-physical call is really "do I need to be honest about hardware?" Most logic, storage, and network findings are identical on an emulator, so default there for speed and disposability. Switch to the physical phone only when attestation, hardware crypto, or sensor behaviour is in scope — and note in the report which target produced which finding.

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.

💡 Corellium also gives you things physical hardware cannot: instant snapshots of a jailbroken device, multiple iOS versions side by side, and built-in instrumentation hooks. If you only ever touch iOS occasionally, the physical route may be cheaper; if iOS is a regular part of the work, Corellium usually pays for itself in saved jailbreak-wrangling time.

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.

TypeSurvives reboot?Notes
Jailbreak typeSurvives reboot?Notes
UntetheredYes, fullyRare on modern versions; the gold standard
Semi-tetheredBoots, but re-run an app to re-jailbreakCommon today (e.g. checkra1n-style on older A-chips, palera1n)
TetheredNo — needs a computer every bootAnnoying 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.

bash
# 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

bash
# 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
The single most common Android proxy failure: you installed the CA as a user certificate (the easy way through Settings), and the app ignores it because it targets API 24+ and only trusts the system store. On a rooted/emulator device you must place the cert in /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.

shell
# 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.

bash
# 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
shell
// 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
  };
});
💡 objection is Frida with the common tasks pre-scripted (keychain dump, pinning/root bypass, storage inspection, memory search). Reach for objection first for routine moves; drop to raw Frida when you need a custom hook objection does not have. Both ride the same frida-server, so getting that running once unlocks everything.
A practical default fleet: a Genymotion (or AVD Google-APIs) rooted image + one Magisk-rooted Pixel for Android; a Corellium subscription and/or one older jailbroken iPhone for iOS; Burp + mitmproxy + MobSF (Docker) + Frida/objection on the workstation; proxy CA in each device's system store. That covers the static, dynamic, and network work for the vast majority of mobile engagements.

Cheatsheet + proxy triage

Lab build cheatsheet

Step / commandWhat it gives you
emulator -avd NAME -writable-system + adb rootRooted Android emulator (Google APIs image)
Genymotion rooted imageFaster Android target, rooted by default
Magisk on a PixelPhysical rooted Android, hideable root
Corellium deviceVirtual jailbroken iOS, any version, snapshot-able
palera1n / version-matched jailbreakPhysical jailbroken iPhone
CA → /system/etc/security/cacerts/HASH.0Trusted 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-pauseSpawn + instrument the app
MobSF in DockerAutomated static+dynamic first pass

"My proxy shows nothing" — fast triage

SymptomLikely causeFix
Burp totally emptyDevice proxy not setSet Wi-Fi proxy → workstation IP:8080
App shows TLS / connection errorsProxy CA not trustedInstall + trust the CA
CA installed, still TLS errors (Android)CA is in the user store, not systemMove it to /system/etc/security/cacerts (root)
Trusted CA, but no HTTPS trafficApp is pinningobjection/Frida pinning bypass
Some traffic missing entirelyNon-HTTP protocol / hardcoded IPUse 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.

Reactions

Related Articles