reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Classify a JSON API call trace and detect behavior signatures.
|
||||
|
||||
Accepts a flexible JSON trace: a list of call objects, or a dict with a "calls"/"apis" list, or a
|
||||
CAPE-style apistats mapping. Categorizes API names and matches ordered sequences indicative of
|
||||
process injection, dynamic API resolution, and self-deletion. Parses JSON only; executes nothing.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
|
||||
CATEGORIES = {
|
||||
"process": ["createprocess", "openprocess", "createremotethread", "ntcreatethreadex",
|
||||
"resumethread", "terminateprocess"],
|
||||
"memory": ["virtualalloc", "virtualallocex", "virtualprotect", "writeprocessmemory",
|
||||
"readprocessmemory", "ntmapviewofsection", "ntunmapviewofsection"],
|
||||
"file": ["createfile", "writefile", "readfile", "deletefile", "copyfile", "movefile"],
|
||||
"registry": ["regopenkey", "regsetvalue", "regcreatekey", "regqueryvalue"],
|
||||
"network": ["internetopen", "internetconnect", "httpsendrequest", "wininet", "winhttp",
|
||||
"send", "recv", "connect", "getaddrinfo"],
|
||||
"crypto": ["cryptencrypt", "cryptdecrypt", "cryptacquirecontext", "bcryptencrypt",
|
||||
"cryptstringtobinary"],
|
||||
"resolve": ["loadlibrary", "getprocaddress", "ldrloaddll", "ldrgetprocedureaddress"],
|
||||
"sync": ["createmutex", "openmutex", "createevent", "sleep", "waitforsingleobject"],
|
||||
}
|
||||
|
||||
SIGNATURES = [
|
||||
("remote_process_injection", ["virtualallocex", "writeprocessmemory", "createremotethread"],
|
||||
"T1055.002"),
|
||||
("process_hollowing", ["createprocess", "ntunmapviewofsection", "writeprocessmemory"],
|
||||
"T1055.012"),
|
||||
("dynamic_api_resolution", ["loadlibrary", "getprocaddress"], "T1027.007"),
|
||||
("crypto_routine", ["cryptacquirecontext", "cryptdecrypt"], "T1027"),
|
||||
]
|
||||
|
||||
|
||||
def _extract_calls(obj) -> list[str]:
|
||||
if isinstance(obj, list):
|
||||
out = []
|
||||
for c in obj:
|
||||
if isinstance(c, str):
|
||||
out.append(c)
|
||||
elif isinstance(c, dict):
|
||||
out.append(c.get("api") or c.get("name") or c.get("function") or "")
|
||||
return [c for c in out if c]
|
||||
if isinstance(obj, dict):
|
||||
for key in ("calls", "apis", "apistats"):
|
||||
if key in obj:
|
||||
val = obj[key]
|
||||
if isinstance(val, dict):
|
||||
return list(val.keys())
|
||||
return _extract_calls(val)
|
||||
return []
|
||||
|
||||
|
||||
def _category(api: str) -> str:
|
||||
low = api.lower()
|
||||
for cat, names in CATEGORIES.items():
|
||||
if any(n in low for n in names):
|
||||
return cat
|
||||
return "other"
|
||||
|
||||
|
||||
def classify(path: str) -> dict:
|
||||
data = json.loads(open(path, encoding="utf-8-sig").read())
|
||||
calls = _extract_calls(data)
|
||||
lowered = [c.lower() for c in calls]
|
||||
cats: dict[str, int] = {}
|
||||
unknown = set()
|
||||
for api in calls:
|
||||
cat = _category(api)
|
||||
cats[cat] = cats.get(cat, 0) + 1
|
||||
if cat == "other":
|
||||
unknown.add(api)
|
||||
detected = []
|
||||
for name, seq, tech in SIGNATURES:
|
||||
pos = 0
|
||||
for needle in seq:
|
||||
found = next((i for i in range(pos, len(lowered)) if needle in lowered[i]), -1)
|
||||
if found == -1:
|
||||
break
|
||||
pos = found + 1
|
||||
else:
|
||||
detected.append({"behavior": name, "attack": tech})
|
||||
return {
|
||||
"total_calls": len(calls),
|
||||
"categories": cats,
|
||||
"behaviors": detected,
|
||||
"unknown_apis": sorted(unknown)[:50],
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Classify an API call trace")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("classify")
|
||||
p.add_argument("trace")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(classify(args.trace), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user