reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
Apache License 2.0 — see the repository root LICENSE file for the full text.
|
||||
|
||||
Copyright the AnalystAIPack contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
name: extracting-config-from-a-running-sample
|
||||
description: 'Extracts an embedded malware configuration (C2 hosts, ports, campaign IDs, keys) from
|
||||
a process memory dump by locating decrypted config structures and decoding common obfuscation
|
||||
layers. Activates for requests to extract malware config from a memory dump, recover C2 settings
|
||||
from a running sample, or decode an in-memory configuration block.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- config-extraction
|
||||
- memory
|
||||
- c2
|
||||
- dynamic-analysis
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1027
|
||||
- T1140
|
||||
- T1071
|
||||
d3fend:
|
||||
- D3-SDA
|
||||
- D3-DA
|
||||
references:
|
||||
- 'MITRE ATT&CK T1140 Deobfuscate/Decode Files or Information — https://attack.mitre.org/techniques/T1140/'
|
||||
- 'MITRE ATT&CK T1027 Obfuscated Files or Information — https://attack.mitre.org/techniques/T1027/'
|
||||
---
|
||||
|
||||
# Extracting Config From a Running Sample
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a process memory dump of a detonated sample whose config is decrypted in memory.
|
||||
- You need to recover C2 endpoints, ports, campaign/botnet IDs, mutexes, and keys.
|
||||
|
||||
**Do not use** this to detonate the sample — it consumes a dump already captured in an isolated
|
||||
sandbox. For static-only config decryption, use the dedicated config-decryptor workflow.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A process memory dump (`.dmp`/raw region) captured after the config was decrypted.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Treat the dump as malicious data; read it inertly and defang recovered endpoints.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Hunt config indicators
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py hunt process.dmp
|
||||
```
|
||||
|
||||
Scans for URLs, IPv4:port pairs, mutex-like tokens, base64 blobs, and printable key/value
|
||||
candidates in decrypted regions.
|
||||
|
||||
### Step 2: Decode obfuscation layers
|
||||
|
||||
For candidate blobs, try common transforms (single-byte XOR brute force, base64) and re-scan the
|
||||
decoded output for endpoints.
|
||||
|
||||
### Step 3: Structure and validate the config
|
||||
|
||||
Assemble recovered fields into a structured config and sanity-check (valid hosts, plausible
|
||||
ports, consistent campaign IDs).
|
||||
|
||||
### Step 4: Defang and report
|
||||
|
||||
Defang hosts/URLs and document the offsets where the config was recovered.
|
||||
|
||||
## Validation
|
||||
|
||||
- Recovered endpoints are real, parseable hosts/ports — not random byte noise.
|
||||
- XOR/base64 decode is confirmed by the decoded output containing new endpoints/strings.
|
||||
- The final config is defanged before sharing.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Mistaking unrelated in-memory URLs (browser, OS) for C2 — corroborate with the sample's region.
|
||||
- Single-byte XOR brute force producing coincidental strings; require multiple corroborating hits.
|
||||
- Reporting raw, live C2 endpoints without defanging.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the hunter/decoder.
|
||||
- ATT&CK T1140 and T1027 (linked in frontmatter).
|
||||
@@ -0,0 +1,38 @@
|
||||
# API Reference: Extracting Config From a Running Sample
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` scans a process memory dump for config indicators (URLs, IPv4:port pairs,
|
||||
mutex tokens) and attempts base64 and single-byte XOR decoding of candidate blobs to surface
|
||||
hidden endpoints. It reads bytes statically, defangs hosts, and executes nothing.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | base64, regex, JSON, file IO |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `hunt(path)`
|
||||
Returns defanged URLs, IP:port pairs, mutex tokens, and base64-decoded endpoints.
|
||||
|
||||
### `_xor_scan(blob)`
|
||||
Brute-forces single-byte XOR keys and returns decoded URL/IP:port hits with the key used.
|
||||
|
||||
## Commands
|
||||
|
||||
- `hunt <dump>` — broad indicator sweep + base64 decode.
|
||||
- `xor <dump>` — single-byte XOR brute force across the whole dump (noisier; corroborate hits).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py hunt process.dmp
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- MITRE ATT&CK T1140 Deobfuscate/Decode Information — https://attack.mitre.org/techniques/T1140/
|
||||
- MITRE ATT&CK T1027 Obfuscated Files or Information — https://attack.mitre.org/techniques/T1027/
|
||||
- Common malware config patterns (Global\\ mutexes, XOR/base64 blobs) from public family teardowns
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Hunt for malware configuration indicators in a process memory dump.
|
||||
|
||||
Scans a raw memory dump for URLs, IPv4:port pairs, mutex-like tokens, and base64 blobs, and
|
||||
attempts single-byte XOR / base64 decoding of candidate blobs to surface hidden endpoints.
|
||||
Reads bytes statically; executes nothing. Defangs recovered hosts.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import binascii
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
RE_URL = re.compile(rb"https?://[\x21-\x7e]{4,}")
|
||||
RE_IPPORT = re.compile(rb"\b(?:\d{1,3}\.){3}\d{1,3}:\d{1,5}\b")
|
||||
RE_MUTEX = re.compile(rb"(?:Global|Local)\\[\x21-\x7e]{4,40}")
|
||||
RE_B64 = re.compile(rb"[A-Za-z0-9+/]{24,}={0,2}")
|
||||
|
||||
|
||||
def _defang(s: str) -> str:
|
||||
return s.replace("http", "hxxp").replace(".", "[.]")
|
||||
|
||||
|
||||
def _xor_scan(blob: bytes) -> list[str]:
|
||||
found = []
|
||||
for key in range(1, 256):
|
||||
dec = bytes(b ^ key for b in blob)
|
||||
for m in RE_URL.findall(dec) + RE_IPPORT.findall(dec):
|
||||
found.append(f"xor:{key:#04x} {_defang(m.decode('ascii', 'ignore'))}")
|
||||
return sorted(set(found))[:20]
|
||||
|
||||
|
||||
def hunt(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
urls = sorted({_defang(m.decode("ascii", "ignore")) for m in RE_URL.findall(data)})
|
||||
ipports = sorted({_defang(m.decode("ascii", "ignore")) for m in RE_IPPORT.findall(data)})
|
||||
mutexes = sorted({m.decode("ascii", "ignore") for m in RE_MUTEX.findall(data)})
|
||||
decoded_hits = []
|
||||
for blob in RE_B64.findall(data)[:2000]:
|
||||
try:
|
||||
raw = base64.b64decode(blob, validate=True)
|
||||
except (binascii.Error, ValueError):
|
||||
continue
|
||||
for m in RE_URL.findall(raw) + RE_IPPORT.findall(raw):
|
||||
decoded_hits.append("b64 " + _defang(m.decode("ascii", "ignore")))
|
||||
return {
|
||||
"urls": urls[:50],
|
||||
"ip_port_pairs": ipports[:50],
|
||||
"mutexes": mutexes[:50],
|
||||
"base64_decoded_endpoints": sorted(set(decoded_hits))[:50],
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Hunt config indicators in a memory dump")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("hunt")
|
||||
p.add_argument("dump")
|
||||
p2 = sub.add_parser("xor")
|
||||
p2.add_argument("dump")
|
||||
args = parser.parse_args(argv)
|
||||
if args.cmd == "xor":
|
||||
print(json.dumps({"xor_endpoints": _xor_scan(Path(args.dump).read_bytes())}, indent=2))
|
||||
else:
|
||||
print(json.dumps(hunt(args.dump), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user