This is the first tutorial in the Android Bug Bounty: Zero to Zero-Day series. Before we can reverse engineer apps, hook methods, or find vulnerabilities, we need a reliable, isolated environment where we can work without risk to personal devices or data. This tutorial walks through building that lab from scratch.
Note
Scope
Everything in this series targets authorized security testing: your own apps, bug bounty program targets with explicit scope, or deliberately vulnerable training apps. Never test apps or devices you don’t have permission to examine.
Why a Dedicated Lab?
Three reasons to keep your security testing isolated:
- Safety. Rooting a device, installing custom certificates, and running instrumentation frameworks changes the device’s security posture. You don’t want that on a phone with your banking apps.
- Reproducibility. Snapshots and clean emulator images let you reset to a known state between tests. When you find a bug, you can reproduce it from scratch.
- Legal clarity. A dedicated lab makes it unambiguous that your testing is intentional and authorized. If you’re participating in a bug bounty program, you want a clean separation between personal use and research.
Hardware vs Emulator
| Android Emulator | Physical Device | |
|---|---|---|
| Cost | Free | $200-400 (Pixel) |
| Root access | Built-in with Google APIs images | Requires bootloader unlock + Magisk |
| Snapshots | Native support | Not practical |
| Performance | Good with KVM/HAXM | Native speed |
| Hardware features | No Bluetooth, limited sensors | Full hardware access |
| Kernel testing | Custom kernel possible but limited | Full kernel access |
| Detection | Apps can detect emulators | Harder to detect (with Magisk) |
Start with the emulator. It’s free, resettable, and sufficient for Tutorials 0-5. You’ll want a real device when you reach kernel and driver research in Tutorial 6, or when testing apps with aggressive emulator detection.
Setting Up the Android Emulator
Install Android SDK Command-Line Tools
If you already have Android Studio installed, the SDK tools are included. For a lighter setup, install just the command-line tools:
# Download command-line tools from developer.android.com
# Extract to ~/Android/Sdk/cmdline-tools/latest/
# Add to your PATH
export ANDROID_HOME="$HOME/Android/Sdk"
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$PATH"Install System Image and Platform Tools
# Accept licenses
sdkmanager --licenses
# Install required packages
sdkmanager "platform-tools" "emulator" \
"platforms;android-36" \
"system-images;android-36;google_apis;x86_64"Note
Why Android 16?
Android security research is most valuable when your lab matches what current apps and devices actually run. This tutorial therefore uses Android 16 (API 36). That choice changes the interception workflow: modern Android no longer maps cleanly to the old “drop a cert in
/system/etc/security/cacerts” approach, so HTTPS proxying must be treated as version- and app-specific rather than assumed to work by default.
Warning
Google APIs vs Google Play
Use
google_apisimages, notgoogle_play. Google Play images have a locked system partition and a production-signed build, so you cannot get root access or remount the system partition. Google APIs images give youadb rootout of the box.
Create the AVD
avdmanager create avd \
-n bug_bounty \
-k "system-images;android-36;google_apis;x86_64" \
-d pixel_6Boot the Emulator
emulator -avd bug_bounty -writable-system -no-snapshot-loadThe -writable-system flag gives you room for advanced lab modifications and experiments. On modern Android releases, some security-relevant components, including parts of certificate handling, no longer live purely under /system, so do not assume writable /system alone is enough to make a proxy trusted everywhere. -no-snapshot-load ensures a clean boot.
Verify ADB Connectivity
adb devices
# List of devices attached
# emulator-5554 device
adb shell whoami
# shell
adb root
adb shell whoami
# rootIf adb root succeeds, your emulator is ready for security testing.
Rooting the Emulator
On Google APIs emulator images, root is already available via adb root. This restarts the ADB daemon as root, giving you full access to the filesystem and all app sandboxes.
# Restart ADB as root
adb root
# Verify
adb shell id
# uid=0(root) gid=0(root)
# Remount system partition as read-write
adb remountFor physical devices, the process is more involved:
- Unlock the bootloader (
fastboot flashing unlockon Pixel devices: this wipes the device) - Patch the boot image with Magisk
- Flash the patched boot image via fastboot
Magisk provides root access, and Zygisk DenyList can reduce some obvious root signals for some apps. It is not a universal bypass for modern root or integrity checks, so treat it as a convenience for lab work, not a guarantee that a target app will behave like it does on a stock device. Refer to the Magisk documentation for device-specific instructions.
Essential Toolchain
ADB and Fastboot
Already installed via platform-tools above. Verify:
adb version
# Android Debug Bridge version 1.0.41
fastboot --versionjadx: DEX Decompiler
jadx converts Android DEX bytecode back to readable Java source. We’ll use it heavily in Tutorial 1.
# Download from github.com/skylot/jadx/releases
# Or install via package manager:
brew install jadx # macOS
sudo apt install jadx # Debian/Ubuntu (may be outdated)apktool: APK Decoder
apktool decodes the binary AndroidManifest.xml and resources that unzip can’t read.
# Download from ibotpeaches.github.io/Apktool/install/
# Or:
brew install apktoolFrida: Dynamic Instrumentation
Frida lets you hook Java and native methods at runtime. Install the host tools and push the server to the device:
# Host tools
pip install frida-tools
# Download frida-server matching your frida version and device arch
FRIDA_VERSION=$(frida --version)
ARCH="x86_64" # For emulator. Use arm64 for physical devices
wget "https://github.com/frida/frida/releases/download/${FRIDA_VERSION}/frida-server-${FRIDA_VERSION}-android-${ARCH}.xz"
xz -d frida-server-*.xz
# Push to device
adb root
adb push frida-server-* /data/local/tmp/frida-server
adb shell chmod 755 /data/local/tmp/frida-serverobjection: Runtime Exploration
objection wraps Frida with convenience commands for common tasks:
pip install objectionGhidra: Native Code Analysis
For reverse engineering .so native libraries (Tutorial 5):
# Download from ghidra-sre.org
# Extract and run:
./ghidraRunBurp Suite Community: HTTP Proxy
Download from portswigger.net. We’ll configure it as a proxy in the next section.
drozer: IPC Attack Framework
drozer lets you interact with Android IPC components (intents, content providers, broadcast receivers) from the command line:
# Install the current drozer host client using the project's release instructions.
# Package names and install steps vary by environment.
# Install the matching drozer agent APK on the device
adb install drozer-agent.apk
# Forward drozer's default port
adb forward tcp:31415 tcp:31415
# Start a session from the host after opening the drozer Agent app
# on the device and enabling "Embedded Server"
drozer console connectThe agent does not start its server automatically. Launch the drozer Agent on the emulator, tap Embedded Server, then tap Enable before running drozer console connect on your host.
Configuring Burp Suite as a Proxy
On Android 16, proxying and TLS interception are separate problems. Setting the proxy is easy; getting a target app to trust Burp’s certificate is app-specific.
Set Up the Proxy Listener
In Burp Suite: Proxy → Options → Add a listener on *:8080 (all interfaces).
Configure the emulator to use the proxy:
# Android Emulator on the same host as Burp
adb shell settings put global http_proxy 10.0.2.2:8080If you’re using a physical device or a different emulator, replace 10.0.2.2 with the IP address the device can use to reach your Burp listener.
Verify Plain HTTP Traffic
Open Chrome on the emulator and visit http://neverssl.com/. You should see the request appear in Burp’s HTTP history. This confirms the emulator is using your proxy even before you deal with certificate trust.
Note
App-Level Certificate Pinning
Modern Android changes the trust story in two ways:
- Since Android 14, the platform root store is managed through the Conscrypt APEX, so the old “push a cert into
/system/etc/security/cacerts” shortcut is no longer a reliable universal method.- Since Android 7, apps do not automatically trust user-added CAs unless their
network_security_configallows it.In practice, that means third-party app interception on Android 16 usually requires one of these approaches:
- Your own debug build or test app that explicitly trusts user CAs
- An app whose
network_security_configalready allows user certificates- A runtime bypass using Frida or objection
We’ll cover the runtime bypass path in Tutorial 2. For this lab tutorial, the goal is to confirm proxy routing and get the rest of the toolchain working.
Verifying the Lab
Run through this checklist to confirm everything is working:
# 1. ADB connected and root
adb shell id
# Expected: uid=0(root)
# 2. Frida server running
adb shell "/data/local/tmp/frida-server >/dev/null 2>&1 &"
frida-ps -U | head -5
# Expected: list of running processes
# 3. jadx works
jadx --version
# Expected: version number
# 4. Burp proxy intercepting
# Visit http://neverssl.com/ in emulator Chrome
# Expected: request visible in Burp HTTP historyThe full lab architecture:
Host Machine Emulator (ADB over USB/TCP)
┌──────────────────────┐ ┌──────────────────────────┐
│ jadx / apktool │ │ Target App │
│ Ghidra │ │ ↕ │
│ Frida CLI ──────────┼──── ADB ────→│ Frida Server │
│ objection │ │ ↕ │
│ drozer │ │ drozer Agent │
│ Burp Suite ←────────┼── HTTP/S ───←│ (all app traffic) │
└──────────────────────┘ └──────────────────────────┘Exercises
-
Install a vulnerable app. Download DIVA (Damn Insecure and Vulnerable App) or InsecureBankv2, install it on the emulator with
adb install, and verify you can decompile it withjadx -d output/ app.apk. -
Test Frida. With Frida server running, write a one-line hook that logs every call to
java.lang.String.equals:frida -U -f com.android.chrome -l hook.jsWhere
hook.jscontains:Java.perform(function() { var StringClass = Java.use("java.lang.String"); var equals = StringClass.equals.overload("java.lang.Object"); equals.implementation = function(arg) { console.log("equals: " + arg); return equals.call(this, arg); }; }); -
Confirm Burp intercept. Open the emulator’s Chrome browser, navigate to
http://neverssl.com/, and verify the full request appears in Burp Suite’s HTTP history. Then pick one app you control or a deliberately vulnerable training app and determine whether it trusts user CAs by inspecting itsnetwork_security_configin Tutorial 1.
What’s next
In the next tutorial, APK Structure, Decompilation, and Static Analysis, we’ll tear apart a real APK to understand its internals (the manifest, DEX bytecode, resources, and native libraries) and learn to spot vulnerabilities without running the app.