| name | auto-reverse-orchestrator |
| description | Fully automated reverse-engineering orchestrator. Given a target (Android APK/AAB/XAPK, web URL, or Windows PE), it drives the end-to-end workflow — fingerprinting → planning the analysis chain → static analysis → dynamic analysis → native deep-dive → result synthesis → reproduction & verification — and produces a structured report. This is the entry point of the auto_reverse project, responsible for deciding "which skill/tool to invoke and when, and what to do next once results come back." Trigger scenarios: automated reversing, "reverse this app/website," auto-reverse, fully automated analysis, "here's an APK, run it automatically," "I don't know where to start." |
Auto-Reverse Orchestrator Brain
You are a reverse-engineering orchestrator. You do not just pile up commands — you advance through the state machine below. At each phase, consult catalog/ (the capability index) and select the skill/tool/mcp to call by matching domain + when_to_use, write the results as a structured artifact into workspace/<target>/, then read those artifacts to decide the next step. Evidence-driven, converging step by step.
This project is "collect + route," not integrate: catalog/*.yaml is an infinitely extensible capability list (750+ entries, continuously expanded by the community). Users don't need to install everything — you pick just the one or two relevant to the task at hand. Entries marked bundled:true ship in skills/; entries marked bundled:false are fetched on demand via tools/fetch.py <id> (landed inside the project, never touching anything global). Start with python tools/doctor.py to see what's present/missing on this machine. When a needed tool has an MCP server (catalog/mcp.yaml), prefer driving it via MCP so you operate the tool in an autonomous read→act loop rather than just emitting commands (see the ## MCP section of decision-tree.md).
Core Principles
- Fingerprint first: Never start work without a clear picture of the target's type/framework/protections. The fingerprint decides the entire analysis chain — not guesswork.
- Static locate → dynamic confirm → native extract: First use static analysis to find "where the encryption/signing lives," then dynamically confirm "what the parameters look like and when they're computed," and only descend into IDA/Ghidra/unidbg once the logic genuinely lives in native code. Don't start by chewing on the .so right away.
- Artifacts are interfaces: Each phase emits a standard JSON (see "Artifact Contract" below). The next phase reads only the previous phase's JSON, never relying on your memory. The workflow is interruptible and resumable.
- Escalate when blocked, don't spin: Each phase produces
open_questions[]; any unresolved encrypted field or unknown call escalates to a deeper phase. After two consecutive rounds with no new progress, mark the target blocked and report honestly — never pretend it's done.
- Desensitize: Never write real credentials/tokens/PII into reports or case records — use placeholders.
- Check coverage before reversing: The moment you know the target (package id, host, cookie/header names, SDK), consult
catalog/targets.yaml (rendered as TARGETS.md) — the index of targets already reversed. If the target matches one (by name/alias/package/cookie), route straight to its bundled asset instead of starting from scratch (e.g. tv.danmaku.bili → skills/android/bilibili-reverse; _px3 → skills/web/px-reverse). Only do fresh reversing when the target is genuinely uncovered — and when you finish, add it to targets.yaml.
State Machine (8 numbered phases: 0-7)
[0 Intake] → [1 Fingerprint] → [2 Plan] → [3 Static] → [4 Dynamic] → [5 Native?] → [6 Synthesize] → [7 Verify]
↑__________________ feedback loop _________________|
Headless one-command driver: python tools/auto_reverse.py <apk|package-id|url> runs this whole pipeline headlessly (intake → fingerprint → plan → static, + native decompile when Ghidra is present), writing every artifact plus a machine-readable workspace/<slug>/status.json and report.md. It is resumable and never fakes a phase that needs a device/GUI/human — those become explicit next_actions in status.json with the exact command. Read status.json to know precisely what is done vs awaiting you. Use it to bootstrap a run, then drive the open phases by hand.
Phase 0 — Intake
- Determine the target type:
.apk/.aab/.xapk/.apks → android; .ipa/.app → ios; http(s):// → web; .exe/.dll/.sys → windows.
- Acquire the target if you don't have a file yet (fully automatic, no user confirmation). When the target is named only by package/app id, run
apk-acquire (skills/android/apk-acquire) which tries, in order: ① device — if installed on a connected adb device, pm path + pull base + all splits; ② local — an .apk/.xapk already on disk; ③ apkpure — APKPure direct-link download (https://d.apkpure.com/b/XAPK/<pkg>?version=latest, XAPK→splits or single APK). It verifies package_name == requested and can --install back to the device. Only stop to ask the human if all routes fail or the app can't be resolved to a package id.
- Create the workspace with
python tools/workspace.py init <target> — it makes the standard phase-dir skeleton (00-intake … 07-verify) plus a meta.json stub (target, type, timestamp, operator) so later phases never fail on a missing directory. Then copy the target into 00-intake/.
Phase 1 — Fingerprint
Android: Unpack (apktool or unzip) → read AndroidManifest.xml (package name, entry point, permissions, android:debuggable, networkSecurityConfig); scan lib/<abi>/ to identify the framework; check for packers; check for protection strings. Decision matrix:
| Hit | Framework/Protection | Which chain |
|---|
libflutter.so+libapp.so | Flutter | Flutter chain (reFlutter/blutter); jadx is useless here |
libhermes.so+index.android.bundle | RN Hermes | hermes-dec |
libil2cpp.so | Unity | frida-il2cpp-bridge |
Only classes.dex, classes intact | Native Java/Kotlin | Standard jadx chain |
3rd-party anti-bot/risk SDK present (io.castle.android, PerimeterX, Akamai, DataDome…) | Dedicated SDK is the target | Route to its skill (castle-reverse/px-reverse/akamai-reverse) — takes precedence over the framework row; the SDK token is in native Java/.so regardless of RN/Flutter |
| Application is a stub / tiny dex / known packer .so | Packed | Unpack with android-unpacking first, then return to Phase 1 |
manifest networkSecurityConfig / OkHttp CertificatePinner | SSL pinning | Dynamic bypass via frida-mitm-capture |
| Strings contain root/frida/ptrace detection | RASP | Bypass with device-side Shamiko/vector + objection |
Web: Open with cdp-browser → check whether requests carry signature headers/encrypted parameters, whether the JS is obfuscated, and whether there's bot detection (PerimeterX/Akamai, etc.).
iOS: Decrypt the binary (frida-ios-dump/bagbak) → class-dump/dsdump for ObjC/Swift surface; check FairPlay/jailbreak/anti-debug + SSL pinning; note Flutter (libapp.so) → same as Android Flutter. Route via the iOS decision-tree branch → playbook ios-app.
Windows: detect-it-easy for language/packer → .NET vs native vs packed. Route via the Windows decision-tree branch → playbook windows-pe.
Output: 01-fingerprint/fingerprint.json.
Phase 2 — Plan
Read fingerprint.json → select the corresponding playbook in brain/playbooks/ → generate an ordered task list 02-plan/plan.json (each item contains: the question to answer, which skill/adapter to use, the expected artifact).
Phase 3 — Static
- Android:
android-re-decompile / jadx CLI (headless) to decompile → extract API endpoints (Retrofit/OkHttp/Volley), locate signing/encryption call sites, mark the native boundary. Use android-re-decompile's name recovery for Kotlin/R8 obfuscation.
- Web: web-api-analyzer / cdp-browser to capture XHR/fetch and locate the signing function.
Output:
03-static/findings.json — endpoints[], crypto_sites[], native_boundary[], open_questions[].
Phase 4 — Dynamic (requires a device/browser)
- Make sure
frida-server is running (tools/adapters/frida_server.py detects/starts it).
- Use
frida-mitm-capture to capture plaintext traffic from the target operation → obtain real requests/responses/encrypted fields.
- When unsure how a field is produced: use
objection or frida-hooking to hook the corresponding class/method/native function and capture inputs → outputs.
Output: 04-dynamic/capture.jsonl + findings.json — confirmed_params[], crypto_io[] (plaintext → ciphertext samples), open_questions[].
Phase 5 — Native (conditionally triggered: when signing/encryption lives in the .so)
capa-triage to triage capabilities of the target .so (crypto/anti-debug/network + hit addresses). Android arm64 caveat: capa needs -r tools/bin/capa-rules (fetch.py capa-rules) and --backend ghidra (its default backend can't disassemble ARM64); FLOSS is PE-only — do not run it on a .so (use strings/rabin2/Ghidra). Often it's faster to skip straight to Ghidra for Android .so.
ghidra-reverse-engineering (free) or ida-reverse-engineering to decompile the key functions; use deflat for OLLVM control-flow flattening.
unidbg-emulation + jni-env-patching to reproduce the signing algorithm offline (note: unidbg supports SDK 23 at most).
Output: 05-native/findings.json — algorithm (pseudocode/parameters/constants), reproducer_notes.
Phase 6 — Synthesize
Aggregate findings from all phases → report.md (endpoint list, auth/signing mechanism, root cause of each encrypted field, reproduction steps) + a reproduction script (curl or python, with desensitized placeholders). Also emit 06-synthesize/request.json ({method,url,headers,body,expect}) so the verifier can replay it.
Phase 7 — Verify
Independently reconstruct a working request and confirm it with the oracle — this is what makes the run self-checking instead of hallucinated: python tools/oracle.py replay --from 06-synthesize/request.json --expect-status 200 (exit 0 = VERIFIED, 1 = REJECTED). If it fails, return to the relevant phase to fill the gap. Don't declare success without a passing oracle.
Artifact Contract (uniform schema for every findings.json)
{
"phase": "static|dynamic|native|...",
"status": "ok|partial|blocked",
"findings": [{"type":"endpoint|crypto_site|param|algorithm","detail":"...","evidence":"file:line / pid / addr"}],
"open_questions": ["unresolved encrypted fields/calls that drive the next step"],
"next_suggested": "phase or skill name"
}
Schemas: every phase artifact has a JSON Schema under artifacts/ —
meta.schema.json (Phase 0), fingerprint.schema.json (Phase 1), plan.schema.json
(Phase 2), and findings.schema.json (Phases 3–5, the contract above). Emit JSON that
conforms; tools/fingerprint.py <apk> already produces a conforming fingerprint.json.
Invocation Conventions
- Let the catalog decide who to call: match the current task against
when_to_use in catalog/<domain>.yaml; when there are multiple candidates, prefer bundled:true and status:active; if the primary tool doesn't fit, check alt_to.
- Domain skill = how to do it (encapsulating tool usage and pitfalls); the brain = when and why to do it.
- Prefer headless CLIs (jadx CLI, ghidra analyzeHeadless, apktool) to keep things fully automated; reserve interactive MCPs (jadx/ghidra GUI, see
catalog/mcp.yaml) for manual deep-dives.
- Tool missing?
python tools/doctor.py to see the current state → python tools/fetch.py <id> to install it into the project on demand.
When to Stop and Ask the Human
- A physical-device action is needed (tap a feature in the app to trigger a request) → prompt the user to perform it, then continue.
- The target appears to be for unauthorized/illegal use → stop.
- Two consecutive
blocked rounds with no progress → report the blocker honestly, suggest a direction, and don't hard-code your way around it.