reverse-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok609hecl1pq6qi
2026-10-20 02:17:15 +03:00
commit da24c2bd20
2227 changed files with 443931 additions and 0 deletions

View File

@@ -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

View File

@@ -0,0 +1,109 @@
---
name: extracting-cobalt-strike-beacon-config
description: 'Extracts and interprets a Cobalt Strike Beacon configuration: decoding the
encoded config block, recovering C2 servers, malleable profile fields, sleep/jitter,
watermark, and pipe names to build IOCs and attribution leads. Activates for requests to
analyze a Cobalt Strike beacon, extract beacon config, or interpret malleable C2 settings.'
domain: cybersecurity
subdomain: malware-analysis
tags:
- malware
- cobalt-strike
- c2
- beacon-config
- malleable
- threat-intel
version: 1.0.0
author: analyst-ai-pack
license: Apache-2.0
mitre_attack:
- T1071.001
- T1573
- T1027
- T1090
d3fend:
- D3-NTA
- D3-FCR
references:
- 'Cobalt Strike documentation (malleable C2) — https://www.cobaltstrike.com/'
- 'SentinelOne / community CS config parsers (concepts) — https://github.com/Sentinel-One/CobaltStrikeParser'
---
# Extracting Cobalt Strike Beacon Config
## When to Use
- You have a suspected Cobalt Strike Beacon (PE, DLL, shellcode, or extracted blob) and need
its configuration.
- You want C2 servers, the malleable profile shape, sleep/jitter, watermark, and named pipes.
- You are building network IOCs and attribution leads from a beacon.
**Do not use** an extracted config alone for hard attribution — watermarks can be shared or
cracked. Treat it as one signal among several.
## Prerequisites
- The beacon payload (or a memory dump containing it) in neutralized form inside the lab.
- Familiarity with Cobalt Strike's encoded config block (commonly XOR-encoded with 0x2e in
older versions, 0x69 in others; varies by version).
## Safety & Handling
- Static extraction only; do not run the beacon.
- Defang recovered C2 hosts/URIs before sharing.
## Workflow
### Step 1: Locate the encoded config block
Beacon stores settings as a packed, XOR-encoded table of TLV-like entries. Brute the
single-byte XOR key by scanning for the decoded table's known opening bytes (the first
setting index/type/length pattern).
### Step 2: Decode and parse settings
Once the key is found, walk the entries. Each has an index (setting type), a type
(short/int/string), and a length. The script decodes and maps known indices:
```bash
python scripts/analyst.py parse beacon.bin
```
### Step 3: Recover key fields
Pull the high-value settings:
```text
C2 servers / GET-URI / POST-URI -> network IOCs
User-Agent -> detection signal
Sleep time / jitter -> beacon cadence
Watermark -> license/team grouping lead
Pipe name / process-inject targets -> host IOCs
Spawn-to / DNS settings -> behavior
```
### Step 4: Interpret the malleable profile
The URIs, headers, and transform settings reveal which malleable C2 profile the operator used
(e.g. masquerading as a known web service) — useful for network detection.
### Step 5: Build IOCs
Emit network IOCs (hosts, URIs, UA), host IOCs (pipe names), and the watermark for tracking.
## Validation
- The recovered C2 host(s) and URIs are well-formed and consistent with the profile.
- Sleep/jitter values are plausible and match any observed beaconing intervals.
- The same config reproduces from an independent extractor or memory copy.
## Pitfalls
- Hardcoding one XOR key — it varies across versions; brute-force per sample.
- Confusing a watermark match with attribution; cracked/leaked kits share watermarks.
- Stopping at the first C2 when the config lists multiple/backup servers.
## References
- See [`references/api-reference.md`](references/api-reference.md) for the config parser.
- Cobalt Strike malleable C2 docs and community parser concepts (linked in frontmatter).

View File

@@ -0,0 +1,49 @@
# API Reference: Analyzing Cobalt Strike Beacon Config
## Overview
`scripts/analyst.py` brute-forces the single-byte XOR key protecting a Beacon configuration
block, locates the TLV settings table, and parses a well-known subset of settings (C2 server,
URIs, sleep/jitter, watermark, pipe name). It reads bytes only.
## Dependencies
| Package | Version | Purpose |
|---------|---------|---------|
| (stdlib) | — | struct, JSON |
## External tools
| Tool | Purpose |
|------|---------|
| CobaltStrikeParser (community) | Reference config parsing across versions |
| 1768.py (Didier Stevens) | Alternative beacon config extractor |
## Core functions
### `extract_config(data)`
Tries XOR keys 1–255, finds the decoded settings table (anchored on the BeaconType entry),
and returns the parsed config plus the recovered key.
### `_parse_table(buf)`
Walks TLV entries (index, type, length, value), mapping known indices to names. Returns
`None` on malformed data so the brute-force can reject wrong keys.
## Usage
```bash
python scripts/analyst.py parse beacon.bin
```
## Notes
The setting index catalog and XOR-encoded table layout are publicly documented. This parser
intentionally decodes a limited subset for triage; use a full community parser for complete,
version-specific extraction.
## Sources
- Cobalt Strike malleable C2 documentation — https://www.cobaltstrike.com/
- CobaltStrikeParser (community reference for config layout) —
https://github.com/Sentinel-One/CobaltStrikeParser
- Publicly documented Beacon config TLV structure

View File

@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Extract a Cobalt Strike Beacon configuration by brute-forcing the XOR key.
Cobalt Strike Beacon stores its settings as a single-byte XOR-encoded table of TLV entries.
This tool scans candidate keys, locates the decoded table, and parses well-known setting
indices into a readable config. It reads bytes only and never executes the beacon.
This is original code based on the publicly documented config-block layout. It decodes a
limited, well-known subset of settings for analyst triage.
"""
from __future__ import annotations
import argparse
import json
import struct
import sys
from pathlib import Path
# Subset of well-known Beacon setting indices (type catalog is public).
SETTINGS = {
1: "BeaconType",
2: "Port",
3: "SleepTime",
4: "MaxGetSize",
5: "Jitter",
7: "PublicKey",
8: "C2Server",
9: "UserAgent",
10: "HttpPostUri",
12: "HttpGet_Metadata",
13: "HttpPost_Metadata",
19: "SpawnTo",
26: "PipeName",
37: "Watermark",
43: "DNS_Idle",
}
TYPE_SHORT = 1
TYPE_INT = 2
TYPE_STR = 3
def _parse_table(buf: bytes) -> dict | None:
"""Parse a decoded settings table. Returns dict or None if it does not look valid."""
out: dict = {}
off = 0
parsed = 0
while off + 6 <= len(buf):
index, vtype, length = struct.unpack_from(">HHH", buf, off)
off += 6
if index == 0 and vtype == 0 and length == 0:
break
if vtype == TYPE_SHORT and length == 2:
(val,) = struct.unpack_from(">H", buf, off)
elif vtype == TYPE_INT and length == 4:
(val,) = struct.unpack_from(">I", buf, off)
elif vtype == TYPE_STR and 0 < length <= 4096:
val = buf[off:off + length].rstrip(b"\x00").decode("latin-1", "replace")
else:
return None # malformed -> wrong key
off += length
parsed += 1
name = SETTINGS.get(index, f"Setting_{index}")
out[name] = val
if parsed > 128:
break
return out if parsed >= 5 else None
def extract_config(data: bytes) -> dict | None:
"""Brute-force the single-byte XOR key and parse the first valid-looking table."""
for key in range(1, 256):
decoded = bytes(b ^ key for b in data)
# the table begins with index=1 (BeaconType), type=1 (short), len=2
marker = struct.pack(">HHH", 1, TYPE_SHORT, 2)
pos = decoded.find(marker)
if pos == -1:
continue
cfg = _parse_table(decoded[pos:])
if cfg:
cfg["_xor_key"] = key
return cfg
return None
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Extract Cobalt Strike beacon config")
sub = parser.add_subparsers(dest="cmd", required=True)
p = sub.add_parser("parse")
p.add_argument("file")
args = parser.parse_args(argv)
data = Path(args.file).read_bytes()
cfg = extract_config(data)
if cfg is None:
print(json.dumps({"found": False,
"note": "no XOR-decoded config table located"}, indent=2))
return 1
print(json.dumps({"found": True, "config": cfg}, indent=2))
return 0
if __name__ == "__main__":
sys.exit(main())