Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MobHunt

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.

Status and disclaimer

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.

How it works

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
Loading

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.

Architecture

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

Why the models are heterogeneous

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
Loading

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

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

Validation

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.

Design principles

  1. Narrow agent scope. One vulnerability class or attack surface per agent. Broad objectives produce noise.
  2. Configurable LLMs. Every agent's provider, model tier, and reasoning effort is set in config/providers.json.
  3. iOS-first. iOS decryption is the hardest piece, so it was built first. See docs/ios-decryption.md.
  4. 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.
  5. Validation gates. Nothing reaches a report without passing them.
  6. The agentic loop. Insufficient findings triggers re-targeting, not a longer single pass.
  7. 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.

Two rules I do not bend

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.

Install

MobHunt targets macOS on Apple Silicon. The Android path works elsewhere in principle, but the iOS decryption path does not.

1. External tools

brew install ipatool jadx apktool

Ghidra 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.

2. Python environment

Always use the project virtual environment.

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Python 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.

3. The SAST engines

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-trench

The 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.

4. Optional: on-device iOS decryption

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 ideviceinstaller

frida-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.

5. Build the decryptor

cd tools/decryption/MobHuntDecrypt
swift build -c release

Requires macOS 12 or later on Apple Silicon.

6. Configuration

cp config/config.example.json config/config.json
cp config/providers.example.json config/providers.json

config/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.

Usage

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 --validate

It 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.

Known limitations

  • 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.

A note on App Store purchases

Two paths in this repo can acquire an app onto your own Apple ID rather than merely downloading one you already have:

  • ipatool download --purchase associates a free app with your Apple ID before downloading it.
  • The Mac App Store route in tools/acquisition/mac_ipa_download.py can 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.

Credits and prior art

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.

License

MIT. See LICENSE.

About

An agentic mobile security research system built as a Claude Code project. An orchestrator spawns focused sub-agents, each with one narrow objective.

Resources

Stars

10 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages