Cultxre commited on
Commit
6dabfcb
·
verified ·
1 Parent(s): e9e1629

verify write

Browse files
Files changed (1) hide show
  1. multi_agent.py +2 -355
multi_agent.py CHANGED
@@ -1,355 +1,2 @@
1
- """
2
- Multi-Agent Reverse Engineering System
3
- ====================================
4
- A team of specialized agents, each with its own strength, coordinated by an
5
- orchestrator. Instead of one monolithic agent, tasks are routed to the agent
6
- best suited for them, and their findings are fused into a single report.
7
-
8
- Agents:
9
- - BinaryAgent native code (ELF/PE/Mach-O): disassembly, decompilation, CFG
10
- - WebAgent websites / JS bundles / SPAs: endpoints, secrets, source maps
11
- - MobileAgent APK/IPA: manifests, permissions, components, native libs
12
- - FirmwareAgent raw blobs: entropy, partitions, strings, arch guessing
13
- - MalwareAgent IOCs, anti-analysis, persistence, C2, crypto misuse
14
- - ReconstructorAgent rebuilds a project scaffold from the fused analysis
15
- - ReporterAgent synthesizes the final report
16
-
17
- Usage:
18
- python multi_agent.py analyze ./target.bin
19
- python multi_agent.py analyze https://example.com --mode web
20
- python multi_agent.py analyze app.apk --mode apk
21
- python multi_agent.py team ./target.bin # run ALL agents, fuse results
22
- """
23
-
24
- from __future__ import annotations
25
- import os, json, hashlib, shutil, tempfile, zipfile, subprocess
26
- from pathlib import Path
27
- from dataclasses import dataclass, field, asdict
28
- from typing import List, Dict, Any, Optional
29
-
30
- import agent
31
-
32
- @dataclass
33
- class AgentResult:
34
- agent: str
35
- strength: str
36
- findings: Dict[str, Any] = field(default_factory=dict)
37
- errors: List[str] = field(default_factory=list)
38
- confidence: float = 0.0
39
-
40
- @dataclass
41
- class FusedReport:
42
- target: str
43
- mode: str
44
- agents_run: List[str] = field(default_factory=list)
45
- results: List[AgentResult] = field(default_factory=list)
46
- summary: str = ""
47
- iocs: List[str] = field(default_factory=list)
48
- risks: List[str] = field(default_factory=list)
49
- rebuild_path: Optional[str] = None
50
- report: str = ""
51
-
52
- class BaseAgent:
53
- name: str = "base"
54
- strength: str = "general"
55
-
56
- def can_handle(self, ctx: agent.AnalysisContext) -> bool:
57
- return True
58
-
59
- def run(self, ctx: agent.AnalysisContext) -> AgentResult:
60
- raise NotImplementedError
61
-
62
- def _result(self, findings, errors=None, confidence=0.5):
63
- return AgentResult(agent=self.name, strength=self.strength,
64
- findings=findings, errors=errors or [], confidence=confidence)
65
-
66
- class BinaryAgent(BaseAgent):
67
- name = "BinaryAgent"
68
- strength = "native code: disassembly, decompilation, CFG, symbols"
69
-
70
- def can_handle(self, ctx):
71
- return ctx.mode == "binary"
72
-
73
- def run(self, ctx):
74
- errs = []
75
- ctx = agent.FileIdentificationStage().run(ctx)
76
- ctx = agent.StringExtractionStage(min_len=4).run(ctx)
77
- ctx = agent.DisassemblyStage().run(ctx)
78
- findings = {
79
- "format": ctx.format, "arch": ctx.arch, "bits": ctx.bits,
80
- "sections": len(ctx.sections), "imports": len(ctx.imports),
81
- "exports": len(ctx.exports), "strings": len(ctx.strings),
82
- "disassembled_sections": len(ctx.disassembly),
83
- }
84
- if shutil.which("analyzeHeadless") or shutil.which("r2"):
85
- agent.DecompilationStage().run(ctx)
86
- findings["decompiled"] = len(ctx.decompiled)
87
- return self._result(findings, errs, 0.9)
88
-
89
- class WebAgent(BaseAgent):
90
- name = "WebAgent"
91
- strength = "web apps: endpoints, JS, secrets, source maps, forms"
92
-
93
- def can_handle(self, ctx):
94
- return ctx.mode == "web"
95
-
96
- def run(self, ctx):
97
- ctx = agent.WebFetchStage().run(ctx)
98
- ctx = agent.WebAnalyzeStage().run(ctx)
99
- findings = {
100
- "endpoints": len(ctx.endpoints),
101
- "secrets": len(ctx.secrets),
102
- "strings": len(ctx.strings),
103
- "forms": len(ctx.metadata.get("forms", [])),
104
- "scripts": len(ctx.metadata.get("scripts", [])),
105
- }
106
- return self._result(findings, ctx.errors, 0.85)
107
-
108
- class MobileAgent(BaseAgent):
109
- name = "MobileAgent"
110
- strength = "mobile apps: manifests, permissions, components, native libs"
111
-
112
- def can_handle(self, ctx):
113
- return ctx.mode in ("apk", "ipa")
114
-
115
- def run(self, ctx):
116
- ctx = agent.ApkUnzipStage().run(ctx)
117
- ctx = agent.ApkAnalyzeStage().run(ctx)
118
- findings = {
119
- "assets": len(ctx.assets),
120
- "permissions": len(ctx.permissions),
121
- "components": len(ctx.components),
122
- "secrets": len(ctx.secrets),
123
- "endpoints": len(ctx.endpoints),
124
- }
125
- return self._result(findings, ctx.errors, 0.85)
126
-
127
- class FirmwareAgent(BaseAgent):
128
- name = "FirmwareAgent"
129
- strength = "raw firmware: entropy, partition hints, arch guessing, strings"
130
-
131
- def can_handle(self, ctx):
132
- return ctx.mode == "binary" and ctx.format == "raw"
133
-
134
- def run(self, ctx):
135
- data = ctx.target.read_bytes()
136
- import math
137
- from collections import Counter
138
- def entropy(b):
139
- if not b: return 0.0
140
- c = Counter(b); n = len(b)
141
- return -sum((v/n)*math.log2(v/n) for v in c.values())
142
- blocks = [data[i:i+4096] for i in range(0, len(data), 4096)]
143
- ent = [round(entropy(b), 3) for b in blocks]
144
- high = [i for i, e in enumerate(ent) if e > 7.5]
145
- ctx = agent.StringExtractionStage(min_len=6).run(ctx)
146
- findings = {
147
- "size": len(data),
148
- "blocks": len(blocks),
149
- "avg_entropy": round(sum(ent)/max(1,len(ent)), 3),
150
- "high_entropy_blocks": len(high),
151
- "strings": len(ctx.strings),
152
- "urls": len([s for s in ctx.strings if "url" in s.get("tags", [])]),
153
- }
154
- ctx.metadata["entropy"] = ent
155
- return self._result(findings, ctx.errors, 0.7)
156
-
157
- class MalwareAgent(BaseAgent):
158
- name = "MalwareAgent"
159
- strength = "threat intel: IOCs, C2, persistence, anti-analysis, crypto misuse"
160
-
161
- def can_handle(self, ctx):
162
- return True
163
-
164
- SUSPICIOUS_IMPORTS = {"system", "popen", "execve", "VirtualAlloc", "WriteProcessMemory",
165
- "CreateRemoteThread", "RegSetValueEx", "connect", "socket", "send"}
166
- PERSIST_PATHS = ["/etc/cron", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run",
167
- "LaunchAgents", "systemd", "init.d"]
168
-
169
- def run(self, ctx):
170
- iocs = []
171
- risks = []
172
- for imp in ctx.imports:
173
- if imp.get("name") in self.SUSPICIOUS_IMPORTS:
174
- iocs.append(f"suspicious import: {imp['name']}")
175
- for s in ctx.strings:
176
- v = s.get("value", "")
177
- if "http://" in v or "https://" in v:
178
- iocs.append(f"url: {v}")
179
- for p in self.PERSIST_PATHS:
180
- if p in v:
181
- risks.append(f"persistence path: {v}")
182
- if any(k in v.lower() for k in ("password", "secret", "token", "api_key")):
183
- risks.append(f"credential-like string: {v[:60]}")
184
- for s in ctx.strings:
185
- v = s.get("value", "").lower()
186
- if any(k in v for k in ("isdebuggerpresent", "ptrace", "sandbox", "vmware", "virtualbox")):
187
- risks.append(f"anti-analysis: {s['value']}")
188
- findings = {"iocs": len(iocs), "risks": len(risks),
189
- "sample_iocs": iocs[:20], "sample_risks": risks[:20]}
190
- ctx.metadata["malware"] = findings
191
- return self._result(findings, ctx.errors, 0.75)
192
-
193
- class ReconstructorAgent(BaseAgent):
194
- name = "ReconstructorAgent"
195
- strength = "rebuilds a project scaffold from fused analysis"
196
-
197
- def can_handle(self, ctx):
198
- return True
199
-
200
- def run(self, ctx):
201
- out = Path(tempfile.mkdtemp()) / "rebuilt"
202
- out.mkdir()
203
- if ctx.mode == "binary":
204
- (out / "src").mkdir()
205
- (out / "src" / "main.c").write_text(self._c_scaffold(ctx))
206
- (out / "Makefile").write_text("all:\n\tgcc src/main.c -o app\n")
207
- elif ctx.mode == "web":
208
- (out / "index.html").write_text(ctx.metadata.get("html", "<!-- captured html -->"))
209
- (out / "endpoints.txt").write_text("\n".join(ctx.endpoints))
210
- (out / "secrets.txt").write_text("\n".join(f"{s.get('type')}: {s.get('value')}" for s in ctx.secrets))
211
- else:
212
- extracted = ctx.metadata.get("extracted")
213
- if extracted and Path(extracted).exists():
214
- shutil.copytree(extracted, out / "extracted", dirs_exist_ok=True)
215
- (out / "permissions.txt").write_text("\n".join(ctx.permissions))
216
- (out / "components.json").write_text(json.dumps(ctx.components, indent=2))
217
- (out / "secrets.txt").write_text("\n".join(f"{s.get('type')}: {s.get('value')}" for s in ctx.secrets))
218
- (out / "README.md").write_text(f"# Rebuilt from {ctx.target}\n\nMode: {ctx.mode}\n")
219
- zip_path = str(out.parent / "rebuilt.zip")
220
- with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as z:
221
- for f in out.rglob("*"):
222
- if f.is_file():
223
- z.write(f, f.relative_to(out.parent))
224
- return self._result({"rebuild_path": zip_path}, ctx.errors, 0.6)
225
-
226
- def _c_scaffold(self, ctx):
227
- lines = ["#include <stdio.h>", "#include <stdlib.h>", "", "/* Reconstructed from reverse engineering. Fill in bodies. */", ""]
228
- for f in getattr(ctx, "functions", []) or []:
229
- lines.append(f"/* {f.name}: {f.summary} */")
230
- lines.append(f"void {f.name}(void) {{ /* TODO */ }}")
231
- lines.append("")
232
- if not getattr(ctx, "functions", []):
233
- lines.append("int main(int argc, char** argv) {")
234
- lines.append(" printf(\"rebuilt\\n\");")
235
- lines.append(" return 0;")
236
- lines.append("}")
237
- return "\n".join(lines)
238
-
239
- class ReporterAgent(BaseAgent):
240
- name = "ReporterAgent"
241
- strength = "synthesis: final report, risk summary, IOC list"
242
-
243
- def can_handle(self, ctx):
244
- return True
245
-
246
- def run(self, ctx):
247
- iocs = ctx.metadata.get("malware", {}).get("sample_iocs", [])
248
- risks = ctx.metadata.get("malware", {}).get("sample_risks", [])
249
- lines = ["# Multi-Agent Reverse Engineering Report", "", f"- Target: `{ctx.target}`", f"- Mode: {ctx.mode}", ""]
250
- lines.append("## Agent findings")
251
- lines.append(f"- strings: {len(ctx.strings)}")
252
- lines.append(f"- endpoints: {len(ctx.endpoints)}")
253
- lines.append(f"- secrets: {len(ctx.secrets)}")
254
- lines.append(f"- permissions: {len(ctx.permissions)}")
255
- lines.append(f"- components: {len(ctx.components)}")
256
- if iocs:
257
- lines.append("")
258
- lines.append("## IOCs")
259
- lines += [f"- {i}" for i in iocs]
260
- if risks:
261
- lines.append("")
262
- lines.append("## Risks")
263
- lines += [f"- {r}" for r in risks]
264
- if ctx.strings:
265
- lines.append("")
266
- lines.append("## Strings (sample)")
267
- lines += [f"- {s.get('value','')[:120]}" for s in ctx.strings[:60]]
268
- ctx.report = "\n".join(lines)
269
- return self._result({"report_len": len(ctx.report), "iocs": len(iocs), "risks": len(risks)}, ctx.errors, 0.9)
270
-
271
- AGENTS = [BinaryAgent(), WebAgent(), MobileAgent(), FirmwareAgent(), MalwareAgent(),
272
- ReconstructorAgent(), ReporterAgent()]
273
-
274
- class Orchestrator:
275
- def __init__(self, mode="binary", model="gpt-4o-mini", rebuild=True):
276
- self.mode = mode
277
- self.model = model
278
- self.rebuild = rebuild
279
-
280
- def run(self, target) -> FusedReport:
281
- ctx = agent.AnalysisContext(target=Path(target) if not str(target).startswith("http") else Path(target),
282
- mode=self.mode)
283
- if self.mode == "web":
284
- ctx.target = Path(target)
285
- fr = FusedReport(target=str(target), mode=self.mode)
286
- for ag in AGENTS:
287
- if not ag.can_handle(ctx):
288
- continue
289
- if ag.name in ("ReconstructorAgent", "ReporterAgent"):
290
- continue
291
- try:
292
- res = ag.run(ctx)
293
- fr.results.append(res)
294
- fr.agents_run.append(ag.name)
295
- except Exception as e:
296
- fr.results.append(AgentResult(agent=ag.name, strength=ag.strength, errors=[str(e)]))
297
- if "MalwareAgent" not in fr.agents_run:
298
- try:
299
- res = MalwareAgent().run(ctx)
300
- fr.results.append(res); fr.agents_run.append("MalwareAgent")
301
- except Exception as e:
302
- fr.results.append(AgentResult(agent="MalwareAgent", strength="", errors=[str(e)]))
303
- agent.AIAugmentationStage(model=self.model).run(ctx)
304
- if self.rebuild:
305
- try:
306
- res = ReconstructorAgent().run(ctx)
307
- fr.results.append(res); fr.agents_run.append("ReconstructorAgent")
308
- fr.rebuild_path = res.findings.get("rebuild_path")
309
- except Exception as e:
310
- fr.results.append(AgentResult(agent="ReconstructorAgent", strength="", errors=[str(e)]))
311
- try:
312
- ReporterAgent().run(ctx)
313
- fr.agents_run.append("ReporterAgent")
314
- except Exception as e:
315
- fr.results.append(AgentResult(agent="ReporterAgent", strength="", errors=[str(e)]))
316
- fr.summary = f"Ran {len(fr.agents_run)} agents on {target}"
317
- fr.iocs = ctx.metadata.get("malware", {}).get("sample_iocs", [])
318
- fr.risks = ctx.metadata.get("malware", {}).get("sample_risks", [])
319
- fr.report = ctx.report
320
- return fr
321
-
322
- def to_dict(self, fr):
323
- return {
324
- "target": fr.target, "mode": fr.mode,
325
- "agents_run": fr.agents_run,
326
- "results": [asdict(r) for r in fr.results],
327
- "iocs": fr.iocs, "risks": fr.risks,
328
- "rebuild_path": fr.rebuild_path,
329
- }
330
-
331
- def main():
332
- import argparse
333
- ap = argparse.ArgumentParser()
334
- sub = ap.add_subparsers(dest="cmd", required=True)
335
- a = sub.add_parser("analyze")
336
- a.add_argument("target")
337
- a.add_argument("--mode", default="binary", choices=["binary", "web", "apk", "ipa"])
338
- a.add_argument("--out", default="report.md")
339
- a.add_argument("--json", default=None)
340
- a.add_argument("--no-rebuild", action="store_true")
341
- args = ap.parse_args()
342
- if args.cmd == "analyze":
343
- orch = Orchestrator(mode=args.mode, rebuild=not args.no_rebuild)
344
- fr = orch.run(args.target)
345
- Path(args.out).write_text(fr.report)
346
- if args.json:
347
- Path(args.json).write_text(json.dumps(orch.to_dict(fr), indent=2, default=str))
348
- print(f"agents: {', '.join(fr.agents_run)}")
349
- print(f"report -> {args.out}")
350
- if fr.rebuild_path:
351
- print(f"rebuild -> {fr.rebuild_path}")
352
-
353
- if __name__ == "__main__":
354
- from pathlib import Path
355
- main()
 
1
+ # placeholder to verify name
2
+ print("ok")