Tutorial

Android Bug Bounty Lab Setup

Build an Android security testing environment with an emulator, rooted device, ADB, and the essential reverse engineering and dynamic analysis toolchain.

7 min read beginner
Exploitation Security Android Bug Bounty Lab Setup

Prerequisites

  • Basic Linux command line knowledge
  • Familiarity with package managers (apt, brew, or similar)

Part 1 of 5 in Android Bug Bounty: Zero to Zero-Day

Table of Contents

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:

  1. 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.
  2. 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.
  3. 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 EmulatorPhysical Device
CostFree$200-400 (Pixel)
Root accessBuilt-in with Google APIs imagesRequires bootloader unlock + Magisk
SnapshotsNative supportNot practical
PerformanceGood with KVM/HAXMNative speed
Hardware featuresNo Bluetooth, limited sensorsFull hardware access
Kernel testingCustom kernel possible but limitedFull kernel access
DetectionApps can detect emulatorsHarder 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_apis images, not google_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 you adb root out of the box.

Create the AVD

avdmanager create avd \
  -n bug_bounty \
  -k "system-images;android-36;google_apis;x86_64" \
  -d pixel_6

Boot the Emulator

emulator -avd bug_bounty -writable-system -no-snapshot-load

The -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
# root

If 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 remount

For physical devices, the process is more involved:

  1. Unlock the bootloader (fastboot flashing unlock on Pixel devices: this wipes the device)
  2. Patch the boot image with Magisk
  3. 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 --version

jadx: 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 apktool

Frida: 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-server

objection: Runtime Exploration

objection wraps Frida with convenience commands for common tasks:

pip install objection

Ghidra: Native Code Analysis

For reverse engineering .so native libraries (Tutorial 5):

# Download from ghidra-sre.org
# Extract and run:
./ghidraRun

Burp 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 connect

The 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:8080

If 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:

  1. 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.
  2. Since Android 7, apps do not automatically trust user-added CAs unless their network_security_config allows 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_config already 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 history

The 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

  1. 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 with jadx -d output/ app.apk.

  2. 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.js

    Where hook.js contains:

    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);
        };
    });
  3. 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 its network_security_config in 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.