PARALLAX — About the Project
Inspiration
I wanted to reverse-engineer a real Android banking trojan (SharkBot) and got frustrated by how every tool gives you a verdict but not the story. Static analyzers dump permission lists. Sandbox tools dump raw logs. Threat-intel platforms dump IOC lists. None of them explain why a sample is dangerous in a way a junior analyst can verify.
Around the same time, band-sdk shipped — a Python SDK for real multi-agent chatrooms. That clicked:
What if 8 specialized AI agents argued about an APK in a chatroom, then proved each other right or wrong with real Frida hooks?
The verdict wouldn't be one model's opinion — it'd be the consensus of agents that ran real code against the sample.
That's PARALLAX.
What it does
PARALLAX ingests an Android APK and runs two layers of analysis in parallel:
- Static — extracts permissions, intent filters, embedded URLs, cryptographic constants, suspicious API usage (
SmsManager,AccessibilityService,Cipher,DexClassLoader). - Dynamic — runs the APK inside an instrumented Frida sandbox, with hypothesis-driven JavaScript hooks that prove or disprove specific behavioral claims.
A Hook Planner agent maps each analytical hypothesis to a concrete Frida JS payload from a curated API dictionary — never hallucinated, only from a whitelisted api_signatures.json of 15 high-signal Android APIs.
A two-layer risk score combines the static signal density with the dynamic observation count into an explainable verdict — BENIGN → LOW → MEDIUM → HIGH → CRITICAL. Every score component is traceable back to a specific finding.
The result: an analyst sees not just "this APK is 9.2 CRITICAL" but "agent X hypothesized SMS exfil, agent Y's Frida hook observed SmsManager.sendTextMessage called with destination +1-XXX from com.target.app, evidence attached."
How I built it
Architecture. Backend in FastAPI + Pydantic v2, frontend in React 19 + Vite + Tailwind, persistence in PostgreSQL + Neo4j + Qdrant (vector) + Redis (queue) + MinIO (raw APK + Frida logs).
The 8 agents
Hosted as band-sdk processes that join a real band.ai chatroom. Each agent filters incoming @mentions, performs its specialty, and replies to the thread:
| Agent | Role |
|---|---|
StaticAnalyst |
Permissions, intent filters, code structure |
DynamicAnalyst |
Frida hooks in the live sandbox |
HookPlanner |
Maps hypotheses → Frida JS payloads from api_signatures.json |
RiskScorer |
Two-layer risk aggregation |
Reasoner |
Reconciles static + dynamic, writes the narrative |
ThreatHunter |
Cross-references IOCs against the threat-graph |
IOCEnricher |
Enriches URLs, hashes, IPs |
ReportWriter |
Bundles evidence + transcript |
The hook grammar
The Hook Planner LLM's output is wrapped in <<<HOOK_START>>>...<<<HOOK_END>>> markers, validated for structure, then injected into Frida via:
setImmediate(() => {
globalThis.SESSION_ID = "<UUID>";
});
Each hook fires a send() payload with hypothesis_id, captured args, return value, exception info, and caller package derived from the stack trace.
Rate limiting
Each hook checks a per-hook counter — >100 fires in 60s → suppress send(), log locally.
Challenges I ran into
API hallucination. The first version of the Hook Planner invented method overloads. Fixed by constraining the LLM to a strict
api_signatures.jsonwhitelist and explicitly telling it to emit empty scripts for unsupported behavior (reason: "native code, requires Phase 3.5").Vercel-only deploy, no backend. The live demo at
parallax-mocha-beta.vercel.apphas no/api/v1/*route. Solved by adding a?demo=truemode that loads realistic seeded data (1,247 mock submissions, 8-node TAIG graph, ticker of synthetic system events).band-sdk tier limits. Pro tier blocks agent creation on the
/menamespace. Initial probes returned 401 — turned out the right path was/api/v1/agent, not/api/v1/me.LLM ease-curve typing. Framer Motion's
easearray is typed as a tuple, notnumber[]. Caught only bytsc -bon atsxfile.Context compression. Multiple times during development, the working context window hit its limit mid-task and the assistant had to recover from a summary. Wrote durable facts to memory so the next session could pick up cleanly.
Accomplishments that I'm proud of
An 8-agent chatroom that runs against a real APK. Not a demo of "agents talking about a fake APK" — actual
band-sdkPython processes joining a real room,@mention-ing each other, and producing a real evidence bundle.Explainable two-layer risk scoring. Every score component is traceable to a specific finding (static signal + dynamic observation), not a black-box
predict_proba()call.No hallucinated hooks. Strict
api_signatures.jsonwhitelist means the LLM cannot generate Frida payloads for APIs we haven't vetted. Wrong-but-confident → "empty script + reason comment."A frontend that doesn't lie about being live. Instead of pretending to call a real backend, the
?demo=truemode loads realistic seeded data and tells the analyst exactly that it's a demo.End-to-end evidence trail. From hypothesis → hook → observation → score → narrative, every step is persisted and queryable.
What I learned
Multi-agent systems need a real protocol, not a prompt. Treating agents as REST endpoints that
@mentioneach other gave me a clean way to compose them without one agent becoming a god-prompt.Whitelists beat guardrails. Telling the LLM "don't hallucinate" doesn't work. Giving it
api_signatures.jsonand saying "emit empty + reason if not in this list" works.Frontend honesty matters more than frontend polish. A
?demo=truemode that openly tells the user "this is seeded data, not a live backend" is more credible than a fake live system that 404s on every API call.Memory is a tool, not a vibe. Saving durable facts (band-sdk tier limits, Frida version, mock-data shape) into persistent memory meant context-compression handoffs didn't lose critical state.
Static analysis alone lies. Dynamic alone lies. Together they're useful. The two-layer risk score is what makes the verdict defensible.
What's next for PARALLAX
The verdict is no longer the artifact — the debate is. Tomorrow's malware analyst doesn't want a
9.2 CRITICALstamp; they want to scrub through 8 agents arguing about whether the SMS sender really is malicious, see which Frida hook settled the argument, and replay the transcript to teach the next junior hire. PARALLAX becomes the Loom for malware reasoning.An agent room per APK, replayable forever. Every submission becomes a persistent, time-ordered, signed transcript — agents, hypotheses, hooks, observations, scores. Drop it in front of a regulator, a court, or a customer. The verdict is reproducible from the transcript alone.
Native code is the last frontier. Right now we hand-wave at JNI with an empty hook. Ghidra + radare2 in the loop means the agents reason over decompiled
.solibraries the same way they reason overSmsManager.sendTextMessagetoday. Banking trojans that hide their payload in native code stop being a blind spot.A shared threat-graph for the community. Every PARALLAX submission enriches a global Neo4j graph: families ↔ indicators ↔ samples ↔ hypotheses ↔ observations. The hundredth analyst to look at SharkBot shouldn't start from scratch — they should inherit a 100-debate-deep consensus.
Self-tuning hook dictionary. The 15-API
api_signatures.jsonwhitelist is hand-curated today. Tomorrow it grows automatically: every new confirmed finding teaches the system which Android APIs are signal-rich and which are noise. The whitelist becomes a living knowledge base, not a static file.Multi-agent consensus as the audit trail. When an agent room disagrees, we surface the disagreement — not paper over it. "StaticAnalyst said LOW, DynamicAnalyst said HIGH because hook X observed Y." That disagreement is the most valuable signal in the system, because that's where the next unknown family will reveal itself.
From analyst tool to incident-response co-pilot. A SOC analyst pastes a phishing URL, PARALLAX reverse-engineers the dropper, runs the 8-agent debate, opens a Frida sandbox, and walks back with: "This is SharkBot variant X, here are the IOCs, here is the Frida evidence, here is what the 8 agents disagreed about, and here is what I'd block at the gateway."
Built With
- alembic
- androguard
- android-debug-bridge(adb)
- band-sdk
- docker
- framer-motion
- frida
- grafana
- helm
- jadx
- kubernetes
- langchain
- logcat
- minio(object-storage)
- neo4j
- postgresql
- prometheus
- qdrant(vector)
- react
- redis
- sqlalchemy
- typescript
- yara
Log in or sign up for Devpost to join the conversation.