MobHunt is an agentic mobile security research system, built as a Claude Code project. An orchestrator agent runs a fixed pipeline over an iOS or Android app and spawns focused sub-agents along the way. Each sub-agent gets one narrow objective, one vulnerability class or one attack surface, instead of one agent being asked to look at a whole app and report back with whatever it finds.
That single decision drives most of the design. A hunter told to "check whether the
myapp-auth:// URL scheme carries an OAuth callback that a competing app can register" produces
evidence. A hunter told to "find vulnerabilities in this app" produces a list of configuration
observations that a triager will close as informational. The rest of the repo exists to keep the
first thing happening and the second thing from reaching a submission.
Almost all of the intelligence here lives in markdown prompts, not in imperative code. The Python
and shell under tools/ is a set of thin, deterministic wrappers around real analysis tooling. The
agents, commands, skills, and rules under .claude/ and rules/ are where the actual method is
written down.
This is research tooling. It is built for authorized security testing only.
- Use it only against applications you own or are explicitly authorized to test.
- Follow the scope and the rules of the bug bounty program you are working under. If binary analysis or client-side findings are out of scope, they are out of scope; the validator checks for this, but you are responsible for it.
- No warranty of any kind. You are responsible for what you run and what you submit.
I make no claims here about bugs found, bounties earned, or program outcomes. Judge the project on its design and read the prompts.
flowchart TD
START(["/hunt program"]) --> SCOPE
SCOPE["1. SCOPE<br/>scope-agent<br/>scope.json: assets, boundaries, exclusions"]:::haiku
ACQ["2. ACQUIRE<br/>acquisition-agent<br/>official stores only, then decrypt"]:::sonnet
MODE{"decryption<br/>succeeded?"}
RF["3. RECON, analysis_mode: full<br/>plists, entitlements, URL schemes, strings,<br/>Swift metadata, SDKs, disassembly"]:::direct
RP["3. RECON, analysis_mode: partial<br/>resources, plists, Hermes bundle, SDKs<br/>tools that need plaintext __TEXT are skipped"]:::direct
HUNT["4. HUNT<br/>13 hunters, concurrent<br/>see the fan-out below"]:::opus
CHAIN["5. CHAIN<br/>chain-builder<br/>combine findings into exploit chains"]:::sonnet
GATE["6a. VALIDATE, automated<br/>tools/validation/validate.py<br/>Mobile 7-Question Gate + never-submit list"]:::direct
VAL["6b. VALIDATE, judgement<br/>validator<br/>4 post-gates"]:::sonnet
REP["7. REPORT<br/>report-writer<br/>HackerOne / Bugcrowd format"]:::opus
LOOP{"8. LOOP DECISION"}
OUT(["reports/"]):::artifact
KILL(["findings/killed/<br/>+ dead-end records"]):::artifact
SCOPE --> ACQ --> MODE
MODE -->|full| RF
MODE -->|partial| RP
RF --> HUNT
RP --> HUNT
HUNT --> CHAIN --> GATE
GATE -->|passes gates| VAL
GATE -->|auto-killed| KILL
VAL -->|validated| REP
VAL -->|killed| KILL
REP --> LOOP
LOOP -->|"2+ validated, or all classes covered,<br/>or iteration cap, or all hunters clean"| OUT
LOOP -->|"otherwise: re-target uncovered classes"| HUNT
classDef haiku fill:#065f46,stroke:#10b981,color:#ffffff
classDef sonnet fill:#1e3a8a,stroke:#3b82f6,color:#ffffff
classDef opus fill:#4c1d95,stroke:#8b5cf6,color:#ffffff
classDef direct fill:#374151,stroke:#9ca3af,color:#ffffff
classDef artifact fill:#7c2d12,stroke:#f97316,color:#ffffff
Green is Haiku, blue is Sonnet, purple is Opus, grey is the orchestrator running tools directly with no sub-agent, and orange is an output on disk. Dead-end records are the reason the loop does not re-check ground it already cleared.
Each phase has one deliverable, and the methodology skill tells the orchestrator not to skip or
merge phases. The time budgets below come from .claude/skills/mobile-hunting/SKILL.md; they are
guidance for the orchestrator's pacing, not hard timeouts.
| Phase | Deliverable | Time budget |
|---|---|---|
| SCOPE | scope.json: assets, boundaries, exclusions |
2 min |
| ACQUIRE | Extracted and decrypted app in workspace/ |
5 min |
| RECON | Attack surface map in recon/ |
10 min |
| HUNT | Raw findings in findings/ |
30 min (5 hunters, about 6 min each) |
| CHAIN | Chain proposals in findings/chains/ |
10 min |
| VALIDATE | Validated and killed findings | 10 min |
| REPORT | Submission-ready reports in reports/ |
10 min per finding |
The loop closes back into HUNT. The orchestrator re-hunts when fewer than two findings survived validation and the iteration cap has not been reached, re-targeting vulnerability classes that nothing covered on the previous pass. It stops on any of: two or more validated findings, all classes covered with documented dead ends, the iteration cap, or every hunter reporting clean.
Eleven agent definitions live in .claude/agents/. The model tiers below are the balanced cost
profile, which is the default.
| Agent | File | Model | Purpose |
|---|---|---|---|
| Orchestrator | .claude/agents/orchestrator.md |
Opus | Pipeline coordination, agentic loop |
| Scope Agent | .claude/agents/scope-agent.md |
Haiku | Identify mobile assets from bug bounty programs |
| Acquisition Agent | .claude/agents/acquisition-agent.md |
Sonnet | Download, extract, decrypt apps |
| iOS Hunter | .claude/agents/ios-hunter.md |
Opus | iOS vulnerability hunting (up to 5 parallel) |
| Android Hunter | .claude/agents/android-hunter.md |
Opus | Android vulnerability hunting (up to 5 parallel) |
| Android Mariana Hunter | .claude/agents/android-mariana-hunter.md |
Opus (1M context) | Mariana Trench taint analysis (x1) |
| Android Semgrep Hunter | .claude/agents/android-semgrep-hunter.md |
Sonnet (1M context) | Semgrep triage over jadx Java (x1) |
| iOS Semgrep Hunter | .claude/agents/ios-semgrep-hunter.md |
Sonnet (1M context) | Semgrep over IPA resources and embedded sources (x1) |
| Chain Builder | .claude/agents/chain-builder.md |
Sonnet | Combine findings into exploit chains |
| Validator | .claude/agents/validator.md |
Sonnet | Mobile 7-Question Gate plus 4 post-gates |
| Report Writer | .claude/agents/report-writer.md |
Opus | Generate HackerOne / Bugcrowd reports |
Using the largest model everywhere is expensive and, for several of these jobs, no better.
- Haiku for scope. Reading a program policy page and pulling out bundle IDs, package names, and exclusion clauses is structured extraction. It is not reasoning, and a fast, cheap model does it as well as a slow one.
- Sonnet for validation, chaining, and triage. The gates are written down explicitly in
.claude/skills/triage-validation/SKILL.md, and the chain catalog is a finite list of known mobile patterns. What matters here is applying the same rule the same way every time. Consistency beats creativity, and a mid-tier model applies a checklist more predictably than a large one that wants to reason its way around it. - Sonnet with 1M context for SAST triage. Semgrep match sets and jadx trees are large and mostly mechanical. The constraint is context size, not depth.
- Opus for hunting and reporting. Deciding whether a decompiled path is reachable, or whether a hardcoded key actually grants anything, is where depth pays. Report quality is the other place: the report is the product, and a badly framed one gets closed regardless of the underlying bug.
- Opus with 1M context for taint analysis. Mariana Trench traces span the whole call graph, so the agent needs both depth and room.
At peak, Phase 4 runs 13 hunters concurrently: 5 iOS, 5 Android, and 3 SAST specialists. The SAST hunters run alongside the classic hunters rather than after them, and their findings enter the same chain, validate, and report flow. The orchestrator skips classic hunter instances when recon shows no surface for them (no URL schemes registered, no native libraries, no WebViews) and spends the budget on a deeper pass elsewhere.
flowchart TD
ORCH["orchestrator"]:::opus
subgraph GA["Group A: vulnerability-class specialists, up to 10 instances"]
IOS["ios-hunter, up to 5, Opus<br/>IPC · storage · network · binary · SDK"]:::opus
AND["android-hunter, up to 5, Opus<br/>components · intents · storage · network · native"]:::opus
end
subgraph GB["Group B: tool specialists, one instance each"]
MT["android-mariana-hunter<br/>Opus, 1M context"]:::opus
SA["android-semgrep-hunter<br/>Sonnet, 1M context"]:::sonnet
SI["ios-semgrep-hunter<br/>Sonnet, 1M context"]:::sonnet
end
MTT["Mariana Trench<br/>taint traces over APK + jadx"]:::direct
SAT["Semgrep<br/>patterns over jadx Java"]:::direct
SIT["Semgrep<br/>plists, embedded JS/HTML, bundled sources"]:::direct
FIND(["findings/"]):::artifact
NEXT["5. CHAIN, then VALIDATE, then REPORT"]:::sonnet
ORCH --> IOS
ORCH --> AND
ORCH --> MT
ORCH --> SA
ORCH --> SI
MT --> MTT
SA --> SAT
SI --> SIT
IOS --> FIND
AND --> FIND
MTT --> FIND
SAT --> FIND
SIT --> FIND
FIND --> NEXT
classDef sonnet fill:#1e3a8a,stroke:#3b82f6,color:#ffffff
classDef opus fill:#4c1d95,stroke:#8b5cf6,color:#ffffff
classDef direct fill:#374151,stroke:#9ca3af,color:#ffffff
classDef artifact fill:#7c2d12,stroke:#f97316,color:#ffffff
Group A reasons about the app; Group B drives an external analysis engine and triages what it
returns. Both write into the same findings/ directory and go through the same gates, so a Semgrep
match gets no easier a ride than a hunter's hand-traced finding.
Three cost profiles ship in config/providers.example.json: quality, balanced (default), and
economy. They set the model tier, the reasoning effort, the token budget, the agent spawn cap,
and the loop iteration cap for every agent. Switch with the cost_profile key.
Skills are contextual knowledge loaded by agents and commands, in .claude/skills/.
| Skill | What it covers |
|---|---|
mobile-hunting |
Overall hunting discipline: phase deliverables, hunter spawning strategy, confidence calibration, dead ends |
ios-analysis |
Mach-O structure, entitlements, URL schemes, ATS, Keychain, iOS-specific tooling |
android-analysis |
APK structure, manifest and component analysis, intents, WebViews, DEX and native libraries |
mobile-vuln-classes |
The vulnerability knowledge base: OWASP Mobile Top 10 mapping plus 25 mobile-specific classes |
binary-reversing |
Ghidra headless workflow (primary) and Binary Ninja (optional) for Mach-O and ELF |
mobile-recon |
Static attack surface mapping: entry points, data flows, sensitive operations |
sdk-analysis |
Identifying and version-fingerprinting third-party SDKs, and assessing their exposure |
triage-validation |
The gate definitions: quick triage, 7-Question Gate, post-gates, verdicts |
report-writing |
Impact-first titles, mobile reproduction steps, evidence standards, platform formats |
This is the opinionated part of the project and the best reason to read it. Mobile static analysis produces an enormous number of true-but-worthless observations. Everything below exists to stop those from reaching a triager.
Quick triage (2 minutes). /triage asks three questions and nothing else. Is it exploitable
without device modification? Does the attacker gain something concrete? Is it on the never-submit
list? Three passes means the finding is worth full validation. Any failure means kill it with a
reason and move on.
The Mobile 7-Question Gate. Every finding faces all seven before anything else happens: exploitability on an unmodified device, program scope alignment, production-build confirmation, access feasibility, novelty against documented platform behavior, concrete impact proof, and the never-submit check. Q6 is the one that kills the most: "the component is exported" is not a finding, "ATS is disabled" is not a finding, and "key found in binary" is not a finding until someone tests what the key opens.
Four post-validation gates. Applied after the seven questions pass, to catch findings that are technically valid but not submission-worthy. Gate 0 is a 30-second reality check (can this be demonstrated right now?). Gate 1 is attacker gain (specific, novel, meaningful). Gate 2 is deduplication against disclosed reports and platform advisories. Gate 3 is report quality, and it can downgrade rather than kill.
The never-submit list is data, not prose. tools/validation/mobile_gates.py encodes ten rules,
NS-01 through NS-10, each with match patterns, a description, and a chain_eligible flag that says
whether a proven chain can rescue it:
| ID | Rule | Chain-eligible |
|---|---|---|
| NS-01 | Missing certificate pinning alone | yes |
| NS-02 | android:allowBackup="true" alone |
yes |
| NS-03 | android:debuggable="true" on production |
no |
| NS-04 | Missing root or jailbreak detection alone | no |
| NS-05 | Cleartext traffic configuration alone | yes |
| NS-06 | Missing binary protections on a modern OS | no |
| NS-07 | Self-signed certificates in the app bundle | no |
| NS-08 | Analytics SDK data collection (privacy, not security) | no |
| NS-09 | Clipboard access on iOS 16+ | yes |
| NS-10 | Missing code obfuscation | no |
Because it is a table rather than a paragraph in a prompt, the same kill happens every run,
regardless of which model tier the validator is on. check_scope_alignment,
check_confidence, and check_platform_specific in the same file add the mechanical parts of the
gate: program exclusions, LOW-confidence rejection, and platform version caveats such as
PendingIntent mutability changing at API 31 or implicit export applying only below targetSdk 31.
The philosophy, in the project's own words (.claude/skills/triage-validation/SKILL.md):
Be strict. False positives damage researcher reputation and waste triager time.
When in doubt, KILL. The agentic loop can catch missed findings in the next iteration.
That second line is what makes the strictness affordable. Killing aggressively is only reasonable because the loop gets another pass.
Dead-end records. When a hunter finds nothing in its assigned area, it does not simply return empty. It writes a structured record naming the focus area, the checks it actually performed, and why the area is clean. This stops the agentic loop from re-checking ground that has already been covered, and it gives the orchestrator something to reason about when choosing what to re-target. An honest clean result is a useful output here, not a failure.
- Narrow agent scope. One vulnerability class or attack surface per agent. Broad objectives produce noise.
- Configurable LLMs. Every agent's provider, model tier, and reasoning effort is set in
config/providers.json. - iOS-first. iOS decryption is the hardest piece, so it was built first. See
docs/ios-decryption.md. - Ghidra by default. Ghidra headless is the primary binary analyzer. Binary Ninja is optional and enabled through config, so nobody needs a commercial license to run this.
- Validation gates. Nothing reaches a report without passing them.
- The agentic loop. Insufficient findings triggers re-targeting, not a longer single pass.
- The intelligence lives in the prompts. Agent, command, and skill markdown files are the
system.
tools/is deterministic plumbing. If you disagree with how MobHunt hunts, you edit markdown, not Python.
These are enforced in CLAUDE.md and loaded into every session.
App binaries come only from official stores. Never APKPure, APKMirror, Aptoide, or any other
third-party mirror. Third-party mirrors cannot be trusted; they may serve tampered, repackaged, or
stale binaries, and a vulnerability report written against a binary that is not the one users
install is worse than no report at all. iOS comes from the App Store through ipatool with your
own authenticated Apple ID. Android comes from Google Play or Aurora Store on an emulator you
control, pulled off with ADB via tools/acquisition/android_acquire.sh. If the official path
fails, MobHunt asks you to supply the binary yourself; it does not fall back to a mirror.
No handing credentials to third-party CLI tools. Passwords, tokens, and API keys do not get
passed to third-party binaries or libraries beyond minimal crypto primitives. Where that
capability is needed, this project builds it rather than shelling out to someone else's tool.
Credentials are never passed in argv either, since anything in argv is visible to ps; use the
documented environment variables.
MobHunt targets macOS on Apple Silicon. The Android path works elsewhere in principle, but the iOS decryption path does not.
brew install ipatool jadx apktoolGhidra is installed separately from wherever you prefer (Homebrew cask, or the release archive from
the NSA project page). MobHunt does not care which, as long as GHIDRA_HOME points at it.
| Tool | Required | Purpose |
|---|---|---|
ipatool |
yes (iOS) | App Store IPA download with your Apple ID |
jadx |
yes (Android) | DEX to Java decompiler |
apktool |
yes (Android) | APK resource decoder |
| Ghidra | yes | Binary analysis, primary and default |
| Binary Ninja | no | Alternative binary analyzer; set analysis.binary_analyzer to "binja" |
adb |
yes (Android) | Pull APKs from your emulator |
| Xcode Command Line Tools | yes (iOS) | Provides otool, codesign, plutil, nm, lipo, strings, swift |
Point Ghidra at your own installation. tools/binary_analysis/ghidra_analyze.sh reads
GHIDRA_HEADLESS and GHIDRA_HOME from the environment only; it does not read
config/config.json. Resolution order is $GHIDRA_HEADLESS, then
$GHIDRA_HOME/support/analyzeHeadless, then analyzeHeadless on PATH.
export GHIDRA_HOME="/path/to/your/ghidra"The tool_paths.ghidra_home and tool_paths.ghidra_headless keys in config/config.json record
the same paths alongside the rest of the tool inventory, but the driver script does not consult
them; set the environment variables.
Always use the project virtual environment.
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtPython 3.9 or later. The analysis scripts run standalone, outside Claude Code, if you want to use them on their own.
Every analysis script under tools/ is pure standard library. What requirements.txt installs is
the set of external analysis tools that MobHunt shells out to and that happen to be distributed on
PyPI: semgrep, mariana-trench, and frida-tools. The two sections below explain what those are
for. If you only want the classic hunters, you can skip them; the wrappers exit cleanly when the
engine is absent.
The three SAST hunter agents need these. Without them, the classic hunters still run and the SAST
hunters exit cleanly. Both install as ordinary pip packages into the same .venv; no Homebrew, no
global installs. Mariana Trench ships prebuilt binaries in its wheel. requirements.txt already
pulls them in, so this is only needed if you installed selectively:
source .venv/bin/activate
pip install semgrep mariana-trenchThe wrappers look for the engine on PATH first and then in <repo root>/.venv/bin/, which is why
the virtual environment has to live at .venv inside the repo.
- Mariana Trench (Meta) does taint and data-flow analysis for Android. It understands sources, sinks, and propagators across Java and Kotlin, and it finds IPC and injection chains that pattern matchers miss. There is no Swift or Objective-C frontend, so it is Android only.
- Semgrep is an AST pattern matcher. On Android it runs over jadx output. On iOS it is a resource and embedded-source scanner: plists, entitlements, embedded JS and HTML, and any bundled source or open-source SDK. It does not decompile binaries; that is Ghidra's job.
Starter rules ship in rules/semgrep/android/starter.yml and rules/semgrep/ios/starter.yml.
Custom Mariana Trench model generators, rules, and lifecycles go under rules/mt/; see
rules/mt/README.md. The runners are tools/analysis/android/mariana_trench_runner.py,
tools/analysis/android/semgrep_android_runner.py, and tools/analysis/ios/semgrep_ios_runner.py.
Each is a thin wrapper: it runs the engine, preserves raw output under recon/<target>/sast/, and
emits normalized candidates for the agent to triage. None of them classifies severity or decides
exploitability; that is the agent's job.
Only needed if you fall back to the jailbroken-device or TrollStore decryption strategies.
frida-tools is already in requirements.txt; the rest is not.
pip install frida-ios-dump
brew install libimobiledevice ideviceinstallerfrida-ios-dump packaging varies across releases, so cloning the upstream repository is often
easier. Either way you must tell MobHunt where its dump.py is, with an absolute path in
decryption.strategies.frida.dump_script, the MOBHUNT_FRIDA_DUMP_SCRIPT environment variable, or
--dump-script. frida_dump.py deliberately never searches PATH for it, because it executes
that file. Left unset, the frida strategy reports a clear error and the chain moves on.
cd tools/decryption/MobHuntDecrypt
swift build -c releaseRequires macOS 12 or later on Apple Silicon.
cp config/config.example.json config/config.json
cp config/providers.example.json config/providers.jsonconfig/config.json holds tool paths, the emulator serial, bug bounty platform API tokens, and
decryption strategy toggles. config/providers.json holds provider endpoints, model IDs, per-agent
assignments for each cost profile, and the token budget caps. Both are gitignored. Apple ID
credentials are read from the APPLE_ID and APPLE_ID_PASSWORD environment variables, not from
the command line.
MobHunt runs inside Claude Code. The command definitions live in .claude/commands/, so Claude
Code discovers them automatically once you open the project directory; there is nothing to
register.
cd /path/to/MobHunt
claude| Command | Description |
|---|---|
/hunt <program> |
Full pipeline: scope, acquire, recon, hunt, chain, validate, report |
/scope <program> |
Identify mobile assets in a bug bounty program |
/acquire <app> |
Download, extract, and decrypt a mobile app |
/recon <app> |
Static reconnaissance and attack surface mapping |
/hunt-ios <app> |
iOS-specific vulnerability hunting |
/hunt-android <app> |
Android-specific vulnerability hunting |
/chain |
Build exploit chains from multiple findings |
/validate |
Apply the mobile validation gates to findings |
/triage |
Rapid 2-minute go/no-go on a single finding |
/report |
Generate a submission-ready report |
/hunt example-program
/hunt https://hackerone.com/<program-handle> --platform ios
/acquire com.example.app
/validate
The hunt script in the project root is a thin wrapper around claude -p for non-interactive use:
./hunt example-program
./hunt example-program --platform ios
./hunt --scope example-program
./hunt --validateIt still requires the Claude Code CLI to be installed and authenticated. Output lands in
recon/, workspace/, findings/, and reports/, all created on demand and all gitignored.
- macOS and Apple Silicon for iOS work. The decryption approach relies on
mremap_encrypted, a private macOS kernel facility available on Apple Silicon. Intel Macs cannot use it, and neither can Linux. Ghidra, jadx, and the Android path are portable in principle, but nothing outside macOS has been the target of this project. - iOS decryption needs an app on your own Apple ID. FairPlay keys are tied to the account that acquired the app, so you can only decrypt something you legitimately hold, free or paid. If the app has no Mac App Store build, the fallback chain drops to on-device strategies (which need a jailbroken or TrollStore device) and finally to partial analysis of the unencrypted resources only. Partial analysis is genuinely partial: no decompiled binary.
- Android needs an emulator with Play or Aurora Store. Because MobHunt refuses third-party mirrors, acquisition means installing the app on an emulator you control and pulling it with ADB. That is more setup than a download, and geo-restricted or device-restricted apps may simply be unavailable to you.
- Static analysis only. There is no dynamic instrumentation of production apps here. That bounds what can be found and, more importantly, bounds what can be proven.
- It is a research assistant, not an oracle. Every finding needs human verification before submission. The gates are strict, but they are still a language model applying a checklist. Read the evidence, reproduce the bug yourself, and only then submit.
- No standalone runtime. Orchestration depends on the Claude Code runtime. The individual analysis scripts run on their own; the pipeline does not.
Two paths in this repo can acquire an app onto your own Apple ID rather than merely downloading one you already have:
ipatool download --purchaseassociates a free app with your Apple ID before downloading it.- The Mac App Store route in
tools/acquisition/mac_ipa_download.pycan install the macOS build of an iOS app on your machine, which is what makes SINF-compatible decryption possible.
Both are opt-in in this release, and neither runs unless you ask for it. Say so plainly to
yourself before you do: these actions touch your real Apple account, add entries to your purchase
history, and are subject to Apple's terms. Only free apps should ever go through --purchase, and
only for targets you are authorized to test.
MobHunt's scaffolding is adapted from claude-bug-bounty (MIT), a web-focused Claude Code bug bounty project. What I took from it: the project layout of agents, commands, skills, and rules; the hierarchical orchestrator-plus-sub-agents model; and the validation gate pattern of a question gate followed by post-gates. The tools, the vulnerability classes, the gate contents, and everything mobile are different.
The iOS decryption work stands on published research by others, credited inline in
docs/ios-decryption.md, including the mremap_encrypted implementations in UnFairPlay,
appdecrypt, flexdecrypt, and fouldecrypt, the on-device tooling in frida-ios-dump and TrollDecrypt,
and the FairPlay SINF analysis published by pwn0rz, Efiens, and others. MobHunt reimplements the
approach in Swift for its own pipeline; it did not invent it.
See NOTICE for the complete attribution list.
MIT. See LICENSE.