reorganize
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
generated/
|
||||
sources/*
|
||||
!sources/README.md
|
||||
config.yaml
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
14
README.md
14
README.md
@@ -205,6 +205,20 @@ build.py # Build: .md → .yaml + .json + .prompt.md
|
||||
generated/ # Build output (gitignored)
|
||||
```
|
||||
|
||||
## Source Mirrors
|
||||
|
||||
External source repositories used during integration are kept under `sources/`:
|
||||
|
||||
```text
|
||||
sources/
|
||||
├── Anthropic-Cybersecurity-Skills/
|
||||
├── paperclip-docs-main/
|
||||
└── temp-cyber-skills/
|
||||
```
|
||||
|
||||
- `build.py` prefers `personas/_shared` as canonical input.
|
||||
- If `personas/_shared` is missing, `build.py` falls back to known mirrors under `sources/`.
|
||||
|
||||
### Variant Types
|
||||
|
||||
| Type | Purpose | Example |
|
||||
|
||||
580
build.py
580
build.py
@@ -8,6 +8,7 @@ New users: copy config.example.yaml → config.yaml and customize.
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
@@ -27,10 +28,48 @@ def load_config(root: Path) -> dict:
|
||||
|
||||
example_path = root / "config.example.yaml"
|
||||
if example_path.exists():
|
||||
print("WARN: No config.yaml found. Using defaults. Copy config.example.yaml → config.yaml to customize.")
|
||||
print(
|
||||
"WARN: No config.yaml found. Using defaults. Copy config.example.yaml → config.yaml to customize."
|
||||
)
|
||||
return {}
|
||||
|
||||
|
||||
def resolve_shared_dir(root: Path, personas_dir: Path) -> Path | None:
|
||||
"""Resolve canonical shared library path.
|
||||
|
||||
Primary location is personas/_shared. If that is missing, fall back to
|
||||
known source mirrors under sources/.
|
||||
"""
|
||||
primary = personas_dir / "_shared"
|
||||
if primary.exists():
|
||||
return primary
|
||||
|
||||
sources_dir = root / "sources"
|
||||
fallbacks = [
|
||||
sources_dir / "temp-cyber-skills" / "personas" / "_shared",
|
||||
sources_dir / "paperclip-docs-main" / "_shared",
|
||||
]
|
||||
for candidate in fallbacks:
|
||||
if candidate.exists():
|
||||
return candidate
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def discover_sources(root: Path) -> list[str]:
|
||||
"""List known source mirrors under root/sources."""
|
||||
sources_dir = root / "sources"
|
||||
if not sources_dir.exists():
|
||||
return []
|
||||
|
||||
known = [
|
||||
"Anthropic-Cybersecurity-Skills",
|
||||
"paperclip-docs-main",
|
||||
"temp-cyber-skills",
|
||||
]
|
||||
return [name for name in known if (sources_dir / name).exists()]
|
||||
|
||||
|
||||
def flatten_config(config: dict, prefix: str = "") -> dict:
|
||||
"""Flatten nested config dict for template substitution.
|
||||
|
||||
@@ -44,7 +83,9 @@ def flatten_config(config: dict, prefix: str = "") -> dict:
|
||||
elif isinstance(value, list):
|
||||
flat[full_key] = value
|
||||
flat[f"{full_key}.count"] = len(value)
|
||||
flat[f"{full_key}.csv"] = ", ".join(str(v) for v in value if not isinstance(v, dict))
|
||||
flat[f"{full_key}.csv"] = ", ".join(
|
||||
str(v) for v in value if not isinstance(v, dict)
|
||||
)
|
||||
else:
|
||||
flat[full_key] = value
|
||||
return flat
|
||||
@@ -52,6 +93,7 @@ def flatten_config(config: dict, prefix: str = "") -> dict:
|
||||
|
||||
def inject_config(content: str, flat_config: dict) -> str:
|
||||
"""Replace {{config.key}} placeholders with config values."""
|
||||
|
||||
def replacer(match):
|
||||
key = match.group(1).strip()
|
||||
value = flat_config.get(key, match.group(0)) # keep original if not found
|
||||
@@ -66,6 +108,7 @@ def inject_config(content: str, flat_config: dict) -> str:
|
||||
|
||||
def check_conditionals(content: str, flat_config: dict) -> str:
|
||||
"""Process {{#if key}}...{{/if}} and {{#unless key}}...{{/unless}} blocks."""
|
||||
|
||||
# Handle {{#if key}}content{{/if}}
|
||||
def if_replacer(match):
|
||||
key = match.group(1).strip()
|
||||
@@ -75,7 +118,9 @@ def check_conditionals(content: str, flat_config: dict) -> str:
|
||||
return body
|
||||
return ""
|
||||
|
||||
content = re.sub(r"\{\{#if (.+?)\}\}(.*?)\{\{/if\}\}", if_replacer, content, flags=re.DOTALL)
|
||||
content = re.sub(
|
||||
r"\{\{#if (.+?)\}\}(.*?)\{\{/if\}\}", if_replacer, content, flags=re.DOTALL
|
||||
)
|
||||
|
||||
# Handle {{#unless key}}content{{/unless}}
|
||||
def unless_replacer(match):
|
||||
@@ -86,7 +131,12 @@ def check_conditionals(content: str, flat_config: dict) -> str:
|
||||
return body
|
||||
return ""
|
||||
|
||||
content = re.sub(r"\{\{#unless (.+?)\}\}(.*?)\{\{/unless\}\}", unless_replacer, content, flags=re.DOTALL)
|
||||
content = re.sub(
|
||||
r"\{\{#unless (.+?)\}\}(.*?)\{\{/unless\}\}",
|
||||
unless_replacer,
|
||||
content,
|
||||
flags=re.DOTALL,
|
||||
)
|
||||
|
||||
return content
|
||||
|
||||
@@ -118,7 +168,9 @@ def parse_persona_md(filepath: Path, flat_config: dict) -> dict:
|
||||
if line.startswith("## "):
|
||||
if current_section:
|
||||
sections[current_section] = "\n".join(current_content).strip()
|
||||
current_section = line[3:].strip().lower().replace(" ", "_").replace("&", "and")
|
||||
current_section = (
|
||||
line[3:].strip().lower().replace(" ", "_").replace("&", "and")
|
||||
)
|
||||
current_content = []
|
||||
else:
|
||||
current_content.append(line)
|
||||
@@ -133,7 +185,14 @@ def parse_persona_md(filepath: Path, flat_config: dict) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def build_persona(persona_dir: Path, output_dir: Path, flat_config: dict, config: dict, escalation_graph: dict = None, skills_index: dict = None):
|
||||
def build_persona(
|
||||
persona_dir: Path,
|
||||
output_dir: Path,
|
||||
flat_config: dict,
|
||||
config: dict,
|
||||
escalation_graph: dict = None,
|
||||
skills_index: dict = None,
|
||||
):
|
||||
"""Build all variants for a persona directory."""
|
||||
md_files = sorted(persona_dir.glob("*.md"))
|
||||
if not md_files:
|
||||
@@ -168,14 +227,27 @@ def build_persona(persona_dir: Path, output_dir: Path, flat_config: dict, config
|
||||
continue
|
||||
|
||||
# Build output object
|
||||
output = {**meta, **parsed["metadata"], "variant": variant, "sections": parsed["sections"]}
|
||||
output = {
|
||||
**meta,
|
||||
**parsed["metadata"],
|
||||
"variant": variant,
|
||||
"sections": parsed["sections"],
|
||||
}
|
||||
|
||||
# Inject config metadata
|
||||
if config:
|
||||
output["_config"] = {
|
||||
"user": config.get("user", {}).get("name", "unknown"),
|
||||
"tools": {k: v for k, v in config.get("infrastructure", {}).get("tools", {}).items() if v is True},
|
||||
"frameworks": {k: v for k, v in config.get("frameworks", {}).items() if v is True},
|
||||
"tools": {
|
||||
k: v
|
||||
for k, v in config.get("infrastructure", {})
|
||||
.get("tools", {})
|
||||
.items()
|
||||
if v is True
|
||||
},
|
||||
"frameworks": {
|
||||
k: v for k, v in config.get("frameworks", {}).items() if v is True
|
||||
},
|
||||
"regional_focus": config.get("regional_focus", {}),
|
||||
}
|
||||
|
||||
@@ -207,13 +279,17 @@ def build_persona(persona_dir: Path, output_dir: Path, flat_config: dict, config
|
||||
# Write YAML
|
||||
yaml_out = out_path / f"{variant}.yaml"
|
||||
yaml_out.write_text(
|
||||
yaml.dump(output, allow_unicode=True, default_flow_style=False, sort_keys=False),
|
||||
yaml.dump(
|
||||
output, allow_unicode=True, default_flow_style=False, sort_keys=False
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write JSON
|
||||
json_out = out_path / f"{variant}.json"
|
||||
json_out.write_text(json.dumps(output, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
json_out.write_text(
|
||||
json.dumps(output, ensure_ascii=False, indent=2), encoding="utf-8"
|
||||
)
|
||||
|
||||
# Write plain system prompt (just the body, no config metadata)
|
||||
prompt_out = out_path / f"{variant}.prompt.md"
|
||||
@@ -227,37 +303,192 @@ def build_persona(persona_dir: Path, output_dir: Path, flat_config: dict, config
|
||||
|
||||
DEFAULT_SKILL_PERSONA_MAP = {
|
||||
# Cybersecurity skills → personas
|
||||
"pentest": ["neo"], "nmap-recon": ["neo", "vortex"], "security-scanner": ["neo", "phantom"],
|
||||
"sql-injection-testing": ["neo", "phantom"], "stealth-browser": ["neo", "oracle"],
|
||||
"security-audit-toolkit": ["neo", "forge"], "pwnclaw-security-scan": ["neo"],
|
||||
"senior-secops": ["bastion"], "clawsec": ["neo", "vortex"],
|
||||
"pcap-analyzer": ["vortex", "bastion"], "sys-guard-linux-remediator": ["bastion"],
|
||||
"ctf-writeup-generator": ["neo"], "dns-networking": ["vortex", "architect"],
|
||||
"network-scanner": ["neo", "vortex"], "security-skill-scanner": ["neo"],
|
||||
"pentest-active-directory": ["neo"], "pentest-api-attacker": ["neo", "phantom"],
|
||||
"pentest-auth-bypass": ["neo", "phantom"], "pentest-c2-operator": ["neo", "sentinel"],
|
||||
"pentest": ["neo"],
|
||||
"nmap-recon": ["neo", "vortex"],
|
||||
"security-scanner": ["neo", "phantom"],
|
||||
"sql-injection-testing": ["neo", "phantom"],
|
||||
"stealth-browser": ["neo", "oracle"],
|
||||
"security-audit-toolkit": ["neo", "forge"],
|
||||
"pwnclaw-security-scan": ["neo"],
|
||||
"senior-secops": ["bastion"],
|
||||
"clawsec": ["neo", "vortex"],
|
||||
"pcap-analyzer": ["vortex", "bastion"],
|
||||
"sys-guard-linux-remediator": ["bastion"],
|
||||
"ctf-writeup-generator": ["neo"],
|
||||
"dns-networking": ["vortex", "architect"],
|
||||
"network-scanner": ["neo", "vortex"],
|
||||
"security-skill-scanner": ["neo"],
|
||||
"pentest-active-directory": ["neo"],
|
||||
"pentest-api-attacker": ["neo", "phantom"],
|
||||
"pentest-auth-bypass": ["neo", "phantom"],
|
||||
"pentest-c2-operator": ["neo", "sentinel"],
|
||||
"gov-cybersecurity": ["sentinel", "bastion"],
|
||||
# Intelligence skills → personas
|
||||
"osint-investigator": ["oracle"], "seithar-intel": ["sentinel", "frodo"],
|
||||
"freshrss": ["frodo", "oracle"], "freshrss-reader": ["frodo", "oracle"],
|
||||
"war-intel-monitor": ["frodo", "marshal"], "news-crawler": ["frodo", "herald"],
|
||||
"dellight-intelligence-ops": ["frodo", "echo"], "dellight-strategic-intelligence": ["frodo"],
|
||||
"agent-intelligence-network-scan": ["oracle"], "social-trust-manipulation-detector": ["ghost"],
|
||||
"osint-investigator": ["oracle"],
|
||||
"seithar-intel": ["sentinel", "frodo"],
|
||||
"freshrss": ["frodo", "oracle"],
|
||||
"freshrss-reader": ["frodo", "oracle"],
|
||||
"war-intel-monitor": ["frodo", "marshal"],
|
||||
"news-crawler": ["frodo", "herald"],
|
||||
"dellight-intelligence-ops": ["frodo", "echo"],
|
||||
"dellight-strategic-intelligence": ["frodo"],
|
||||
"agent-intelligence-network-scan": ["oracle"],
|
||||
"social-trust-manipulation-detector": ["ghost"],
|
||||
# Infrastructure skills → personas
|
||||
"docker-essentials": ["architect"], "session-logs": ["architect"],
|
||||
"docker-essentials": ["architect"],
|
||||
"session-logs": ["architect"],
|
||||
# Document processing → personas
|
||||
"image-ocr": ["oracle", "scribe"], "mistral-ocr": ["oracle", "scribe"],
|
||||
"pdf-text-extractor": ["scribe", "scholar"], "youtube-transcript": ["herald", "scholar"],
|
||||
"image-ocr": ["oracle", "scribe"],
|
||||
"mistral-ocr": ["oracle", "scribe"],
|
||||
"pdf-text-extractor": ["scribe", "scholar"],
|
||||
"youtube-transcript": ["herald", "scholar"],
|
||||
# Web scraping → personas
|
||||
"deep-scraper": ["oracle"], "crawl-for-ai": ["oracle", "herald"],
|
||||
"deep-scraper": ["oracle"],
|
||||
"crawl-for-ai": ["oracle", "herald"],
|
||||
}
|
||||
|
||||
|
||||
VALID_PERSONAS = {
|
||||
"arbiter",
|
||||
"architect",
|
||||
"bastion",
|
||||
"centurion",
|
||||
"chronos",
|
||||
"cipher",
|
||||
"corsair",
|
||||
"echo",
|
||||
"forge",
|
||||
"frodo",
|
||||
"gambit",
|
||||
"ghost",
|
||||
"herald",
|
||||
"ledger",
|
||||
"marshal",
|
||||
"medic",
|
||||
"neo",
|
||||
"oracle",
|
||||
"phantom",
|
||||
"polyglot",
|
||||
"sage",
|
||||
"scholar",
|
||||
"scribe",
|
||||
"sentinel",
|
||||
"specter",
|
||||
"tribune",
|
||||
"vortex",
|
||||
"warden",
|
||||
"wraith",
|
||||
}
|
||||
|
||||
|
||||
def parse_skill_frontmatter(skill_md: Path) -> dict:
|
||||
"""Parse YAML frontmatter from SKILL.md; return empty dict if absent/invalid."""
|
||||
content = skill_md.read_text(encoding="utf-8")
|
||||
fm_match = re.match(r"^---\n(.*?)\n---\n", content, re.DOTALL)
|
||||
if not fm_match:
|
||||
return {}
|
||||
parsed = yaml.safe_load(fm_match.group(1))
|
||||
return parsed if isinstance(parsed, dict) else {}
|
||||
|
||||
|
||||
def infer_personas_from_skill_metadata(skill_name: str, metadata: dict) -> list:
|
||||
"""Infer likely persona mappings using skill frontmatter metadata and naming."""
|
||||
name = (skill_name or "").lower()
|
||||
domain = str(metadata.get("domain", "")).lower()
|
||||
subdomain = str(metadata.get("subdomain", "")).lower()
|
||||
description = str(metadata.get("description", "")).lower()
|
||||
tags = [str(t).lower() for t in metadata.get("tags", []) if t is not None]
|
||||
blob = " ".join([name, domain, subdomain, description] + tags)
|
||||
|
||||
personas = set()
|
||||
|
||||
# Subdomain affinity
|
||||
subdomain_map = {
|
||||
"penetration-testing": ["neo", "phantom", "vortex"],
|
||||
"application-security": ["phantom", "neo"],
|
||||
"api-security": ["phantom", "neo"],
|
||||
"web-security": ["phantom", "neo"],
|
||||
"malware-analysis": ["specter", "bastion", "sentinel"],
|
||||
"memory-forensics": ["specter", "bastion"],
|
||||
"forensics": ["specter", "bastion"],
|
||||
"threat-intelligence": ["sentinel", "frodo", "oracle"],
|
||||
"incident-response": ["bastion", "sentinel", "medic"],
|
||||
"soc-operations": ["bastion", "sentinel"],
|
||||
"threat-hunting": ["sentinel", "bastion", "vortex"],
|
||||
"network-security": ["vortex", "bastion"],
|
||||
"network-forensics": ["vortex", "specter", "bastion"],
|
||||
"cloud-security": ["architect", "bastion", "sentinel"],
|
||||
"identity-security": ["cipher", "neo", "bastion"],
|
||||
"active-directory": ["cipher", "neo", "bastion"],
|
||||
"vulnerability-management": ["bastion", "forge"],
|
||||
"compliance": ["ledger", "arbiter", "bastion"],
|
||||
"ot-security": ["centurion", "bastion", "sentinel"],
|
||||
}
|
||||
personas.update(subdomain_map.get(subdomain, []))
|
||||
|
||||
# Keyword affinity fallback
|
||||
keyword_map = {
|
||||
"apt": ["sentinel", "frodo"],
|
||||
"threat intel": ["sentinel", "oracle", "frodo"],
|
||||
"ioc": ["sentinel", "bastion"],
|
||||
"misp": ["sentinel", "oracle"],
|
||||
"siem": ["bastion", "sentinel"],
|
||||
"splunk": ["bastion", "sentinel"],
|
||||
"soc": ["bastion", "sentinel"],
|
||||
"incident response": ["bastion", "medic", "sentinel"],
|
||||
"phishing": ["bastion", "oracle", "sentinel"],
|
||||
"malware": ["specter", "bastion", "sentinel"],
|
||||
"ransomware": ["specter", "bastion", "sentinel"],
|
||||
"forensic": ["specter", "bastion"],
|
||||
"volatility": ["specter", "bastion"],
|
||||
"yara": ["specter", "bastion"],
|
||||
"memory": ["specter", "bastion"],
|
||||
"network": ["vortex", "bastion"],
|
||||
"zeek": ["vortex", "bastion", "sentinel"],
|
||||
"wireshark": ["vortex", "bastion"],
|
||||
"nmap": ["neo", "vortex"],
|
||||
"pentest": ["neo", "phantom", "vortex"],
|
||||
"red team": ["neo", "phantom", "specter"],
|
||||
"web": ["phantom", "neo"],
|
||||
"xss": ["phantom", "neo"],
|
||||
"sql injection": ["phantom", "neo"],
|
||||
"api": ["phantom", "neo"],
|
||||
"kubernetes": ["architect", "bastion", "sentinel"],
|
||||
"docker": ["architect", "bastion"],
|
||||
"aws": ["architect", "bastion", "sentinel"],
|
||||
"azure": ["architect", "bastion", "sentinel"],
|
||||
"gcp": ["architect", "bastion", "sentinel"],
|
||||
"iam": ["cipher", "architect", "bastion"],
|
||||
"active directory": ["cipher", "neo", "bastion"],
|
||||
"kerberos": ["cipher", "neo", "bastion"],
|
||||
"compliance": ["ledger", "arbiter", "bastion"],
|
||||
"nist": ["ledger", "bastion", "sentinel"],
|
||||
"ot": ["centurion", "bastion", "sentinel"],
|
||||
"scada": ["centurion", "bastion", "sentinel"],
|
||||
"ics": ["centurion", "bastion", "sentinel"],
|
||||
}
|
||||
for keyword, mapped_personas in keyword_map.items():
|
||||
if keyword in blob:
|
||||
personas.update(mapped_personas)
|
||||
|
||||
# Conservative fallback for unmapped cybersecurity skills
|
||||
if not personas and "cyber" in domain:
|
||||
personas.update(["bastion"])
|
||||
|
||||
# Keep only valid personas and deterministic order
|
||||
return sorted([p for p in personas if p in VALID_PERSONAS])
|
||||
|
||||
|
||||
def load_skill_persona_map(config: dict) -> dict:
|
||||
"""Load skill→persona mapping from config.yaml or use defaults."""
|
||||
custom = config.get("skill_persona_map", {})
|
||||
merged = dict(DEFAULT_SKILL_PERSONA_MAP)
|
||||
merged.update(custom)
|
||||
merged = {
|
||||
k: [p for p in v if p in VALID_PERSONAS]
|
||||
for k, v in DEFAULT_SKILL_PERSONA_MAP.items()
|
||||
}
|
||||
for skill, personas in custom.items():
|
||||
if isinstance(personas, list):
|
||||
merged[skill] = [p for p in personas if p in VALID_PERSONAS]
|
||||
return merged
|
||||
|
||||
|
||||
@@ -289,7 +520,9 @@ def search_skills(shared_dir: Path, query: str):
|
||||
desc = ""
|
||||
for line in content.split("\n"):
|
||||
line = line.strip()
|
||||
if line and not line.startswith(("---", "#", "name:", "description:")):
|
||||
if line and not line.startswith(
|
||||
("---", "#", "name:", "description:")
|
||||
):
|
||||
desc = line[:100]
|
||||
break
|
||||
results.append((score, name, skills_subdir, desc))
|
||||
@@ -297,7 +530,7 @@ def search_skills(shared_dir: Path, query: str):
|
||||
results.sort(key=lambda x: -x[0])
|
||||
print(f"\n Search: '{query}' — {len(results)} results\n")
|
||||
for i, (score, name, source, desc) in enumerate(results[:20]):
|
||||
print(f" {i+1:2}. [{score:3}] {name} ({source})")
|
||||
print(f" {i + 1:2}. [{score:3}] {name} ({source})")
|
||||
if desc:
|
||||
print(f" {desc}")
|
||||
if len(results) > 20:
|
||||
@@ -351,20 +584,26 @@ def run_tests(personas_dir: Path, target: str = None):
|
||||
# Check must_include keywords exist in persona definition
|
||||
for keyword in expect.get("must_include", []):
|
||||
if keyword.lower() not in prompt_content:
|
||||
warnings.append(f" {persona_name}/{test_name}: '{keyword}' not in persona prompt")
|
||||
warnings.append(
|
||||
f" {persona_name}/{test_name}: '{keyword}' not in persona prompt"
|
||||
)
|
||||
test_passed = False
|
||||
|
||||
# Check escalation targets are defined
|
||||
if expect.get("escalation"):
|
||||
target_persona = expect["escalation"].lower()
|
||||
if target_persona not in prompt_content:
|
||||
warnings.append(f" {persona_name}/{test_name}: escalation to '{target_persona}' not defined in boundaries")
|
||||
warnings.append(
|
||||
f" {persona_name}/{test_name}: escalation to '{target_persona}' not defined in boundaries"
|
||||
)
|
||||
test_passed = False
|
||||
|
||||
# Check confidence language for intel personas
|
||||
if expect.get("confidence"):
|
||||
if "confidence" not in prompt_content and "high" not in prompt_content:
|
||||
warnings.append(f" {persona_name}/{test_name}: confidence levels not defined in persona")
|
||||
warnings.append(
|
||||
f" {persona_name}/{test_name}: confidence levels not defined in persona"
|
||||
)
|
||||
test_passed = False
|
||||
|
||||
if test_passed:
|
||||
@@ -384,9 +623,16 @@ def run_tests(personas_dir: Path, target: str = None):
|
||||
|
||||
|
||||
def build_skills_index(shared_dir: Path, config: dict = None) -> dict:
|
||||
"""Index all shared skills from _shared/skills/ and _shared/paperclip-skills/."""
|
||||
"""Index all shared skills from _shared/{skills,paperclip-skills,community-skills}/."""
|
||||
skill_map = load_skill_persona_map(config or {})
|
||||
index = {"skills": {}, "paperclip_skills": {}, "design_brands": [], "ui_ux_styles": 0, "_skill_persona_map": skill_map}
|
||||
index = {
|
||||
"skills": {},
|
||||
"paperclip_skills": {},
|
||||
"community_skills": {},
|
||||
"design_brands": [],
|
||||
"ui_ux_styles": 0,
|
||||
"_skill_persona_map": skill_map,
|
||||
}
|
||||
|
||||
# Index shared-skills
|
||||
skills_dir = shared_dir / "skills"
|
||||
@@ -396,16 +642,33 @@ def build_skills_index(shared_dir: Path, config: dict = None) -> dict:
|
||||
continue
|
||||
skill_md = skill_dir / "SKILL.md"
|
||||
if skill_md.exists():
|
||||
skill_meta = parse_skill_frontmatter(skill_md)
|
||||
inferred_personas = infer_personas_from_skill_metadata(
|
||||
skill_dir.name, skill_meta
|
||||
)
|
||||
configured_personas = skill_map.get(skill_dir.name, [])
|
||||
merged_personas = sorted(
|
||||
set(configured_personas).union(inferred_personas)
|
||||
)
|
||||
content = skill_md.read_text(encoding="utf-8")
|
||||
first_line = ""
|
||||
for line in content.split("\n"):
|
||||
line = line.strip()
|
||||
if line and not line.startswith(("---", "#", "name:", "description:")):
|
||||
if line and not line.startswith(
|
||||
("---", "#", "name:", "description:")
|
||||
):
|
||||
first_line = line[:120]
|
||||
break
|
||||
index["skills"][skill_dir.name] = {
|
||||
"personas": skill_map.get(skill_dir.name, []),
|
||||
"personas": merged_personas,
|
||||
"summary": first_line,
|
||||
"domain": str(skill_meta.get("domain", "")),
|
||||
"subdomain": str(skill_meta.get("subdomain", "")),
|
||||
"tags": skill_meta.get("tags", []),
|
||||
"mapped_by": {
|
||||
"explicit": configured_personas,
|
||||
"inferred": inferred_personas,
|
||||
},
|
||||
"has_references": (skill_dir / "references").is_dir(),
|
||||
}
|
||||
|
||||
@@ -419,10 +682,22 @@ def build_skills_index(shared_dir: Path, config: dict = None) -> dict:
|
||||
if skill_md.exists():
|
||||
index["paperclip_skills"][skill_dir.name] = True
|
||||
|
||||
# Index community-skills
|
||||
cskills_dir = shared_dir / "community-skills"
|
||||
if cskills_dir.exists():
|
||||
for skill_dir in sorted(cskills_dir.iterdir()):
|
||||
if not skill_dir.is_dir():
|
||||
continue
|
||||
skill_md = skill_dir / "SKILL.md"
|
||||
if skill_md.exists():
|
||||
index["community_skills"][skill_dir.name] = True
|
||||
|
||||
# Index design brands
|
||||
design_dir = shared_dir / "design-md"
|
||||
if design_dir.exists():
|
||||
index["design_brands"] = sorted([d.name for d in design_dir.iterdir() if d.is_dir()])
|
||||
index["design_brands"] = sorted(
|
||||
[d.name for d in design_dir.iterdir() if d.is_dir()]
|
||||
)
|
||||
|
||||
# Count UI/UX data
|
||||
uiux_dir = shared_dir / "ui-ux-pro-max" / "data"
|
||||
@@ -477,7 +752,9 @@ def validate_persona(persona_name: str, parsed: dict) -> list:
|
||||
if section not in parsed.get("sections", {}):
|
||||
warnings.append(f"Missing section: {section}")
|
||||
elif len(parsed["sections"][section].split()) < 30:
|
||||
warnings.append(f"Thin section ({len(parsed['sections'][section].split())} words): {section}")
|
||||
warnings.append(
|
||||
f"Thin section ({len(parsed['sections'][section].split())} words): {section}"
|
||||
)
|
||||
|
||||
fm = parsed.get("metadata", {})
|
||||
for field in ["codename", "name", "domain", "address_to", "tone"]:
|
||||
@@ -487,7 +764,13 @@ def validate_persona(persona_name: str, parsed: dict) -> list:
|
||||
return warnings
|
||||
|
||||
|
||||
def build_catalog(personas_dir: Path, output_dir: Path, config: dict, flat_config: dict):
|
||||
def build_catalog(
|
||||
personas_dir: Path,
|
||||
output_dir: Path,
|
||||
config: dict,
|
||||
flat_config: dict,
|
||||
shared_dir: Path | None,
|
||||
):
|
||||
"""Generate CATALOG.md with stats, escalation paths, and trigger index."""
|
||||
addresses = config.get("persona_defaults", {}).get("custom_addresses", {})
|
||||
|
||||
@@ -515,7 +798,11 @@ def build_catalog(personas_dir: Path, output_dir: Path, config: dict, flat_confi
|
||||
meta = yaml.safe_load(meta_file.read_text(encoding="utf-8")) or {}
|
||||
codename = meta.get("codename", persona_dir.name)
|
||||
address = addresses.get(persona_dir.name, meta.get("address_to", "N/A"))
|
||||
variants = [f.stem for f in sorted(persona_dir.glob("*.md")) if not f.name.startswith("_")]
|
||||
variants = [
|
||||
f.stem
|
||||
for f in sorted(persona_dir.glob("*.md"))
|
||||
if not f.name.startswith("_")
|
||||
]
|
||||
|
||||
# Parse general.md for stats
|
||||
general = persona_dir / "general.md"
|
||||
@@ -540,7 +827,9 @@ def build_catalog(personas_dir: Path, output_dir: Path, config: dict, flat_confi
|
||||
catalog_lines.append(f"- **Domain:** {meta.get('domain', 'N/A')}")
|
||||
catalog_lines.append(f"- **Hitap:** {address}")
|
||||
catalog_lines.append(f"- **Variants:** {', '.join(variants)}")
|
||||
catalog_lines.append(f"- **Depth:** {word_count:,} words, {section_count} sections")
|
||||
catalog_lines.append(
|
||||
f"- **Depth:** {word_count:,} words, {section_count} sections"
|
||||
)
|
||||
if escalates_to:
|
||||
catalog_lines.append(f"- **Escalates to:** {', '.join(escalates_to)}")
|
||||
catalog_lines.append("")
|
||||
@@ -559,7 +848,9 @@ def build_catalog(personas_dir: Path, output_dir: Path, config: dict, flat_confi
|
||||
catalog_lines.append("## Build Statistics\n")
|
||||
catalog_lines.append(f"- Total prompt content: {total_words:,} words")
|
||||
catalog_lines.append(f"- Total sections: {total_sections}")
|
||||
catalog_lines.append(f"- Escalation connections: {sum(len(v) for v in escalation_graph.values())}")
|
||||
catalog_lines.append(
|
||||
f"- Escalation connections: {sum(len(v) for v in escalation_graph.values())}"
|
||||
)
|
||||
catalog_lines.append(f"- Unique triggers: {len(trigger_index)}")
|
||||
catalog_lines.append("")
|
||||
|
||||
@@ -580,13 +871,18 @@ def build_catalog(personas_dir: Path, output_dir: Path, config: dict, flat_confi
|
||||
print(f" Index: {index_path}/escalation_graph.json, trigger_index.json")
|
||||
|
||||
# Write skills index if shared dir exists
|
||||
shared_dir = personas_dir / "_shared"
|
||||
if shared_dir.exists():
|
||||
si = build_skills_index(shared_dir)
|
||||
if shared_dir and shared_dir.exists():
|
||||
si = build_skills_index(shared_dir, config)
|
||||
(index_path / "skills_index.json").write_text(
|
||||
json.dumps(si, indent=2, ensure_ascii=False), encoding="utf-8"
|
||||
)
|
||||
print(f" Skills: {len(si.get('skills', {}))} shared + {len(si.get('paperclip_skills', {}))} paperclip + {len(si.get('design_brands', []))} design brands + {si.get('ui_ux_styles', 0)} UI/UX data files")
|
||||
print(
|
||||
f" Skills: {len(si.get('skills', {}))} shared + "
|
||||
f"{len(si.get('paperclip_skills', {}))} paperclip + "
|
||||
f"{len(si.get('community_skills', {}))} community + "
|
||||
f"{len(si.get('design_brands', []))} design brands + "
|
||||
f"{si.get('ui_ux_styles', 0)} UI/UX data files"
|
||||
)
|
||||
|
||||
# Print validation warnings
|
||||
if all_warnings:
|
||||
@@ -597,7 +893,9 @@ def build_catalog(personas_dir: Path, output_dir: Path, config: dict, flat_confi
|
||||
return total_words
|
||||
|
||||
|
||||
def print_summary(config: dict, total_personas: int, total_variants: int, total_words: int = 0):
|
||||
def print_summary(
|
||||
config: dict, total_personas: int, total_variants: int, total_words: int = 0
|
||||
):
|
||||
"""Print build summary with config status."""
|
||||
print("\n" + "=" * 50)
|
||||
print(f"BUILD COMPLETE")
|
||||
@@ -609,8 +907,14 @@ def print_summary(config: dict, total_personas: int, total_variants: int, total_
|
||||
|
||||
if config:
|
||||
user = config.get("user", {}).get("name", "?")
|
||||
tools_on = sum(1 for v in config.get("infrastructure", {}).get("tools", {}).values() if v is True)
|
||||
frameworks_on = sum(1 for v in config.get("frameworks", {}).values() if v is True)
|
||||
tools_on = sum(
|
||||
1
|
||||
for v in config.get("infrastructure", {}).get("tools", {}).values()
|
||||
if v is True
|
||||
)
|
||||
frameworks_on = sum(
|
||||
1 for v in config.get("frameworks", {}).values() if v is True
|
||||
)
|
||||
regions = config.get("regional_focus", {}).get("primary", [])
|
||||
print(f"\n Config: {user}")
|
||||
print(f" Tools: {tools_on} enabled")
|
||||
@@ -641,7 +945,11 @@ def install_claude(output_dir: Path):
|
||||
for prompt_file in persona_dir.glob("*.prompt.md"):
|
||||
variant = prompt_file.stem
|
||||
codename = persona_dir.name
|
||||
cmd_name = f"persona-{codename}" if variant == "general" else f"persona-{codename}-{variant}"
|
||||
cmd_name = (
|
||||
f"persona-{codename}"
|
||||
if variant == "general"
|
||||
else f"persona-{codename}-{variant}"
|
||||
)
|
||||
dest = commands_dir / f"{cmd_name}.md"
|
||||
content = prompt_file.read_text(encoding="utf-8")
|
||||
command_content = f"{content}\n\n---\nUser query: $ARGUMENTS\n"
|
||||
@@ -683,10 +991,24 @@ def install_claude(output_dir: Path):
|
||||
"name": codename,
|
||||
"description": f"{name} ({address_to}) — {role}. {domain}.",
|
||||
"instructions": instructions,
|
||||
"allowedTools": ["Read(*)", "Edit(*)", "Write(*)", "Bash(*)", "Glob(*)", "Grep(*)", "WebFetch(*)", "WebSearch(*)"],
|
||||
"allowedTools": [
|
||||
"Read(*)",
|
||||
"Edit(*)",
|
||||
"Write(*)",
|
||||
"Bash(*)",
|
||||
"Glob(*)",
|
||||
"Grep(*)",
|
||||
"WebFetch(*)",
|
||||
"WebSearch(*)",
|
||||
],
|
||||
}
|
||||
agent_file = agents_dir / f"{codename}.yml"
|
||||
agent_file.write_text(yaml.dump(agent, allow_unicode=True, default_flow_style=False, sort_keys=False), encoding="utf-8")
|
||||
agent_file.write_text(
|
||||
yaml.dump(
|
||||
agent, allow_unicode=True, default_flow_style=False, sort_keys=False
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
agent_count += 1
|
||||
|
||||
print(f" Claude: {cmd_count} commands + {agent_count} agents installed")
|
||||
@@ -730,10 +1052,13 @@ def install_gemini(output_dir: Path):
|
||||
gem = {
|
||||
"name": f"{name} — {variant}" if variant != "general" else name,
|
||||
"description": f"{data.get('role', '')} | {data.get('domain', '')}",
|
||||
"system_instruction": data.get("sections", {}).get("soul", "") + "\n\n" +
|
||||
data.get("sections", {}).get("expertise", "") + "\n\n" +
|
||||
data.get("sections", {}).get("methodology", "") + "\n\n" +
|
||||
data.get("sections", {}).get("behavior_rules", ""),
|
||||
"system_instruction": data.get("sections", {}).get("soul", "")
|
||||
+ "\n\n"
|
||||
+ data.get("sections", {}).get("expertise", "")
|
||||
+ "\n\n"
|
||||
+ data.get("sections", {}).get("methodology", "")
|
||||
+ "\n\n"
|
||||
+ data.get("sections", {}).get("behavior_rules", ""),
|
||||
"metadata": {
|
||||
"codename": codename,
|
||||
"variant": variant,
|
||||
@@ -744,17 +1069,26 @@ def install_gemini(output_dir: Path):
|
||||
},
|
||||
}
|
||||
dest = gems_dir / f"{codename}-{variant}.json"
|
||||
dest.write_text(json.dumps(gem, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
dest.write_text(
|
||||
json.dumps(gem, ensure_ascii=False, indent=2), encoding="utf-8"
|
||||
)
|
||||
count += 1
|
||||
print(f" Gemini: {count} gems generated to {gems_dir}")
|
||||
return count
|
||||
|
||||
|
||||
def install_paperclip(output_dir: Path, personas_dir: Path):
|
||||
def install_paperclip(output_dir: Path, personas_dir: Path, shared_dir: Path | None):
|
||||
"""Install personas as Paperclip agents (SOUL.md + hermes-config.yaml + AGENTS.md per agent)."""
|
||||
pc_dir = output_dir / "_paperclip"
|
||||
agents_dir = pc_dir / "agents"
|
||||
skills_dir = pc_dir / "skills"
|
||||
|
||||
# Recreate output for deterministic full migration.
|
||||
if pc_dir.exists():
|
||||
import shutil
|
||||
|
||||
shutil.rmtree(pc_dir)
|
||||
|
||||
agents_dir.mkdir(parents=True, exist_ok=True)
|
||||
skills_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -860,11 +1194,13 @@ def install_paperclip(output_dir: Path, personas_dir: Path):
|
||||
agents_md_lines.append(f"- → {target}")
|
||||
agents_md_lines.append("")
|
||||
|
||||
(agent_dir / "AGENTS.md").write_text("\n".join(agents_md_lines), encoding="utf-8")
|
||||
(agent_dir / "AGENTS.md").write_text(
|
||||
"\n".join(agents_md_lines), encoding="utf-8"
|
||||
)
|
||||
agent_count += 1
|
||||
|
||||
# Copy shared skills as Paperclip skills (SKILL.md format already compatible)
|
||||
shared_skills = personas_dir / "_shared" / "skills"
|
||||
shared_skills = shared_dir / "skills" if shared_dir else Path("__missing__")
|
||||
if shared_skills.exists():
|
||||
for skill_dir in sorted(shared_skills.iterdir()):
|
||||
if not skill_dir.is_dir():
|
||||
@@ -873,15 +1209,18 @@ def install_paperclip(output_dir: Path, personas_dir: Path):
|
||||
if skill_md.exists():
|
||||
dest = skills_dir / skill_dir.name
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
(dest / "SKILL.md").write_text(skill_md.read_text(encoding="utf-8"), encoding="utf-8")
|
||||
(dest / "SKILL.md").write_text(
|
||||
skill_md.read_text(encoding="utf-8"), encoding="utf-8"
|
||||
)
|
||||
refs = skill_dir / "references"
|
||||
if refs.is_dir():
|
||||
import shutil
|
||||
|
||||
shutil.copytree(refs, dest / "references", dirs_exist_ok=True)
|
||||
skill_count += 1
|
||||
|
||||
# Copy paperclip-specific skills
|
||||
pc_skills = personas_dir / "_shared" / "paperclip-skills"
|
||||
pc_skills = shared_dir / "paperclip-skills" if shared_dir else Path("__missing__")
|
||||
if pc_skills.exists():
|
||||
for skill_dir in sorted(pc_skills.iterdir()):
|
||||
if not skill_dir.is_dir():
|
||||
@@ -890,25 +1229,54 @@ def install_paperclip(output_dir: Path, personas_dir: Path):
|
||||
if skill_md.exists() and not (skills_dir / skill_dir.name).exists():
|
||||
dest = skills_dir / skill_dir.name
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
(dest / "SKILL.md").write_text(skill_md.read_text(encoding="utf-8"), encoding="utf-8")
|
||||
(dest / "SKILL.md").write_text(
|
||||
skill_md.read_text(encoding="utf-8"), encoding="utf-8"
|
||||
)
|
||||
refs = skill_dir / "references"
|
||||
if refs.is_dir():
|
||||
import shutil
|
||||
|
||||
shutil.copytree(refs, dest / "references", dirs_exist_ok=True)
|
||||
scripts = skill_dir / "scripts"
|
||||
if scripts.is_dir():
|
||||
import shutil
|
||||
|
||||
shutil.copytree(scripts, dest / "scripts", dirs_exist_ok=True)
|
||||
skill_count += 1
|
||||
|
||||
# Deploy original Paperclip company agents from _shared/paperclip-agents/
|
||||
pc_agents_src = personas_dir / "_shared" / "paperclip-agents"
|
||||
pc_agents_src = (
|
||||
shared_dir / "paperclip-agents" if shared_dir else Path("__missing__")
|
||||
)
|
||||
pc_agent_count = 0
|
||||
|
||||
def normalize_agent_name(name: str) -> str:
|
||||
"""Normalize escaped/unicode-heavy names to stable ASCII directory names."""
|
||||
decoded = re.sub(
|
||||
r"#U([0-9A-Fa-f]{4})",
|
||||
lambda m: chr(int(m.group(1), 16)),
|
||||
name,
|
||||
)
|
||||
ascii_name = (
|
||||
unicodedata.normalize("NFKD", decoded)
|
||||
.encode("ascii", "ignore")
|
||||
.decode("ascii")
|
||||
)
|
||||
# Keep names filesystem-safe and deterministic.
|
||||
slug = re.sub(r"[^a-zA-Z0-9]+", "-", ascii_name).strip("-").lower()
|
||||
return slug or decoded
|
||||
|
||||
if pc_agents_src.exists():
|
||||
seen_company_agents = set()
|
||||
collision_count = 0
|
||||
for agent_src in sorted(pc_agents_src.iterdir()):
|
||||
if not agent_src.is_dir():
|
||||
continue
|
||||
agent_name = agent_src.name
|
||||
agent_name = normalize_agent_name(agent_src.name)
|
||||
if agent_name in seen_company_agents:
|
||||
collision_count += 1
|
||||
continue
|
||||
seen_company_agents.add(agent_name)
|
||||
# Skip if persona-based agent already exists with same name
|
||||
if (agents_dir / agent_name).exists():
|
||||
continue
|
||||
@@ -916,11 +1284,19 @@ def install_paperclip(output_dir: Path, personas_dir: Path):
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
for f in agent_src.iterdir():
|
||||
if f.is_file():
|
||||
(dest / f.name).write_text(f.read_text(encoding="utf-8"), encoding="utf-8")
|
||||
(dest / f.name).write_text(
|
||||
f.read_text(encoding="utf-8"), encoding="utf-8"
|
||||
)
|
||||
pc_agent_count += 1
|
||||
if collision_count:
|
||||
print(
|
||||
f" Note: skipped {collision_count} duplicate company agent source dirs after name normalization"
|
||||
)
|
||||
|
||||
total_agents = agent_count + pc_agent_count
|
||||
print(f" Paperclip: {agent_count} persona agents + {pc_agent_count} company agents + {skill_count} skills to {pc_dir}")
|
||||
print(
|
||||
f" Paperclip: {agent_count} persona agents + {pc_agent_count} company agents + {skill_count} skills to {pc_dir}"
|
||||
)
|
||||
return total_agents
|
||||
|
||||
|
||||
@@ -955,13 +1331,28 @@ def install_openclaw(output_dir: Path):
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description="Build persona library and optionally install to platforms.")
|
||||
parser.add_argument("--install", choices=["claude", "antigravity", "gemini", "openclaw", "paperclip", "all"],
|
||||
help="Install generated personas to a target platform")
|
||||
parser.add_argument("--search", type=str, metavar="QUERY",
|
||||
help="Search across all shared skills (e.g. --search 'pentest AD')")
|
||||
parser.add_argument("--test", nargs="?", const="__all__", metavar="PERSONA",
|
||||
help="Run persona test suite (optionally specify persona name)")
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Build persona library and optionally install to platforms."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--install",
|
||||
choices=["claude", "antigravity", "gemini", "openclaw", "paperclip", "all"],
|
||||
help="Install generated personas to a target platform",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--search",
|
||||
type=str,
|
||||
metavar="QUERY",
|
||||
help="Search across all shared skills (e.g. --search 'pentest AD')",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--test",
|
||||
nargs="?",
|
||||
const="__all__",
|
||||
metavar="PERSONA",
|
||||
help="Run persona test suite (optionally specify persona name)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
root = Path(__file__).parent
|
||||
@@ -979,17 +1370,28 @@ def main():
|
||||
|
||||
# Find all persona directories
|
||||
persona_dirs = [
|
||||
d for d in sorted(personas_dir.iterdir()) if d.is_dir() and not d.name.startswith((".", "_"))
|
||||
d
|
||||
for d in sorted(personas_dir.iterdir())
|
||||
if d.is_dir() and not d.name.startswith((".", "_"))
|
||||
]
|
||||
|
||||
if not persona_dirs:
|
||||
print("No persona directories found.")
|
||||
sys.exit(1)
|
||||
|
||||
shared_dir = personas_dir / "_shared"
|
||||
shared_dir = resolve_shared_dir(root, personas_dir)
|
||||
source_mirrors = discover_sources(root)
|
||||
|
||||
if source_mirrors:
|
||||
print(f"Detected source mirrors: {', '.join(source_mirrors)}")
|
||||
else:
|
||||
print("Detected source mirrors: none")
|
||||
|
||||
# Handle search-only mode
|
||||
if args.search:
|
||||
if not shared_dir:
|
||||
print("No shared skill library found.")
|
||||
return
|
||||
search_skills(shared_dir, args.search)
|
||||
return
|
||||
|
||||
@@ -1004,18 +1406,26 @@ def main():
|
||||
|
||||
# Pre-build escalation graph and skills index
|
||||
escalation_graph = build_escalation_graph(personas_dir, flat_config)
|
||||
skills_index = build_skills_index(shared_dir, config) if shared_dir.exists() else {}
|
||||
skills_index = build_skills_index(shared_dir, config) if shared_dir else {}
|
||||
|
||||
total_variants = 0
|
||||
for pdir in persona_dirs:
|
||||
total_variants += build_persona(pdir, output_dir, flat_config, config, escalation_graph, skills_index)
|
||||
total_variants += build_persona(
|
||||
pdir, output_dir, flat_config, config, escalation_graph, skills_index
|
||||
)
|
||||
|
||||
total_words = build_catalog(personas_dir, output_dir, config, flat_config)
|
||||
total_words = build_catalog(
|
||||
personas_dir, output_dir, config, flat_config, shared_dir
|
||||
)
|
||||
|
||||
# Platform installation
|
||||
if args.install:
|
||||
print(f"\n--- Installing to: {args.install} ---\n")
|
||||
targets = ["claude", "antigravity", "gemini", "openclaw", "paperclip"] if args.install == "all" else [args.install]
|
||||
targets = (
|
||||
["claude", "antigravity", "gemini", "openclaw", "paperclip"]
|
||||
if args.install == "all"
|
||||
else [args.install]
|
||||
)
|
||||
for target in targets:
|
||||
if target == "claude":
|
||||
install_claude(output_dir)
|
||||
@@ -1026,7 +1436,7 @@ def main():
|
||||
elif target == "openclaw":
|
||||
install_openclaw(output_dir)
|
||||
elif target == "paperclip":
|
||||
install_paperclip(output_dir, personas_dir)
|
||||
install_paperclip(output_dir, personas_dir, shared_dir)
|
||||
|
||||
print_summary(config, len(persona_dirs), total_variants, total_words)
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "anthropic-cybersecurity-skills",
|
||||
"owner": {
|
||||
"name": "mukul975",
|
||||
"email": "mukuljangra5@gmail.com"
|
||||
},
|
||||
"metadata": {
|
||||
"description": "754 cybersecurity skills for AI agents mapped to 5 frameworks: MITRE ATT&CK, NIST CSF 2.0, MITRE ATLAS, D3FEND, and NIST AI RMF.",
|
||||
"version": "1.2.0"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "cybersecurity-skills",
|
||||
"source": "./",
|
||||
"description": "754 cybersecurity skills covering web security, pentesting, DFIR, threat intelligence, cloud security, malware analysis, and more. Mapped to 5 frameworks.",
|
||||
"version": "1.2.0",
|
||||
"author": {
|
||||
"name": "mukul975"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [
|
||||
"cybersecurity",
|
||||
"pentesting",
|
||||
"forensics",
|
||||
"threat-intelligence",
|
||||
"cloud-security",
|
||||
"malware-analysis",
|
||||
"incident-response",
|
||||
"zero-trust",
|
||||
"devsecops"
|
||||
],
|
||||
"category": "security",
|
||||
"homepage": "https://github.com/mukul975/Anthropic-Cybersecurity-Skills",
|
||||
"repository": "https://github.com/mukul975/Anthropic-Cybersecurity-Skills"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "cybersecurity-skills",
|
||||
"description": "753 cybersecurity skills covering web security, pentesting, DFIR, threat intelligence, cloud security, malware analysis, and more.",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
2
personas/_shared/anthropic-cybersecurity-skills/.github/FUNDING.yml
vendored
Normal file
2
personas/_shared/anthropic-cybersecurity-skills/.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
github: mukul975
|
||||
custom: ["https://paypal.me/mahipaljangra"]
|
||||
50
personas/_shared/anthropic-cybersecurity-skills/.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
50
personas/_shared/anthropic-cybersecurity-skills/.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: Bug Report
|
||||
description: Report a SKILL.md validation error, broken script, or incorrect content
|
||||
title: "[Bug]: "
|
||||
labels: ["bug", "needs-triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to report a bug!
|
||||
- type: input
|
||||
id: skill-name
|
||||
attributes:
|
||||
label: Skill Name
|
||||
description: Which skill has the issue?
|
||||
placeholder: e.g., analyzing-disk-image-with-autopsy
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: bug-type
|
||||
attributes:
|
||||
label: Bug Type
|
||||
options:
|
||||
- SKILL.md validation error
|
||||
- Broken/incorrect script
|
||||
- Wrong instructions or commands
|
||||
- Missing required files
|
||||
- Incorrect metadata/frontmatter
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: What is the issue?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: What should happen instead?
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: ai-agent
|
||||
attributes:
|
||||
label: AI Agent Used
|
||||
description: Which AI agent were you using?
|
||||
placeholder: e.g., Claude Code, GitHub Copilot, Codex CLI
|
||||
8
personas/_shared/anthropic-cybersecurity-skills/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
personas/_shared/anthropic-cybersecurity-skills/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Security Vulnerability
|
||||
url: https://github.com/mukul975/Anthropic-Cybersecurity-Skills/security/advisories/new
|
||||
about: Report a security vulnerability in this repository
|
||||
- name: Discussion
|
||||
url: https://github.com/mukul975/Anthropic-Cybersecurity-Skills/discussions
|
||||
about: Ask questions or discuss ideas
|
||||
19
personas/_shared/anthropic-cybersecurity-skills/.github/ISSUE_TEMPLATE/improve-skill.md
vendored
Normal file
19
personas/_shared/anthropic-cybersecurity-skills/.github/ISSUE_TEMPLATE/improve-skill.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: Improve existing skill
|
||||
about: Suggest improvements to an existing skill
|
||||
title: '[IMPROVE] skill-name-here'
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## Skill to improve
|
||||
<!-- Folder name of the skill -->
|
||||
|
||||
## What needs improvement?
|
||||
- [ ] agent.py has errors or placeholders
|
||||
- [ ] api-reference.md is incomplete
|
||||
- [ ] SKILL.md frontmatter is missing fields
|
||||
- [ ] ATT&CK mapping is incorrect
|
||||
- [ ] Other:
|
||||
|
||||
## Suggested improvement
|
||||
58
personas/_shared/anthropic-cybersecurity-skills/.github/ISSUE_TEMPLATE/new-skill-request.yml
vendored
Normal file
58
personas/_shared/anthropic-cybersecurity-skills/.github/ISSUE_TEMPLATE/new-skill-request.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: New Skill Request
|
||||
description: Request a new cybersecurity skill to be added to the database
|
||||
title: "[Skill Request]: "
|
||||
labels: ["enhancement", "new-skill", "help wanted"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Request a new cybersecurity skill. The more detail you provide, the faster we can add it!
|
||||
- type: input
|
||||
id: skill-name
|
||||
attributes:
|
||||
label: Proposed Skill Name
|
||||
description: Kebab-case gerund form (e.g., analyzing-memory-dump-with-volatility)
|
||||
placeholder: performing-task-name
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: category
|
||||
attributes:
|
||||
label: Category
|
||||
options:
|
||||
- Threat Detection
|
||||
- Incident Response
|
||||
- Penetration Testing
|
||||
- Digital Forensics
|
||||
- Compliance & Governance
|
||||
- Network Security
|
||||
- Cloud Security
|
||||
- Application Security
|
||||
- Malware Analysis
|
||||
- OSINT
|
||||
- Zero Trust Architecture
|
||||
- OT/ICS Security
|
||||
- DevSecOps
|
||||
- Ransomware Defense
|
||||
- Threat Intelligence
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Skill Description
|
||||
description: What should this skill teach an AI agent to do?
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: mitre-attack
|
||||
attributes:
|
||||
label: MITRE ATT&CK Technique(s)
|
||||
description: Optional - relevant technique IDs
|
||||
placeholder: e.g., T1059, T1078
|
||||
- type: textarea
|
||||
id: tools
|
||||
attributes:
|
||||
label: Key Tools
|
||||
description: What tools/commands should this skill cover?
|
||||
25
personas/_shared/anthropic-cybersecurity-skills/.github/ISSUE_TEMPLATE/new-skill.md
vendored
Normal file
25
personas/_shared/anthropic-cybersecurity-skills/.github/ISSUE_TEMPLATE/new-skill.md
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: Add new skill
|
||||
about: Propose a new cybersecurity skill for the database
|
||||
title: '[NEW SKILL] skill-name-here'
|
||||
labels: 'new-skill, good first issue'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## Skill name (kebab-case)
|
||||
<!-- e.g., detecting-lateral-movement-in-azure -->
|
||||
|
||||
## Domain / Subdomain
|
||||
<!-- e.g., cybersecurity / threat-hunting -->
|
||||
|
||||
## Description
|
||||
<!-- One sentence describing what this skill does -->
|
||||
|
||||
## MITRE ATT&CK techniques
|
||||
<!-- e.g., T1021.001, T1078.004 -->
|
||||
|
||||
## NIST CSF function
|
||||
<!-- Identify, Protect, Detect, Respond, or Recover -->
|
||||
|
||||
## Why is this skill needed?
|
||||
<!-- What problem does it solve for security practitioners? -->
|
||||
41
personas/_shared/anthropic-cybersecurity-skills/.github/ISSUE_TEMPLATE/skill-improvement.yml
vendored
Normal file
41
personas/_shared/anthropic-cybersecurity-skills/.github/ISSUE_TEMPLATE/skill-improvement.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Skill Improvement
|
||||
description: Suggest improvements to an existing skill
|
||||
title: "[Improvement]: "
|
||||
labels: ["enhancement", "skill-improvement"]
|
||||
body:
|
||||
- type: input
|
||||
id: skill-name
|
||||
attributes:
|
||||
label: Skill Name
|
||||
placeholder: e.g., analyzing-network-traffic-with-wireshark
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: improvement-type
|
||||
attributes:
|
||||
label: Type of Improvement
|
||||
options:
|
||||
- More accurate/updated instructions
|
||||
- Better workflow steps
|
||||
- Add missing tools or commands
|
||||
- Improve description for agent discovery
|
||||
- Add MITRE ATT&CK mapping
|
||||
- Add NIST CSF alignment
|
||||
- Improve scripts/assets
|
||||
- Fix outdated content
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: current-issue
|
||||
attributes:
|
||||
label: Current Issue
|
||||
description: What is wrong or missing?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: suggested-improvement
|
||||
attributes:
|
||||
label: Suggested Improvement
|
||||
description: What should be changed or added?
|
||||
validations:
|
||||
required: true
|
||||
39
personas/_shared/anthropic-cybersecurity-skills/.github/workflows/sync-marketplace-version.yml
vendored
Normal file
39
personas/_shared/anthropic-cybersecurity-skills/.github/workflows/sync-marketplace-version.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Sync Marketplace Version on Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
sync-version:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract version from tag
|
||||
id: version
|
||||
run: |
|
||||
VERSION=${GITHUB_REF_NAME#v}
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "tag=$GITHUB_REF_NAME" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update marketplace.json version
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
jq --arg v "$VERSION" '.metadata.version = $v | .plugins[].version = $v' .claude-plugin/marketplace.json > tmp.json
|
||||
mv tmp.json .claude-plugin/marketplace.json
|
||||
echo "Updated marketplace.json to version $VERSION"
|
||||
|
||||
- name: Commit and push
|
||||
run: |
|
||||
git config user.name "mukul975"
|
||||
git config user.email "mukuljangra5@gmail.com"
|
||||
git add .claude-plugin/marketplace.json
|
||||
git diff --staged --quiet || git commit -m "chore: bump marketplace version to ${{ steps.version.outputs.tag }}"
|
||||
git push
|
||||
70
personas/_shared/anthropic-cybersecurity-skills/.github/workflows/update-index.yml
vendored
Normal file
70
personas/_shared/anthropic-cybersecurity-skills/.github/workflows/update-index.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Update marketplace index
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'skills/**'
|
||||
- '.github/workflows/update-index.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-index:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Regenerate index.json
|
||||
run: |
|
||||
python3 << 'EOF'
|
||||
import os, json, re
|
||||
from datetime import datetime, timezone
|
||||
|
||||
skills_dir = "skills"
|
||||
skills = []
|
||||
|
||||
for skill_name in sorted(os.listdir(skills_dir)):
|
||||
skill_md = os.path.join(skills_dir, skill_name, "SKILL.md")
|
||||
if not os.path.isfile(skill_md):
|
||||
continue
|
||||
with open(skill_md, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
fm_match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
|
||||
description = ""
|
||||
if fm_match:
|
||||
m = re.search(r"^description:\s*(.+)$", fm_match.group(1), re.MULTILINE)
|
||||
if m:
|
||||
description = m.group(1).strip().strip('"')
|
||||
skills.append({
|
||||
"name": skill_name,
|
||||
"description": description,
|
||||
"domain": "cybersecurity",
|
||||
"path": f"skills/{skill_name}"
|
||||
})
|
||||
|
||||
index = {
|
||||
"version": "1.1.0",
|
||||
"generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||
"repository": "https://github.com/mukul975/Anthropic-Cybersecurity-Skills",
|
||||
"domain": "cybersecurity",
|
||||
"total_skills": len(skills),
|
||||
"skills": skills
|
||||
}
|
||||
|
||||
with open("index.json", "w", encoding="utf-8") as f:
|
||||
json.dump(index, f, separators=(',', ':'))
|
||||
|
||||
print(f"Updated index.json: {len(skills)} skills")
|
||||
EOF
|
||||
|
||||
- name: Commit updated index
|
||||
run: |
|
||||
git config user.name "mukul975"
|
||||
git config user.email "mukuljangra5@gmail.com"
|
||||
git add index.json
|
||||
git diff --staged --quiet || git commit -m "chore: auto-update index.json"
|
||||
git push
|
||||
128
personas/_shared/anthropic-cybersecurity-skills/.github/workflows/validate-skills.yml
vendored
Normal file
128
personas/_shared/anthropic-cybersecurity-skills/.github/workflows/validate-skills.yml
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
name: Validate SKILL.md files
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'skills/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'skills/**'
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
name: Validate SKILL.md frontmatter
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Validate SKILL.md frontmatter with Python
|
||||
run: |
|
||||
python3 << 'EOF'
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
REQUIRED_FIELDS = ['name', 'description', 'domain', 'subdomain', 'tags', 'version', 'author', 'license']
|
||||
errors = []
|
||||
checked = 0
|
||||
|
||||
for root, dirs, files in os.walk('skills'):
|
||||
for file in files:
|
||||
if file == 'SKILL.md':
|
||||
path = os.path.join(root, file)
|
||||
checked += 1
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Check frontmatter exists
|
||||
fm_match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
|
||||
if not fm_match:
|
||||
errors.append(f"{path}: Missing YAML frontmatter")
|
||||
continue
|
||||
|
||||
fm = fm_match.group(1)
|
||||
|
||||
# Check required fields
|
||||
for field in REQUIRED_FIELDS:
|
||||
if not re.search(rf'^{field}:', fm, re.MULTILINE):
|
||||
errors.append(f"{path}: Missing required field '{field}'")
|
||||
|
||||
# Check name format (kebab-case)
|
||||
name_match = re.search(r'^name:\s*(.+)$', fm, re.MULTILINE)
|
||||
if name_match:
|
||||
name = name_match.group(1).strip().strip('"')
|
||||
if not re.match(r'^[a-z0-9-]+$', name):
|
||||
errors.append(f"{path}: Name '{name}' must be kebab-case")
|
||||
if len(name) > 64:
|
||||
errors.append(f"{path}: Name '{name}' exceeds 64 characters")
|
||||
|
||||
print(f"Checked {checked} SKILL.md files")
|
||||
|
||||
if errors:
|
||||
print(f"\n{len(errors)} validation error(s):")
|
||||
for e in errors:
|
||||
print(f" ❌ {e}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"✅ All {checked} skills valid")
|
||||
EOF
|
||||
|
||||
- name: Check for duplicate skill names
|
||||
run: |
|
||||
python3 << 'EOF'
|
||||
import os
|
||||
import re
|
||||
from collections import Counter
|
||||
|
||||
names = []
|
||||
for root, dirs, files in os.walk('skills'):
|
||||
for file in files:
|
||||
if file == 'SKILL.md':
|
||||
path = os.path.join(root, file)
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
fm_match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
|
||||
if fm_match:
|
||||
name_match = re.search(r'^name:\s*(.+)$', fm_match.group(1), re.MULTILINE)
|
||||
if name_match:
|
||||
names.append(name_match.group(1).strip().strip('"'))
|
||||
|
||||
duplicates = [name for name, count in Counter(names).items() if count > 1]
|
||||
if duplicates:
|
||||
print(f"❌ Duplicate skill names found: {duplicates}")
|
||||
exit(1)
|
||||
print(f"✅ No duplicate names in {len(names)} skills")
|
||||
EOF
|
||||
|
||||
- name: Report skill counts
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Skill Database Stats" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
python3 << 'EOF'
|
||||
import os
|
||||
import re
|
||||
from collections import Counter
|
||||
|
||||
subdomain_counts = Counter()
|
||||
total = 0
|
||||
for root, dirs, files in os.walk('skills'):
|
||||
for file in files:
|
||||
if file == 'SKILL.md':
|
||||
total += 1
|
||||
path = os.path.join(root, file)
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
fm_match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
|
||||
if fm_match:
|
||||
sd_match = re.search(r'^subdomain:\s*(.+)$', fm_match.group(1), re.MULTILINE)
|
||||
if sd_match:
|
||||
subdomain_counts[sd_match.group(1).strip()] += 1
|
||||
|
||||
print(f"**Total Skills: {total}**")
|
||||
print("")
|
||||
print("| Subdomain | Count |")
|
||||
print("|-----------|-------|")
|
||||
for sd, count in sorted(subdomain_counts.items(), key=lambda x: -x[1]):
|
||||
print(f"| {sd} | {count} |")
|
||||
EOF
|
||||
@@ -0,0 +1,509 @@
|
||||
# MITRE ATT&CK Coverage Map
|
||||
|
||||
<p align="center">
|
||||
<a href="https://attack.mitre.org/"><img src="https://img.shields.io/badge/MITRE_ATT%26CK-v16-red?style=for-the-badge&logo=shield&logoColor=white" alt="MITRE ATT&CK" /></a>
|
||||
<img src="https://img.shields.io/badge/Techniques-291+-blueviolet?style=for-the-badge" alt="Techniques" />
|
||||
<img src="https://img.shields.io/badge/Tactics-14%2F14-green?style=for-the-badge" alt="Tactics" />
|
||||
</p>
|
||||
|
||||
This document maps all **291 unique MITRE ATT&CK techniques** (across **149 parent techniques**) referenced in our **753+ cybersecurity skills** to the 14 Enterprise ATT&CK tactics. Use this to identify coverage gaps, plan detection engineering priorities, or validate your security program against the ATT&CK framework.
|
||||
|
||||
> **How to read this:** Each technique links to its official ATT&CK page. Skills listed under each technique are the ones in this repository that teach detection, hunting, exploitation, or response for that technique.
|
||||
|
||||
---
|
||||
|
||||
## Coverage Summary
|
||||
|
||||
| Tactic | Techniques | Coverage |
|
||||
|:-------|:---------:|:---------|
|
||||
| 🔎 **Reconnaissance** | **12** | `████████████░░░░░░░░░░░░░░░░░░` |
|
||||
| 🏗️ **Resource Development** | **7** | `███████░░░░░░░░░░░░░░░░░░░░░░░` |
|
||||
| 🚪 **Initial Access** | **18** | `██████████████████░░░░░░░░░░░░` |
|
||||
| ⚡ **Execution** | **18** | `██████████████████░░░░░░░░░░░░` |
|
||||
| 🔩 **Persistence** | **36** | `██████████████████████████████` |
|
||||
| ⬆️ **Privilege Escalation** | **11** | `███████████░░░░░░░░░░░░░░░░░░░` |
|
||||
| 🥷 **Defense Evasion** | **48** | `██████████████████████████████` |
|
||||
| 🔑 **Credential Access** | **27** | `███████████████████████████░░░` |
|
||||
| 🗺️ **Discovery** | **20** | `████████████████████░░░░░░░░░░` |
|
||||
| ↔️ **Lateral Movement** | **9** | `█████████░░░░░░░░░░░░░░░░░░░░░` |
|
||||
| 📦 **Collection** | **13** | `█████████████░░░░░░░░░░░░░░░░░` |
|
||||
| 📡 **Command and Control** | **20** | `████████████████████░░░░░░░░░░` |
|
||||
| 📤 **Exfiltration** | **12** | `████████████░░░░░░░░░░░░░░░░░░` |
|
||||
| 💥 **Impact** | **6** | `██████░░░░░░░░░░░░░░░░░░░░░░░░` |
|
||||
| 🔧 **Other/Cross-tactic** | **34** | |
|
||||
| | **291** | **Total unique techniques** |
|
||||
|
||||
---
|
||||
|
||||
## 🔎 Reconnaissance
|
||||
|
||||
**12 techniques covered**
|
||||
|
||||
| Technique | Skills |
|
||||
|:----------|:-------|
|
||||
| [T1589](https://attack.mitre.org/techniques/T1589/) | `conducting-full-scope-red-team-engagement`, `conducting-social-engineering-pretext-call`, `performing-open-source-intelligence-gathering` |
|
||||
| [T1590](https://attack.mitre.org/techniques/T1590/) | `performing-open-source-intelligence-gathering` |
|
||||
| [T1591](https://attack.mitre.org/techniques/T1591/) | `collecting-open-source-intelligence`, `conducting-social-engineering-pretext-call`, `performing-open-source-intelligence-gathering` |
|
||||
| [T1592](https://attack.mitre.org/techniques/T1592/) | `performing-open-source-intelligence-gathering` |
|
||||
| [T1593](https://attack.mitre.org/techniques/T1593/) | `conducting-full-scope-red-team-engagement`, `performing-open-source-intelligence-gathering` |
|
||||
| [T1594](https://attack.mitre.org/techniques/T1594/) | `performing-open-source-intelligence-gathering` |
|
||||
| [T1595](https://attack.mitre.org/techniques/T1595/) | `executing-red-team-engagement-planning`, `triaging-security-incident` |
|
||||
| [T1595.001](https://attack.mitre.org/techniques/T1595/001/) | `performing-open-source-intelligence-gathering` |
|
||||
| [T1595.002](https://attack.mitre.org/techniques/T1595/002/) | `performing-open-source-intelligence-gathering` |
|
||||
| [T1596](https://attack.mitre.org/techniques/T1596/) | `performing-open-source-intelligence-gathering` |
|
||||
| [T1598](https://attack.mitre.org/techniques/T1598/) | `conducting-social-engineering-pretext-call` |
|
||||
| [T1598.003](https://attack.mitre.org/techniques/T1598/003/) | `conducting-social-engineering-pretext-call`, `conducting-spearphishing-simulation-campaign` |
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Resource Development
|
||||
|
||||
**7 techniques covered**
|
||||
|
||||
| Technique | Skills |
|
||||
|:----------|:-------|
|
||||
| [T1583.001](https://attack.mitre.org/techniques/T1583/001/) | `building-red-team-c2-infrastructure-with-havoc`, `conducting-full-scope-red-team-engagement`, `conducting-spearphishing-simulation-campaign`, `implementing-mitre-attack-coverage-mapping` |
|
||||
| [T1583.003](https://attack.mitre.org/techniques/T1583/003/) | `building-red-team-c2-infrastructure-with-havoc` |
|
||||
| [T1584.001](https://attack.mitre.org/techniques/T1584/001/) | `hunting-for-dns-based-persistence` |
|
||||
| [T1585.002](https://attack.mitre.org/techniques/T1585/002/) | `conducting-spearphishing-simulation-campaign` |
|
||||
| [T1587.001](https://attack.mitre.org/techniques/T1587/001/) | `building-red-team-c2-infrastructure-with-havoc`, `conducting-full-scope-red-team-engagement` |
|
||||
| [T1608.001](https://attack.mitre.org/techniques/T1608/001/) | `conducting-spearphishing-simulation-campaign` |
|
||||
| [T1608.005](https://attack.mitre.org/techniques/T1608/005/) | `conducting-spearphishing-simulation-campaign` |
|
||||
|
||||
---
|
||||
|
||||
## 🚪 Initial Access
|
||||
|
||||
**18 techniques covered**
|
||||
|
||||
| Technique | Skills |
|
||||
|:----------|:-------|
|
||||
| [T1078](https://attack.mitre.org/techniques/T1078/) | `analyzing-apt-group-with-mitre-navigator`, `analyzing-powershell-script-block-logging`, `analyzing-windows-event-logs-in-splunk`, `building-threat-hunt-hypothesis-framework`, `conducting-full-scope-red-team-engagement` +13 more |
|
||||
| [T1078.001](https://attack.mitre.org/techniques/T1078/001/) | `detecting-service-account-abuse` |
|
||||
| [T1078.002](https://attack.mitre.org/techniques/T1078/002/) | `conducting-domain-persistence-with-dcsync`, `detecting-service-account-abuse`, `exploiting-active-directory-certificate-services-esc1`, `exploiting-constrained-delegation-abuse`, `exploiting-nopac-cve-2021-42278-42287` +1 more |
|
||||
| [T1078.003](https://attack.mitre.org/techniques/T1078/003/) | `performing-privilege-escalation-assessment` |
|
||||
| [T1078.004](https://attack.mitre.org/techniques/T1078/004/) | `detecting-azure-lateral-movement`, `detecting-azure-service-principal-abuse`, `implementing-mitre-attack-coverage-mapping`, `implementing-threat-modeling-with-mitre-attack` |
|
||||
| [T1091](https://attack.mitre.org/techniques/T1091/) | `executing-red-team-engagement-planning`, `performing-physical-intrusion-assessment` |
|
||||
| [T1133](https://attack.mitre.org/techniques/T1133/) | `executing-red-team-engagement-planning`, `performing-threat-landscape-assessment-for-sector` |
|
||||
| [T1190](https://attack.mitre.org/techniques/T1190/) | `conducting-full-scope-red-team-engagement`, `executing-red-team-engagement-planning`, `exploiting-ms17-010-eternalblue-vulnerability`, `hunting-for-webshell-activity`, `performing-threat-landscape-assessment-for-sector` +1 more |
|
||||
| [T1195](https://attack.mitre.org/techniques/T1195/) | `analyzing-supply-chain-malware-artifacts`, `performing-threat-landscape-assessment-for-sector` |
|
||||
| [T1195.001](https://attack.mitre.org/techniques/T1195/001/) | `hunting-for-supply-chain-compromise` |
|
||||
| [T1195.002](https://attack.mitre.org/techniques/T1195/002/) | `hunting-for-supply-chain-compromise` |
|
||||
| [T1199](https://attack.mitre.org/techniques/T1199/) | `hunting-for-supply-chain-compromise`, `performing-physical-intrusion-assessment` |
|
||||
| [T1200](https://attack.mitre.org/techniques/T1200/) | `executing-red-team-engagement-planning`, `performing-physical-intrusion-assessment` |
|
||||
| [T1566](https://attack.mitre.org/techniques/T1566/) | `analyzing-apt-group-with-mitre-navigator`, `analyzing-threat-actor-ttps-with-mitre-attack`, `analyzing-threat-landscape-with-misp`, `building-attack-pattern-library-from-cti-reports`, `hunting-advanced-persistent-threats` +3 more |
|
||||
| [T1566.001](https://attack.mitre.org/techniques/T1566/001/) | `analyzing-apt-group-with-mitre-navigator`, `analyzing-campaign-attribution-evidence`, `analyzing-macro-malware-in-office-documents`, `analyzing-threat-actor-ttps-with-mitre-navigator`, `building-attack-pattern-library-from-cti-reports` +13 more |
|
||||
| [T1566.002](https://attack.mitre.org/techniques/T1566/002/) | `building-attack-pattern-library-from-cti-reports`, `conducting-spearphishing-simulation-campaign`, `hunting-for-spearphishing-indicators`, `implementing-continuous-security-validation-with-bas`, `implementing-mitre-attack-coverage-mapping` +1 more |
|
||||
| [T1566.003](https://attack.mitre.org/techniques/T1566/003/) | `conducting-spearphishing-simulation-campaign`, `hunting-for-spearphishing-indicators`, `implementing-continuous-security-validation-with-bas` |
|
||||
| [T1566.004](https://attack.mitre.org/techniques/T1566/004/) | `conducting-social-engineering-pretext-call` |
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Execution
|
||||
|
||||
**18 techniques covered**
|
||||
|
||||
| Technique | Skills |
|
||||
|:----------|:-------|
|
||||
| [T1047](https://attack.mitre.org/techniques/T1047/) | `conducting-full-scope-red-team-engagement`, `detecting-fileless-attacks-on-endpoints`, `detecting-lateral-movement-with-splunk`, `detecting-living-off-the-land-attacks`, `detecting-living-off-the-land-with-lolbas` +8 more |
|
||||
| [T1053](https://attack.mitre.org/techniques/T1053/) | `analyzing-apt-group-with-mitre-navigator`, `analyzing-persistence-mechanisms-in-linux`, `hunting-advanced-persistent-threats`, `hunting-for-persistence-mechanisms-in-windows`, `implementing-mitre-attack-coverage-mapping` +4 more |
|
||||
| [T1053.002](https://attack.mitre.org/techniques/T1053/002/) | `hunting-for-scheduled-task-persistence` |
|
||||
| [T1053.003](https://attack.mitre.org/techniques/T1053/003/) | `analyzing-persistence-mechanisms-in-linux`, `hunting-for-scheduled-task-persistence`, `performing-privilege-escalation-assessment`, `performing-privilege-escalation-on-linux` |
|
||||
| [T1053.005](https://attack.mitre.org/techniques/T1053/005/) | `analyzing-apt-group-with-mitre-navigator`, `analyzing-campaign-attribution-evidence`, `analyzing-windows-event-logs-in-splunk`, `building-attack-pattern-library-from-cti-reports`, `building-detection-rule-with-splunk-spl` +17 more |
|
||||
| [T1059](https://attack.mitre.org/techniques/T1059/) | `analyzing-apt-group-with-mitre-navigator`, `analyzing-threat-actor-ttps-with-mitre-attack`, `analyzing-windows-event-logs-in-splunk`, `building-incident-timeline-with-timesketch`, `deobfuscating-powershell-obfuscated-malware` +7 more |
|
||||
| [T1059.001](https://attack.mitre.org/techniques/T1059/001/) | `analyzing-apt-group-with-mitre-navigator`, `analyzing-campaign-attribution-evidence`, `analyzing-macro-malware-in-office-documents`, `analyzing-powershell-empire-artifacts`, `analyzing-powershell-script-block-logging` +29 more |
|
||||
| [T1059.003](https://attack.mitre.org/techniques/T1059/003/) | `building-attack-pattern-library-from-cti-reports`, `building-detection-rule-with-splunk-spl`, `detecting-suspicious-powershell-execution`, `mapping-mitre-attack-techniques`, `performing-purple-team-atomic-testing` |
|
||||
| [T1059.004](https://attack.mitre.org/techniques/T1059/004/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1059.005](https://attack.mitre.org/techniques/T1059/005/) | `analyzing-macro-malware-in-office-documents`, `detecting-living-off-the-land-attacks`, `executing-red-team-exercise`, `hunting-for-living-off-the-land-binaries`, `hunting-for-lolbins-execution-in-endpoint-logs` +2 more |
|
||||
| [T1059.006](https://attack.mitre.org/techniques/T1059/006/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1059.007](https://attack.mitre.org/techniques/T1059/007/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1129](https://attack.mitre.org/techniques/T1129/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1203](https://attack.mitre.org/techniques/T1203/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1204.001](https://attack.mitre.org/techniques/T1204/001/) | `conducting-spearphishing-simulation-campaign` |
|
||||
| [T1204.002](https://attack.mitre.org/techniques/T1204/002/) | `analyzing-macro-malware-in-office-documents`, `conducting-full-scope-red-team-engagement`, `conducting-spearphishing-simulation-campaign`, `detecting-living-off-the-land-attacks`, `executing-red-team-engagement-planning` +4 more |
|
||||
| [T1569](https://attack.mitre.org/techniques/T1569/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1569.002](https://attack.mitre.org/techniques/T1569/002/) | `detecting-lateral-movement-in-network`, `detecting-lateral-movement-with-splunk`, `exploiting-ms17-010-eternalblue-vulnerability`, `performing-purple-team-atomic-testing` |
|
||||
|
||||
---
|
||||
|
||||
## 🔩 Persistence
|
||||
|
||||
**36 techniques covered**
|
||||
|
||||
| Technique | Skills |
|
||||
|:----------|:-------|
|
||||
| [T1098](https://attack.mitre.org/techniques/T1098/) | `analyzing-windows-event-logs-in-splunk`, `conducting-domain-persistence-with-dcsync`, `hunting-for-t1098-account-manipulation`, `implementing-mitre-attack-coverage-mapping`, `implementing-siem-use-cases-for-detection` +1 more |
|
||||
| [T1098.001](https://attack.mitre.org/techniques/T1098/001/) | `conducting-cloud-penetration-testing`, `detecting-azure-lateral-movement`, `detecting-azure-service-principal-abuse`, `hunting-for-t1098-account-manipulation`, `implementing-mitre-attack-coverage-mapping` |
|
||||
| [T1098.002](https://attack.mitre.org/techniques/T1098/002/) | `detecting-azure-lateral-movement`, `detecting-email-forwarding-rules-attack` |
|
||||
| [T1098.004](https://attack.mitre.org/techniques/T1098/004/) | `analyzing-persistence-mechanisms-in-linux`, `implementing-security-monitoring-with-datadog` |
|
||||
| [T1136](https://attack.mitre.org/techniques/T1136/) | `detecting-privilege-escalation-in-kubernetes-pods`, `implementing-mitre-attack-coverage-mapping`, `performing-purple-team-atomic-testing` |
|
||||
| [T1136.001](https://attack.mitre.org/techniques/T1136/001/) | `analyzing-windows-event-logs-in-splunk`, `performing-purple-team-atomic-testing` |
|
||||
| [T1136.002](https://attack.mitre.org/techniques/T1136/002/) | `exploiting-nopac-cve-2021-42278-42287` |
|
||||
| [T1197](https://attack.mitre.org/techniques/T1197/) | `detecting-living-off-the-land-attacks`, `detecting-living-off-the-land-with-lolbas`, `hunting-for-living-off-the-land-binaries`, `hunting-for-lolbins-execution-in-endpoint-logs`, `performing-purple-team-atomic-testing` |
|
||||
| [T1505](https://attack.mitre.org/techniques/T1505/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1505.003](https://attack.mitre.org/techniques/T1505/003/) | `building-attack-pattern-library-from-cti-reports`, `hunting-for-webshell-activity`, `performing-purple-team-atomic-testing` |
|
||||
| [T1542.001](https://attack.mitre.org/techniques/T1542/001/) | `analyzing-uefi-bootkit-persistence` |
|
||||
| [T1542.003](https://attack.mitre.org/techniques/T1542/003/) | `analyzing-uefi-bootkit-persistence` |
|
||||
| [T1543](https://attack.mitre.org/techniques/T1543/) | `analyzing-persistence-mechanisms-in-linux`, `hunting-for-persistence-mechanisms-in-windows`, `performing-purple-team-atomic-testing` |
|
||||
| [T1543.002](https://attack.mitre.org/techniques/T1543/002/) | `analyzing-persistence-mechanisms-in-linux`, `performing-privilege-escalation-on-linux` |
|
||||
| [T1543.003](https://attack.mitre.org/techniques/T1543/003/) | `detecting-lateral-movement-with-splunk`, `detecting-living-off-the-land-attacks`, `detecting-privilege-escalation-attempts`, `hunting-for-persistence-mechanisms-in-windows`, `hunting-for-unusual-service-installations` +2 more |
|
||||
| [T1546](https://attack.mitre.org/techniques/T1546/) | `analyzing-persistence-mechanisms-in-linux`, `performing-purple-team-atomic-testing` |
|
||||
| [T1546.001](https://attack.mitre.org/techniques/T1546/001/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1546.003](https://attack.mitre.org/techniques/T1546/003/) | `analyzing-windows-event-logs-in-splunk`, `detecting-fileless-attacks-on-endpoints`, `detecting-fileless-malware-techniques`, `detecting-wmi-persistence`, `hunting-for-lateral-movement-via-wmi` +3 more |
|
||||
| [T1546.004](https://attack.mitre.org/techniques/T1546/004/) | `analyzing-persistence-mechanisms-in-linux` |
|
||||
| [T1546.010](https://attack.mitre.org/techniques/T1546/010/) | `hunting-for-persistence-mechanisms-in-windows` |
|
||||
| [T1546.012](https://attack.mitre.org/techniques/T1546/012/) | `hunting-for-persistence-mechanisms-in-windows`, `hunting-for-registry-persistence-mechanisms` |
|
||||
| [T1546.015](https://attack.mitre.org/techniques/T1546/015/) | `hunting-for-persistence-mechanisms-in-windows`, `hunting-for-registry-persistence-mechanisms` |
|
||||
| [T1547](https://attack.mitre.org/techniques/T1547/) | `analyzing-apt-group-with-mitre-navigator`, `analyzing-malware-persistence-with-autoruns`, `hunting-advanced-persistent-threats`, `hunting-for-persistence-mechanisms-in-windows`, `implementing-siem-use-cases-for-detection` +3 more |
|
||||
| [T1547.001](https://attack.mitre.org/techniques/T1547/001/) | `analyzing-apt-group-with-mitre-navigator`, `analyzing-windows-event-logs-in-splunk`, `building-attack-pattern-library-from-cti-reports`, `conducting-full-scope-red-team-engagement`, `detecting-fileless-attacks-on-endpoints` +10 more |
|
||||
| [T1547.004](https://attack.mitre.org/techniques/T1547/004/) | `hunting-for-persistence-mechanisms-in-windows`, `hunting-for-registry-persistence-mechanisms`, `performing-purple-team-atomic-testing` |
|
||||
| [T1547.005](https://attack.mitre.org/techniques/T1547/005/) | `hunting-for-persistence-mechanisms-in-windows` |
|
||||
| [T1547.009](https://attack.mitre.org/techniques/T1547/009/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1556](https://attack.mitre.org/techniques/T1556/) | `performing-initial-access-with-evilginx3` |
|
||||
| [T1556.007](https://attack.mitre.org/techniques/T1556/007/) | `detecting-azure-lateral-movement` |
|
||||
| [T1574](https://attack.mitre.org/techniques/T1574/) | `analyzing-persistence-mechanisms-in-linux`, `performing-purple-team-atomic-testing` |
|
||||
| [T1574.001](https://attack.mitre.org/techniques/T1574/001/) | `detecting-dll-sideloading-attacks`, `hunting-for-persistence-mechanisms-in-windows`, `performing-purple-team-atomic-testing` |
|
||||
| [T1574.002](https://attack.mitre.org/techniques/T1574/002/) | `analyzing-windows-event-logs-in-splunk`, `building-attack-pattern-library-from-cti-reports`, `detecting-dll-sideloading-attacks`, `implementing-siem-use-cases-for-detection`, `performing-purple-team-atomic-testing` |
|
||||
| [T1574.006](https://attack.mitre.org/techniques/T1574/006/) | `analyzing-persistence-mechanisms-in-linux`, `detecting-dll-sideloading-attacks`, `performing-privilege-escalation-on-linux` |
|
||||
| [T1574.008](https://attack.mitre.org/techniques/T1574/008/) | `detecting-dll-sideloading-attacks` |
|
||||
| [T1574.009](https://attack.mitre.org/techniques/T1574/009/) | `detecting-privilege-escalation-attempts` |
|
||||
| [T1574.011](https://attack.mitre.org/techniques/T1574/011/) | `detecting-privilege-escalation-attempts` |
|
||||
|
||||
---
|
||||
|
||||
## ⬆️ Privilege Escalation
|
||||
|
||||
**11 techniques covered**
|
||||
|
||||
| Technique | Skills |
|
||||
|:----------|:-------|
|
||||
| [T1068](https://attack.mitre.org/techniques/T1068/) | `conducting-full-scope-red-team-engagement`, `detecting-container-escape-attempts`, `detecting-privilege-escalation-attempts`, `detecting-privilege-escalation-in-kubernetes-pods`, `executing-red-team-engagement-planning` +5 more |
|
||||
| [T1134](https://attack.mitre.org/techniques/T1134/) | `analyzing-windows-event-logs-in-splunk`, `detecting-privilege-escalation-attempts` |
|
||||
| [T1134.001](https://attack.mitre.org/techniques/T1134/001/) | `detecting-privilege-escalation-attempts`, `exploiting-constrained-delegation-abuse`, `performing-purple-team-atomic-testing` |
|
||||
| [T1134.005](https://attack.mitre.org/techniques/T1134/005/) | `hunting-for-t1098-account-manipulation`, `performing-active-directory-compromise-investigation` |
|
||||
| [T1484](https://attack.mitre.org/techniques/T1484/) | `exploiting-active-directory-certificate-services-esc1`, `performing-active-directory-vulnerability-assessment` |
|
||||
| [T1484.001](https://attack.mitre.org/techniques/T1484/001/) | `deploying-active-directory-honeytokens`, `performing-active-directory-compromise-investigation` |
|
||||
| [T1548](https://attack.mitre.org/techniques/T1548/) | `detecting-container-escape-attempts`, `detecting-privilege-escalation-in-kubernetes-pods`, `detecting-t1548-abuse-elevation-control-mechanism`, `performing-privilege-escalation-assessment` |
|
||||
| [T1548.001](https://attack.mitre.org/techniques/T1548/001/) | `detecting-privilege-escalation-attempts`, `detecting-privilege-escalation-in-kubernetes-pods`, `detecting-t1548-abuse-elevation-control-mechanism`, `performing-privilege-escalation-assessment`, `performing-privilege-escalation-on-linux` |
|
||||
| [T1548.002](https://attack.mitre.org/techniques/T1548/002/) | `conducting-full-scope-red-team-engagement`, `detecting-privilege-escalation-attempts`, `detecting-t1548-abuse-elevation-control-mechanism`, `performing-purple-team-atomic-testing` |
|
||||
| [T1548.003](https://attack.mitre.org/techniques/T1548/003/) | `detecting-privilege-escalation-attempts`, `detecting-t1548-abuse-elevation-control-mechanism`, `performing-privilege-escalation-assessment`, `performing-privilege-escalation-on-linux` |
|
||||
| [T1548.004](https://attack.mitre.org/techniques/T1548/004/) | `detecting-t1548-abuse-elevation-control-mechanism` |
|
||||
|
||||
---
|
||||
|
||||
## 🥷 Defense Evasion
|
||||
|
||||
**48 techniques covered**
|
||||
|
||||
| Technique | Skills |
|
||||
|:----------|:-------|
|
||||
| [T1027](https://attack.mitre.org/techniques/T1027/) | `analyzing-apt-group-with-mitre-navigator`, `analyzing-powershell-empire-artifacts`, `analyzing-powershell-script-block-logging`, `building-attack-pattern-library-from-cti-reports`, `conducting-full-scope-red-team-engagement` +3 more |
|
||||
| [T1036](https://attack.mitre.org/techniques/T1036/) | `detecting-evasion-techniques-in-endpoint-logs`, `implementing-mitre-attack-coverage-mapping`, `implementing-siem-use-cases-for-detection`, `performing-purple-team-atomic-testing` |
|
||||
| [T1036.005](https://attack.mitre.org/techniques/T1036/005/) | `detecting-process-injection-techniques`, `performing-purple-team-atomic-testing` |
|
||||
| [T1055](https://attack.mitre.org/techniques/T1055/) | `building-attack-pattern-library-from-cti-reports`, `building-red-team-c2-infrastructure-with-havoc`, `conducting-full-scope-red-team-engagement`, `detecting-evasion-techniques-in-endpoint-logs`, `detecting-fileless-attacks-on-endpoints` +13 more |
|
||||
| [T1055.001](https://attack.mitre.org/techniques/T1055/001/) | `detecting-process-hollowing-technique`, `detecting-process-injection-techniques`, `detecting-t1055-process-injection-with-sysmon`, `hunting-for-process-injection-techniques`, `performing-purple-team-atomic-testing` +1 more |
|
||||
| [T1055.002](https://attack.mitre.org/techniques/T1055/002/) | `detecting-process-injection-techniques`, `detecting-t1055-process-injection-with-sysmon` |
|
||||
| [T1055.003](https://attack.mitre.org/techniques/T1055/003/) | `detecting-process-hollowing-technique`, `detecting-process-injection-techniques`, `detecting-t1055-process-injection-with-sysmon`, `performing-purple-team-atomic-testing` |
|
||||
| [T1055.004](https://attack.mitre.org/techniques/T1055/004/) | `detecting-process-hollowing-technique`, `detecting-process-injection-techniques`, `detecting-t1055-process-injection-with-sysmon`, `hunting-for-process-injection-techniques` |
|
||||
| [T1055.005](https://attack.mitre.org/techniques/T1055/005/) | `detecting-process-injection-techniques`, `detecting-t1055-process-injection-with-sysmon` |
|
||||
| [T1055.008](https://attack.mitre.org/techniques/T1055/008/) | `detecting-process-injection-techniques` |
|
||||
| [T1055.009](https://attack.mitre.org/techniques/T1055/009/) | `detecting-process-injection-techniques` |
|
||||
| [T1055.011](https://attack.mitre.org/techniques/T1055/011/) | `detecting-process-injection-techniques` |
|
||||
| [T1055.012](https://attack.mitre.org/techniques/T1055/012/) | `conducting-malware-incident-response`, `detecting-fileless-malware-techniques`, `detecting-process-hollowing-technique`, `detecting-process-injection-techniques`, `detecting-t1055-process-injection-with-sysmon` +2 more |
|
||||
| [T1055.013](https://attack.mitre.org/techniques/T1055/013/) | `detecting-process-hollowing-technique`, `detecting-process-injection-techniques`, `detecting-t1055-process-injection-with-sysmon` |
|
||||
| [T1055.014](https://attack.mitre.org/techniques/T1055/014/) | `detecting-process-injection-techniques` |
|
||||
| [T1055.015](https://attack.mitre.org/techniques/T1055/015/) | `detecting-process-injection-techniques`, `detecting-t1055-process-injection-with-sysmon` |
|
||||
| [T1070](https://attack.mitre.org/techniques/T1070/) | `detecting-evasion-techniques-in-endpoint-logs`, `implementing-siem-use-cases-for-detection`, `implementing-velociraptor-for-ir-collection`, `performing-purple-team-atomic-testing` |
|
||||
| [T1070.001](https://attack.mitre.org/techniques/T1070/001/) | `detecting-evasion-techniques-in-endpoint-logs`, `implementing-mitre-attack-coverage-mapping`, `performing-purple-team-atomic-testing`, `performing-purple-team-exercise` |
|
||||
| [T1070.004](https://attack.mitre.org/techniques/T1070/004/) | `implementing-threat-modeling-with-mitre-attack`, `performing-purple-team-atomic-testing` |
|
||||
| [T1070.006](https://attack.mitre.org/techniques/T1070/006/) | `detecting-evasion-techniques-in-endpoint-logs`, `hunting-for-defense-evasion-via-timestomping` |
|
||||
| [T1112](https://attack.mitre.org/techniques/T1112/) | `detecting-fileless-malware-techniques`, `performing-purple-team-atomic-testing` |
|
||||
| [T1127](https://attack.mitre.org/techniques/T1127/) | `detecting-evasion-techniques-in-endpoint-logs`, `detecting-living-off-the-land-with-lolbas`, `hunting-for-lolbins-execution-in-endpoint-logs` |
|
||||
| [T1127.001](https://attack.mitre.org/techniques/T1127/001/) | `detecting-living-off-the-land-attacks`, `detecting-living-off-the-land-with-lolbas`, `hunting-for-lolbins-execution-in-endpoint-logs` |
|
||||
| [T1140](https://attack.mitre.org/techniques/T1140/) | `analyzing-powershell-script-block-logging`, `detecting-fileless-attacks-on-endpoints`, `detecting-living-off-the-land-with-lolbas`, `hunting-for-living-off-the-land-binaries`, `hunting-for-lolbins-execution-in-endpoint-logs` +1 more |
|
||||
| [T1202](https://attack.mitre.org/techniques/T1202/) | `hunting-for-living-off-the-land-binaries`, `hunting-for-lolbins-execution-in-endpoint-logs` |
|
||||
| [T1218](https://attack.mitre.org/techniques/T1218/) | `detecting-evasion-techniques-in-endpoint-logs`, `detecting-living-off-the-land-attacks`, `detecting-living-off-the-land-with-lolbas`, `hunting-advanced-persistent-threats`, `hunting-for-living-off-the-land-binaries` +3 more |
|
||||
| [T1218.001](https://attack.mitre.org/techniques/T1218/001/) | `hunting-for-living-off-the-land-binaries`, `hunting-for-lolbins-execution-in-endpoint-logs`, `performing-purple-team-atomic-testing` |
|
||||
| [T1218.002](https://attack.mitre.org/techniques/T1218/002/) | `hunting-for-living-off-the-land-binaries` |
|
||||
| [T1218.003](https://attack.mitre.org/techniques/T1218/003/) | `detecting-living-off-the-land-attacks`, `detecting-living-off-the-land-with-lolbas`, `hunting-for-living-off-the-land-binaries`, `hunting-for-lolbins-execution-in-endpoint-logs`, `performing-purple-team-atomic-testing` |
|
||||
| [T1218.004](https://attack.mitre.org/techniques/T1218/004/) | `detecting-living-off-the-land-attacks`, `hunting-for-lolbins-execution-in-endpoint-logs` |
|
||||
| [T1218.005](https://attack.mitre.org/techniques/T1218/005/) | `detecting-fileless-malware-techniques`, `detecting-living-off-the-land-attacks`, `detecting-living-off-the-land-with-lolbas`, `hunting-for-living-off-the-land-binaries`, `hunting-for-lolbins-execution-in-endpoint-logs` +1 more |
|
||||
| [T1218.007](https://attack.mitre.org/techniques/T1218/007/) | `hunting-for-living-off-the-land-binaries`, `hunting-for-lolbins-execution-in-endpoint-logs` |
|
||||
| [T1218.010](https://attack.mitre.org/techniques/T1218/010/) | `detecting-living-off-the-land-attacks`, `detecting-living-off-the-land-with-lolbas`, `hunting-for-living-off-the-land-binaries`, `hunting-for-lolbins-execution-in-endpoint-logs`, `performing-purple-team-atomic-testing` |
|
||||
| [T1218.011](https://attack.mitre.org/techniques/T1218/011/) | `detecting-living-off-the-land-attacks`, `detecting-living-off-the-land-with-lolbas`, `hunting-for-living-off-the-land-binaries`, `hunting-for-lolbins-execution-in-endpoint-logs`, `performing-dynamic-analysis-with-any-run` +1 more |
|
||||
| [T1218.013](https://attack.mitre.org/techniques/T1218/013/) | `detecting-living-off-the-land-attacks` |
|
||||
| [T1222.001](https://attack.mitre.org/techniques/T1222/001/) | `conducting-domain-persistence-with-dcsync` |
|
||||
| [T1497](https://attack.mitre.org/techniques/T1497/) | `analyzing-malware-sandbox-evasion-techniques` |
|
||||
| [T1497.001](https://attack.mitre.org/techniques/T1497/001/) | `analyzing-malware-sandbox-evasion-techniques` |
|
||||
| [T1497.002](https://attack.mitre.org/techniques/T1497/002/) | `analyzing-malware-sandbox-evasion-techniques` |
|
||||
| [T1497.003](https://attack.mitre.org/techniques/T1497/003/) | `analyzing-malware-sandbox-evasion-techniques` |
|
||||
| [T1550](https://attack.mitre.org/techniques/T1550/) | `performing-lateral-movement-detection` |
|
||||
| [T1550.001](https://attack.mitre.org/techniques/T1550/001/) | `detecting-azure-lateral-movement` |
|
||||
| [T1550.002](https://attack.mitre.org/techniques/T1550/002/) | `analyzing-windows-event-logs-in-splunk`, `building-attack-pattern-library-from-cti-reports`, `conducting-full-scope-red-team-engagement`, `detecting-lateral-movement-in-network`, `detecting-lateral-movement-with-splunk` +6 more |
|
||||
| [T1550.003](https://attack.mitre.org/techniques/T1550/003/) | `conducting-pass-the-ticket-attack`, `detecting-pass-the-hash-attacks`, `detecting-pass-the-ticket-attacks`, `exploiting-constrained-delegation-abuse` |
|
||||
| [T1550.004](https://attack.mitre.org/techniques/T1550/004/) | `performing-initial-access-with-evilginx3` |
|
||||
| [T1562](https://attack.mitre.org/techniques/T1562/) | `detecting-evasion-techniques-in-endpoint-logs`, `performing-purple-team-atomic-testing` |
|
||||
| [T1562.001](https://attack.mitre.org/techniques/T1562/001/) | `analyzing-powershell-script-block-logging`, `building-attack-pattern-library-from-cti-reports`, `detecting-evasion-techniques-in-endpoint-logs`, `detecting-fileless-attacks-on-endpoints`, `detecting-suspicious-powershell-execution` +1 more |
|
||||
| [T1610](https://attack.mitre.org/techniques/T1610/) | `detecting-container-escape-attempts`, `detecting-container-escape-with-falco-rules` |
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Credential Access
|
||||
|
||||
**27 techniques covered**
|
||||
|
||||
| Technique | Skills |
|
||||
|:----------|:-------|
|
||||
| [T1003](https://attack.mitre.org/techniques/T1003/) | `analyzing-powershell-script-block-logging`, `building-attack-pattern-library-from-cti-reports`, `building-detection-rules-with-sigma`, `detecting-container-escape-with-falco-rules`, `detecting-credential-dumping-techniques` +10 more |
|
||||
| [T1003.001](https://attack.mitre.org/techniques/T1003/001/) | `analyzing-campaign-attribution-evidence`, `analyzing-powershell-script-block-logging`, `analyzing-windows-event-logs-in-splunk`, `building-attack-pattern-library-from-cti-reports`, `building-detection-rule-with-splunk-spl` +13 more |
|
||||
| [T1003.002](https://attack.mitre.org/techniques/T1003/002/) | `detecting-credential-dumping-techniques`, `detecting-t1003-credential-dumping-with-edr`, `performing-purple-team-atomic-testing` |
|
||||
| [T1003.003](https://attack.mitre.org/techniques/T1003/003/) | `detecting-credential-dumping-techniques`, `detecting-t1003-credential-dumping-with-edr`, `performing-purple-team-atomic-testing` |
|
||||
| [T1003.004](https://attack.mitre.org/techniques/T1003/004/) | `detecting-t1003-credential-dumping-with-edr`, `performing-credential-access-with-lazagne`, `performing-purple-team-atomic-testing` |
|
||||
| [T1003.005](https://attack.mitre.org/techniques/T1003/005/) | `detecting-t1003-credential-dumping-with-edr`, `performing-purple-team-atomic-testing` |
|
||||
| [T1003.006](https://attack.mitre.org/techniques/T1003/006/) | `analyzing-windows-event-logs-in-splunk`, `conducting-domain-persistence-with-dcsync`, `conducting-full-scope-red-team-engagement`, `conducting-internal-network-penetration-test`, `detecting-dcsync-attack-in-active-directory` +8 more |
|
||||
| [T1110](https://attack.mitre.org/techniques/T1110/) | `analyzing-windows-event-logs-in-splunk`, `building-detection-rule-with-splunk-spl`, `conducting-internal-network-penetration-test`, `implementing-mitre-attack-coverage-mapping`, `implementing-siem-use-cases-for-detection` +3 more |
|
||||
| [T1110.001](https://attack.mitre.org/techniques/T1110/001/) | `analyzing-windows-event-logs-in-splunk`, `building-detection-rule-with-splunk-spl`, `implementing-siem-use-cases-for-detection`, `performing-false-positive-reduction-in-siem`, `performing-purple-team-atomic-testing` |
|
||||
| [T1110.002](https://attack.mitre.org/techniques/T1110/002/) | `exploiting-kerberoasting-with-impacket` |
|
||||
| [T1110.003](https://attack.mitre.org/techniques/T1110/003/) | `detecting-pass-the-ticket-attacks`, `implementing-siem-use-cases-for-detection`, `performing-purple-team-atomic-testing` |
|
||||
| [T1187](https://attack.mitre.org/techniques/T1187/) | `detecting-ntlm-relay-with-event-correlation` |
|
||||
| [T1528](https://attack.mitre.org/techniques/T1528/) | `detecting-azure-lateral-movement`, `detecting-azure-service-principal-abuse` |
|
||||
| [T1539](https://attack.mitre.org/techniques/T1539/) | `performing-credential-access-with-lazagne`, `performing-initial-access-with-evilginx3` |
|
||||
| [T1552](https://attack.mitre.org/techniques/T1552/) | `performing-cloud-incident-containment-procedures`, `performing-purple-team-atomic-testing` |
|
||||
| [T1552.001](https://attack.mitre.org/techniques/T1552/001/) | `performing-credential-access-with-lazagne`, `performing-purple-team-atomic-testing` |
|
||||
| [T1552.002](https://attack.mitre.org/techniques/T1552/002/) | `performing-credential-access-with-lazagne` |
|
||||
| [T1552.005](https://attack.mitre.org/techniques/T1552/005/) | `conducting-cloud-penetration-testing` |
|
||||
| [T1552.006](https://attack.mitre.org/techniques/T1552/006/) | `deploying-active-directory-honeytokens` |
|
||||
| [T1557](https://attack.mitre.org/techniques/T1557/) | `performing-initial-access-with-evilginx3` |
|
||||
| [T1557.001](https://attack.mitre.org/techniques/T1557/001/) | `conducting-internal-network-penetration-test`, `detecting-ntlm-relay-with-event-correlation`, `hunting-for-ntlm-relay-attacks` |
|
||||
| [T1558](https://attack.mitre.org/techniques/T1558/) | `analyzing-windows-event-logs-in-splunk`, `conducting-pass-the-ticket-attack`, `exploiting-kerberoasting-with-impacket`, `exploiting-nopac-cve-2021-42278-42287`, `performing-lateral-movement-detection` +1 more |
|
||||
| [T1558.001](https://attack.mitre.org/techniques/T1558/001/) | `analyzing-windows-event-logs-in-splunk`, `conducting-domain-persistence-with-dcsync`, `detecting-golden-ticket-attacks-in-kerberos-logs`, `detecting-golden-ticket-forgery`, `detecting-kerberoasting-attacks` +3 more |
|
||||
| [T1558.002](https://attack.mitre.org/techniques/T1558/002/) | `performing-active-directory-compromise-investigation` |
|
||||
| [T1558.003](https://attack.mitre.org/techniques/T1558/003/) | `analyzing-windows-event-logs-in-splunk`, `building-attack-pattern-library-from-cti-reports`, `conducting-full-scope-red-team-engagement`, `conducting-internal-network-penetration-test`, `deploying-active-directory-honeytokens` +12 more |
|
||||
| [T1558.004](https://attack.mitre.org/techniques/T1558/004/) | `detecting-kerberoasting-attacks` |
|
||||
| [T1649](https://attack.mitre.org/techniques/T1649/) | `exploiting-active-directory-certificate-services-esc1` |
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ Discovery
|
||||
|
||||
**20 techniques covered**
|
||||
|
||||
| Technique | Skills |
|
||||
|:----------|:-------|
|
||||
| [T1016](https://attack.mitre.org/techniques/T1016/) | `conducting-full-scope-red-team-engagement`, `conducting-internal-reconnaissance-with-bloodhound-ce`, `exploiting-active-directory-with-bloodhound`, `performing-purple-team-atomic-testing` |
|
||||
| [T1018](https://attack.mitre.org/techniques/T1018/) | `conducting-full-scope-red-team-engagement`, `conducting-internal-reconnaissance-with-bloodhound-ce`, `detecting-network-scanning-with-ids-signatures`, `exploiting-active-directory-with-bloodhound`, `performing-active-directory-bloodhound-analysis` |
|
||||
| [T1033](https://attack.mitre.org/techniques/T1033/) | `conducting-internal-reconnaissance-with-bloodhound-ce`, `detecting-privilege-escalation-attempts`, `exploiting-active-directory-with-bloodhound`, `performing-purple-team-atomic-testing` |
|
||||
| [T1040](https://attack.mitre.org/techniques/T1040/) | `implementing-continuous-security-validation-with-bas` |
|
||||
| [T1046](https://attack.mitre.org/techniques/T1046/) | `detecting-network-scanning-with-ids-signatures`, `detecting-privilege-escalation-attempts`, `performing-packet-injection-attack`, `triaging-security-incident` |
|
||||
| [T1049](https://attack.mitre.org/techniques/T1049/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1057](https://attack.mitre.org/techniques/T1057/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1069](https://attack.mitre.org/techniques/T1069/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1069.001](https://attack.mitre.org/techniques/T1069/001/) | `performing-active-directory-bloodhound-analysis`, `performing-purple-team-atomic-testing` |
|
||||
| [T1069.002](https://attack.mitre.org/techniques/T1069/002/) | `conducting-internal-reconnaissance-with-bloodhound-ce`, `exploiting-active-directory-with-bloodhound`, `performing-active-directory-bloodhound-analysis`, `performing-kerberoasting-attack`, `performing-purple-team-atomic-testing` |
|
||||
| [T1082](https://attack.mitre.org/techniques/T1082/) | `conducting-full-scope-red-team-engagement`, `performing-purple-team-atomic-testing` |
|
||||
| [T1083](https://attack.mitre.org/techniques/T1083/) | `implementing-canary-tokens-for-network-intrusion`, `performing-purple-team-atomic-testing` |
|
||||
| [T1087](https://attack.mitre.org/techniques/T1087/) | `conducting-full-scope-red-team-engagement`, `executing-red-team-engagement-planning`, `implementing-continuous-security-validation-with-bas`, `performing-purple-team-atomic-testing` |
|
||||
| [T1087.001](https://attack.mitre.org/techniques/T1087/001/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1087.002](https://attack.mitre.org/techniques/T1087/002/) | `conducting-internal-reconnaissance-with-bloodhound-ce`, `deploying-active-directory-honeytokens`, `exploiting-active-directory-certificate-services-esc1`, `exploiting-active-directory-with-bloodhound`, `exploiting-kerberoasting-with-impacket` +3 more |
|
||||
| [T1087.004](https://attack.mitre.org/techniques/T1087/004/) | `detecting-azure-service-principal-abuse`, `implementing-mitre-attack-coverage-mapping` |
|
||||
| [T1482](https://attack.mitre.org/techniques/T1482/) | `conducting-internal-reconnaissance-with-bloodhound-ce`, `exploiting-active-directory-with-bloodhound`, `performing-active-directory-bloodhound-analysis` |
|
||||
| [T1518](https://attack.mitre.org/techniques/T1518/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1518.001](https://attack.mitre.org/techniques/T1518/001/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1580](https://attack.mitre.org/techniques/T1580/) | `implementing-mitre-attack-coverage-mapping` |
|
||||
|
||||
---
|
||||
|
||||
## ↔️ Lateral Movement
|
||||
|
||||
**9 techniques covered**
|
||||
|
||||
| Technique | Skills |
|
||||
|:----------|:-------|
|
||||
| [T1021](https://attack.mitre.org/techniques/T1021/) | `detecting-lateral-movement-in-network`, `detecting-lateral-movement-with-splunk`, `detecting-service-account-abuse`, `executing-red-team-engagement-planning`, `exploiting-constrained-delegation-abuse` +10 more |
|
||||
| [T1021.001](https://attack.mitre.org/techniques/T1021/001/) | `analyzing-campaign-attribution-evidence`, `analyzing-windows-event-logs-in-splunk`, `building-attack-pattern-library-from-cti-reports`, `building-detection-rule-with-splunk-spl`, `building-threat-hunt-hypothesis-framework` +8 more |
|
||||
| [T1021.002](https://attack.mitre.org/techniques/T1021/002/) | `analyzing-windows-event-logs-in-splunk`, `building-attack-pattern-library-from-cti-reports`, `building-detection-rule-with-splunk-spl`, `conducting-full-scope-red-team-engagement`, `conducting-internal-network-penetration-test` +10 more |
|
||||
| [T1021.003](https://attack.mitre.org/techniques/T1021/003/) | `detecting-lateral-movement-with-splunk`, `hunting-for-dcom-lateral-movement`, `performing-lateral-movement-detection`, `performing-lateral-movement-with-wmiexec`, `performing-purple-team-atomic-testing` |
|
||||
| [T1021.004](https://attack.mitre.org/techniques/T1021/004/) | `detecting-lateral-movement-with-splunk`, `performing-purple-team-atomic-testing` |
|
||||
| [T1021.006](https://attack.mitre.org/techniques/T1021/006/) | `building-attack-pattern-library-from-cti-reports`, `detecting-lateral-movement-with-splunk`, `performing-lateral-movement-detection`, `performing-purple-team-atomic-testing` |
|
||||
| [T1210](https://attack.mitre.org/techniques/T1210/) | `exploiting-ms17-010-eternalblue-vulnerability`, `exploiting-zerologon-vulnerability-cve-2020-1472` |
|
||||
| [T1534](https://attack.mitre.org/techniques/T1534/) | `implementing-mitre-attack-coverage-mapping` |
|
||||
| [T1570](https://attack.mitre.org/techniques/T1570/) | `detecting-lateral-movement-in-network`, `detecting-lateral-movement-with-splunk`, `performing-lateral-movement-with-wmiexec`, `performing-purple-team-atomic-testing` |
|
||||
|
||||
---
|
||||
|
||||
## 📦 Collection
|
||||
|
||||
**13 techniques covered**
|
||||
|
||||
| Technique | Skills |
|
||||
|:----------|:-------|
|
||||
| [T1005](https://attack.mitre.org/techniques/T1005/) | `conducting-malware-incident-response`, `detecting-container-escape-with-falco-rules`, `performing-purple-team-atomic-testing` |
|
||||
| [T1039](https://attack.mitre.org/techniques/T1039/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1074](https://attack.mitre.org/techniques/T1074/) | `building-attack-pattern-library-from-cti-reports`, `executing-red-team-exercise`, `hunting-for-data-staging-before-exfiltration` |
|
||||
| [T1074.001](https://attack.mitre.org/techniques/T1074/001/) | `hunting-for-data-staging-before-exfiltration`, `performing-purple-team-atomic-testing` |
|
||||
| [T1074.002](https://attack.mitre.org/techniques/T1074/002/) | `hunting-for-data-staging-before-exfiltration` |
|
||||
| [T1113](https://attack.mitre.org/techniques/T1113/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1114.002](https://attack.mitre.org/techniques/T1114/002/) | `detecting-email-forwarding-rules-attack` |
|
||||
| [T1114.003](https://attack.mitre.org/techniques/T1114/003/) | `detecting-business-email-compromise`, `detecting-email-forwarding-rules-attack` |
|
||||
| [T1115](https://attack.mitre.org/techniques/T1115/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1213](https://attack.mitre.org/techniques/T1213/) | `conducting-full-scope-red-team-engagement` |
|
||||
| [T1530](https://attack.mitre.org/techniques/T1530/) | `detecting-insider-threat-behaviors`, `implementing-mitre-attack-coverage-mapping`, `performing-cloud-incident-containment-procedures` |
|
||||
| [T1560](https://attack.mitre.org/techniques/T1560/) | `conducting-full-scope-red-team-engagement`, `hunting-for-data-staging-before-exfiltration` |
|
||||
| [T1560.001](https://attack.mitre.org/techniques/T1560/001/) | `hunting-for-data-staging-before-exfiltration`, `performing-purple-team-atomic-testing` |
|
||||
|
||||
---
|
||||
|
||||
## 📡 Command and Control
|
||||
|
||||
**20 techniques covered**
|
||||
|
||||
| Technique | Skills |
|
||||
|:----------|:-------|
|
||||
| [T1071](https://attack.mitre.org/techniques/T1071/) | `analyzing-apt-group-with-mitre-navigator`, `analyzing-network-covert-channels-in-malware`, `analyzing-ransomware-network-indicators`, `analyzing-threat-actor-ttps-with-mitre-attack`, `hunting-advanced-persistent-threats` +6 more |
|
||||
| [T1071.001](https://attack.mitre.org/techniques/T1071/001/) | `analyzing-apt-group-with-mitre-navigator`, `analyzing-campaign-attribution-evidence`, `analyzing-powershell-empire-artifacts`, `analyzing-powershell-script-block-logging`, `building-attack-pattern-library-from-cti-reports` +13 more |
|
||||
| [T1071.004](https://attack.mitre.org/techniques/T1071/004/) | `building-attack-pattern-library-from-cti-reports`, `building-c2-infrastructure-with-sliver-framework`, `hunting-for-beaconing-with-frequency-analysis`, `hunting-for-command-and-control-beaconing`, `hunting-for-dns-tunneling-with-zeek` +3 more |
|
||||
| [T1090](https://attack.mitre.org/techniques/T1090/) | `implementing-mitre-attack-coverage-mapping`, `performing-purple-team-atomic-testing` |
|
||||
| [T1090.001](https://attack.mitre.org/techniques/T1090/001/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1090.002](https://attack.mitre.org/techniques/T1090/002/) | `building-c2-infrastructure-with-sliver-framework`, `building-red-team-c2-infrastructure-with-havoc` |
|
||||
| [T1090.004](https://attack.mitre.org/techniques/T1090/004/) | `hunting-for-domain-fronting-c2-traffic` |
|
||||
| [T1095](https://attack.mitre.org/techniques/T1095/) | `hunting-for-command-and-control-beaconing`, `hunting-for-unusual-network-connections` |
|
||||
| [T1102](https://attack.mitre.org/techniques/T1102/) | `hunting-for-living-off-the-cloud-techniques` |
|
||||
| [T1105](https://attack.mitre.org/techniques/T1105/) | `analyzing-powershell-script-block-logging`, `building-attack-pattern-library-from-cti-reports`, `building-c2-infrastructure-with-sliver-framework`, `building-red-team-c2-infrastructure-with-havoc`, `detecting-fileless-attacks-on-endpoints` +7 more |
|
||||
| [T1132](https://attack.mitre.org/techniques/T1132/) | `hunting-for-command-and-control-beaconing`, `performing-purple-team-atomic-testing` |
|
||||
| [T1132.001](https://attack.mitre.org/techniques/T1132/001/) | `building-c2-infrastructure-with-sliver-framework`, `performing-purple-team-atomic-testing` |
|
||||
| [T1219](https://attack.mitre.org/techniques/T1219/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1568](https://attack.mitre.org/techniques/T1568/) | `hunting-for-command-and-control-beaconing`, `implementing-mitre-attack-coverage-mapping` |
|
||||
| [T1568.002](https://attack.mitre.org/techniques/T1568/002/) | `hunting-for-beaconing-with-frequency-analysis` |
|
||||
| [T1571](https://attack.mitre.org/techniques/T1571/) | `hunting-for-unusual-network-connections`, `implementing-mitre-attack-coverage-mapping` |
|
||||
| [T1572](https://attack.mitre.org/techniques/T1572/) | `building-c2-infrastructure-with-sliver-framework`, `hunting-for-command-and-control-beaconing`, `hunting-for-dns-tunneling-with-zeek`, `implementing-mitre-attack-coverage-mapping` |
|
||||
| [T1573](https://attack.mitre.org/techniques/T1573/) | `analyzing-ransomware-network-indicators`, `hunting-for-beaconing-with-frequency-analysis`, `hunting-for-command-and-control-beaconing`, `implementing-mitre-attack-coverage-mapping`, `performing-purple-team-atomic-testing` |
|
||||
| [T1573.001](https://attack.mitre.org/techniques/T1573/001/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1573.002](https://attack.mitre.org/techniques/T1573/002/) | `building-c2-infrastructure-with-sliver-framework`, `building-red-team-c2-infrastructure-with-havoc` |
|
||||
|
||||
---
|
||||
|
||||
## 📤 Exfiltration
|
||||
|
||||
**12 techniques covered**
|
||||
|
||||
| Technique | Skills |
|
||||
|:----------|:-------|
|
||||
| [T1020](https://attack.mitre.org/techniques/T1020/) | `hunting-for-data-exfiltration-indicators` |
|
||||
| [T1029](https://attack.mitre.org/techniques/T1029/) | `hunting-for-data-exfiltration-indicators` |
|
||||
| [T1030](https://attack.mitre.org/techniques/T1030/) | `hunting-for-data-exfiltration-indicators` |
|
||||
| [T1041](https://attack.mitre.org/techniques/T1041/) | `analyzing-campaign-attribution-evidence`, `analyzing-ransomware-network-indicators`, `building-attack-pattern-library-from-cti-reports`, `conducting-full-scope-red-team-engagement`, `conducting-malware-incident-response` +6 more |
|
||||
| [T1048](https://attack.mitre.org/techniques/T1048/) | `building-attack-pattern-library-from-cti-reports`, `building-detection-rule-with-splunk-spl`, `conducting-full-scope-red-team-engagement`, `hunting-for-data-exfiltration-indicators`, `implementing-continuous-security-validation-with-bas` +2 more |
|
||||
| [T1048.001](https://attack.mitre.org/techniques/T1048/001/) | `hunting-for-data-exfiltration-indicators` |
|
||||
| [T1048.002](https://attack.mitre.org/techniques/T1048/002/) | `hunting-for-data-exfiltration-indicators` |
|
||||
| [T1048.003](https://attack.mitre.org/techniques/T1048/003/) | `conducting-full-scope-red-team-engagement`, `hunting-for-data-exfiltration-indicators`, `hunting-for-dns-tunneling-with-zeek`, `implementing-continuous-security-validation-with-bas`, `implementing-mitre-attack-coverage-mapping` +2 more |
|
||||
| [T1052](https://attack.mitre.org/techniques/T1052/) | `hunting-for-data-exfiltration-indicators` |
|
||||
| [T1537](https://attack.mitre.org/techniques/T1537/) | `hunting-for-data-exfiltration-indicators`, `hunting-for-living-off-the-cloud-techniques`, `implementing-mitre-attack-coverage-mapping`, `implementing-threat-modeling-with-mitre-attack`, `performing-cloud-incident-containment-procedures` |
|
||||
| [T1567](https://attack.mitre.org/techniques/T1567/) | `detecting-insider-threat-behaviors`, `hunting-for-data-exfiltration-indicators`, `hunting-for-living-off-the-cloud-techniques`, `implementing-continuous-security-validation-with-bas`, `performing-purple-team-atomic-testing` |
|
||||
| [T1567.002](https://attack.mitre.org/techniques/T1567/002/) | `hunting-for-data-exfiltration-indicators`, `performing-purple-team-atomic-testing` |
|
||||
|
||||
---
|
||||
|
||||
## 💥 Impact
|
||||
|
||||
**6 techniques covered**
|
||||
|
||||
| Technique | Skills |
|
||||
|:----------|:-------|
|
||||
| [T1485](https://attack.mitre.org/techniques/T1485/) | `hunting-for-shadow-copy-deletion`, `performing-purple-team-atomic-testing` |
|
||||
| [T1486](https://attack.mitre.org/techniques/T1486/) | `analyzing-ransomware-network-indicators`, `building-attack-pattern-library-from-cti-reports`, `building-threat-hunt-hypothesis-framework`, `conducting-full-scope-red-team-engagement`, `hunting-for-shadow-copy-deletion` +7 more |
|
||||
| [T1489](https://attack.mitre.org/techniques/T1489/) | `conducting-full-scope-red-team-engagement`, `performing-purple-team-atomic-testing` |
|
||||
| [T1490](https://attack.mitre.org/techniques/T1490/) | `building-soc-playbook-for-ransomware`, `hunting-for-shadow-copy-deletion`, `performing-purple-team-atomic-testing`, `performing-purple-team-exercise` |
|
||||
| [T1491](https://attack.mitre.org/techniques/T1491/) | `performing-purple-team-atomic-testing` |
|
||||
| [T1491.002](https://attack.mitre.org/techniques/T1491/002/) | `performing-purple-team-atomic-testing` |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Other / Cross-Tactic Techniques
|
||||
|
||||
| Technique | Skills |
|
||||
|:----------|:-------|
|
||||
| T0157 | `exploiting-kerberoasting-with-impacket` |
|
||||
| T0200 | `building-vulnerability-scanning-workflow`, `performing-authenticated-scan-with-openvas` |
|
||||
| T0802 | `detecting-attacks-on-historian-servers` |
|
||||
| T0809 | `detecting-attacks-on-historian-servers` |
|
||||
| T0814 | `detecting-modbus-command-injection-attacks` |
|
||||
| T0816 | `detecting-dnp3-protocol-anomalies` |
|
||||
| T0830 | `detecting-modbus-protocol-anomalies` |
|
||||
| T0831 | `detecting-modbus-protocol-anomalies` |
|
||||
| T0832 | `detecting-attacks-on-historian-servers` |
|
||||
| T0833 | `detecting-stuxnet-style-attacks` |
|
||||
| T0836 | `detecting-modbus-command-injection-attacks`, `detecting-modbus-protocol-anomalies`, `detecting-stuxnet-style-attacks` |
|
||||
| T0839 | `detecting-dnp3-protocol-anomalies`, `detecting-stuxnet-style-attacks` |
|
||||
| T0843 | `detecting-modbus-command-injection-attacks`, `performing-s7comm-protocol-security-analysis` |
|
||||
| T0847 | `detecting-stuxnet-style-attacks` |
|
||||
| T0855 | `detecting-dnp3-protocol-anomalies`, `detecting-modbus-command-injection-attacks`, `detecting-modbus-protocol-anomalies` |
|
||||
| T0856 | `detecting-stuxnet-style-attacks` |
|
||||
| T0862 | `detecting-stuxnet-style-attacks` |
|
||||
| T0866 | `detecting-stuxnet-style-attacks` |
|
||||
| T0869 | `detecting-dnp3-protocol-anomalies` |
|
||||
| T0881 | `performing-s7comm-protocol-security-analysis` |
|
||||
| T0886 | `detecting-modbus-protocol-anomalies` |
|
||||
| T1404 | `analyzing-android-malware-with-apktool` |
|
||||
| T1417 | `analyzing-android-malware-with-apktool` |
|
||||
| T1418 | `analyzing-android-malware-with-apktool` |
|
||||
| T1553.006 | `analyzing-uefi-bootkit-persistence` |
|
||||
| T1555 | `performing-credential-access-with-lazagne`, `performing-purple-team-atomic-testing` |
|
||||
| T1555.003 | `performing-credential-access-with-lazagne`, `performing-purple-team-atomic-testing` |
|
||||
| T1555.004 | `performing-credential-access-with-lazagne` |
|
||||
| T1578 | `performing-cloud-incident-containment-procedures` |
|
||||
| T1582 | `analyzing-android-malware-with-apktool` |
|
||||
| T1611 | `detecting-container-escape-attempts`, `detecting-container-escape-with-falco-rules` |
|
||||
| T1615 | `conducting-internal-reconnaissance-with-bloodhound-ce`, `exploiting-active-directory-with-bloodhound`, `performing-active-directory-bloodhound-analysis` |
|
||||
| T1620 | `detecting-fileless-attacks-on-endpoints` |
|
||||
| T5577 | `performing-physical-intrusion-assessment` |
|
||||
|
||||
---
|
||||
|
||||
## How This Was Generated
|
||||
|
||||
This coverage map was automatically generated by scanning all 753+ SKILL.md and agent.py files for MITRE ATT&CK technique IDs (pattern: `T####` and `T####.###`). Each technique was mapped to its parent tactic using the [MITRE ATT&CK Enterprise Matrix v16](https://attack.mitre.org/matrices/enterprise/).
|
||||
|
||||
To regenerate: `python3 extract_attack.py`
|
||||
|
||||
---
|
||||
|
||||
## MITRE ATLAS Coverage (v5.5.0)
|
||||
|
||||
81 skills mapped to ATLAS adversarial ML techniques.
|
||||
|
||||
Key techniques applied:
|
||||
- AML.T0051 — LLM Prompt Injection (Execution)
|
||||
- AML.T0054 — LLM Jailbreak (Privilege Escalation)
|
||||
- AML.T0088 — Generate Deepfakes (AI Attack Staging)
|
||||
- AML.T0010 — AI Supply Chain Compromise (Initial Access)
|
||||
- AML.T0020 — Poison Training Data (Resource Development)
|
||||
- AML.T0070 — RAG Poisoning (Persistence)
|
||||
- AML.T0080 — AI Agent Context Poisoning (Persistence)
|
||||
- AML.T0056 — Extract LLM System Prompt (Exfiltration)
|
||||
|
||||
## MITRE D3FEND Coverage (v1.3)
|
||||
|
||||
11 skills mapped to D3FEND defensive countermeasures.
|
||||
|
||||
Countermeasures applied span D3FEND tactical categories:
|
||||
Harden, Detect, Isolate, Deceive, Evict, Restore.
|
||||
Each skill's d3fend_techniques field lists the top 5 most relevant
|
||||
defensive countermeasures derived from the skill's ATT&CK technique tags.
|
||||
|
||||
## NIST AI RMF Coverage (AI 100-1)
|
||||
|
||||
85 skills mapped to NIST AI Risk Management Framework subcategories.
|
||||
|
||||
Core functions covered:
|
||||
- GOVERN: Organizational accountability for AI risk (GOVERN-1.1, GOVERN-6.1, GOVERN-6.2)
|
||||
- MAP: AI risk identification and context (MAP-5.1, MAP-5.2, MAP-1.6)
|
||||
- MEASURE: AI risk analysis and evaluation (MEASURE-2.5, MEASURE-2.7, MEASURE-2.8, MEASURE-2.11)
|
||||
- MANAGE: AI risk response and recovery (MANAGE-2.4, MANAGE-3.1)
|
||||
|
||||
GenAI-specific subcategories applied: GOVERN-6.1, GOVERN-6.2 (responsible deployment policies).
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<sub>Part of <a href="https://github.com/mukul975/Anthropic-Cybersecurity-Skills">Anthropic Cybersecurity Skills</a> — 753+ open-source cybersecurity skills for AI agents</sub>
|
||||
</p>
|
||||
32
personas/_shared/anthropic-cybersecurity-skills/CITATION.cff
Normal file
32
personas/_shared/anthropic-cybersecurity-skills/CITATION.cff
Normal file
@@ -0,0 +1,32 @@
|
||||
cff-version: 1.2.0
|
||||
message: "If you use this repository in your research, tools, or publications, please cite it as below."
|
||||
type: software
|
||||
title: "Anthropic-Cybersecurity-Skills"
|
||||
abstract: >
|
||||
A structured collection of 753 cybersecurity skills for AI agents, covering
|
||||
penetration testing, digital forensics, threat intelligence, incident response,
|
||||
cloud security, OT/SCADA security, AI security, and more. Each skill follows
|
||||
a standardized format with YAML frontmatter metadata, step-by-step procedures,
|
||||
tool commands, expected outputs, and MITRE ATT&CK mappings. Compatible with
|
||||
Claude Code, GitHub Copilot, Cursor, Windsurf, Gemini CLI, and 20+ AI agent
|
||||
platforms.
|
||||
authors:
|
||||
- name: "Mahipal"
|
||||
email: mukuljangra5@gmail.com
|
||||
alias: mukul975
|
||||
repository-code: "https://github.com/mukul975/Anthropic-Cybersecurity-Skills"
|
||||
url: "https://github.com/mukul975/Anthropic-Cybersecurity-Skills"
|
||||
license: Apache-2.0
|
||||
version: "1.1.0"
|
||||
date-released: "2026-03-21"
|
||||
keywords:
|
||||
- cybersecurity
|
||||
- AI agents
|
||||
- skills
|
||||
- penetration testing
|
||||
- digital forensics
|
||||
- threat intelligence
|
||||
- incident response
|
||||
- MITRE ATT&CK
|
||||
- Claude Code
|
||||
- open source
|
||||
@@ -0,0 +1,83 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at mukuljangra5@gmail.com. All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
@@ -0,0 +1,74 @@
|
||||
# Contributing to Anthropic-Cybersecurity-Skills
|
||||
|
||||
## How to add a new skill
|
||||
|
||||
1. Create a new directory: `skills/your-skill-name/`
|
||||
2. Add a `SKILL.md` file with required YAML frontmatter:
|
||||
```yaml
|
||||
---
|
||||
name: your-skill-name
|
||||
description: >-
|
||||
Clear description of what this skill does and when
|
||||
an AI agent should activate it. Include keywords.
|
||||
domain: cybersecurity
|
||||
subdomain: [category]
|
||||
tags: [tag1, tag2, tag3]
|
||||
version: "1.0"
|
||||
author: your-github-username
|
||||
license: Apache-2.0
|
||||
---
|
||||
```
|
||||
3. Write clear, step-by-step instructions in the Markdown body using these sections:
|
||||
- ## When to Use
|
||||
- ## Prerequisites
|
||||
- ## Workflow (numbered steps with real commands)
|
||||
- ## Key Concepts (table)
|
||||
- ## Tools & Systems
|
||||
- ## Common Scenarios
|
||||
- ## Output Format
|
||||
4. (Optional) Add supporting files:
|
||||
- `references/standards.md` — Real standard numbers, CVE refs, NIST/MITRE links
|
||||
- `references/workflows.md` — Deep technical procedure
|
||||
- `scripts/process.py` — Real working helper script
|
||||
- `assets/template.md` — Real filled-in checklist/template
|
||||
5. Submit a PR with title: `Add skill: your-skill-name`
|
||||
|
||||
## Skill quality checklist
|
||||
- [ ] Name is lowercase with hyphens (kebab-case), 1–64 characters
|
||||
- [ ] Description is clear and includes agent-discovery keywords
|
||||
- [ ] Instructions are actionable with real commands and tool names
|
||||
- [ ] Domain and subdomain are set correctly
|
||||
- [ ] Tags include relevant tools, frameworks, and techniques
|
||||
|
||||
## Subdomains
|
||||
Choose the most appropriate subdomain for your skill:
|
||||
- web-application-security
|
||||
- network-security
|
||||
- penetration-testing
|
||||
- red-teaming
|
||||
- digital-forensics
|
||||
- malware-analysis
|
||||
- threat-intelligence
|
||||
- cloud-security
|
||||
- container-security
|
||||
- identity-access-management
|
||||
- cryptography
|
||||
- vulnerability-management
|
||||
- compliance-governance
|
||||
- zero-trust-architecture
|
||||
- ot-ics-security
|
||||
- devsecops
|
||||
- soc-operations
|
||||
- incident-response
|
||||
- phishing-defense
|
||||
- ransomware-defense
|
||||
- api-security
|
||||
- mobile-security
|
||||
- endpoint-security
|
||||
- threat-hunting
|
||||
|
||||
## Code of Conduct
|
||||
This project follows the [Contributor Covenant](CODE_OF_CONDUCT.md). By participating, you agree to uphold this code.
|
||||
|
||||
## License
|
||||
By contributing, you agree that your contributions will be licensed under Apache-2.0.
|
||||
201
personas/_shared/anthropic-cybersecurity-skills/LICENSE
Normal file
201
personas/_shared/anthropic-cybersecurity-skills/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
358
personas/_shared/anthropic-cybersecurity-skills/README.md
Normal file
358
personas/_shared/anthropic-cybersecurity-skills/README.md
Normal file
@@ -0,0 +1,358 @@
|
||||
<p align="center">
|
||||
<img src="assets/banner.png" alt="Anthropic Cybersecurity Skills" width="100%">
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
|
||||
# Anthropic Cybersecurity Skills
|
||||
|
||||
### The largest open-source cybersecurity skills library for AI agents
|
||||
|
||||
[](LICENSE)
|
||||
[](#whats-inside--26-security-domains)
|
||||
[](#five-frameworks-one-skill-library)
|
||||
[](#whats-inside--26-security-domains)
|
||||
[](#compatible-platforms)
|
||||
[](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/stargazers)
|
||||
[](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/network/members)
|
||||
[](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/commits/main)
|
||||
[](https://agentskills.io)
|
||||
[](CONTRIBUTING.md)
|
||||
|
||||
**754 production-grade cybersecurity skills · 26 security domains · 5 framework mappings · 26+ AI platforms**
|
||||
|
||||
[Get Started](#quick-start) · [What's Inside](#whats-inside--26-security-domains) · [Frameworks](#five-frameworks-one-skill-library) · [Platforms](#compatible-platforms) · [Contributing](#contributing)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
> ⚠️ **Community Project** — This is an independent, community-created project. Not affiliated with Anthropic PBC.
|
||||
|
||||
## Give any AI agent the security skills of a senior analyst
|
||||
|
||||
A junior analyst knows which Volatility3 plugin to run on a suspicious memory dump, which Sigma rules catch Kerberoasting, and how to scope a cloud breach across three providers. **Your AI agent doesn't — unless you give it these skills.**
|
||||
|
||||
This repo contains **754 structured cybersecurity skills** spanning **26 security domains**, each following the [agentskills.io](https://agentskills.io) open standard. Every skill is mapped to **five industry frameworks** — MITRE ATT&CK, NIST CSF 2.0, MITRE ATLAS, MITRE D3FEND, and NIST AI RMF — making this the only open-source skills library with unified cross-framework coverage. Clone it, point your agent at it, and your next security investigation gets expert-level guidance in seconds.
|
||||
|
||||
## Five frameworks, one skill library
|
||||
|
||||
No other open-source skills library maps every skill to all five frameworks. One skill, five compliance checkboxes.
|
||||
|
||||
| Framework | Version | Scope in this repo | What it maps |
|
||||
|---|---|---|---|
|
||||
| [MITRE ATT&CK](https://attack.mitre.org) | v18 | 14 tactics · 200+ techniques | Adversary behaviors and TTPs |
|
||||
| [NIST CSF 2.0](https://www.nist.gov/cyberframework) | 2.0 | 6 functions · 22 categories | Organizational security posture |
|
||||
| [MITRE ATLAS](https://atlas.mitre.org) | v5.4 | 16 tactics · 84 techniques | AI/ML adversarial threats |
|
||||
| [MITRE D3FEND](https://d3fend.mitre.org) | v1.3 | 7 categories · 267 techniques | Defensive countermeasures |
|
||||
| [NIST AI RMF](https://airc.nist.gov/AI_RMF) | 1.0 | 4 functions · 72 subcategories | AI risk management |
|
||||
|
||||
**Example — a single skill maps across all five:**
|
||||
|
||||
| Skill | ATT&CK | NIST CSF | ATLAS | D3FEND | AI RMF |
|
||||
|---|---|---|---|---|---|
|
||||
| `analyzing-network-traffic-of-malware` | T1071 | DE.CM | AML.T0047 | D3-NTA | MEASURE-2.6 |
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
# Option 1: npx (recommended)
|
||||
npx skills add mukul975/Anthropic-Cybersecurity-Skills
|
||||
|
||||
# Option 2: Git clone
|
||||
git clone https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
|
||||
cd Anthropic-Cybersecurity-Skills
|
||||
```
|
||||
|
||||
Works immediately with Claude Code, GitHub Copilot, OpenAI Codex CLI, Cursor, Gemini CLI, and any [agentskills.io](https://agentskills.io)-compatible platform.
|
||||
|
||||
## Why this exists
|
||||
|
||||
The cybersecurity workforce gap hit **4.8 million unfilled roles** globally in 2024 (ISC2). AI agents can help close that gap — but only if they have structured domain knowledge to work from. Today's agents can write code and search the web, but they lack the practitioner playbooks that turn a generic LLM into a capable security analyst.
|
||||
|
||||
Existing security tool repos give you wordlists, payloads, or exploit code. None of them give an AI agent the structured decision-making workflow a senior analyst follows: when to use each technique, what prerequisites to check, how to execute step-by-step, and how to verify results. That is the gap this project fills.
|
||||
|
||||
**Anthropic Cybersecurity Skills** is not a collection of scripts or checklists. It is an **AI-native knowledge base** built from the ground up for the agentskills.io standard — YAML frontmatter for sub-second discovery, structured Markdown for step-by-step execution, and reference files for deep technical context. Every skill encodes real practitioner workflows, not generated summaries.
|
||||
|
||||
## What's inside — 26 security domains
|
||||
|
||||
| Domain | Skills | Key capabilities |
|
||||
|---|---|---|
|
||||
| Cloud Security | 60 | AWS, Azure, GCP hardening · CSPM · cloud forensics |
|
||||
| Threat Hunting | 55 | Hypothesis-driven hunts · LOTL detection · behavioral analytics |
|
||||
| Threat Intelligence | 50 | STIX/TAXII · MISP · feed integration · actor profiling |
|
||||
| Web Application Security | 42 | OWASP Top 10 · SQLi · XSS · SSRF · deserialization |
|
||||
| Network Security | 40 | IDS/IPS · firewall rules · VLAN segmentation · traffic analysis |
|
||||
| Malware Analysis | 39 | Static/dynamic analysis · reverse engineering · sandboxing |
|
||||
| Digital Forensics | 37 | Disk imaging · memory forensics · timeline reconstruction |
|
||||
| Security Operations | 36 | SIEM correlation · log analysis · alert triage |
|
||||
| Identity & Access Management | 35 | IAM policies · PAM · zero trust identity · Okta · SailPoint |
|
||||
| SOC Operations | 33 | Playbooks · escalation workflows · metrics · tabletop exercises |
|
||||
| Container Security | 30 | K8s RBAC · image scanning · Falco · container forensics |
|
||||
| OT/ICS Security | 28 | Modbus · DNP3 · IEC 62443 · historian defense · SCADA |
|
||||
| API Security | 28 | GraphQL · REST · OWASP API Top 10 · WAF bypass |
|
||||
| Vulnerability Management | 25 | Nessus · scanning workflows · patch prioritization · CVSS |
|
||||
| Incident Response | 25 | Breach containment · ransomware response · IR playbooks |
|
||||
| Red Teaming | 24 | Full-scope engagements · AD attacks · phishing simulation |
|
||||
| Penetration Testing | 23 | Network · web · cloud · mobile · wireless pentesting |
|
||||
| Endpoint Security | 17 | EDR · LOTL detection · fileless malware · persistence hunting |
|
||||
| DevSecOps | 17 | CI/CD security · code signing · Terraform auditing |
|
||||
| Phishing Defense | 16 | Email authentication · BEC detection · phishing IR |
|
||||
| Cryptography | 14 | TLS · Ed25519 · certificate transparency · key management |
|
||||
| Zero Trust Architecture | 13 | BeyondCorp · CISA maturity model · microsegmentation |
|
||||
| Mobile Security | 12 | Android/iOS analysis · mobile pentesting · MDM forensics |
|
||||
| Ransomware Defense | 7 | Precursor detection · response · recovery · encryption analysis |
|
||||
| Compliance & Governance | 5 | CIS benchmarks · SOC 2 · regulatory frameworks |
|
||||
| Deception Technology | 2 | Honeytokens · breach detection canaries |
|
||||
|
||||
## How AI agents use these skills
|
||||
|
||||
Each skill costs **~30 tokens to scan** (frontmatter only) and **500–2,000 tokens to fully load** (complete workflow). This progressive disclosure architecture lets agents search all 754 skills in a single pass without blowing context windows.
|
||||
|
||||
```
|
||||
User prompt: "Analyze this memory dump for signs of credential theft"
|
||||
|
||||
Agent's internal process:
|
||||
|
||||
1. Scans 754 skill frontmatters (~30 tokens each)
|
||||
→ identifies 12 relevant skills by matching tags, description, domain
|
||||
|
||||
2. Loads top 3 matches:
|
||||
• performing-memory-forensics-with-volatility3
|
||||
• hunting-for-credential-dumping-lsass
|
||||
• analyzing-windows-event-logs-for-credential-access
|
||||
|
||||
3. Executes the structured Workflow section step-by-step
|
||||
→ runs Volatility3 plugins, checks LSASS access patterns,
|
||||
correlates with event log evidence
|
||||
|
||||
4. Validates results using the Verification section
|
||||
→ confirms IOCs, maps findings to ATT&CK T1003 (Credential Dumping)
|
||||
```
|
||||
|
||||
**Without these skills**, the agent guesses at tool commands and misses critical steps. **With them**, it follows the same playbook a senior DFIR analyst would use.
|
||||
|
||||
## Skill anatomy
|
||||
|
||||
Every skill follows a consistent directory structure:
|
||||
|
||||
```
|
||||
skills/performing-memory-forensics-with-volatility3/
|
||||
├── SKILL.md ← Skill definition (YAML frontmatter + Markdown body)
|
||||
├── references/
|
||||
│ ├── standards.md ← MITRE ATT&CK, ATLAS, D3FEND, NIST mappings
|
||||
│ └── workflows.md ← Deep technical procedure reference
|
||||
├── scripts/
|
||||
│ └── process.py ← Working helper scripts
|
||||
└── assets/
|
||||
└── template.md ← Filled-in checklists and report templates
|
||||
```
|
||||
|
||||
|
||||
### YAML frontmatter (real example)
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: performing-memory-forensics-with-volatility3
|
||||
description: >-
|
||||
Analyze memory dumps to extract running processes, network connections,
|
||||
injected code, and malware artifacts using the Volatility3 framework.
|
||||
domain: cybersecurity
|
||||
subdomain: digital-forensics
|
||||
tags: [forensics, memory-analysis, volatility3, incident-response, dfir]
|
||||
atlas_techniques: [AML.T0047]
|
||||
d3fend_techniques: [D3-MA, D3-PSMD]
|
||||
nist_ai_rmf: [MEASURE-2.6]
|
||||
nist_csf: [DE.CM-01, RS.AN-03]
|
||||
version: "1.2"
|
||||
author: mukul975
|
||||
license: Apache-2.0
|
||||
---
|
||||
```
|
||||
|
||||
|
||||
### Markdown body sections
|
||||
|
||||
```markdown
|
||||
## When to Use
|
||||
Trigger conditions — when should an AI agent activate this skill?
|
||||
|
||||
## Prerequisites
|
||||
Required tools, access levels, and environment setup.
|
||||
|
||||
## Workflow
|
||||
Step-by-step execution guide with specific commands and decision points.
|
||||
|
||||
## Verification
|
||||
How to confirm the skill was executed successfully.
|
||||
```
|
||||
|
||||
Frontmatter fields: `name` (kebab-case, 1–64 chars), `description` (keyword-rich for agent discovery), `domain`, `subdomain`, `tags`, `atlas_techniques` (MITRE ATLAS IDs), `d3fend_techniques` (MITRE D3FEND IDs), `nist_ai_rmf` (NIST AI RMF references), `nist_csf` (NIST CSF 2.0 categories). MITRE ATT&CK technique mappings are documented in each skill's `references/standards.md` file and in the ATT&CK Navigator layer included with releases.
|
||||
|
||||
<details>
|
||||
<summary><strong>📊 MITRE ATT&CK Enterprise coverage — all 14 tactics</strong></summary>
|
||||
|
||||
|
||||
|
||||
| Tactic | ID | Coverage | Key skills |
|
||||
|---|---|---|---|
|
||||
| Reconnaissance | TA0043 | Strong | OSINT, subdomain enumeration, DNS recon |
|
||||
| Resource Development | TA0042 | Moderate | Phishing infrastructure, C2 setup detection |
|
||||
| Initial Access | TA0001 | Strong | Phishing simulation, exploit detection, forced browsing |
|
||||
| Execution | TA0002 | Strong | PowerShell analysis, fileless malware, script block logging |
|
||||
| Persistence | TA0003 | Strong | Scheduled tasks, registry, service accounts, LOTL |
|
||||
| Privilege Escalation | TA0004 | Strong | Kerberoasting, AD attacks, cloud privilege escalation |
|
||||
| Defense Evasion | TA0005 | Strong | Obfuscation, rootkit analysis, evasion detection |
|
||||
| Credential Access | TA0006 | Strong | Mimikatz detection, pass-the-hash, credential dumping |
|
||||
| Discovery | TA0007 | Moderate | BloodHound, AD enumeration, network scanning |
|
||||
| Lateral Movement | TA0008 | Strong | SMB exploits, lateral movement detection with Splunk |
|
||||
| Collection | TA0009 | Moderate | Email forensics, data staging detection |
|
||||
| Command and Control | TA0011 | Strong | C2 beaconing, DNS tunneling, Cobalt Strike analysis |
|
||||
| Exfiltration | TA0010 | Strong | DNS exfiltration, DLP controls, data loss detection |
|
||||
| Impact | TA0040 | Strong | Ransomware defense, encryption analysis, recovery |
|
||||
|
||||
An **ATT&CK Navigator layer file** is included in the [v1.0.0 release assets](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/releases/tag/v1.0.0) for visual coverage mapping.
|
||||
|
||||
> **Note:** ATT&CK v19 lands April 28, 2026 — splitting Defense Evasion (TA0005) into two new tactics: *Stealth* and *Impair Defenses*. Skill mappings will be updated in a forthcoming release.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>📊 NIST CSF 2.0 alignment — all 6 functions</strong></summary>
|
||||
|
||||
|
||||
|
||||
| Function | Skills | Examples |
|
||||
|---|---|---|
|
||||
| **Govern (GV)** | 30+ | Risk strategy, policy frameworks, roles & responsibilities |
|
||||
| **Identify (ID)** | 120+ | Asset discovery, threat landscape assessment, risk analysis |
|
||||
| **Protect (PR)** | 150+ | IAM hardening, WAF rules, zero trust, encryption |
|
||||
| **Detect (DE)** | 200+ | Threat hunting, SIEM correlation, anomaly detection |
|
||||
| **Respond (RS)** | 160+ | Incident response, forensics, breach containment |
|
||||
| **Recover (RC)** | 40+ | Ransomware recovery, BCP, disaster recovery |
|
||||
|
||||
NIST CSF 2.0 (February 2024) added the **Govern** function and expanded scope from critical infrastructure to all organizations. Skill mappings align to all 22 categories and reference 106 subcategories.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>📊 Framework deep dive — ATLAS, D3FEND, AI RMF</strong></summary>
|
||||
|
||||
|
||||
|
||||
### MITRE ATLAS v5.4 — AI/ML adversarial threats
|
||||
ATLAS maps adversarial tactics, techniques, and case studies specific to AI and machine learning systems. Version 5.4 covers **16 tactics and 84 techniques** including agentic AI attack vectors added in late 2025: AI agent context poisoning, tool invocation abuse, MCP server compromises, and malicious agent deployment. Skills mapped to ATLAS help agents identify and defend against threats to ML pipelines, model weights, inference APIs, and autonomous workflows.
|
||||
|
||||
### MITRE D3FEND v1.3 — Defensive countermeasures
|
||||
D3FEND is an NSA-funded knowledge graph of **267 defensive techniques** organized across 7 tactical categories: Model, Harden, Detect, Isolate, Deceive, Evict, and Restore. Built on OWL 2 ontology, it uses a shared Digital Artifact layer to bidirectionally map defensive countermeasures to ATT&CK offensive techniques. Skills tagged with D3FEND identifiers let agents recommend specific countermeasures for detected threats.
|
||||
|
||||
### NIST AI RMF 1.0 + GenAI Profile (AI 600-1)
|
||||
The AI Risk Management Framework defines 4 core functions — Govern, Map, Measure, Manage — with **72 subcategories** for trustworthy AI development. The GenAI Profile (AI 600-1, July 2024) adds **12 risk categories** specific to generative AI, from confabulation and data privacy to prompt injection and supply chain risks. Colorado's AI Act (effective February 2026) provides a **legal safe harbor** for organizations complying with NIST AI RMF, making these mappings directly relevant to regulatory compliance.
|
||||
|
||||
</details>
|
||||
|
||||
## Compatible platforms
|
||||
|
||||
**AI code assistants**
|
||||
Claude Code (Anthropic) · GitHub Copilot (Microsoft) · Cursor · Windsurf · Cline · Aider · Continue · Roo Code · Amazon Q Developer · Tabnine · Sourcegraph Cody · JetBrains AI
|
||||
|
||||
**CLI agents**
|
||||
OpenAI Codex CLI · Gemini CLI (Google)
|
||||
|
||||
**Autonomous agents**
|
||||
Devin · Replit Agent · SWE-agent · OpenHands
|
||||
|
||||
**Agent frameworks & SDKs**
|
||||
LangChain · CrewAI · AutoGen · Semantic Kernel · Haystack · Vercel AI SDK · Any MCP-compatible agent
|
||||
|
||||
All platforms that support the [agentskills.io](https://agentskills.io) standard can load these skills with zero configuration.
|
||||
|
||||
## What people are saying
|
||||
|
||||
> *"A database of real, organized security skills that any AI agent can plug into and use. Not tutorials. Not blog posts."*
|
||||
> — **[Hasan Toor (@hasantoxr)](https://x.com/hasantoxr/status/2033193922349179249)**, AI/tech creator
|
||||
|
||||
> *"This is not a random collection of security scripts. It's a structured operational knowledge base designed for AI-driven security workflows."*
|
||||
> — **[fazal-sec](https://fazal-sec.medium.com/claude-skills-ai-powered-cybersecurity-the-complete-guide-to-building-intelligent-security-7bb7e9d14c8e)**, Medium
|
||||
|
||||
## Featured in
|
||||
|
||||
| Where | Type | Link |
|
||||
|---|---|---|
|
||||
| **awesome-agent-skills** | Awesome List (1,000+ skills index) | [VoltAgent/awesome-agent-skills](https://github.com/VoltAgent/awesome-agent-skills) |
|
||||
| **awesome-ai-security** | Awesome List (AI security tools) | [ottosulin/awesome-ai-security](https://github.com/ottosulin/awesome-ai-security) |
|
||||
| **awesome-codex-cli** | Awesome List (Codex CLI resources) | [RoggeOhta/awesome-codex-cli](https://github.com/RoggeOhta/awesome-codex-cli) |
|
||||
| **SkillsLLM** | Skills directory & marketplace | [skillsllm.com/skill/anthropic-cybersecurity-skills](https://skillsllm.com/skill/anthropic-cybersecurity-skills) |
|
||||
| **Openflows** | Signal analysis & tracking | [openflows.org](https://openflows.org/currency/currents/anthropic-cybersecurity-skills/) |
|
||||
| **NeverSight skills_feed** | Automated skills index | [NeverSight/skills_feed](https://github.com/NeverSight/skills_feed) |
|
||||
|
||||
## Star history
|
||||
|
||||
<a href="https://star-history.com/#mukul975/Anthropic-Cybersecurity-Skills&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=mukul975/Anthropic-Cybersecurity-Skills&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=mukul975/Anthropic-Cybersecurity-Skills&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=mukul975/Anthropic-Cybersecurity-Skills&type=Date" width="100%" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
## Releases
|
||||
|
||||
| Version | Date | Highlights |
|
||||
|---|---|---|
|
||||
| [v1.0.0](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/releases/tag/v1.0.0) | March 11, 2026 | 734 skills · 26 domains · MITRE ATT&CK + NIST CSF 2.0 mapping · ATT&CK Navigator layer |
|
||||
|
||||
Skills have continued to grow on `main` since v1.0.0 — the library now contains **754 skills** with **5-framework mapping** (MITRE ATLAS, D3FEND, and NIST AI RMF added post-release). Check [Releases](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/releases) for the latest tagged version.
|
||||
|
||||
## Contributing
|
||||
|
||||
This project grows through community contributions. Here is how to get involved:
|
||||
|
||||
**Add a new skill** — Domains like Deception Technology (2 skills) and Compliance & Governance (5 skills) need the most help. Follow the template in [CONTRIBUTING.md](CONTRIBUTING.md) and submit a PR with the title `Add skill: your-skill-name`.
|
||||
|
||||
**Improve existing skills** — Add framework mappings, fix workflows, update tool references, or contribute scripts and templates.
|
||||
|
||||
**Report issues** — Found an inaccurate procedure or broken script? [Open an issue](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/issues).
|
||||
|
||||
Every PR is reviewed for technical accuracy and agentskills.io standard compliance within 48 hours. Check [good first issues](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) for a starting point.
|
||||
|
||||
This project follows the [Contributor Covenant](https://www.contributor-covenant.org/). By participating, you agree to uphold this code.
|
||||
|
||||
## Community
|
||||
|
||||
💬 [Discussions](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/discussions) — Questions, ideas, and roadmap conversations
|
||||
🐛 [Issues](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/issues) — Bug reports and feature requests
|
||||
🔒 [Security Policy](SECURITY.md) — Responsible disclosure process (48-hour acknowledgment)
|
||||
|
||||
## Citation
|
||||
|
||||
If you use this project in research or publications:
|
||||
|
||||
```bibtex
|
||||
@software{anthropic_cybersecurity_skills,
|
||||
author = {Jangra, Mahipal},
|
||||
title = {Anthropic Cybersecurity Skills},
|
||||
year = {2026},
|
||||
url = {https://github.com/mukul975/Anthropic-Cybersecurity-Skills},
|
||||
license = {Apache-2.0},
|
||||
note = {754 structured cybersecurity skills for AI agents,
|
||||
mapped to MITRE ATT\&CK, NIST CSF 2.0, MITRE ATLAS,
|
||||
MITRE D3FEND, and NIST AI RMF}
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [Apache License 2.0](LICENSE). You are free to use, modify, and distribute these skills in both personal and commercial projects.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
**If this project helps your security work, consider giving it a ⭐**
|
||||
|
||||
[⭐ Star](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/stargazers) · [🍴 Fork](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/fork) · [💬 Discuss](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/discussions) · [📝 Contribute](CONTRIBUTING.md)
|
||||
|
||||
Community project by [@mukul975](https://github.com/mukul975). Not affiliated with Anthropic PBC.
|
||||
|
||||
</div>
|
||||
47
personas/_shared/anthropic-cybersecurity-skills/SECURITY.md
Normal file
47
personas/_shared/anthropic-cybersecurity-skills/SECURITY.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
All skill content in this repository is covered by this security policy.
|
||||
|
||||
| Component | Supported |
|
||||
|-----------|-----------|
|
||||
| Skill definitions (SKILL.md files) | Yes |
|
||||
| Scripts and automation | Yes |
|
||||
| Documentation | Yes |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you discover a security issue with any skill's scripts, instructions, or content, please report it responsibly:
|
||||
|
||||
1. **Do not** open a public issue
|
||||
2. Use GitHub's private security advisory: [Report a vulnerability](https://github.com/mukul975/Anthropic-Cybersecurity-Skills/security/advisories/new)
|
||||
3. Include in your report:
|
||||
- Affected skill name and file path
|
||||
- Nature of the vulnerability
|
||||
- Potential impact
|
||||
- Steps to reproduce (if applicable)
|
||||
- Suggested fix (if you have one)
|
||||
|
||||
## Response Timeline
|
||||
|
||||
- **Initial acknowledgment:** Within 48 hours
|
||||
- **Assessment and triage:** Within 1 week
|
||||
- **Fix or mitigation:** Based on severity, typically within 2 weeks
|
||||
|
||||
## Scope
|
||||
|
||||
The following are in scope for security reports:
|
||||
|
||||
- Skills that contain commands or scripts that could cause unintended harm
|
||||
- Instructions that could lead to unauthorized access if followed incorrectly
|
||||
- Sensitive data accidentally included in skill content
|
||||
- Dependencies or external references that have become compromised
|
||||
|
||||
## Recognition
|
||||
|
||||
We credit responsible disclosures in our changelog. If you report a valid security issue, we will acknowledge your contribution unless you prefer to remain anonymous.
|
||||
|
||||
## Contact
|
||||
|
||||
For security matters that cannot be reported through GitHub's advisory system, reach out via the repository's discussion forum.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 662 KiB |
File diff suppressed because one or more lines are too long
@@ -0,0 +1,88 @@
|
||||
# MITRE ATT&CK Navigator Layer - Anthropic Cybersecurity Skills
|
||||
|
||||
This directory contains a MITRE ATT&CK Navigator layer file that maps the coverage of the Anthropic Cybersecurity Skills repository against the ATT&CK Enterprise matrix.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `attack-navigator-layer.json` | ATT&CK Navigator layer (v4.5 format, Enterprise ATT&CK v14) |
|
||||
|
||||
## How to View
|
||||
|
||||
1. Open the [MITRE ATT&CK Navigator](https://mitre-attack.github.io/attack-navigator/)
|
||||
2. Click **Open Existing Layer**
|
||||
3. Select **Upload from local** and choose `attack-navigator-layer.json`
|
||||
4. The matrix will display with blue-shaded techniques indicating coverage
|
||||
|
||||
Alternatively, paste the raw JSON URL into the Navigator's "Load from URL" option if this file is hosted publicly.
|
||||
|
||||
## Coverage Statistics
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Total skills scanned | 742 |
|
||||
| Unique ATT&CK techniques referenced | 218 |
|
||||
| Parent techniques | 94 |
|
||||
| Sub-techniques | 124 |
|
||||
| Tactics with coverage | 14/14 |
|
||||
|
||||
## Coverage by Tactic
|
||||
|
||||
| Tactic | Techniques Covered |
|
||||
|--------|-------------------|
|
||||
| Defense Evasion | 36 |
|
||||
| Credential Access | 33 |
|
||||
| Persistence | 29 |
|
||||
| Initial Access | 17 |
|
||||
| Command and Control | 17 |
|
||||
| Privilege Escalation | 13 |
|
||||
| Discovery | 12 |
|
||||
| Exfiltration | 12 |
|
||||
| Reconnaissance | 11 |
|
||||
| Collection | 10 |
|
||||
| Lateral Movement | 9 |
|
||||
| Execution | 8 |
|
||||
| Resource Development | 6 |
|
||||
| Impact | 5 |
|
||||
|
||||
## Color Scale
|
||||
|
||||
The layer uses a blue gradient to indicate coverage depth:
|
||||
|
||||
- **Light blue** (`#cfe2f3`): 1-2 skills reference this technique
|
||||
- **Medium blue** (`#6fa8dc`): 3-5 skills reference this technique
|
||||
- **Dark blue** (`#3d85c6`): 6-10 skills reference this technique
|
||||
- **Deep blue** (`#1155cc`): 11+ skills reference this technique
|
||||
|
||||
## Top 10 Most Covered Techniques
|
||||
|
||||
| Technique | Name | Skills |
|
||||
|-----------|------|--------|
|
||||
| T1059.001 | PowerShell | 26 |
|
||||
| T1055 | Process Injection | 17 |
|
||||
| T1053.005 | Scheduled Task | 16 |
|
||||
| T1566.001 | Spearphishing Attachment | 15 |
|
||||
| T1558.003 | Kerberoasting | 14 |
|
||||
| T1547.001 | Registry Run Keys / Startup Folder | 13 |
|
||||
| T1078 | Valid Accounts | 13 |
|
||||
| T1003.006 | DCSync | 13 |
|
||||
| T1071.001 | Web Protocols | 12 |
|
||||
| T1021.002 | SMB/Windows Admin Shares | 12 |
|
||||
|
||||
## Methodology
|
||||
|
||||
Techniques were extracted by scanning all `SKILL.md` files in the repository for ATT&CK technique ID patterns (`T1XXX` and `T1XXX.XXX`). Each technique's score is proportional to the number of distinct skills that reference it, normalized to a 1-100 scale.
|
||||
|
||||
## Layer Format
|
||||
|
||||
- **Format version**: 4.5
|
||||
- **ATT&CK version**: 14 (Enterprise)
|
||||
- **Navigator version**: 4.9.1
|
||||
- **Domain**: enterprise-attack
|
||||
|
||||
## Related Links
|
||||
|
||||
- [MITRE ATT&CK Framework](https://attack.mitre.org/)
|
||||
- [ATT&CK Navigator](https://mitre-attack.github.io/attack-navigator/)
|
||||
- [ATT&CK Navigator GitHub](https://github.com/mitre-attack/attack-navigator)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,102 @@
|
||||
# MITRE ATT&CK Mapping
|
||||
|
||||
This directory maps the cybersecurity skills in this repository to the [MITRE ATT&CK](https://attack.mitre.org/) framework (Enterprise v15).
|
||||
|
||||
## Overview
|
||||
|
||||
MITRE ATT&CK is a curated knowledge base and model for cyber adversary behavior, reflecting the various phases of an adversary's lifecycle and the platforms they target. This mapping connects our hands-on skills to ATT&CK tactics and techniques, enabling:
|
||||
|
||||
- **Threat-informed defense** -- prioritize skill development based on real adversary behavior
|
||||
- **Gap analysis** -- identify ATT&CK techniques not yet covered by available skills
|
||||
- **Purple team exercises** -- pair offensive (red team) and defensive (blue team) skills for each technique
|
||||
- **Agent-driven discovery** -- AI agents can query skills by ATT&CK ID for automated security workflows
|
||||
|
||||
## Mapping Methodology
|
||||
|
||||
### Tactic Mapping (TA00xx)
|
||||
|
||||
Each of the 14 ATT&CK Enterprise tactics represents a distinct adversary objective. Skills are mapped to tactics based on which adversary goal they help achieve (offensive) or defend against (defensive):
|
||||
|
||||
| Tactic | ID | Offensive Skills | Defensive Skills |
|
||||
|--------|-----|-----------------|------------------|
|
||||
| Reconnaissance | TA0043 | penetration-testing, red-teaming | threat-intelligence, phishing-defense |
|
||||
| Resource Development | TA0042 | red-teaming | threat-intelligence |
|
||||
| Initial Access | TA0001 | web-application-security, penetration-testing | phishing-defense, endpoint-security |
|
||||
| Execution | TA0002 | penetration-testing, red-teaming | malware-analysis, endpoint-security, soc-operations |
|
||||
| Persistence | TA0003 | red-teaming, penetration-testing | threat-hunting, digital-forensics, endpoint-security |
|
||||
| Privilege Escalation | TA0004 | penetration-testing, red-teaming | endpoint-security, identity-access-management |
|
||||
| Defense Evasion | TA0005 | red-teaming | malware-analysis, endpoint-security, threat-hunting |
|
||||
| Credential Access | TA0006 | penetration-testing, red-teaming | identity-access-management, soc-operations |
|
||||
| Discovery | TA0007 | penetration-testing, red-teaming | threat-hunting, network-security |
|
||||
| Lateral Movement | TA0008 | red-teaming, penetration-testing | network-security, threat-hunting, soc-operations |
|
||||
| Collection | TA0009 | red-teaming | digital-forensics, threat-hunting |
|
||||
| Command and Control | TA0011 | red-teaming | threat-intelligence, network-security, soc-operations |
|
||||
| Exfiltration | TA0010 | red-teaming | threat-hunting, digital-forensics, network-security |
|
||||
| Impact | TA0040 | red-teaming | ransomware-defense, incident-response |
|
||||
|
||||
### Technique Mapping (T1xxx)
|
||||
|
||||
Skills are mapped to specific techniques based on their content. Examples:
|
||||
|
||||
| Technique | ID | Example Skills |
|
||||
|-----------|-----|---------------|
|
||||
| Phishing | T1566 | analyzing-phishing-email-headers, analyzing-certificate-transparency-for-phishing |
|
||||
| Exploit Public-Facing Application | T1190 | web-application-security skills (SQL injection, XSS, SSRF) |
|
||||
| OS Credential Dumping | T1003 | penetration-testing credential harvesting skills |
|
||||
| PowerShell | T1059.001 | analyzing-windows-event-logs-in-splunk, malware-analysis skills |
|
||||
| Remote Services | T1021 | network-security lateral movement skills |
|
||||
| Data Encrypted for Impact | T1486 | analyzing-ransomware-encryption-mechanisms |
|
||||
| Command and Scripting Interpreter | T1059 | malware-analysis script deobfuscation skills |
|
||||
| Scheduled Task/Job | T1053 | analyzing-malware-persistence-with-autoruns |
|
||||
| Registry Run Keys | T1547.001 | analyzing-windows-registry-for-artifacts |
|
||||
| DLL Side-Loading | T1574.002 | analyzing-bootkit-and-rootkit-samples |
|
||||
|
||||
### Sub-technique Mapping (T1xxx.xxx)
|
||||
|
||||
Where applicable, skills are mapped to sub-techniques for precision. For example:
|
||||
|
||||
- `T1566.001` (Spearphishing Attachment) -- analyzing-email-headers-for-phishing-investigation
|
||||
- `T1566.002` (Spearphishing Link) -- analyzing-certificate-transparency-for-phishing
|
||||
- `T1003.001` (LSASS Memory) -- analyzing-memory-dumps-with-volatility
|
||||
|
||||
## ATT&CK Navigator Integration
|
||||
|
||||
You can visualize our skill coverage using the [ATT&CK Navigator](https://mitre-attack.github.io/attack-navigator/). To generate a Navigator layer:
|
||||
|
||||
1. Use the coverage summary in [`coverage-summary.md`](coverage-summary.md) to identify covered tactics
|
||||
2. Import the tactic/technique IDs into a Navigator layer JSON
|
||||
3. Color-code by coverage depth (number of skills per technique)
|
||||
|
||||
### Suggested Color Scale
|
||||
|
||||
| Coverage | Color | Meaning |
|
||||
|----------|-------|---------|
|
||||
| 0 skills | White | No coverage -- gap |
|
||||
| 1-2 skills | Light blue | Basic coverage |
|
||||
| 3-5 skills | Medium blue | Moderate coverage |
|
||||
| 6+ skills | Dark blue | Strong coverage |
|
||||
|
||||
## Skill Tag Convention
|
||||
|
||||
Skills relevant to ATT&CK carry these tags in their YAML frontmatter:
|
||||
|
||||
- `mitre-attack` -- general ATT&CK relevance (56 skills currently tagged)
|
||||
- Technique-specific tags like `privilege-escalation`, `lateral-movement`, `persistence`
|
||||
- Tool-specific tags that map to ATT&CK software entries (e.g., `cobalt-strike`, `mimikatz`)
|
||||
|
||||
## How to Contribute Mappings
|
||||
|
||||
1. **Identify the skill** -- Read the skill's SKILL.md to understand what it teaches
|
||||
2. **Find the ATT&CK technique** -- Search [attack.mitre.org](https://attack.mitre.org/) for the matching technique
|
||||
3. **Determine offensive vs. defensive** -- Is the skill about performing or detecting/preventing the technique?
|
||||
4. **Update the mapping** -- Add the technique ID to the appropriate table in this directory
|
||||
5. **Update skill tags** -- Add `mitre-attack` and technique-specific tags to the skill's frontmatter
|
||||
6. **Submit a PR** -- Include the ATT&CK technique URL as justification
|
||||
|
||||
## References
|
||||
|
||||
- [MITRE ATT&CK Enterprise Matrix](https://attack.mitre.org/matrices/enterprise/)
|
||||
- [MITRE ATT&CK Navigator](https://mitre-attack.github.io/attack-navigator/)
|
||||
- [ATT&CK v15 Release Notes](https://attack.mitre.org/resources/updates/)
|
||||
- [MITRE ATT&CK for ICS](https://attack.mitre.org/matrices/ics/) -- relevant for ot-ics-security skills
|
||||
- [MITRE ATT&CK for Mobile](https://attack.mitre.org/matrices/mobile/) -- relevant for mobile-security skills
|
||||
@@ -0,0 +1,177 @@
|
||||
# ATT&CK Coverage Summary
|
||||
|
||||
Coverage analysis of the 753 cybersecurity skills mapped to MITRE ATT&CK Enterprise v15 tactics.
|
||||
|
||||
## Tactic Coverage Matrix
|
||||
|
||||
| ATT&CK Tactic | ID | Relevant Subdomains | Skills Count |
|
||||
|---------------|-----|---------------------|--------------|
|
||||
| Reconnaissance | TA0043 | threat-intelligence, penetration-testing, red-teaming | ~48 |
|
||||
| Resource Development | TA0042 | threat-intelligence, red-teaming | ~30 |
|
||||
| Initial Access | TA0001 | web-application-security, phishing-defense, api-security | ~45 |
|
||||
| Execution | TA0002 | malware-analysis, endpoint-security, soc-operations | ~32 |
|
||||
| Persistence | TA0003 | threat-hunting, digital-forensics, endpoint-security | ~28 |
|
||||
| Privilege Escalation | TA0004 | penetration-testing, red-teaming, identity-access-management | ~40 |
|
||||
| Defense Evasion | TA0005 | malware-analysis, endpoint-security, threat-hunting | ~25 |
|
||||
| Credential Access | TA0006 | identity-access-management, penetration-testing | ~30 |
|
||||
| Discovery | TA0007 | penetration-testing, threat-hunting, network-security | ~35 |
|
||||
| Lateral Movement | TA0008 | red-teaming, network-security, soc-operations | ~28 |
|
||||
| Collection | TA0009 | digital-forensics, threat-hunting | ~22 |
|
||||
| Command and Control | TA0011 | threat-intelligence, network-security, soc-operations | ~30 |
|
||||
| Exfiltration | TA0010 | threat-hunting, digital-forensics, network-security | ~20 |
|
||||
| Impact | TA0040 | ransomware-defense, incident-response, ot-ics-security | ~35 |
|
||||
|
||||
## Subdomain-to-Tactic Heat Map
|
||||
|
||||
Shows which subdomains contribute skills to each ATT&CK tactic. Intensity indicates relevance (H = High, M = Medium, L = Low).
|
||||
|
||||
| Subdomain (skills) | Recon | Res Dev | Init Access | Exec | Persist | Priv Esc | Def Evasion | Cred Access | Disc | Lat Mov | Collect | C2 | Exfil | Impact |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| web-application-security (41) | L | - | **H** | M | L | M | L | M | L | - | - | - | - | M |
|
||||
| threat-intelligence (43) | **H** | **H** | M | L | L | - | L | - | M | - | - | **H** | L | L |
|
||||
| threat-hunting (35) | L | - | M | M | **H** | M | **H** | M | **H** | M | **H** | M | **H** | M |
|
||||
| digital-forensics (34) | - | - | L | M | **H** | L | M | L | L | M | **H** | L | M | M |
|
||||
| malware-analysis (34) | - | L | M | **H** | **H** | M | **H** | L | L | L | M | **H** | L | M |
|
||||
| identity-access-management (33) | - | - | M | L | M | **H** | L | **H** | L | M | - | - | - | - |
|
||||
| network-security (33) | M | - | M | L | L | L | L | L | M | **H** | L | **H** | **H** | L |
|
||||
| soc-operations (33) | L | - | M | **H** | M | M | M | M | M | M | M | M | M | M |
|
||||
| cloud-security (48) | M | M | **H** | M | M | **H** | M | **H** | **H** | M | M | L | M | M |
|
||||
| api-security (28) | L | - | **H** | M | L | M | L | **H** | L | - | M | - | M | L |
|
||||
| ot-ics-security (28) | M | L | M | M | M | L | L | M | **H** | M | **H** | M | L | **H** |
|
||||
| container-security (26) | L | L | M | **H** | M | **H** | **H** | M | M | L | L | L | M | M |
|
||||
| incident-response (24) | - | - | M | M | M | M | M | M | L | M | M | M | M | **H** |
|
||||
| vulnerability-management (24) | M | - | **H** | M | L | M | L | L | **H** | L | - | - | - | M |
|
||||
| penetration-testing (23) | **H** | M | **H** | **H** | M | **H** | M | **H** | **H** | M | M | M | M | L |
|
||||
| red-teaming (24) | **H** | **H** | **H** | **H** | **H** | **H** | **H** | **H** | **H** | **H** | **H** | **H** | **H** | **H** |
|
||||
| devsecops (16) | L | L | M | M | L | M | L | M | L | - | - | - | - | L |
|
||||
| endpoint-security (16) | - | - | M | **H** | **H** | **H** | **H** | M | M | M | M | M | L | M |
|
||||
| phishing-defense (16) | M | M | **H** | M | - | - | M | **H** | - | - | M | L | L | L |
|
||||
| cryptography (13) | - | - | L | - | - | - | M | **H** | - | - | M | M | **H** | L |
|
||||
| zero-trust-architecture (13) | - | - | M | L | L | **H** | L | **H** | L | **H** | L | L | M | - |
|
||||
| mobile-security (12) | M | L | **H** | M | M | M | M | M | M | L | M | M | M | L |
|
||||
| compliance-governance (5) | L | L | L | - | - | L | - | L | L | - | - | - | - | L |
|
||||
| ransomware-defense (5) | - | - | M | M | M | L | M | - | - | - | M | M | L | **H** |
|
||||
|
||||
## Key Technique Coverage
|
||||
|
||||
High-confidence technique-to-skill mappings based on skill content analysis.
|
||||
|
||||
### Initial Access (TA0001) -- 45 skills
|
||||
|
||||
| Technique | ID | Primary Skills |
|
||||
|-----------|----|---------------|
|
||||
| Phishing | T1566 | analyzing-phishing-email-headers, analyzing-certificate-transparency-for-phishing, 14 phishing-defense skills |
|
||||
| Exploit Public-Facing Application | T1190 | 41 web-application-security skills, 28 api-security skills |
|
||||
| External Remote Services | T1133 | network-security VPN/remote access skills |
|
||||
| Valid Accounts | T1078 | identity-access-management credential skills |
|
||||
| Supply Chain Compromise | T1195 | analyzing-supply-chain-malware-artifacts, devsecops dependency scanning |
|
||||
|
||||
### Execution (TA0002) -- 32 skills
|
||||
|
||||
| Technique | ID | Primary Skills |
|
||||
|-----------|----|---------------|
|
||||
| Command and Scripting Interpreter | T1059 | malware-analysis script analysis skills |
|
||||
| Exploitation for Client Execution | T1203 | web-application-security exploit skills |
|
||||
| User Execution | T1204 | phishing-defense awareness skills |
|
||||
| Container Administration Command | T1609 | container-security skills |
|
||||
|
||||
### Persistence (TA0003) -- 28 skills
|
||||
|
||||
| Technique | ID | Primary Skills |
|
||||
|-----------|----|---------------|
|
||||
| Boot or Logon Autostart Execution | T1547 | analyzing-malware-persistence-with-autoruns, analyzing-windows-registry-for-artifacts |
|
||||
| Scheduled Task/Job | T1053 | endpoint-security scheduled task skills |
|
||||
| Create Account | T1136 | identity-access-management monitoring skills |
|
||||
| Implant Internal Image | T1525 | container-security image scanning skills |
|
||||
|
||||
### Privilege Escalation (TA0004) -- 40 skills
|
||||
|
||||
| Technique | ID | Primary Skills |
|
||||
|-----------|----|---------------|
|
||||
| Exploitation for Privilege Escalation | T1068 | penetration-testing privilege escalation skills |
|
||||
| Access Token Manipulation | T1134 | identity-access-management token skills |
|
||||
| Container Escape | T1611 | container-security escape detection skills |
|
||||
| Domain Policy Modification | T1484 | identity-access-management AD skills |
|
||||
|
||||
### Defense Evasion (TA0005) -- 25 skills
|
||||
|
||||
| Technique | ID | Primary Skills |
|
||||
|-----------|----|---------------|
|
||||
| Obfuscated Files or Information | T1027 | analyzing-packed-malware-with-upx-unpacker, malware deobfuscation skills |
|
||||
| Masquerading | T1036 | threat-hunting detection skills |
|
||||
| Rootkit | T1014 | analyzing-bootkit-and-rootkit-samples |
|
||||
| Indicator Removal | T1070 | digital-forensics anti-forensics skills |
|
||||
|
||||
### Credential Access (TA0006) -- 30 skills
|
||||
|
||||
| Technique | ID | Primary Skills |
|
||||
|-----------|----|---------------|
|
||||
| OS Credential Dumping | T1003 | analyzing-memory-dumps-with-volatility, penetration-testing credential skills |
|
||||
| Brute Force | T1110 | identity-access-management authentication skills |
|
||||
| Steal Web Session Cookie | T1539 | web-application-security session skills |
|
||||
| Unsecured Credentials | T1552 | cloud-security secrets management skills |
|
||||
|
||||
### Discovery (TA0007) -- 35 skills
|
||||
|
||||
| Technique | ID | Primary Skills |
|
||||
|-----------|----|---------------|
|
||||
| Network Service Discovery | T1046 | network-security scanning skills, penetration-testing recon |
|
||||
| System Information Discovery | T1082 | threat-hunting system enumeration skills |
|
||||
| Cloud Infrastructure Discovery | T1580 | cloud-security asset discovery skills |
|
||||
| Account Discovery | T1087 | identity-access-management enumeration skills |
|
||||
|
||||
### Lateral Movement (TA0008) -- 28 skills
|
||||
|
||||
| Technique | ID | Primary Skills |
|
||||
|-----------|----|---------------|
|
||||
| Remote Services | T1021 | network-security remote access skills |
|
||||
| Lateral Tool Transfer | T1570 | threat-hunting lateral movement detection skills |
|
||||
| Use Alternate Authentication Material | T1550 | identity-access-management pass-the-hash skills |
|
||||
| Exploitation of Remote Services | T1210 | penetration-testing exploitation skills |
|
||||
|
||||
### Collection (TA0009) -- 22 skills
|
||||
|
||||
| Technique | ID | Primary Skills |
|
||||
|-----------|----|---------------|
|
||||
| Data from Local System | T1005 | digital-forensics disk/file analysis skills |
|
||||
| Data from Network Shared Drive | T1039 | threat-hunting data access monitoring skills |
|
||||
| Email Collection | T1114 | analyzing-outlook-pst-for-email-forensics |
|
||||
| Screen Capture | T1113 | malware-analysis behavior analysis skills |
|
||||
|
||||
### Command and Control (TA0011) -- 30 skills
|
||||
|
||||
| Technique | ID | Primary Skills |
|
||||
|-----------|----|---------------|
|
||||
| Application Layer Protocol | T1071 | analyzing-command-and-control-communication, network-security C2 detection |
|
||||
| Encrypted Channel | T1573 | analyzing-network-covert-channels-in-malware |
|
||||
| Ingress Tool Transfer | T1105 | analyzing-cobalt-strike-beacon-configuration |
|
||||
| Proxy | T1090 | network-security proxy analysis skills |
|
||||
|
||||
### Exfiltration (TA0010) -- 20 skills
|
||||
|
||||
| Technique | ID | Primary Skills |
|
||||
|-----------|----|---------------|
|
||||
| Exfiltration Over C2 Channel | T1041 | analyzing-dns-logs-for-exfiltration |
|
||||
| Exfiltration Over Alternative Protocol | T1048 | network-security protocol analysis skills |
|
||||
| Exfiltration Over Web Service | T1567 | cloud-security data loss prevention skills |
|
||||
|
||||
### Impact (TA0040) -- 35 skills
|
||||
|
||||
| Technique | ID | Primary Skills |
|
||||
|-----------|----|---------------|
|
||||
| Data Encrypted for Impact | T1486 | analyzing-ransomware-encryption-mechanisms, 5 ransomware-defense skills |
|
||||
| Service Stop | T1489 | incident-response service restoration skills |
|
||||
| Inhibit System Recovery | T1490 | ransomware-defense recovery skills |
|
||||
| Manipulation of Control | T0831 | ot-ics-security control system skills |
|
||||
|
||||
## Coverage Gaps
|
||||
|
||||
Areas where additional skills would improve ATT&CK coverage:
|
||||
|
||||
| Gap Area | ATT&CK Techniques | Recommendation |
|
||||
|----------|-------------------|----------------|
|
||||
| Firmware attacks | T1542 (Pre-OS Boot) | Add UEFI/firmware analysis skills |
|
||||
| Audio/video capture | T1123, T1125 | Add surveillance detection skills |
|
||||
| Cloud-specific lateral movement | T1550.001 (Web Session Cookie in cloud) | Expand cloud-security lateral movement |
|
||||
| Hardware additions | T1200 | Add physical security assessment skills |
|
||||
| Traffic signaling | T1205 | Add network covert channel detection skills |
|
||||
@@ -0,0 +1,133 @@
|
||||
# NIST Cybersecurity Framework 2.0 Mapping
|
||||
|
||||
This directory maps the cybersecurity skills in this repository to the [NIST Cybersecurity Framework (CSF) 2.0](https://www.nist.gov/cyberframework), published February 2024.
|
||||
|
||||
## Overview
|
||||
|
||||
NIST CSF 2.0 organizes cybersecurity activities into 6 core functions that represent the full lifecycle of managing cybersecurity risk. This mapping enables organizations to:
|
||||
|
||||
- **Align skill development** to their CSF implementation tier
|
||||
- **Identify training gaps** across the CSF functions
|
||||
- **Build role-based learning paths** using CSF categories
|
||||
- **Automate compliance mapping** through AI agent queries
|
||||
|
||||
## CSF 2.0 Functions and Skill Alignment
|
||||
|
||||
### Govern (GV) -- Cybersecurity Risk Management Strategy
|
||||
|
||||
Establishing and monitoring the organization's cybersecurity risk management strategy, expectations, and policy.
|
||||
|
||||
| Category | ID | Mapped Subdomains | Skills |
|
||||
|----------|-----|-------------------|--------|
|
||||
| Organizational Context | GV.OC | compliance-governance | 5 |
|
||||
| Risk Management Strategy | GV.RM | compliance-governance, vulnerability-management | 29 |
|
||||
| Roles, Responsibilities, and Authorities | GV.RR | compliance-governance, identity-access-management | 38 |
|
||||
| Policy | GV.PO | compliance-governance, zero-trust-architecture | 18 |
|
||||
| Oversight | GV.OV | compliance-governance, soc-operations | 38 |
|
||||
| Cybersecurity Supply Chain Risk Management | GV.SC | devsecops, container-security | 42 |
|
||||
|
||||
**Primary subdomains:** compliance-governance (5), identity-access-management (33), devsecops (16)
|
||||
|
||||
### Identify (ID) -- Understanding Organizational Cybersecurity Risk
|
||||
|
||||
Understanding the organization's current cybersecurity risks.
|
||||
|
||||
| Category | ID | Mapped Subdomains | Skills |
|
||||
|----------|-----|-------------------|--------|
|
||||
| Asset Management | ID.AM | cloud-security, container-security, network-security | 107 |
|
||||
| Risk Assessment | ID.RA | vulnerability-management, threat-intelligence | 67 |
|
||||
| Improvement | ID.IM | soc-operations, compliance-governance | 38 |
|
||||
|
||||
**Primary subdomains:** vulnerability-management (24), threat-intelligence (43), cloud-security (48)
|
||||
|
||||
### Protect (PR) -- Safeguarding Assets
|
||||
|
||||
Using safeguards to prevent or reduce cybersecurity risk.
|
||||
|
||||
| Category | ID | Mapped Subdomains | Skills |
|
||||
|----------|-----|-------------------|--------|
|
||||
| Identity Management, Authentication, and Access Control | PR.AA | identity-access-management, zero-trust-architecture | 46 |
|
||||
| Awareness and Training | PR.AT | phishing-defense, compliance-governance | 21 |
|
||||
| Data Security | PR.DS | cryptography, cloud-security, api-security | 89 |
|
||||
| Platform Security | PR.PS | endpoint-security, container-security, devsecops | 58 |
|
||||
| Technology Infrastructure Resilience | PR.IR | network-security, zero-trust-architecture | 46 |
|
||||
|
||||
**Primary subdomains:** zero-trust-architecture (13), devsecops (16), identity-access-management (33), cryptography (13)
|
||||
|
||||
### Detect (DE) -- Finding and Analyzing Cybersecurity Events
|
||||
|
||||
Finding and analyzing possible cybersecurity compromises and anomalies.
|
||||
|
||||
| Category | ID | Mapped Subdomains | Skills |
|
||||
|----------|-----|-------------------|--------|
|
||||
| Continuous Monitoring | DE.CM | soc-operations, threat-hunting, network-security | 101 |
|
||||
| Adverse Event Analysis | DE.AE | threat-hunting, malware-analysis, soc-operations | 102 |
|
||||
|
||||
**Primary subdomains:** threat-hunting (35), soc-operations (33), malware-analysis (34)
|
||||
|
||||
### Respond (RS) -- Taking Action Regarding Detected Incidents
|
||||
|
||||
Managing and responding to detected cybersecurity incidents.
|
||||
|
||||
| Category | ID | Mapped Subdomains | Skills |
|
||||
|----------|-----|-------------------|--------|
|
||||
| Incident Management | RS.MA | incident-response, soc-operations | 57 |
|
||||
| Incident Analysis | RS.AN | digital-forensics, malware-analysis, threat-intelligence | 111 |
|
||||
| Incident Response Reporting and Communication | RS.CO | incident-response, compliance-governance | 29 |
|
||||
| Incident Mitigation | RS.MI | incident-response, endpoint-security, network-security | 73 |
|
||||
|
||||
**Primary subdomains:** incident-response (24), digital-forensics (34), malware-analysis (34)
|
||||
|
||||
### Recover (RC) -- Restoring Capabilities After an Incident
|
||||
|
||||
Restoring assets and operations affected by a cybersecurity incident.
|
||||
|
||||
| Category | ID | Mapped Subdomains | Skills |
|
||||
|----------|-----|-------------------|--------|
|
||||
| Incident Recovery Plan Execution | RC.RP | incident-response, ransomware-defense | 29 |
|
||||
| Incident Recovery Communication | RC.CO | incident-response, compliance-governance | 29 |
|
||||
|
||||
**Primary subdomains:** incident-response (24), ransomware-defense (5)
|
||||
|
||||
## Function Coverage Distribution
|
||||
|
||||
```
|
||||
Govern (GV): ████████████░░░░░░░░ ~54 skills (compliance, IAM, devsecops)
|
||||
Identify (ID): ██████████████████░░ ~115 skills (vuln-mgmt, threat-intel, cloud)
|
||||
Protect (PR): ████████████████████ ~160 skills (IAM, ZTA, devsecops, crypto)
|
||||
Detect (DE): ████████████████░░░░ ~102 skills (threat-hunting, SOC, malware)
|
||||
Respond (RS): ██████████████████░░ ~111 skills (IR, forensics, malware)
|
||||
Recover (RC): ████░░░░░░░░░░░░░░░░ ~29 skills (IR recovery, ransomware)
|
||||
```
|
||||
|
||||
## How to Use This Mapping
|
||||
|
||||
### For Organizations
|
||||
|
||||
1. Determine your target CSF implementation tier (Partial, Risk Informed, Repeatable, Adaptive)
|
||||
2. Identify your CSF function priorities
|
||||
3. Use the category tables above to find relevant skill subdomains
|
||||
4. Deploy skills from those subdomains to your team's training plan
|
||||
|
||||
### For AI Agents
|
||||
|
||||
Query skills by CSF function using subdomain filters:
|
||||
|
||||
```
|
||||
# Find all Detect (DE) function skills
|
||||
Filter: subdomain IN (threat-hunting, soc-operations, malware-analysis)
|
||||
|
||||
# Find all Protect (PR) function skills
|
||||
Filter: subdomain IN (identity-access-management, zero-trust-architecture, devsecops, cryptography)
|
||||
```
|
||||
|
||||
### For Security Teams
|
||||
|
||||
Use the alignment table in [`csf-alignment.md`](csf-alignment.md) for a complete subdomain-to-category cross-reference.
|
||||
|
||||
## References
|
||||
|
||||
- [NIST CSF 2.0 (February 2024)](https://www.nist.gov/cyberframework)
|
||||
- [NIST SP 800-53 Rev. 5 Control Mapping](https://csrc.nist.gov/publications/detail/sp/800-53/rev-5/final)
|
||||
- [CSF 2.0 Quick Start Guides](https://www.nist.gov/cyberframework/getting-started)
|
||||
- [CSF 2.0 Reference Tool](https://csrc.nist.gov/Projects/Cybersecurity-Framework/Filters)
|
||||
@@ -0,0 +1,102 @@
|
||||
# NIST CSF 2.0 Alignment Table
|
||||
|
||||
Complete mapping of each skill subdomain to NIST CSF 2.0 functions and categories.
|
||||
|
||||
## Subdomain-to-CSF Alignment
|
||||
|
||||
| Subdomain | Skills | GV | ID | PR | PR | DE | RS | RC |
|
||||
|-----------|--------|-----|-----|-----|-----|-----|-----|-----|
|
||||
| | | Govern | Identify | Protect | Protect | Detect | Respond | Recover |
|
||||
|
||||
### Detailed Alignment
|
||||
|
||||
| Subdomain (Skills) | Primary CSF Function | CSF Categories | Alignment Rationale |
|
||||
|---------------------|---------------------|----------------|---------------------|
|
||||
| api-security (28) | Protect (PR) | PR.DS, PR.PS | API hardening, authentication, input validation |
|
||||
| cloud-security (48) | Identify (ID), Protect (PR) | ID.AM, PR.DS, PR.PS, PR.IR | Cloud asset management, data protection, infrastructure resilience |
|
||||
| compliance-governance (5) | Govern (GV) | GV.OC, GV.RM, GV.RR, GV.PO, GV.OV | Risk strategy, policy, organizational oversight |
|
||||
| container-security (26) | Protect (PR) | PR.PS, GV.SC | Platform security, supply chain risk management |
|
||||
| cryptography (13) | Protect (PR) | PR.DS | Data confidentiality and integrity at rest and in transit |
|
||||
| devsecops (16) | Protect (PR), Govern (GV) | PR.PS, GV.SC | Secure development lifecycle, supply chain security |
|
||||
| digital-forensics (34) | Respond (RS) | RS.AN, RS.MA | Incident analysis, evidence collection and examination |
|
||||
| endpoint-security (16) | Protect (PR), Detect (DE) | PR.PS, DE.CM, DE.AE | Endpoint hardening, continuous monitoring, threat detection |
|
||||
| identity-access-management (33) | Protect (PR), Govern (GV) | PR.AA, GV.RR | Identity lifecycle, authentication, authorization, access governance |
|
||||
| incident-response (24) | Respond (RS), Recover (RC) | RS.MA, RS.AN, RS.MI, RS.CO, RC.RP, RC.CO | Full incident lifecycle from detection through recovery |
|
||||
| malware-analysis (34) | Detect (DE), Respond (RS) | DE.AE, RS.AN | Adverse event analysis, reverse engineering, threat characterization |
|
||||
| mobile-security (12) | Protect (PR) | PR.PS, PR.DS | Mobile platform security, application data protection |
|
||||
| network-security (33) | Protect (PR), Detect (DE) | PR.IR, DE.CM | Network infrastructure resilience, traffic monitoring |
|
||||
| ot-ics-security (28) | Protect (PR), Detect (DE) | PR.PS, PR.IR, DE.CM | Industrial control system protection and monitoring |
|
||||
| penetration-testing (23) | Identify (ID) | ID.RA | Risk assessment through offensive security testing |
|
||||
| phishing-defense (16) | Protect (PR), Detect (DE) | PR.AT, DE.CM, DE.AE | Security awareness training, phishing detection |
|
||||
| ransomware-defense (5) | Respond (RS), Recover (RC) | RS.MI, RC.RP | Ransomware mitigation and recovery planning |
|
||||
| red-teaming (24) | Identify (ID) | ID.RA, ID.IM | Adversary simulation for risk assessment and program improvement |
|
||||
| soc-operations (33) | Detect (DE), Respond (RS) | DE.CM, DE.AE, RS.MA | Continuous monitoring, alert triage, incident management |
|
||||
| threat-hunting (35) | Detect (DE) | DE.CM, DE.AE | Proactive threat detection, hypothesis-driven analysis |
|
||||
| threat-intelligence (43) | Identify (ID), Detect (DE) | ID.RA, DE.AE | Threat landscape understanding, intelligence-driven detection |
|
||||
| vulnerability-management (24) | Identify (ID) | ID.RA, GV.RM | Vulnerability identification, risk assessment, remediation prioritization |
|
||||
| web-application-security (41) | Protect (PR), Identify (ID) | PR.DS, PR.PS, ID.RA | Application security testing and hardening |
|
||||
| zero-trust-architecture (13) | Protect (PR) | PR.AA, PR.IR | Zero trust access control and network segmentation |
|
||||
|
||||
## CSF Category Coverage Summary
|
||||
|
||||
### Govern (GV)
|
||||
|
||||
| Category | ID | Description | Subdomain Coverage |
|
||||
|----------|-----|------------|-------------------|
|
||||
| Organizational Context | GV.OC | Understanding the organizational mission and stakeholder expectations | compliance-governance |
|
||||
| Risk Management Strategy | GV.RM | Risk management priorities, constraints, and appetite | compliance-governance, vulnerability-management |
|
||||
| Roles, Responsibilities, and Authorities | GV.RR | Cybersecurity roles and authorities are established | compliance-governance, identity-access-management |
|
||||
| Policy | GV.PO | Organizational cybersecurity policy is established | compliance-governance, zero-trust-architecture |
|
||||
| Oversight | GV.OV | Results of cybersecurity activities are reviewed | compliance-governance, soc-operations |
|
||||
| Cybersecurity Supply Chain Risk Management | GV.SC | Supply chain risks are managed | devsecops, container-security |
|
||||
|
||||
### Identify (ID)
|
||||
|
||||
| Category | ID | Description | Subdomain Coverage |
|
||||
|----------|-----|------------|-------------------|
|
||||
| Asset Management | ID.AM | Assets that enable the organization are identified and managed | cloud-security, container-security, network-security |
|
||||
| Risk Assessment | ID.RA | The cybersecurity risk to the organization is understood | vulnerability-management, threat-intelligence, penetration-testing, red-teaming |
|
||||
| Improvement | ID.IM | Improvements to organizational cybersecurity are identified | soc-operations, red-teaming, compliance-governance |
|
||||
|
||||
### Protect (PR)
|
||||
|
||||
| Category | ID | Description | Subdomain Coverage |
|
||||
|----------|-----|------------|-------------------|
|
||||
| Identity Management, Authentication, and Access Control | PR.AA | Access is limited to authorized users, services, and hardware | identity-access-management, zero-trust-architecture |
|
||||
| Awareness and Training | PR.AT | Personnel are provided cybersecurity awareness and training | phishing-defense, compliance-governance |
|
||||
| Data Security | PR.DS | Data are managed consistent with the organization's risk strategy | cryptography, cloud-security, api-security |
|
||||
| Platform Security | PR.PS | Hardware, software, and services are managed consistent with risk strategy | endpoint-security, container-security, devsecops, ot-ics-security |
|
||||
| Technology Infrastructure Resilience | PR.IR | Security architectures are managed to protect asset confidentiality, integrity, and availability | network-security, zero-trust-architecture, ot-ics-security |
|
||||
|
||||
### Detect (DE)
|
||||
|
||||
| Category | ID | Description | Subdomain Coverage |
|
||||
|----------|-----|------------|-------------------|
|
||||
| Continuous Monitoring | DE.CM | Assets are monitored to find anomalies and indicators of compromise | soc-operations, threat-hunting, network-security, endpoint-security |
|
||||
| Adverse Event Analysis | DE.AE | Anomalies and potential adverse events are analyzed | threat-hunting, malware-analysis, soc-operations, threat-intelligence |
|
||||
|
||||
### Respond (RS)
|
||||
|
||||
| Category | ID | Description | Subdomain Coverage |
|
||||
|----------|-----|------------|-------------------|
|
||||
| Incident Management | RS.MA | Responses to detected incidents are managed | incident-response, soc-operations |
|
||||
| Incident Analysis | RS.AN | Investigations are conducted to understand the incident | digital-forensics, malware-analysis, threat-intelligence |
|
||||
| Incident Response Reporting and Communication | RS.CO | Response activities are coordinated with internal and external stakeholders | incident-response, compliance-governance |
|
||||
| Incident Mitigation | RS.MI | Activities are performed to prevent expansion and mitigate effects | incident-response, endpoint-security, network-security |
|
||||
|
||||
### Recover (RC)
|
||||
|
||||
| Category | ID | Description | Subdomain Coverage |
|
||||
|----------|-----|------------|-------------------|
|
||||
| Incident Recovery Plan Execution | RC.RP | Restoration activities are performed to ensure operational availability | incident-response, ransomware-defense |
|
||||
| Incident Recovery Communication | RC.CO | Restoration activities are coordinated with internal and external parties | incident-response, compliance-governance |
|
||||
|
||||
## Gap Analysis
|
||||
|
||||
| CSF Category | Current Coverage | Gap |
|
||||
|-------------|-----------------|-----|
|
||||
| GV.OC | Low (5 skills) | Need more organizational security context and mission alignment skills |
|
||||
| GV.PO | Low | Need dedicated policy development and management skills |
|
||||
| PR.AT | Moderate (16 skills) | Could expand security awareness training beyond phishing |
|
||||
| RC.RP | Low (29 skills) | Need more disaster recovery and business continuity skills |
|
||||
| RC.CO | Low | Need dedicated incident communication and stakeholder management skills |
|
||||
@@ -0,0 +1,177 @@
|
||||
# OWASP Top 10 (2025) Mapping
|
||||
|
||||
This directory maps the cybersecurity skills in this repository to the [OWASP Top 10](https://owasp.org/www-project-top-ten/) categories for web application security risks.
|
||||
|
||||
## Overview
|
||||
|
||||
The OWASP Top 10 represents the most critical security risks to web applications. This mapping connects hands-on skills to each risk category, enabling teams to build targeted training programs for secure development and application security testing.
|
||||
|
||||
## OWASP Top 10 2025 Skill Mapping
|
||||
|
||||
### A01:2025 -- Broken Access Control
|
||||
|
||||
Restrictions on what authenticated users are allowed to do are not properly enforced.
|
||||
|
||||
| Relevant Subdomains | Skills | Key Topics |
|
||||
|---------------------|--------|------------|
|
||||
| web-application-security | 41 | IDOR, privilege escalation, path traversal, CORS misconfiguration |
|
||||
| identity-access-management | 33 | RBAC, ABAC, session management, OAuth/OIDC flaws |
|
||||
| api-security | 28 | Broken object level authorization (BOLA), function level authorization |
|
||||
| zero-trust-architecture | 13 | Least privilege enforcement, microsegmentation |
|
||||
|
||||
**Example skills:** Implementing RBAC, testing for IDOR vulnerabilities, configuring OAuth 2.0 securely, enforcing API authorization policies.
|
||||
|
||||
### A02:2025 -- Cryptographic Failures
|
||||
|
||||
Failures related to cryptography that lead to exposure of sensitive data.
|
||||
|
||||
| Relevant Subdomains | Skills | Key Topics |
|
||||
|---------------------|--------|------------|
|
||||
| cryptography | 13 | TLS configuration, key management, hashing, encryption at rest |
|
||||
| web-application-security | 41 | HTTPS enforcement, cookie security flags, certificate validation |
|
||||
| cloud-security | 48 | KMS configuration, secrets management, encryption in transit |
|
||||
| api-security | 28 | API transport security, token encryption |
|
||||
|
||||
**Example skills:** Configuring TLS 1.3, implementing envelope encryption with KMS, securing JWT tokens, certificate pinning.
|
||||
|
||||
### A03:2025 -- Injection
|
||||
|
||||
User-supplied data is sent to an interpreter as part of a command or query without proper validation.
|
||||
|
||||
| Relevant Subdomains | Skills | Key Topics |
|
||||
|---------------------|--------|------------|
|
||||
| web-application-security | 41 | SQL injection, XSS, command injection, LDAP injection |
|
||||
| api-security | 28 | GraphQL injection, NoSQL injection, header injection |
|
||||
| devsecops | 16 | SAST/DAST scanning, input validation, parameterized queries |
|
||||
| penetration-testing | 23 | Injection testing, payload crafting, WAF bypass |
|
||||
|
||||
**Example skills:** Exploiting and remediating SQL injection, testing for stored/reflected XSS, configuring parameterized queries, SAST pipeline integration.
|
||||
|
||||
### A04:2025 -- Insecure Design
|
||||
|
||||
Risks related to design and architectural flaws, calling for more use of threat modeling and secure design patterns.
|
||||
|
||||
| Relevant Subdomains | Skills | Key Topics |
|
||||
|---------------------|--------|------------|
|
||||
| devsecops | 16 | Threat modeling, secure SDLC, security requirements |
|
||||
| zero-trust-architecture | 13 | Zero trust design principles, defense in depth |
|
||||
| compliance-governance | 5 | Security architecture review, risk assessment frameworks |
|
||||
| web-application-security | 41 | Business logic flaws, trust boundary definition |
|
||||
|
||||
**Example skills:** Conducting threat modeling with STRIDE, implementing secure design patterns, defining trust boundaries, security architecture review.
|
||||
|
||||
### A05:2025 -- Security Misconfiguration
|
||||
|
||||
Missing or incorrect security hardening across the application stack.
|
||||
|
||||
| Relevant Subdomains | Skills | Key Topics |
|
||||
|---------------------|--------|------------|
|
||||
| cloud-security | 48 | Cloud service misconfiguration, IAM policy errors, S3 bucket exposure |
|
||||
| container-security | 26 | Container hardening, Kubernetes RBAC, pod security policies |
|
||||
| network-security | 33 | Firewall rules, segmentation errors, default credentials |
|
||||
| endpoint-security | 16 | OS hardening, unnecessary services, default configurations |
|
||||
|
||||
**Example skills:** Auditing AWS S3 bucket permissions, hardening Kubernetes clusters, configuring security headers, CIS benchmark compliance.
|
||||
|
||||
### A06:2025 -- Vulnerable and Outdated Components
|
||||
|
||||
Using components with known vulnerabilities or that are no longer maintained.
|
||||
|
||||
| Relevant Subdomains | Skills | Key Topics |
|
||||
|---------------------|--------|------------|
|
||||
| vulnerability-management | 24 | CVE tracking, vulnerability scanning, patch management |
|
||||
| devsecops | 16 | SCA scanning, dependency management, SBOM generation |
|
||||
| container-security | 26 | Image scanning, base image updates, registry security |
|
||||
| web-application-security | 41 | Third-party library vulnerabilities, framework updates |
|
||||
|
||||
**Example skills:** Running Trivy container scans, implementing SCA in CI/CD, generating and analyzing SBOMs, CVE prioritization with CVSS/EPSS.
|
||||
|
||||
### A07:2025 -- Identification and Authentication Failures
|
||||
|
||||
Weaknesses in authentication and session management.
|
||||
|
||||
| Relevant Subdomains | Skills | Key Topics |
|
||||
|---------------------|--------|------------|
|
||||
| identity-access-management | 33 | MFA implementation, password policies, session fixation |
|
||||
| web-application-security | 41 | Credential stuffing defense, brute force protection |
|
||||
| api-security | 28 | API key management, OAuth token handling, JWT validation |
|
||||
| phishing-defense | 16 | Credential phishing prevention, anti-phishing controls |
|
||||
|
||||
**Example skills:** Implementing FIDO2/WebAuthn, configuring adaptive MFA, securing API authentication, detecting credential stuffing attacks.
|
||||
|
||||
### A08:2025 -- Software and Data Integrity Failures
|
||||
|
||||
Failures related to code and infrastructure that do not protect against integrity violations.
|
||||
|
||||
| Relevant Subdomains | Skills | Key Topics |
|
||||
|---------------------|--------|------------|
|
||||
| devsecops | 16 | CI/CD pipeline security, code signing, artifact integrity |
|
||||
| container-security | 26 | Image signing, admission control, supply chain verification |
|
||||
| cryptography | 13 | Digital signatures, integrity hashing, code signing certificates |
|
||||
| vulnerability-management | 24 | Supply chain risk, dependency integrity verification |
|
||||
|
||||
**Example skills:** Implementing Sigstore for container signing, securing CI/CD pipelines, verifying software supply chain integrity, content trust enforcement.
|
||||
|
||||
### A09:2025 -- Security Logging and Monitoring Failures
|
||||
|
||||
Insufficient logging, detection, monitoring, and active response.
|
||||
|
||||
| Relevant Subdomains | Skills | Key Topics |
|
||||
|---------------------|--------|------------|
|
||||
| soc-operations | 33 | SIEM configuration, log aggregation, alert tuning |
|
||||
| threat-hunting | 35 | Log analysis, detection engineering, hypothesis-driven hunting |
|
||||
| incident-response | 24 | Incident detection, log-based investigation, response automation |
|
||||
| network-security | 33 | Network monitoring, flow analysis, IDS/IPS tuning |
|
||||
|
||||
**Example skills:** Analyzing security logs with Splunk, writing Sigma detection rules, configuring SIEM correlation rules, implementing centralized logging.
|
||||
|
||||
### A10:2025 -- Server-Side Request Forgery (SSRF)
|
||||
|
||||
Fetching a remote resource without validating the user-supplied URL.
|
||||
|
||||
| Relevant Subdomains | Skills | Key Topics |
|
||||
|---------------------|--------|------------|
|
||||
| web-application-security | 41 | SSRF exploitation, URL validation, allowlisting |
|
||||
| cloud-security | 48 | IMDS exploitation, cloud metadata access, VPC endpoint security |
|
||||
| api-security | 28 | API-to-API SSRF, webhook validation |
|
||||
| penetration-testing | 23 | SSRF detection and exploitation techniques |
|
||||
|
||||
**Example skills:** Testing for SSRF vulnerabilities, securing cloud metadata endpoints (IMDSv2), implementing URL validation and allowlisting, detecting SSRF in API integrations.
|
||||
|
||||
## Cross-Reference: OWASP to ATT&CK
|
||||
|
||||
| OWASP Category | Related ATT&CK Techniques |
|
||||
|---------------|--------------------------|
|
||||
| A01: Broken Access Control | T1078 (Valid Accounts), T1548 (Abuse Elevation Control) |
|
||||
| A02: Cryptographic Failures | T1557 (Adversary-in-the-Middle), T1040 (Network Sniffing) |
|
||||
| A03: Injection | T1190 (Exploit Public-Facing App), T1059 (Command and Scripting) |
|
||||
| A04: Insecure Design | T1195 (Supply Chain Compromise), cross-cutting |
|
||||
| A05: Security Misconfiguration | T1574 (Hijack Execution Flow), T1190 |
|
||||
| A06: Vulnerable Components | T1190 (Exploit Public-Facing App), T1195 |
|
||||
| A07: Authentication Failures | T1110 (Brute Force), T1539 (Steal Web Session Cookie) |
|
||||
| A08: Integrity Failures | T1195 (Supply Chain Compromise), T1554 (Compromise Client Software) |
|
||||
| A09: Logging Failures | T1070 (Indicator Removal), T1562 (Impair Defenses) |
|
||||
| A10: SSRF | T1190 (Exploit Public-Facing App) |
|
||||
|
||||
## Cross-Reference: OWASP to NIST CSF 2.0
|
||||
|
||||
| OWASP Category | NIST CSF Functions | CSF Categories |
|
||||
|---------------|-------------------|----------------|
|
||||
| A01: Broken Access Control | Protect | PR.AA |
|
||||
| A02: Cryptographic Failures | Protect | PR.DS |
|
||||
| A03: Injection | Protect, Detect | PR.DS, DE.AE |
|
||||
| A04: Insecure Design | Govern, Protect | GV.RM, PR.PS |
|
||||
| A05: Security Misconfiguration | Protect | PR.PS, PR.IR |
|
||||
| A06: Vulnerable Components | Identify, Govern | ID.RA, GV.SC |
|
||||
| A07: Authentication Failures | Protect | PR.AA |
|
||||
| A08: Integrity Failures | Protect, Govern | PR.DS, GV.SC |
|
||||
| A09: Logging Failures | Detect | DE.CM, DE.AE |
|
||||
| A10: SSRF | Protect, Detect | PR.DS, DE.AE |
|
||||
|
||||
## References
|
||||
|
||||
- [OWASP Top 10 Project](https://owasp.org/www-project-top-ten/)
|
||||
- [OWASP API Security Top 10](https://owasp.org/API-Security/) -- relevant for api-security subdomain
|
||||
- [OWASP Mobile Top 10](https://owasp.org/www-project-mobile-top-10/) -- relevant for mobile-security subdomain
|
||||
- [OWASP Testing Guide](https://owasp.org/www-project-web-security-testing-guide/)
|
||||
- [OWASP ASVS](https://owasp.org/www-project-application-security-verification-standard/) -- Application Security Verification Standard
|
||||
370
personas/_shared/community-skills/olla/SKILL.md
Normal file
370
personas/_shared/community-skills/olla/SKILL.md
Normal file
@@ -0,0 +1,370 @@
|
||||
---
|
||||
name: olla
|
||||
description: Configure and manage Olla LLM proxy gateway — load balancing, model routing, API translation (Anthropic<>OpenAI), health checking, and multi-backend orchestration for local LLM inference. Use when setting up Olla proxy, configuring backends (Ollama, vLLM, LM Studio, llama.cpp, SGLang, LiteLLM), debugging routing issues, or connecting Claude Code to local models via Olla.
|
||||
allowed-tools: Bash(*), Read(*), Write(*), Edit(*)
|
||||
---
|
||||
|
||||
# Olla — LLM Proxy Gateway
|
||||
|
||||
Olla is a high-performance LLM proxy gateway written in Go that unifies multiple local LLM backends behind a single endpoint with intelligent routing, load balancing, health checking, and API translation.
|
||||
|
||||
**Repository**: https://github.com/thushan/olla
|
||||
**Docs**: https://thushan.github.io/olla/
|
||||
**Default Port**: 40114 (mnemonic: "4 OLLA")
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Script install (Linux/macOS)
|
||||
bash <(curl -s https://raw.githubusercontent.com/thushan/olla/main/install.sh)
|
||||
|
||||
# Go install
|
||||
go install github.com/thushan/olla@latest
|
||||
|
||||
# Docker
|
||||
docker pull ghcr.io/thushan/olla:latest
|
||||
docker run -t --name olla -p 40114:40114 ghcr.io/thushan/olla:latest
|
||||
|
||||
# Build from source
|
||||
git clone https://github.com/thushan/olla.git && cd olla && make build-release
|
||||
# Binary: ./bin/olla
|
||||
```
|
||||
|
||||
Verify: `olla --version` | Health: `curl http://localhost:40114/internal/health`
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Proxy Engines
|
||||
- **Sherpa** (default): Simple, shared HTTP transport. Good for dev, <100 concurrent users, lower memory.
|
||||
- **Olla**: Per-endpoint connection pools, advanced circuit breaker. Production, high throughput, streaming-heavy.
|
||||
|
||||
### Load Balancing Strategies
|
||||
- **priority** (default): Routes to highest-priority healthy endpoint. Best for primary/backup hierarchies.
|
||||
- **round-robin**: Even distribution across healthy endpoints. Best for homogeneous servers.
|
||||
- **least-connections**: Routes to endpoint with fewest active connections. Best for variable request durations/streaming.
|
||||
|
||||
Health-aware weighting: healthy=1.0, busy=0.3, warming=0.1, unhealthy/unknown=0.
|
||||
|
||||
### Model Routing
|
||||
- **strict** (default): Only route to endpoints known to have the model. 404 if not found.
|
||||
- **optimistic**: Try any healthy endpoint if model not found. Prioritizes availability.
|
||||
- **discovery**: Refresh model catalog before routing. Adds latency but ensures freshness.
|
||||
|
||||
Fallback behavior: `compatible_only` (reject if not found), `all` (any healthy), `none` (always reject).
|
||||
|
||||
### Model Aliases
|
||||
Map a virtual model name to different actual names across backends:
|
||||
```yaml
|
||||
model_aliases:
|
||||
my-llama:
|
||||
- "llama3.1:8b" # Ollama
|
||||
- llama-3.1-8b-instruct # LM Studio
|
||||
- Meta-Llama-3.1-8B-Instruct.gguf # llama.cpp
|
||||
```
|
||||
|
||||
### API Translation (Anthropic <> OpenAI)
|
||||
Three-stage: Request translation -> Backend processing -> Response translation.
|
||||
Supports streaming SSE, tool use, vision. Overhead: 1-5ms (translation), ~0ms (passthrough).
|
||||
|
||||
**Passthrough mode**: When backend natively supports Anthropic (vLLM v0.11.1+, llama.cpp b4847+, LM Studio v0.4.1+, Ollama v0.14.0+), Olla bypasses translation entirely. Header: `X-Olla-Mode: passthrough`.
|
||||
|
||||
### Health Checking & Circuit Breaker
|
||||
- Continuous monitoring with configurable intervals (default: 5s)
|
||||
- Exponential backoff on failures (2x, 4x, 8x... capped at 60s)
|
||||
- Circuit breaker: Closed -> Open (3 failures) -> Half-Open (30s, test traffic) -> Closed
|
||||
- Auto model discovery on recovery
|
||||
- States: Healthy, Busy, Warming, Offline, Unhealthy
|
||||
|
||||
## Configuration
|
||||
|
||||
Config search order: `--config` flag > `OLLA_CONFIG_FILE` env > `config/config.local.yaml` > `config/config.yaml` > `config.yaml` > `default.yaml`
|
||||
|
||||
Best practice: Create `config/config.local.yaml` with only overrides.
|
||||
|
||||
### Minimal Config Example
|
||||
```yaml
|
||||
server:
|
||||
host: "localhost"
|
||||
port: 40114
|
||||
|
||||
proxy:
|
||||
engine: "sherpa"
|
||||
load_balancer: "priority"
|
||||
|
||||
discovery:
|
||||
type: "static"
|
||||
static:
|
||||
endpoints:
|
||||
- url: "http://localhost:11434"
|
||||
name: "local-ollama"
|
||||
type: "ollama"
|
||||
priority: 100
|
||||
```
|
||||
|
||||
### Full Config Reference
|
||||
|
||||
#### Server
|
||||
```yaml
|
||||
server:
|
||||
host: "localhost" # Bind address
|
||||
port: 40114 # Listen port
|
||||
read_timeout: 30s
|
||||
write_timeout: 0s # MUST be 0s for streaming
|
||||
shutdown_timeout: 10s
|
||||
idle_timeout: 0s
|
||||
request_logging: true
|
||||
request_limits:
|
||||
max_body_size: 104857600 # 100MB
|
||||
max_header_size: 1048576 # 1MB
|
||||
rate_limits:
|
||||
global_requests_per_minute: 1000
|
||||
per_ip_requests_per_minute: 100
|
||||
health_requests_per_minute: 1000
|
||||
burst_size: 50
|
||||
cleanup_interval: 5m
|
||||
trust_proxy_headers: false
|
||||
trusted_proxy_cidrs: ["127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
|
||||
```
|
||||
|
||||
#### Proxy
|
||||
```yaml
|
||||
proxy:
|
||||
engine: "sherpa" # sherpa | olla
|
||||
profile: "auto" # auto | streaming | standard
|
||||
load_balancer: "priority" # priority | round-robin | least-connections
|
||||
connection_timeout: 30s
|
||||
response_timeout: 600s # 10 min default
|
||||
read_timeout: 120s
|
||||
stream_buffer_size: 8192 # 8KB (sherpa), 65536 (olla recommended)
|
||||
profile_filter:
|
||||
include: [] # glob patterns
|
||||
exclude: []
|
||||
```
|
||||
|
||||
#### Discovery & Endpoints
|
||||
```yaml
|
||||
discovery:
|
||||
type: "static"
|
||||
refresh_interval: 30s
|
||||
static:
|
||||
endpoints:
|
||||
- url: "http://localhost:11434"
|
||||
name: "local-ollama"
|
||||
type: "ollama" # ollama|lm-studio|vllm|sglang|llamacpp|lemonade|litellm|openai|docker-model-runner
|
||||
priority: 100 # 0-100, higher = preferred
|
||||
preserve_path: false
|
||||
health_check_url: "" # auto-detected per type
|
||||
model_url: "" # auto-detected per type
|
||||
check_interval: 5s
|
||||
check_timeout: 2s
|
||||
model_discovery:
|
||||
enabled: true
|
||||
interval: 5m
|
||||
timeout: 30s
|
||||
concurrent_workers: 5
|
||||
retry_attempts: 3
|
||||
retry_backoff: 1s
|
||||
```
|
||||
|
||||
#### Model Registry & Routing
|
||||
```yaml
|
||||
model_registry:
|
||||
type: "memory"
|
||||
enable_unifier: true
|
||||
routing_strategy:
|
||||
type: "strict" # strict | optimistic | discovery
|
||||
options:
|
||||
fallback_behavior: "compatible_only" # compatible_only | all | none
|
||||
discovery_timeout: 2s
|
||||
discovery_refresh_on_miss: false
|
||||
unification:
|
||||
enabled: true
|
||||
stale_threshold: 24h
|
||||
cleanup_interval: 5m
|
||||
cache_ttl: 10m
|
||||
|
||||
model_aliases:
|
||||
alias-name:
|
||||
- "backend1-model-name"
|
||||
- "backend2-model-name"
|
||||
```
|
||||
|
||||
#### Translators
|
||||
```yaml
|
||||
translators:
|
||||
anthropic:
|
||||
enabled: true
|
||||
passthrough_enabled: true
|
||||
max_message_size: 10485760 # 10MB
|
||||
inspector:
|
||||
enabled: false
|
||||
output_dir: "logs/inspector/anthropic"
|
||||
session_header: "X-Session-ID"
|
||||
```
|
||||
|
||||
#### Logging & Engineering
|
||||
```yaml
|
||||
logging:
|
||||
level: "info" # debug | info | warn | error
|
||||
format: "json" # json | text
|
||||
output: "stdout" # stdout | file
|
||||
|
||||
engineering:
|
||||
show_nerdstats: false # Memory/GC stats on shutdown
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
Pattern: `OLLA_<SECTION>_<KEY>` (uppercase, underscores)
|
||||
```bash
|
||||
OLLA_SERVER_PORT=8080
|
||||
OLLA_PROXY_ENGINE=olla
|
||||
OLLA_LOG_LEVEL=debug
|
||||
OLLA_CONFIG_FILE=/path/to/config.yaml
|
||||
```
|
||||
|
||||
## Claude Code Integration
|
||||
|
||||
Connect Claude Code to local models through Olla's Anthropic API translation:
|
||||
|
||||
```bash
|
||||
export ANTHROPIC_BASE_URL="http://localhost:40114/olla/anthropic"
|
||||
export ANTHROPIC_API_KEY="not-really-needed"
|
||||
export ANTHROPIC_MODEL="openai/gpt-oss-120b" # or your model name
|
||||
export ANTHROPIC_SMALL_FAST_MODEL="${ANTHROPIC_MODEL}"
|
||||
export ANTHROPIC_DEFAULT_HAIKU_MODEL="${ANTHROPIC_MODEL}"
|
||||
export ANTHROPIC_DEFAULT_SONNET_MODEL="${ANTHROPIC_MODEL}"
|
||||
export ANTHROPIC_DEFAULT_OPUS_MODEL="${ANTHROPIC_MODEL}"
|
||||
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
|
||||
export API_TIMEOUT_MS=3000000
|
||||
```
|
||||
|
||||
**Recommended models for Claude Code**: qwen2.5-coder:32b, deepseek-coder-v2, codellama:34b, llama3.3, qwen3:32b
|
||||
|
||||
### Docker Compose Quick Setup
|
||||
```yaml
|
||||
# compose.yaml
|
||||
services:
|
||||
ollama:
|
||||
image: ollama/ollama
|
||||
container_name: ollama
|
||||
ports: ["11434:11434"]
|
||||
volumes: ["ollama_data:/root/.ollama"]
|
||||
|
||||
olla:
|
||||
image: ghcr.io/thushan/olla:latest
|
||||
container_name: olla
|
||||
ports: ["40114:40114"]
|
||||
volumes: ["./olla.yaml:/app/config/config.local.yaml"]
|
||||
depends_on: [ollama]
|
||||
|
||||
volumes:
|
||||
ollama_data:
|
||||
```
|
||||
|
||||
For Docker: set `server.host: "0.0.0.0"` to bind all interfaces.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### System
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/internal/health` | GET | Health verification |
|
||||
| `/internal/status` | GET | System metrics |
|
||||
| `/internal/status/endpoints` | GET | Backend availability |
|
||||
| `/internal/status/models` | GET | Model registry |
|
||||
| `/internal/stats/models` | GET | Usage by model |
|
||||
| `/internal/stats/translators` | GET | Translator performance |
|
||||
| `/internal/process` | GET | Runtime info |
|
||||
|
||||
### Unified Models
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/olla/models` | GET | All models across all providers |
|
||||
|
||||
### Provider-Specific Routes
|
||||
| Provider | Route Prefix |
|
||||
|----------|-------------|
|
||||
| Ollama | `/olla/ollama/*` |
|
||||
| LM Studio | `/olla/lmstudio/*` |
|
||||
| OpenAI | `/olla/openai/*` |
|
||||
| vLLM | `/olla/vllm/*` |
|
||||
| SGLang | `/olla/sglang/*` |
|
||||
| LiteLLM | `/olla/litellm/*` |
|
||||
| llama.cpp | `/olla/llamacpp/*` |
|
||||
| Lemonade | `/olla/lemonade/*` |
|
||||
|
||||
### Anthropic Translation
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/olla/anthropic/v1/messages` | POST | Chat (streaming supported) |
|
||||
| `/olla/anthropic/v1/models` | GET | List models |
|
||||
| `/olla/anthropic/v1/messages/count_tokens` | POST | Token estimation |
|
||||
|
||||
### Response Headers
|
||||
- `X-Olla-Request-ID` — Unique request ID
|
||||
- `X-Olla-Endpoint` — Selected backend
|
||||
- `X-Olla-Model` — Model identifier
|
||||
- `X-Olla-Backend-Type` — Provider type
|
||||
- `X-Olla-Response-Time` — Processing duration
|
||||
- `X-Olla-Routing-Strategy` — Active strategy
|
||||
- `X-Olla-Routing-Decision` — routed/fallback/rejected
|
||||
- `X-Olla-Mode` — "passthrough" when native Anthropic format
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
```bash
|
||||
# Health check
|
||||
curl http://localhost:40114/internal/health
|
||||
|
||||
# List available models
|
||||
curl http://localhost:40114/olla/anthropic/v1/models | jq
|
||||
|
||||
# Check backend health
|
||||
curl http://localhost:40114/internal/status/endpoints | jq
|
||||
|
||||
# Test streaming
|
||||
curl -N -X POST http://localhost:40114/olla/anthropic/v1/messages \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"llama4:latest","max_tokens":50,"messages":[{"role":"user","content":"Count to 5"}],"stream":true}'
|
||||
|
||||
# Translator stats
|
||||
curl http://localhost:40114/internal/stats/translators | jq
|
||||
|
||||
# Debug logging
|
||||
OLLA_LOG_LEVEL=debug olla --config config.yaml
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
- **Port conflict**: Set `OLLA_SERVER_PORT` or change config
|
||||
- **Streaming broken**: Ensure `server.write_timeout: 0s`
|
||||
- **Docker can't connect**: Set `server.host: "0.0.0.0"`
|
||||
- **Model not found**: Check routing strategy (try `optimistic`), verify with `/olla/models`
|
||||
- **Slow responses**: Switch to `olla` engine, increase `stream_buffer_size`
|
||||
|
||||
## Supported Backends
|
||||
|
||||
| Backend | Type Key | Notes |
|
||||
|---------|----------|-------|
|
||||
| Ollama | `ollama` | Most common local setup |
|
||||
| LM Studio | `lm-studio` | GUI-based, model unification |
|
||||
| vLLM | `vllm` | High-perf, production grade |
|
||||
| vLLM-MLX | `vllm-mlx` | Apple Silicon via MLX |
|
||||
| SGLang | `sglang` | RadixAttention, vision |
|
||||
| llama.cpp | `llamacpp` | GGUF, CPU-first, edge |
|
||||
| Lemonade SDK | `lemonade` | AMD Ryzen AI |
|
||||
| LiteLLM | `litellm` | 100+ cloud providers |
|
||||
| Docker Model Runner | `docker-model-runner` | OCI model distribution |
|
||||
| OpenAI-compatible | `openai` | Generic fallback |
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
git clone https://github.com/thushan/olla.git && cd olla
|
||||
make deps # Install dependencies
|
||||
make dev # Build with hot-reload
|
||||
make test # Run tests
|
||||
make ready # Pre-commit checks (fmt + lint + test)
|
||||
make bench # Benchmarks
|
||||
```
|
||||
|
||||
Architecture: Hexagonal (ports & adapters) — `internal/core/` (domain), `internal/adapter/` (infra), `internal/app/` (HTTP handlers).
|
||||
@@ -0,0 +1,87 @@
|
||||
# Olla Documentation Reference Links
|
||||
|
||||
## Official Documentation
|
||||
- Home: https://thushan.github.io/olla/
|
||||
- Demo: https://thushan.github.io/olla/demo/
|
||||
- FAQ: https://thushan.github.io/olla/faq/
|
||||
- Usage: https://thushan.github.io/olla/usage/
|
||||
- About: https://thushan.github.io/olla/about/
|
||||
|
||||
## Getting Started
|
||||
- Installation: https://thushan.github.io/olla/getting-started/installation/
|
||||
- Quickstart: https://thushan.github.io/olla/getting-started/quickstart/
|
||||
|
||||
## Concepts
|
||||
- Overview: https://thushan.github.io/olla/concepts/overview/
|
||||
- Load Balancing: https://thushan.github.io/olla/concepts/load-balancing/
|
||||
- Model Routing: https://thushan.github.io/olla/concepts/model-routing/
|
||||
- Model Aliases: https://thushan.github.io/olla/concepts/model-aliases/
|
||||
- Model Unification: https://thushan.github.io/olla/concepts/model-unification/
|
||||
- Health Checking: https://thushan.github.io/olla/concepts/health-checking/
|
||||
- API Translation: https://thushan.github.io/olla/concepts/api-translation/
|
||||
- Proxy Engines: https://thushan.github.io/olla/concepts/proxy-engines/
|
||||
- Proxy Profiles: https://thushan.github.io/olla/concepts/proxy-profiles/
|
||||
- Profile System: https://thushan.github.io/olla/concepts/profile-system/
|
||||
- Provider Metrics: https://thushan.github.io/olla/concepts/provider-metrics/
|
||||
|
||||
## Configuration
|
||||
- Overview: https://thushan.github.io/olla/configuration/overview/
|
||||
- Filters: https://thushan.github.io/olla/configuration/filters/
|
||||
- Reference: https://thushan.github.io/olla/configuration/reference/
|
||||
- Examples: https://thushan.github.io/olla/configuration/examples/
|
||||
- Best Practices - Configuration: https://thushan.github.io/olla/configuration/practices/configuration/
|
||||
- Best Practices - Security: https://thushan.github.io/olla/configuration/practices/security/
|
||||
- Best Practices - Performance: https://thushan.github.io/olla/configuration/practices/performance/
|
||||
- Best Practices - Monitoring: https://thushan.github.io/olla/configuration/practices/monitoring/
|
||||
|
||||
## Integrations
|
||||
- Overview: https://thushan.github.io/olla/integrations/overview/
|
||||
|
||||
### Backend
|
||||
- Ollama: https://thushan.github.io/olla/integrations/backend/ollama/
|
||||
- LM Studio: https://thushan.github.io/olla/integrations/backend/lmstudio/
|
||||
- vLLM: https://thushan.github.io/olla/integrations/backend/vllm/
|
||||
- vLLM-MLX: https://thushan.github.io/olla/integrations/backend/vllm-mlx/
|
||||
- SGLang: https://thushan.github.io/olla/integrations/backend/sglang/
|
||||
- Lemonade SDK: https://thushan.github.io/olla/integrations/backend/lemonade/
|
||||
- LiteLLM: https://thushan.github.io/olla/integrations/backend/litellm/
|
||||
- llama.cpp: https://thushan.github.io/olla/integrations/backend/llamacpp/
|
||||
- Docker Model Runner: https://thushan.github.io/olla/integrations/backend/docker-model-runner/
|
||||
|
||||
### Frontend
|
||||
- OpenWebUI: https://thushan.github.io/olla/integrations/frontend/openwebui/
|
||||
- OpenWebUI (OpenAI): https://thushan.github.io/olla/integrations/frontend/openwebui-openai/
|
||||
- Claude Code: https://thushan.github.io/olla/integrations/frontend/claude-code/
|
||||
- OpenCode: https://thushan.github.io/olla/integrations/frontend/opencode/
|
||||
- Crush CLI: https://thushan.github.io/olla/integrations/frontend/crush-cli/
|
||||
|
||||
### API Translation
|
||||
- Anthropic: https://thushan.github.io/olla/integrations/api-translation/anthropic/
|
||||
|
||||
## API Reference
|
||||
- Overview: https://thushan.github.io/olla/api-reference/overview/
|
||||
- System Endpoints: https://thushan.github.io/olla/api-reference/system/
|
||||
- Models API: https://thushan.github.io/olla/api-reference/models/
|
||||
|
||||
## Compare
|
||||
- Overview: https://thushan.github.io/olla/compare/overview/
|
||||
- Integration Patterns: https://thushan.github.io/olla/compare/integration-patterns/
|
||||
- vs GPUStack: https://thushan.github.io/olla/compare/gpustack/
|
||||
- vs LiteLLM: https://thushan.github.io/olla/compare/litellm/
|
||||
- vs LocalAI: https://thushan.github.io/olla/compare/localai/
|
||||
|
||||
## Development
|
||||
- Overview: https://thushan.github.io/olla/development/overview/
|
||||
- Setup: https://thushan.github.io/olla/development/setup/
|
||||
- Architecture: https://thushan.github.io/olla/development/architecture/
|
||||
- Patterns: https://thushan.github.io/olla/development/patterns/
|
||||
- Circuit Breaker: https://thushan.github.io/olla/development/circuit-breaker/
|
||||
- Contributing: https://thushan.github.io/olla/development/contributing/
|
||||
- Testing: https://thushan.github.io/olla/development/testing/
|
||||
- Benchmarking: https://thushan.github.io/olla/development/benchmarking/
|
||||
- Anthropic Inspector: https://thushan.github.io/olla/notes/anthropic-inspector/
|
||||
|
||||
## GitHub
|
||||
- Repository: https://github.com/thushan/olla
|
||||
- Releases: https://github.com/thushan/olla/releases
|
||||
- Issues: https://github.com/thushan/olla/issues
|
||||
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,242 @@
|
||||
---
|
||||
name: acquiring-disk-image-with-dd-and-dcfldd
|
||||
description: Create forensically sound bit-for-bit disk images using dd and dcfldd while preserving evidence integrity through
|
||||
hash verification.
|
||||
domain: cybersecurity
|
||||
subdomain: digital-forensics
|
||||
tags:
|
||||
- forensics
|
||||
- disk-imaging
|
||||
- evidence-acquisition
|
||||
- dd
|
||||
- dcfldd
|
||||
- hash-verification
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- RS.AN-01
|
||||
- RS.AN-03
|
||||
- DE.AE-02
|
||||
- RS.MA-01
|
||||
---
|
||||
|
||||
# Acquiring Disk Image with dd and dcfldd
|
||||
|
||||
## When to Use
|
||||
- When you need to create a forensic copy of a suspect drive for investigation
|
||||
- During incident response when preserving volatile disk evidence before analysis
|
||||
- When law enforcement or legal proceedings require a verified bit-for-bit copy
|
||||
- Before performing any destructive analysis on a storage device
|
||||
- When acquiring images from physical drives, USB devices, or memory cards
|
||||
|
||||
## Prerequisites
|
||||
- Linux-based forensic workstation (SIFT, Kali, or any Linux distro)
|
||||
- `dd` (pre-installed on all Linux systems) or `dcfldd` (enhanced forensic version)
|
||||
- Write-blocker hardware or software write-blocking configured
|
||||
- Destination drive with sufficient storage (larger than source)
|
||||
- Root/sudo privileges on the forensic workstation
|
||||
- SHA-256 or MD5 hashing utilities (`sha256sum`, `md5sum`)
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Identify the Target Device and Enable Write Protection
|
||||
|
||||
```bash
|
||||
# List all connected block devices to identify the target
|
||||
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,MODEL
|
||||
|
||||
# Verify the device details
|
||||
fdisk -l /dev/sdb
|
||||
|
||||
# Enable software write-blocking (if no hardware blocker)
|
||||
blockdev --setro /dev/sdb
|
||||
|
||||
# Verify read-only status
|
||||
blockdev --getro /dev/sdb
|
||||
# Output: 1 (means read-only is enabled)
|
||||
|
||||
# Alternatively, use udev rules for persistent write-blocking
|
||||
echo 'SUBSYSTEM=="block", ATTRS{serial}=="WD-WCAV5H861234", ATTR{ro}="1"' > /etc/udev/rules.d/99-writeblock.rules
|
||||
udevadm control --reload-rules
|
||||
```
|
||||
|
||||
### Step 2: Prepare the Destination and Document the Source
|
||||
|
||||
```bash
|
||||
# Create case directory structure
|
||||
mkdir -p /cases/case-2024-001/{images,hashes,logs,notes}
|
||||
|
||||
# Document source drive information
|
||||
hdparm -I /dev/sdb > /cases/case-2024-001/notes/source_drive_info.txt
|
||||
|
||||
# Record the serial number and model
|
||||
smartctl -i /dev/sdb >> /cases/case-2024-001/notes/source_drive_info.txt
|
||||
|
||||
# Pre-hash the source device
|
||||
sha256sum /dev/sdb | tee /cases/case-2024-001/hashes/source_hash_before.txt
|
||||
```
|
||||
|
||||
### Step 3: Acquire the Image Using dd
|
||||
|
||||
```bash
|
||||
# Basic dd acquisition with progress and error handling
|
||||
dd if=/dev/sdb of=/cases/case-2024-001/images/evidence.dd \
|
||||
bs=4096 \
|
||||
conv=noerror,sync \
|
||||
status=progress 2>&1 | tee /cases/case-2024-001/logs/dd_acquisition.log
|
||||
|
||||
# For compressed images to save space
|
||||
dd if=/dev/sdb bs=4096 conv=noerror,sync status=progress | \
|
||||
gzip -c > /cases/case-2024-001/images/evidence.dd.gz
|
||||
|
||||
# Using dd with a specific count for partial acquisition
|
||||
dd if=/dev/sdb of=/cases/case-2024-001/images/first_1gb.dd \
|
||||
bs=1M count=1024 status=progress
|
||||
```
|
||||
|
||||
### Step 4: Acquire Using dcfldd (Preferred Forensic Method)
|
||||
|
||||
```bash
|
||||
# Install dcfldd if not present
|
||||
apt-get install dcfldd
|
||||
|
||||
# Acquire image with built-in hashing and split output
|
||||
dcfldd if=/dev/sdb \
|
||||
of=/cases/case-2024-001/images/evidence.dd \
|
||||
hash=sha256,md5 \
|
||||
hashwindow=1G \
|
||||
hashlog=/cases/case-2024-001/hashes/acquisition_hashes.txt \
|
||||
bs=4096 \
|
||||
conv=noerror,sync \
|
||||
errlog=/cases/case-2024-001/logs/dcfldd_errors.log
|
||||
|
||||
# Split large images into manageable segments
|
||||
dcfldd if=/dev/sdb \
|
||||
of=/cases/case-2024-001/images/evidence.dd \
|
||||
hash=sha256 \
|
||||
hashlog=/cases/case-2024-001/hashes/split_hashes.txt \
|
||||
bs=4096 \
|
||||
split=2G \
|
||||
splitformat=aa
|
||||
|
||||
# Acquire with verification pass
|
||||
dcfldd if=/dev/sdb \
|
||||
of=/cases/case-2024-001/images/evidence.dd \
|
||||
hash=sha256 \
|
||||
hashlog=/cases/case-2024-001/hashes/verification.txt \
|
||||
vf=/cases/case-2024-001/images/evidence.dd \
|
||||
verifylog=/cases/case-2024-001/logs/verify.log
|
||||
```
|
||||
|
||||
### Step 5: Verify Image Integrity
|
||||
|
||||
```bash
|
||||
# Hash the acquired image
|
||||
sha256sum /cases/case-2024-001/images/evidence.dd | \
|
||||
tee /cases/case-2024-001/hashes/image_hash.txt
|
||||
|
||||
# Compare source and image hashes
|
||||
diff <(sha256sum /dev/sdb | awk '{print $1}') \
|
||||
<(sha256sum /cases/case-2024-001/images/evidence.dd | awk '{print $1}')
|
||||
|
||||
# If using split images, verify each segment
|
||||
sha256sum /cases/case-2024-001/images/evidence.dd.* | \
|
||||
tee /cases/case-2024-001/hashes/split_image_hashes.txt
|
||||
|
||||
# Re-hash source to confirm no changes occurred
|
||||
sha256sum /dev/sdb | tee /cases/case-2024-001/hashes/source_hash_after.txt
|
||||
diff /cases/case-2024-001/hashes/source_hash_before.txt \
|
||||
/cases/case-2024-001/hashes/source_hash_after.txt
|
||||
```
|
||||
|
||||
### Step 6: Document the Acquisition Process
|
||||
|
||||
```bash
|
||||
# Generate acquisition report
|
||||
cat << 'EOF' > /cases/case-2024-001/notes/acquisition_report.txt
|
||||
DISK IMAGE ACQUISITION REPORT
|
||||
==============================
|
||||
Case Number: 2024-001
|
||||
Date/Time: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||
Examiner: [Name]
|
||||
|
||||
Source Device: /dev/sdb
|
||||
Model: [from hdparm output]
|
||||
Serial: [from hdparm output]
|
||||
Size: [from fdisk output]
|
||||
|
||||
Acquisition Tool: dcfldd v1.9.1
|
||||
Block Size: 4096
|
||||
Write Blocker: [Hardware/Software model]
|
||||
|
||||
Image File: evidence.dd
|
||||
Image Hash (SHA-256): [from hash file]
|
||||
Source Hash (SHA-256): [from hash file]
|
||||
Hash Match: YES/NO
|
||||
|
||||
Errors During Acquisition: [from error log]
|
||||
EOF
|
||||
|
||||
# Compress logs for archival
|
||||
tar -czf /cases/case-2024-001/acquisition_package.tar.gz \
|
||||
/cases/case-2024-001/hashes/ \
|
||||
/cases/case-2024-001/logs/ \
|
||||
/cases/case-2024-001/notes/
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Concept | Description |
|
||||
|---------|-------------|
|
||||
| Bit-for-bit copy | Exact replica of source including unallocated space and slack space |
|
||||
| Write blocker | Hardware or software mechanism preventing writes to evidence media |
|
||||
| Hash verification | Cryptographic hash comparing source and image to prove integrity |
|
||||
| Block size (bs) | Transfer chunk size affecting speed; 4096 or 64K typical for forensics |
|
||||
| conv=noerror,sync | Continue on read errors and pad with zeros to maintain offset alignment |
|
||||
| Chain of custody | Documented trail proving evidence has not been tampered with |
|
||||
| Split imaging | Breaking large images into smaller files for storage and transport |
|
||||
| Raw/dd format | Bit-for-bit image format without metadata container overhead |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| dd | Standard Unix disk duplication utility for raw imaging |
|
||||
| dcfldd | DoD Computer Forensics Laboratory enhanced version of dd with hashing |
|
||||
| dc3dd | Another forensic dd variant from the DoD Cyber Crime Center |
|
||||
| sha256sum | SHA-256 hash calculation for integrity verification |
|
||||
| blockdev | Linux command to set block device read-only mode |
|
||||
| hdparm | Drive identification and parameter reporting |
|
||||
| smartctl | S.M.A.R.T. data retrieval for drive health and identification |
|
||||
| lsblk | Block device enumeration and identification |
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
**Scenario 1: Acquiring a Suspect Laptop Hard Drive**
|
||||
Connect the drive via a Tableau T35u hardware write-blocker, identify as `/dev/sdb`, use dcfldd with SHA-256 hashing, split into 4GB segments for DVD archival, verify hashes match, document in case notes.
|
||||
|
||||
**Scenario 2: Imaging a USB Flash Drive from a Compromised Workstation**
|
||||
Use software write-blocking with `blockdev --setro`, acquire with dcfldd including MD5 and SHA-256 dual hashing, image is small enough for single file, verify and store on encrypted case drive.
|
||||
|
||||
**Scenario 3: Remote Acquisition Over Network**
|
||||
Use dd piped through netcat or ssh for remote acquisition: `ssh root@remote "dd if=/dev/sda bs=4096" | dd of=remote_image.dd bs=4096`, hash both ends independently to verify transfer integrity.
|
||||
|
||||
**Scenario 4: Acquiring from a Failing Drive**
|
||||
Use `ddrescue` first to recover readable sectors, then use dd with `conv=noerror,sync` to fill gaps with zeros, document which sectors were unreadable in the error log.
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
Acquisition Summary:
|
||||
Source: /dev/sdb (500GB Western Digital WD5000AAKX)
|
||||
Destination: /cases/case-2024-001/images/evidence.dd
|
||||
Tool: dcfldd 1.9.1
|
||||
Block Size: 4096 bytes
|
||||
Duration: 2h 15m 32s
|
||||
Bytes Copied: 500,107,862,016
|
||||
Errors: 0 bad sectors
|
||||
Source SHA-256: a3f2b8c9d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1
|
||||
Image SHA-256: a3f2b8c9d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1
|
||||
Verification: PASSED - Hashes match
|
||||
```
|
||||
@@ -0,0 +1,99 @@
|
||||
# API Reference: dd and dcfldd Disk Imaging
|
||||
|
||||
## dd - Standard Unix Disk Duplication
|
||||
|
||||
### Basic Syntax
|
||||
```bash
|
||||
dd if=<source> of=<destination> [options]
|
||||
```
|
||||
|
||||
### Key Options
|
||||
| Flag | Description | Example |
|
||||
|------|-------------|---------|
|
||||
| `if=` | Input file (source device) | `if=/dev/sdb` |
|
||||
| `of=` | Output file (destination image) | `of=evidence.dd` |
|
||||
| `bs=` | Block size for read/write | `bs=4096` (forensic standard) |
|
||||
| `count=` | Number of blocks to copy | `count=1024` |
|
||||
| `skip=` | Skip N blocks from input start | `skip=2048` |
|
||||
| `conv=` | Conversion options | `conv=noerror,sync` |
|
||||
| `status=` | Transfer statistics level | `status=progress` |
|
||||
|
||||
### conv= Values
|
||||
- `noerror` - Continue on read errors (do not abort)
|
||||
- `sync` - Pad input blocks with zeros on error (preserves offset alignment)
|
||||
- `notrunc` - Do not truncate output file
|
||||
|
||||
### Output Format
|
||||
```
|
||||
500107862016 bytes (500 GB, 466 GiB) copied, 8132.45 s, 61.5 MB/s
|
||||
976773168+0 records in
|
||||
976773168+0 records out
|
||||
```
|
||||
|
||||
## dcfldd - DoD Forensic dd
|
||||
|
||||
### Basic Syntax
|
||||
```bash
|
||||
dcfldd if=<source> of=<destination> [options]
|
||||
```
|
||||
|
||||
### Extended Options
|
||||
| Flag | Description | Example |
|
||||
|------|-------------|---------|
|
||||
| `hash=` | Hash algorithm(s) | `hash=sha256,md5` |
|
||||
| `hashlog=` | File for hash output | `hashlog=hashes.txt` |
|
||||
| `hashwindow=` | Hash every N bytes | `hashwindow=1G` |
|
||||
| `hashconv=` | Hash before or after conversion | `hashconv=after` |
|
||||
| `errlog=` | Error log file | `errlog=errors.log` |
|
||||
| `split=` | Split output into chunks | `split=2G` |
|
||||
| `splitformat=` | Suffix format for split files | `splitformat=aa` |
|
||||
| `vf=` | Verification file | `vf=evidence.dd` |
|
||||
| `verifylog=` | Verification result log | `verifylog=verify.log` |
|
||||
|
||||
### Output Format
|
||||
```
|
||||
Total (sha256): a3f2b8c9d4e5f6a7b8c9d0e1f2a3b4c5...
|
||||
1024+0 records in
|
||||
1024+0 records out
|
||||
```
|
||||
|
||||
## sha256sum - Hash Verification
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
sha256sum <file_or_device>
|
||||
sha256sum -c <checksum_file>
|
||||
```
|
||||
|
||||
### Output Format
|
||||
```
|
||||
a3f2b8c9d4e5f6... /dev/sdb
|
||||
a3f2b8c9d4e5f6... evidence.dd
|
||||
```
|
||||
|
||||
## blockdev - Write Protection
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
blockdev --setro <device> # Set read-only
|
||||
blockdev --setrw <device> # Set read-write
|
||||
blockdev --getro <device> # Check: 1=RO, 0=RW
|
||||
blockdev --getsize64 <device> # Size in bytes
|
||||
```
|
||||
|
||||
## lsblk - Block Device Enumeration
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,MODEL,SERIAL,RO
|
||||
lsblk -J # JSON output
|
||||
lsblk -p # Full device paths
|
||||
```
|
||||
|
||||
## hdparm - Drive Identification
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
hdparm -I <device> # Detailed drive info
|
||||
hdparm -i <device> # Summary identification
|
||||
```
|
||||
@@ -0,0 +1,181 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Forensic disk image acquisition agent using dd and dcfldd with hash verification."""
|
||||
|
||||
import shlex
|
||||
import subprocess
|
||||
import hashlib
|
||||
import os
|
||||
import datetime
|
||||
import json
|
||||
|
||||
|
||||
def run_cmd(cmd, capture=True):
|
||||
"""Execute a command and return output."""
|
||||
if isinstance(cmd, str):
|
||||
cmd = shlex.split(cmd)
|
||||
result = subprocess.run(cmd, capture_output=capture, text=True, timeout=120)
|
||||
return result.stdout.strip(), result.stderr.strip(), result.returncode
|
||||
|
||||
|
||||
def list_block_devices():
|
||||
"""Enumerate connected block devices."""
|
||||
stdout, _, rc = run_cmd("lsblk -J -o NAME,SIZE,TYPE,MOUNTPOINT,MODEL,SERIAL,RO")
|
||||
if rc == 0 and stdout:
|
||||
return json.loads(stdout)
|
||||
return {"blockdevices": []}
|
||||
|
||||
|
||||
def check_write_protection(device):
|
||||
"""Verify a device is set to read-only mode."""
|
||||
stdout, _, rc = run_cmd(f"blockdev --getro {device}")
|
||||
if rc == 0:
|
||||
return stdout.strip() == "1"
|
||||
return False
|
||||
|
||||
|
||||
def enable_write_protection(device):
|
||||
"""Enable software write-blocking on the target device."""
|
||||
_, _, rc = run_cmd(f"blockdev --setro {device}")
|
||||
if rc != 0:
|
||||
print(f"[ERROR] Failed to set {device} read-only. Run as root.")
|
||||
return False
|
||||
if check_write_protection(device):
|
||||
print(f"[OK] Write protection enabled on {device}")
|
||||
return True
|
||||
print(f"[ERROR] Write protection verification failed for {device}")
|
||||
return False
|
||||
|
||||
|
||||
def compute_hash(path, algorithm="sha256", block_size=65536):
|
||||
"""Compute the SHA-256 or MD5 hash of a file or device."""
|
||||
h = hashlib.new(algorithm)
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
while True:
|
||||
block = f.read(block_size)
|
||||
if not block:
|
||||
break
|
||||
h.update(block)
|
||||
except PermissionError:
|
||||
print(f"[ERROR] Permission denied reading {path}. Run as root.")
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
print(f"[ERROR] Path not found: {path}")
|
||||
return None
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def acquire_with_dd(source, destination, block_size=4096, log_file=None):
|
||||
"""Acquire a forensic image using dd with error handling."""
|
||||
dd_cmd = [
|
||||
"dd", f"if={source}", f"of={destination}",
|
||||
f"bs={block_size}", "conv=noerror,sync", "status=progress"
|
||||
]
|
||||
print(f"[*] Starting dd acquisition: {source} -> {destination}")
|
||||
print(f"[*] Block size: {block_size}")
|
||||
start = datetime.datetime.utcnow()
|
||||
if log_file:
|
||||
dd_proc = subprocess.run(dd_cmd, capture_output=True, text=True, timeout=120)
|
||||
combined = (dd_proc.stdout or "") + (dd_proc.stderr or "")
|
||||
with open(log_file, "w") as lf:
|
||||
lf.write(combined)
|
||||
rc = dd_proc.returncode
|
||||
else:
|
||||
result = subprocess.run(dd_cmd, text=True, timeout=120)
|
||||
rc = result.returncode
|
||||
elapsed = (datetime.datetime.utcnow() - start).total_seconds()
|
||||
print(f"[*] Acquisition completed in {elapsed:.1f} seconds (rc={rc})")
|
||||
return rc == 0
|
||||
|
||||
|
||||
def acquire_with_dcfldd(source, destination, hash_alg="sha256", hash_log=None,
|
||||
error_log=None, block_size=4096, split_size=None):
|
||||
"""Acquire a forensic image using dcfldd with built-in hashing."""
|
||||
cmd = [
|
||||
"dcfldd", f"if={source}", f"of={destination}",
|
||||
f"bs={block_size}", "conv=noerror,sync",
|
||||
f"hash={hash_alg}", "hashwindow=1G",
|
||||
]
|
||||
if hash_log:
|
||||
cmd.append(f"hashlog={hash_log}")
|
||||
if error_log:
|
||||
cmd.append(f"errlog={error_log}")
|
||||
if split_size:
|
||||
cmd.extend([f"split={split_size}", "splitformat=aa"])
|
||||
print(f"[*] Starting dcfldd acquisition: {source} -> {destination}")
|
||||
start = datetime.datetime.utcnow()
|
||||
result = subprocess.run(cmd, text=True, timeout=120)
|
||||
rc = result.returncode
|
||||
elapsed = (datetime.datetime.utcnow() - start).total_seconds()
|
||||
print(f"[*] dcfldd completed in {elapsed:.1f} seconds (rc={rc})")
|
||||
return rc == 0
|
||||
|
||||
|
||||
def verify_image(source, image_path, algorithm="sha256"):
|
||||
"""Verify image integrity by comparing hashes of source and acquired image."""
|
||||
print(f"[*] Computing {algorithm} hash of source: {source}")
|
||||
source_hash = compute_hash(source, algorithm)
|
||||
print(f" Source hash: {source_hash}")
|
||||
print(f"[*] Computing {algorithm} hash of image: {image_path}")
|
||||
image_hash = compute_hash(image_path, algorithm)
|
||||
print(f" Image hash: {image_hash}")
|
||||
if source_hash and image_hash:
|
||||
match = source_hash == image_hash
|
||||
status = "PASSED" if match else "FAILED"
|
||||
print(f"[{'OK' if match else 'FAIL'}] Verification: {status}")
|
||||
return match, source_hash, image_hash
|
||||
return False, source_hash, image_hash
|
||||
|
||||
|
||||
def generate_report(case_dir, source_device, image_path, tool_used,
|
||||
source_hash, image_hash, verified, elapsed_seconds=0):
|
||||
"""Generate a forensic acquisition report."""
|
||||
report = {
|
||||
"report_type": "Disk Image Acquisition",
|
||||
"timestamp": datetime.datetime.utcnow().isoformat() + "Z",
|
||||
"case_directory": case_dir,
|
||||
"source_device": source_device,
|
||||
"image_file": image_path,
|
||||
"acquisition_tool": tool_used,
|
||||
"block_size": 4096,
|
||||
"source_hash_sha256": source_hash,
|
||||
"image_hash_sha256": image_hash,
|
||||
"hash_verified": verified,
|
||||
"duration_seconds": elapsed_seconds,
|
||||
}
|
||||
report_path = os.path.join(case_dir, "acquisition_report.json")
|
||||
with open(report_path, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
print(f"[*] Report saved to {report_path}")
|
||||
return report
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Forensic Disk Image Acquisition Agent")
|
||||
print("Tools: dd / dcfldd with SHA-256 verification")
|
||||
print("=" * 60)
|
||||
|
||||
# Demo: list block devices
|
||||
print("\n[*] Enumerating block devices...")
|
||||
devices = list_block_devices()
|
||||
for dev in devices.get("blockdevices", []):
|
||||
name = dev.get("name", "?")
|
||||
size = dev.get("size", "?")
|
||||
dtype = dev.get("type", "?")
|
||||
model = dev.get("model", "N/A")
|
||||
ro = "RO" if dev.get("ro") else "RW"
|
||||
print(f" /dev/{name} {size} {dtype} {model} [{ro}]")
|
||||
|
||||
# Demo workflow (dry run)
|
||||
demo_source = "/dev/sdb"
|
||||
demo_case = "/cases/demo-case/images"
|
||||
demo_image = os.path.join(demo_case, "evidence.dd")
|
||||
|
||||
print(f"\n[DEMO] Acquisition workflow for {demo_source}:")
|
||||
print(f" 1. Enable write protection: blockdev --setro {demo_source}")
|
||||
print(f" 2. Acquire with dcfldd: dcfldd if={demo_source} of={demo_image} "
|
||||
f"hash=sha256 hashwindow=1G bs=4096 conv=noerror,sync")
|
||||
print(f" 3. Verify: compare SHA-256 of {demo_source} and {demo_image}")
|
||||
print(f" 4. Generate acquisition report with chain-of-custody metadata")
|
||||
print("\n[*] Agent ready. Provide a source device and case directory to begin.")
|
||||
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
name: analyzing-active-directory-acl-abuse
|
||||
description: Detect dangerous ACL misconfigurations in Active Directory using ldap3 to identify GenericAll, WriteDACL, and
|
||||
WriteOwner abuse paths
|
||||
domain: cybersecurity
|
||||
subdomain: identity-security
|
||||
tags:
|
||||
- active-directory
|
||||
- acl-abuse
|
||||
- ldap
|
||||
- privilege-escalation
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- PR.AA-01
|
||||
- PR.AA-05
|
||||
- PR.AA-06
|
||||
---
|
||||
|
||||
|
||||
# Analyzing Active Directory ACL Abuse
|
||||
|
||||
## Overview
|
||||
|
||||
Active Directory Access Control Lists (ACLs) define permissions on AD objects through Discretionary Access Control Lists (DACLs) containing Access Control Entries (ACEs). Misconfigured ACEs can grant non-privileged users dangerous permissions such as GenericAll (full control), WriteDACL (modify permissions), WriteOwner (take ownership), and GenericWrite (modify attributes) on sensitive objects like Domain Admins groups, domain controllers, or GPOs.
|
||||
|
||||
This skill uses the ldap3 Python library to connect to a Domain Controller, query objects with their nTSecurityDescriptor attribute, parse the binary security descriptor into SDDL (Security Descriptor Definition Language) format, and identify ACEs that grant dangerous permissions to non-administrative principals. These misconfigurations are the basis for ACL-based attack paths discovered by tools like BloodHound.
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing active directory acl abuse
|
||||
- When building detection rules or threat hunting queries for this domain
|
||||
- When SOC analysts need structured procedures for this analysis type
|
||||
- When validating security monitoring coverage for related attack techniques
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.9 or later with ldap3 library (`pip install ldap3`)
|
||||
- Domain user credentials with read access to AD objects
|
||||
- Network connectivity to Domain Controller on port 389 (LDAP) or 636 (LDAPS)
|
||||
- Understanding of Active Directory security model and SDDL format
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Connect to Domain Controller**: Establish an LDAP connection using ldap3 with NTLM or simple authentication. Use LDAPS (port 636) for encrypted connections in production.
|
||||
|
||||
2. **Query target objects**: Search the target OU or entire domain for objects including users, groups, computers, and OUs. Request the `nTSecurityDescriptor`, `distinguishedName`, `objectClass`, and `sAMAccountName` attributes.
|
||||
|
||||
3. **Parse security descriptors**: Convert the binary nTSecurityDescriptor into its SDDL string representation. Parse each ACE in the DACL to extract the trustee SID, access mask, and ACE type (allow/deny).
|
||||
|
||||
4. **Resolve SIDs to principals**: Map security identifiers (SIDs) to human-readable account names using LDAP lookups against the domain. Identify well-known SIDs for built-in groups.
|
||||
|
||||
5. **Check for dangerous permissions**: Compare each ACE's access mask against dangerous permission bitmasks: GenericAll (0x10000000), WriteDACL (0x00040000), WriteOwner (0x00080000), GenericWrite (0x40000000), and WriteProperty for specific extended rights.
|
||||
|
||||
6. **Filter non-admin trustees**: Exclude expected administrative trustees (Domain Admins, Enterprise Admins, SYSTEM, Administrators) and flag ACEs where non-privileged users or groups hold dangerous permissions.
|
||||
|
||||
7. **Map attack paths**: For each finding, document the potential attack chain (e.g., GenericAll on user allows password reset, WriteDACL on group allows adding self to group).
|
||||
|
||||
8. **Generate remediation report**: Output a JSON report with all dangerous ACEs, affected objects, non-admin trustees, and recommended remediation steps.
|
||||
|
||||
## Expected Output
|
||||
|
||||
```json
|
||||
{
|
||||
"domain": "corp.example.com",
|
||||
"objects_scanned": 1247,
|
||||
"dangerous_aces_found": 8,
|
||||
"findings": [
|
||||
{
|
||||
"severity": "critical",
|
||||
"target_object": "CN=Domain Admins,CN=Users,DC=corp,DC=example,DC=com",
|
||||
"target_type": "group",
|
||||
"trustee": "CORP\\helpdesk-team",
|
||||
"permission": "GenericAll",
|
||||
"access_mask": "0x10000000",
|
||||
"ace_type": "ACCESS_ALLOWED",
|
||||
"attack_path": "GenericAll on Domain Admins group allows adding arbitrary members",
|
||||
"remediation": "Remove GenericAll ACE for helpdesk-team on Domain Admins"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,94 @@
|
||||
# Active Directory ACL Abuse API Reference
|
||||
|
||||
## ldap3 Python Connection
|
||||
|
||||
```python
|
||||
from ldap3 import Server, Connection, ALL, NTLM, SUBTREE
|
||||
|
||||
server = Server("192.168.1.10", get_info=ALL, use_ssl=False)
|
||||
conn = Connection(server, user="DOMAIN\\user", password="pass",
|
||||
authentication=NTLM, auto_bind=True)
|
||||
|
||||
# Search with nTSecurityDescriptor
|
||||
conn.search(
|
||||
"DC=corp,DC=example,DC=com",
|
||||
"(objectClass=group)",
|
||||
search_scope=SUBTREE,
|
||||
attributes=["distinguishedName", "sAMAccountName",
|
||||
"objectClass", "nTSecurityDescriptor"],
|
||||
)
|
||||
```
|
||||
|
||||
## SDDL ACE Format
|
||||
|
||||
```
|
||||
ACE String: (ace_type;ace_flags;rights;object_guid;inherit_guid;trustee_sid)
|
||||
Example: (A;;GA;;;S-1-5-21-xxx-512)
|
||||
```
|
||||
|
||||
| Component | Description |
|
||||
|-----------|-------------|
|
||||
| `A` | Access Allowed |
|
||||
| `D` | Access Denied |
|
||||
| `OA` | Object Access Allowed |
|
||||
| `GA` | Generic All |
|
||||
| `GW` | Generic Write |
|
||||
| `WD` | Write DACL |
|
||||
| `WO` | Write Owner |
|
||||
|
||||
## Dangerous Permission Bitmasks
|
||||
|
||||
| Permission | Hex Mask | Risk |
|
||||
|-----------|----------|------|
|
||||
| GenericAll | `0x10000000` | Full control over object |
|
||||
| GenericWrite | `0x40000000` | Modify all writable attributes |
|
||||
| WriteDACL | `0x00040000` | Modify object permissions |
|
||||
| WriteOwner | `0x00080000` | Take object ownership |
|
||||
| WriteProperty | `0x00000020` | Write specific properties |
|
||||
| ExtendedRight | `0x00000100` | Extended rights (password reset, etc.) |
|
||||
| Self | `0x00000008` | Self-membership modification |
|
||||
| Delete | `0x00010000` | Delete the object |
|
||||
|
||||
## BloodHound Cypher Queries for ACL Paths
|
||||
|
||||
```cypher
|
||||
-- Find all users with GenericAll on Domain Admins
|
||||
MATCH p=(n:User)-[r:GenericAll]->(g:Group {name:"DOMAIN ADMINS@CORP.COM"})
|
||||
RETURN p
|
||||
|
||||
-- Find WriteDACL paths from non-admins to high-value targets
|
||||
MATCH (n:User {admincount:false})
|
||||
MATCH p=allShortestPaths((n)-[r:WriteDacl|WriteOwner|GenericAll*1..]->(m:Group))
|
||||
WHERE m.highvalue = true
|
||||
RETURN p
|
||||
|
||||
-- Find GenericWrite on computers for RBCD attacks
|
||||
MATCH p=(n:User)-[r:GenericWrite]->(c:Computer)
|
||||
WHERE NOT n.admincount
|
||||
RETURN n.name, c.name
|
||||
|
||||
-- Enumerate all outbound ACL edges for a principal
|
||||
MATCH p=(n {name:"HELPDESK@CORP.COM"})-[r:GenericAll|GenericWrite|WriteDacl|WriteOwner|Owns]->(m)
|
||||
RETURN type(r), m.name, labels(m)
|
||||
|
||||
-- Find shortest ACL abuse path to Domain Admin
|
||||
MATCH (n:User {name:"JSMITH@CORP.COM"})
|
||||
MATCH (da:Group {name:"DOMAIN ADMINS@CORP.COM"})
|
||||
MATCH p=shortestPath((n)-[r:MemberOf|GenericAll|GenericWrite|WriteDacl|WriteOwner|Owns|ForceChangePassword*1..]->(da))
|
||||
RETURN p
|
||||
```
|
||||
|
||||
## PowerView Commands for ACL Enumeration
|
||||
|
||||
```powershell
|
||||
# Get ACL for Domain Admins group
|
||||
Get-DomainObjectAcl -Identity "Domain Admins" -ResolveGUIDs
|
||||
|
||||
# Find interesting ACEs for non-admin users
|
||||
Find-InterestingDomainAcl -ResolveGUIDs | Where-Object {
|
||||
$_.ActiveDirectoryRights -match "GenericAll|WriteDacl|WriteOwner"
|
||||
}
|
||||
|
||||
# Get ACL for specific OU
|
||||
Get-DomainObjectAcl -SearchBase "OU=Servers,DC=corp,DC=com" -ResolveGUIDs
|
||||
```
|
||||
@@ -0,0 +1,258 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Active Directory ACL abuse detection using ldap3 to find dangerous permissions."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import struct
|
||||
|
||||
from ldap3 import Server, Connection, ALL, NTLM, SUBTREE
|
||||
|
||||
|
||||
DANGEROUS_MASKS = {
|
||||
"GenericAll": 0x10000000,
|
||||
"GenericWrite": 0x40000000,
|
||||
"WriteDACL": 0x00040000,
|
||||
"WriteOwner": 0x00080000,
|
||||
"WriteProperty": 0x00000020,
|
||||
"Self": 0x00000008,
|
||||
"ExtendedRight": 0x00000100,
|
||||
"DeleteChild": 0x00000002,
|
||||
"Delete": 0x00010000,
|
||||
}
|
||||
|
||||
ADMIN_SIDS = {
|
||||
"S-1-5-18",
|
||||
"S-1-5-32-544",
|
||||
"S-1-5-9",
|
||||
}
|
||||
|
||||
ADMIN_RID_SUFFIXES = {
|
||||
"-500",
|
||||
"-512",
|
||||
"-516",
|
||||
"-518",
|
||||
"-519",
|
||||
"-498",
|
||||
}
|
||||
|
||||
ATTACK_PATHS = {
|
||||
"GenericAll": {
|
||||
"user": "Full control allows password reset, Kerberoasting via SPN, or shadow credential attack",
|
||||
"group": "Full control allows adding arbitrary members to the group",
|
||||
"computer": "Full control allows resource-based constrained delegation attack",
|
||||
"organizationalUnit": "Full control allows linking malicious GPO or moving objects",
|
||||
},
|
||||
"WriteDACL": {
|
||||
"user": "Can modify DACL to grant self GenericAll, then reset password",
|
||||
"group": "Can modify DACL to grant self write membership, then add self",
|
||||
"computer": "Can modify DACL to grant self full control on machine account",
|
||||
"organizationalUnit": "Can modify DACL to gain control over OU child objects",
|
||||
},
|
||||
"WriteOwner": {
|
||||
"user": "Can take ownership then modify DACL to escalate privileges",
|
||||
"group": "Can take ownership of group then modify membership",
|
||||
"computer": "Can take ownership then configure delegation abuse",
|
||||
"organizationalUnit": "Can take ownership then control OU policies",
|
||||
},
|
||||
"GenericWrite": {
|
||||
"user": "Can write scriptPath for logon script execution or modify SPN for Kerberoasting",
|
||||
"group": "Can modify group attributes including membership",
|
||||
"computer": "Can write msDS-AllowedToActOnBehalfOfOtherIdentity for RBCD attack",
|
||||
"organizationalUnit": "Can modify OU attributes and link GPO",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def is_admin_sid(sid: str, domain_sid: str) -> bool:
|
||||
if sid in ADMIN_SIDS:
|
||||
return True
|
||||
for suffix in ADMIN_RID_SUFFIXES:
|
||||
if sid == domain_sid + suffix:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def parse_sid(raw: bytes) -> str:
|
||||
if len(raw) < 8:
|
||||
return ""
|
||||
revision = raw[0]
|
||||
sub_auth_count = raw[1]
|
||||
authority = int.from_bytes(raw[2:8], byteorder="big")
|
||||
subs = []
|
||||
for i in range(sub_auth_count):
|
||||
offset = 8 + i * 4
|
||||
if offset + 4 > len(raw):
|
||||
break
|
||||
subs.append(struct.unpack("<I", raw[offset:offset + 4])[0])
|
||||
return f"S-{revision}-{authority}-" + "-".join(str(s) for s in subs)
|
||||
|
||||
|
||||
def parse_acl(descriptor_bytes: bytes) -> list:
|
||||
aces = []
|
||||
if len(descriptor_bytes) < 20:
|
||||
return aces
|
||||
revision = descriptor_bytes[0]
|
||||
control = struct.unpack("<H", descriptor_bytes[2:4])[0]
|
||||
dacl_offset = struct.unpack("<I", descriptor_bytes[16:20])[0]
|
||||
if dacl_offset == 0 or dacl_offset >= len(descriptor_bytes):
|
||||
return aces
|
||||
dacl = descriptor_bytes[dacl_offset:]
|
||||
if len(dacl) < 8:
|
||||
return aces
|
||||
acl_size = struct.unpack("<H", dacl[2:4])[0]
|
||||
ace_count = struct.unpack("<H", dacl[4:6])[0]
|
||||
offset = 8
|
||||
for _ in range(ace_count):
|
||||
if offset + 4 > len(dacl):
|
||||
break
|
||||
ace_type = dacl[offset]
|
||||
ace_flags = dacl[offset + 1]
|
||||
ace_size = struct.unpack("<H", dacl[offset + 2:offset + 4])[0]
|
||||
if ace_size < 4 or offset + ace_size > len(dacl):
|
||||
break
|
||||
if ace_type in (0x00, 0x05):
|
||||
if offset + 8 <= len(dacl):
|
||||
access_mask = struct.unpack("<I", dacl[offset + 4:offset + 8])[0]
|
||||
sid_offset = offset + 8
|
||||
if ace_type == 0x05:
|
||||
sid_offset = offset + 8 + 32
|
||||
if sid_offset < offset + ace_size:
|
||||
sid_bytes = dacl[sid_offset:offset + ace_size]
|
||||
sid_str = parse_sid(sid_bytes)
|
||||
matched_perms = []
|
||||
for perm_name, mask_val in DANGEROUS_MASKS.items():
|
||||
if access_mask & mask_val:
|
||||
matched_perms.append(perm_name)
|
||||
if matched_perms:
|
||||
aces.append({
|
||||
"ace_type": "ACCESS_ALLOWED" if ace_type in (0x00, 0x05) else "OTHER",
|
||||
"access_mask": f"0x{access_mask:08x}",
|
||||
"trustee_sid": sid_str,
|
||||
"permissions": matched_perms,
|
||||
})
|
||||
offset += ace_size
|
||||
return aces
|
||||
|
||||
|
||||
def resolve_sid(conn: Connection, base_dn: str, sid: str) -> str:
|
||||
try:
|
||||
conn.search(base_dn, f"(objectSid={sid})", attributes=["sAMAccountName", "cn"])
|
||||
if conn.entries:
|
||||
entry = conn.entries[0]
|
||||
return str(entry.sAMAccountName) if hasattr(entry, "sAMAccountName") else str(entry.cn)
|
||||
except Exception:
|
||||
pass
|
||||
return sid
|
||||
|
||||
|
||||
def get_domain_sid(conn: Connection, base_dn: str) -> str:
|
||||
conn.search(base_dn, "(objectClass=domain)", attributes=["objectSid"])
|
||||
if conn.entries:
|
||||
raw = conn.entries[0].objectSid.raw_values[0]
|
||||
return parse_sid(raw)
|
||||
return ""
|
||||
|
||||
|
||||
def analyze_acls(dc_ip: str, domain: str, username: str, password: str,
|
||||
target_ou: str) -> dict:
|
||||
server = Server(dc_ip, get_info=ALL, use_ssl=False)
|
||||
domain_parts = domain.split(".")
|
||||
base_dn = ",".join(f"DC={p}" for p in domain_parts)
|
||||
search_base = target_ou if target_ou else base_dn
|
||||
ntlm_user = f"{domain}\\{username}"
|
||||
|
||||
conn = Connection(server, user=ntlm_user, password=password,
|
||||
authentication=NTLM, auto_bind=True)
|
||||
domain_sid = get_domain_sid(conn, base_dn)
|
||||
|
||||
conn.search(
|
||||
search_base,
|
||||
"(|(objectClass=user)(objectClass=group)(objectClass=computer)(objectClass=organizationalUnit))",
|
||||
search_scope=SUBTREE,
|
||||
attributes=["distinguishedName", "sAMAccountName", "objectClass", "nTSecurityDescriptor"],
|
||||
)
|
||||
|
||||
findings = []
|
||||
objects_scanned = 0
|
||||
sid_cache = {}
|
||||
|
||||
for entry in conn.entries:
|
||||
objects_scanned += 1
|
||||
dn = str(entry.distinguishedName)
|
||||
obj_classes = [str(c) for c in entry.objectClass.values] if hasattr(entry, "objectClass") else []
|
||||
obj_type = "unknown"
|
||||
for oc in obj_classes:
|
||||
if oc.lower() in ("user", "group", "computer", "organizationalunit"):
|
||||
obj_type = oc.lower()
|
||||
break
|
||||
|
||||
if not hasattr(entry, "nTSecurityDescriptor"):
|
||||
continue
|
||||
raw_sd = entry.nTSecurityDescriptor.raw_values
|
||||
if not raw_sd:
|
||||
continue
|
||||
sd_bytes = raw_sd[0]
|
||||
aces = parse_acl(sd_bytes)
|
||||
|
||||
for ace in aces:
|
||||
trustee_sid = ace["trustee_sid"]
|
||||
if is_admin_sid(trustee_sid, domain_sid):
|
||||
continue
|
||||
if trustee_sid not in sid_cache:
|
||||
sid_cache[trustee_sid] = resolve_sid(conn, base_dn, trustee_sid)
|
||||
trustee_name = sid_cache[trustee_sid]
|
||||
|
||||
for perm in ace["permissions"]:
|
||||
if perm in ("Delete", "DeleteChild", "Self", "WriteProperty", "ExtendedRight"):
|
||||
severity = "medium"
|
||||
else:
|
||||
severity = "critical"
|
||||
attack = ATTACK_PATHS.get(perm, {}).get(obj_type,
|
||||
f"{perm} on {obj_type} may allow privilege escalation")
|
||||
findings.append({
|
||||
"severity": severity,
|
||||
"target_object": dn,
|
||||
"target_type": obj_type,
|
||||
"trustee": trustee_name,
|
||||
"trustee_sid": trustee_sid,
|
||||
"permission": perm,
|
||||
"access_mask": ace["access_mask"],
|
||||
"ace_type": ace["ace_type"],
|
||||
"attack_path": attack,
|
||||
"remediation": f"Remove {perm} ACE for {trustee_name} on {dn}",
|
||||
})
|
||||
|
||||
conn.unbind()
|
||||
findings.sort(key=lambda f: 0 if f["severity"] == "critical" else 1)
|
||||
return {
|
||||
"domain": domain,
|
||||
"domain_sid": domain_sid,
|
||||
"search_base": search_base,
|
||||
"objects_scanned": objects_scanned,
|
||||
"dangerous_aces_found": len(findings),
|
||||
"findings": findings,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Active Directory ACL Abuse Analyzer")
|
||||
parser.add_argument("--dc-ip", required=True, help="Domain Controller IP address")
|
||||
parser.add_argument("--domain", required=True, help="AD domain name (e.g., corp.example.com)")
|
||||
parser.add_argument("--username", required=True, help="Domain username for LDAP bind")
|
||||
parser.add_argument("--password", required=True, help="Domain user password")
|
||||
parser.add_argument("--target-ou", default=None,
|
||||
help="Target OU distinguished name to scope the search")
|
||||
parser.add_argument("--output", default=None, help="Output JSON file path")
|
||||
args = parser.parse_args()
|
||||
|
||||
result = analyze_acls(args.dc_ip, args.domain, args.username,
|
||||
args.password, args.target_ou)
|
||||
report = json.dumps(result, indent=2)
|
||||
if args.output:
|
||||
with open(args.output, "w") as f:
|
||||
f.write(report)
|
||||
print(report)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: analyzing-android-malware-with-apktool
|
||||
description: Perform static analysis of Android APK malware samples using apktool for decompilation, jadx for Java source
|
||||
recovery, and androguard for permission analysis, manifest inspection, and suspicious API call detection.
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- Android
|
||||
- APK
|
||||
- apktool
|
||||
- jadx
|
||||
- androguard
|
||||
- mobile-malware
|
||||
- static-analysis
|
||||
- reverse-engineering
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- DE.AE-02
|
||||
- RS.AN-03
|
||||
- ID.RA-01
|
||||
- DE.CM-01
|
||||
---
|
||||
|
||||
# Analyzing Android Malware with Apktool
|
||||
|
||||
## Overview
|
||||
|
||||
Android malware distributed as APK files can be statically analyzed to extract permissions, activities, services, broadcast receivers, and suspicious API calls without executing the sample. This skill uses androguard for programmatic APK analysis, identifying dangerous permission combinations, obfuscated code patterns, dynamic code loading, reflection-based API calls, and network communication indicators.
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing android malware with apktool
|
||||
- When building detection rules or threat hunting queries for this domain
|
||||
- When SOC analysts need structured procedures for this analysis type
|
||||
- When validating security monitoring coverage for related attack techniques
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.9+ with `androguard`
|
||||
- apktool (for resource decompilation)
|
||||
- jadx (for Java source recovery, optional)
|
||||
- Isolated analysis environment (VM or sandbox)
|
||||
- Sample APK files for analysis
|
||||
|
||||
## Steps
|
||||
|
||||
1. Parse APK with androguard to extract manifest metadata
|
||||
2. Enumerate requested permissions and flag dangerous combinations
|
||||
3. List activities, services, receivers, and providers from manifest
|
||||
4. Scan for suspicious API calls (reflection, crypto, SMS, telephony)
|
||||
5. Detect dynamic code loading patterns (DexClassLoader, Runtime.exec)
|
||||
6. Extract hardcoded URLs, IPs, and C2 indicators from strings
|
||||
7. Generate risk assessment report with MITRE ATT&CK mobile mappings
|
||||
|
||||
## Expected Output
|
||||
|
||||
- JSON report with permission analysis, component listing, suspicious API calls, network indicators, and risk score
|
||||
- Extracted strings and potential IOCs from the APK
|
||||
@@ -0,0 +1,69 @@
|
||||
# API Reference — Analyzing Android Malware with Apktool
|
||||
|
||||
## Libraries Used
|
||||
- **androguard**: Python APK/DEX analysis — `AnalyzeAPK()`, permission enumeration, API call scanning
|
||||
- **re**: Regex extraction of URLs, IPs, base64 patterns from DEX strings
|
||||
- **json**: JSON serialization for analysis reports
|
||||
|
||||
## CLI Interface
|
||||
```
|
||||
python agent.py sample.apk permissions
|
||||
python agent.py sample.apk manifest
|
||||
python agent.py sample.apk apis
|
||||
python agent.py sample.apk strings
|
||||
python agent.py sample.apk full
|
||||
python agent.py sample.apk # defaults to full analysis
|
||||
```
|
||||
|
||||
## Core Functions
|
||||
|
||||
### `analyze_permissions(apk)` — Permission risk assessment
|
||||
Calls `apk.get_permissions()`. Flags 20 dangerous permissions including
|
||||
SEND_SMS, READ_CONTACTS, BIND_DEVICE_ADMIN, BIND_ACCESSIBILITY_SERVICE.
|
||||
Risk: CRITICAL >= 8 dangerous, HIGH >= 5, MEDIUM >= 2, LOW < 2.
|
||||
|
||||
### `analyze_manifest(apk)` — Manifest component extraction
|
||||
Calls `apk.get_activities()`, `get_services()`, `get_receivers()`, `get_providers()`.
|
||||
Returns package name, version, SDK levels, and all component lists.
|
||||
|
||||
### `scan_suspicious_apis(dx)` — Suspicious API call detection
|
||||
Searches DEX analysis for 14 patterns including:
|
||||
- `Runtime.exec`, `ProcessBuilder.start` — command execution
|
||||
- `DexClassLoader.loadClass` — dynamic code loading
|
||||
- `Method.invoke`, `Class.forName` — reflection
|
||||
- `Cipher.getInstance` — cryptographic operations
|
||||
- `SmsManager.sendTextMessage` — SMS abuse
|
||||
|
||||
### `extract_strings(dx, apk)` — IOC extraction from DEX strings
|
||||
Regex extraction of HTTP/HTTPS URLs, external IP addresses, and base64 strings.
|
||||
Filters out private IP ranges (10.x, 192.168.x, 172.16.x, 127.x).
|
||||
|
||||
### `detect_obfuscation(apk, dx)` — Obfuscation indicator detection
|
||||
Checks for single-letter class names (ProGuard), multi-DEX, native libraries.
|
||||
|
||||
### `full_analysis(apk_path)` — Comprehensive malware assessment
|
||||
|
||||
## Androguard API
|
||||
| Method | Returns |
|
||||
|--------|---------|
|
||||
| `AnalyzeAPK(path)` | `(APK, list[DEX], Analysis)` tuple |
|
||||
| `apk.get_permissions()` | List of Android permissions |
|
||||
| `apk.get_activities()` | Activity component names |
|
||||
| `apk.get_services()` | Service component names |
|
||||
| `apk.get_receivers()` | BroadcastReceiver names |
|
||||
| `apk.get_package()` | Package name string |
|
||||
| `dx.find_methods(classname, methodname)` | Matching method analysis objects |
|
||||
| `dx.get_strings()` | All strings from DEX files |
|
||||
| `dx.get_classes()` | All class analysis objects |
|
||||
|
||||
## Risk Scoring
|
||||
| Factor | Max Points |
|
||||
|--------|-----------|
|
||||
| Dangerous permissions (8 pts each) | 40 |
|
||||
| Suspicious API calls (10 pts each) | 30 |
|
||||
| External IPs (5 pts each) | 15 |
|
||||
| Obfuscation detected | 15 |
|
||||
|
||||
## Dependencies
|
||||
- `androguard` >= 3.4.0
|
||||
- Isolated analysis environment recommended
|
||||
@@ -0,0 +1,228 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Agent for static analysis of Android APK malware using androguard."""
|
||||
|
||||
import json
|
||||
import re
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
from androguard.core.apk import APK
|
||||
from androguard.core.dex import DEX
|
||||
from androguard.misc import AnalyzeAPK
|
||||
except ImportError:
|
||||
APK = None
|
||||
AnalyzeAPK = None
|
||||
|
||||
DANGEROUS_PERMISSIONS = [
|
||||
"android.permission.SEND_SMS", "android.permission.READ_SMS",
|
||||
"android.permission.RECEIVE_SMS", "android.permission.READ_CONTACTS",
|
||||
"android.permission.READ_CALL_LOG", "android.permission.RECORD_AUDIO",
|
||||
"android.permission.CAMERA", "android.permission.ACCESS_FINE_LOCATION",
|
||||
"android.permission.READ_PHONE_STATE", "android.permission.CALL_PHONE",
|
||||
"android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.READ_EXTERNAL_STORAGE",
|
||||
"android.permission.INSTALL_PACKAGES", "android.permission.REQUEST_INSTALL_PACKAGES",
|
||||
"android.permission.SYSTEM_ALERT_WINDOW", "android.permission.BIND_ACCESSIBILITY_SERVICE",
|
||||
"android.permission.BIND_DEVICE_ADMIN", "android.permission.RECEIVE_BOOT_COMPLETED",
|
||||
"android.permission.WRITE_SETTINGS", "android.permission.CHANGE_WIFI_STATE",
|
||||
]
|
||||
|
||||
SUSPICIOUS_API_PATTERNS = [
|
||||
r"Ljava/lang/Runtime;->exec",
|
||||
r"Ljava/lang/ProcessBuilder;->start",
|
||||
r"Ldalvik/system/DexClassLoader;->loadClass",
|
||||
r"Ljava/lang/reflect/Method;->invoke",
|
||||
r"Ljava/lang/Class;->forName",
|
||||
r"Ljavax/crypto/Cipher;->getInstance",
|
||||
r"Landroid/telephony/SmsManager;->sendTextMessage",
|
||||
r"Landroid/app/admin/DevicePolicyManager;->lockNow",
|
||||
r"Landroid/content/pm/PackageManager;->setComponentEnabledSetting",
|
||||
r"Ljava/net/HttpURLConnection;->connect",
|
||||
r"Lokhttp3/OkHttpClient;->newCall",
|
||||
r"Landroid/webkit/WebView;->loadUrl",
|
||||
r"Landroid/os/Build;->SERIAL",
|
||||
r"Landroid/provider/Settings\$Secure;->getString",
|
||||
]
|
||||
|
||||
|
||||
def analyze_permissions(apk):
|
||||
"""Analyze requested permissions and flag dangerous ones."""
|
||||
permissions = apk.get_permissions()
|
||||
dangerous = [p for p in permissions if p in DANGEROUS_PERMISSIONS]
|
||||
return {
|
||||
"total_permissions": len(permissions),
|
||||
"permissions": permissions,
|
||||
"dangerous_permissions": dangerous,
|
||||
"dangerous_count": len(dangerous),
|
||||
"permission_risk": "CRITICAL" if len(dangerous) >= 8 else "HIGH" if len(dangerous) >= 5 else "MEDIUM" if len(dangerous) >= 2 else "LOW",
|
||||
}
|
||||
|
||||
|
||||
def analyze_manifest(apk):
|
||||
"""Extract manifest components: activities, services, receivers, providers."""
|
||||
activities = apk.get_activities()
|
||||
services = apk.get_services()
|
||||
receivers = apk.get_receivers()
|
||||
providers = apk.get_providers()
|
||||
return {
|
||||
"package_name": apk.get_package(),
|
||||
"app_name": apk.get_app_name(),
|
||||
"version_name": apk.get_androidversion_name(),
|
||||
"version_code": apk.get_androidversion_code(),
|
||||
"min_sdk": apk.get_min_sdk_version(),
|
||||
"target_sdk": apk.get_target_sdk_version(),
|
||||
"activities": list(activities),
|
||||
"services": list(services),
|
||||
"receivers": list(receivers),
|
||||
"providers": list(providers),
|
||||
"activity_count": len(activities),
|
||||
"service_count": len(services),
|
||||
"receiver_count": len(receivers),
|
||||
"provider_count": len(providers),
|
||||
}
|
||||
|
||||
|
||||
def scan_suspicious_apis(dx):
|
||||
"""Scan DEX analysis for suspicious API calls."""
|
||||
findings = []
|
||||
if not dx:
|
||||
return findings
|
||||
for pattern in SUSPICIOUS_API_PATTERNS:
|
||||
class_name = pattern.split(";->")[0] + ";"
|
||||
method_name = pattern.split(";->")[1] if ";->" in pattern else None
|
||||
for method in dx.find_methods(classname=class_name, methodname=method_name):
|
||||
xrefs = list(method.get_xref_from())
|
||||
if xrefs:
|
||||
findings.append({
|
||||
"api": pattern,
|
||||
"callers": len(xrefs),
|
||||
"first_caller_class": str(xrefs[0][0].name) if xrefs else None,
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
def extract_strings(dx, apk):
|
||||
"""Extract suspicious strings: URLs, IPs, base64 patterns."""
|
||||
url_pattern = re.compile(r'https?://[\w\-._~:/?#\[\]@!$&\'()*+,;=]+', re.IGNORECASE)
|
||||
ip_pattern = re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b')
|
||||
b64_pattern = re.compile(r'[A-Za-z0-9+/]{30,}={0,2}')
|
||||
|
||||
urls = set()
|
||||
ips = set()
|
||||
b64_strings = []
|
||||
|
||||
if dx:
|
||||
for s in dx.get_strings():
|
||||
val = str(s)
|
||||
urls.update(url_pattern.findall(val))
|
||||
ips.update(ip_pattern.findall(val))
|
||||
b64_matches = b64_pattern.findall(val)
|
||||
b64_strings.extend(b64_matches[:5])
|
||||
|
||||
private_ips = {"10.", "192.168.", "172.16.", "127.0."}
|
||||
external_ips = [ip for ip in ips if not any(ip.startswith(p) for p in private_ips)]
|
||||
|
||||
return {
|
||||
"urls": sorted(urls)[:30],
|
||||
"external_ips": sorted(external_ips)[:20],
|
||||
"suspicious_base64": b64_strings[:10],
|
||||
"url_count": len(urls),
|
||||
"external_ip_count": len(external_ips),
|
||||
}
|
||||
|
||||
|
||||
def detect_obfuscation(apk, dx):
|
||||
"""Detect code obfuscation indicators."""
|
||||
indicators = []
|
||||
if dx:
|
||||
short_class_names = 0
|
||||
for cls in dx.get_classes():
|
||||
name = str(cls.name)
|
||||
parts = name.replace("/", ".").split(".")
|
||||
if any(len(p) == 1 and p.isalpha() for p in parts):
|
||||
short_class_names += 1
|
||||
if short_class_names > 10:
|
||||
indicators.append({"type": "single_letter_classes", "count": short_class_names})
|
||||
|
||||
dex_files = [f for f in apk.get_files() if f.endswith(".dex")]
|
||||
if len(dex_files) > 1:
|
||||
indicators.append({"type": "multi_dex", "dex_count": len(dex_files)})
|
||||
|
||||
native_libs = [f for f in apk.get_files() if f.endswith(".so")]
|
||||
if native_libs:
|
||||
indicators.append({"type": "native_libraries", "libs": native_libs[:10]})
|
||||
|
||||
return {
|
||||
"obfuscation_indicators": indicators,
|
||||
"likely_obfuscated": len(indicators) > 0,
|
||||
}
|
||||
|
||||
|
||||
def full_analysis(apk_path):
|
||||
"""Run comprehensive APK malware analysis."""
|
||||
if not APK or not AnalyzeAPK:
|
||||
return {"error": "androguard not installed: pip install androguard"}
|
||||
|
||||
a, d, dx = AnalyzeAPK(apk_path)
|
||||
|
||||
perm_analysis = analyze_permissions(a)
|
||||
manifest = analyze_manifest(a)
|
||||
suspicious_apis = scan_suspicious_apis(dx)
|
||||
strings = extract_strings(dx, a)
|
||||
obfuscation = detect_obfuscation(a, dx)
|
||||
|
||||
risk_score = 0
|
||||
risk_score += min(perm_analysis["dangerous_count"] * 8, 40)
|
||||
risk_score += min(len(suspicious_apis) * 10, 30)
|
||||
risk_score += min(strings["external_ip_count"] * 5, 15)
|
||||
risk_score += 15 if obfuscation["likely_obfuscated"] else 0
|
||||
risk_score = min(risk_score, 100)
|
||||
|
||||
return {
|
||||
"analysis_type": "Android APK Static Analysis",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"file": apk_path,
|
||||
"manifest": manifest,
|
||||
"permissions": perm_analysis,
|
||||
"suspicious_apis": suspicious_apis[:20],
|
||||
"strings": strings,
|
||||
"obfuscation": obfuscation,
|
||||
"risk_score": risk_score,
|
||||
"risk_level": "CRITICAL" if risk_score >= 70 else "HIGH" if risk_score >= 50 else "MEDIUM" if risk_score >= 25 else "LOW",
|
||||
"mitre_techniques": [
|
||||
{"id": "T1418", "name": "Software Discovery"} if manifest["service_count"] > 5 else None,
|
||||
{"id": "T1417", "name": "Input Capture"} if "android.permission.BIND_ACCESSIBILITY_SERVICE" in perm_analysis["permissions"] else None,
|
||||
{"id": "T1582", "name": "SMS Control"} if "android.permission.SEND_SMS" in perm_analysis["permissions"] else None,
|
||||
{"id": "T1404", "name": "Exploitation for Privilege Escalation"} if any("DevicePolicyManager" in a.get("api", "") for a in suspicious_apis) else None,
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Android APK Malware Analysis Agent")
|
||||
parser.add_argument("apk", help="Path to APK file")
|
||||
sub = parser.add_subparsers(dest="command")
|
||||
sub.add_parser("permissions", help="Analyze permissions")
|
||||
sub.add_parser("manifest", help="Extract manifest components")
|
||||
sub.add_parser("apis", help="Scan for suspicious API calls")
|
||||
sub.add_parser("strings", help="Extract URLs, IPs, and encoded strings")
|
||||
sub.add_parser("full", help="Full malware analysis")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "full" or args.command is None:
|
||||
result = full_analysis(args.apk)
|
||||
else:
|
||||
a, d, dx = AnalyzeAPK(args.apk)
|
||||
if args.command == "permissions":
|
||||
result = analyze_permissions(a)
|
||||
elif args.command == "manifest":
|
||||
result = analyze_manifest(a)
|
||||
elif args.command == "apis":
|
||||
result = scan_suspicious_apis(dx)
|
||||
elif args.command == "strings":
|
||||
result = extract_strings(dx, a)
|
||||
print(json.dumps(result, indent=2, default=str))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: analyzing-api-gateway-access-logs
|
||||
description: 'Parses API Gateway access logs (AWS API Gateway, Kong, Nginx) to detect BOLA/IDOR attacks, rate limit bypass,
|
||||
credential scanning, and injection attempts. Uses pandas for statistical analysis of request patterns and anomaly detection.
|
||||
Use when investigating API abuse or building API-specific threat detection rules.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: security-operations
|
||||
tags:
|
||||
- analyzing
|
||||
- api
|
||||
- gateway
|
||||
- access
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- DE.CM-01
|
||||
- RS.MA-01
|
||||
- GV.OV-01
|
||||
- DE.AE-02
|
||||
---
|
||||
|
||||
# Analyzing API Gateway Access Logs
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing api gateway access logs
|
||||
- When building detection rules or threat hunting queries for this domain
|
||||
- When SOC analysts need structured procedures for this analysis type
|
||||
- When validating security monitoring coverage for related attack techniques
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Familiarity with security operations concepts and tools
|
||||
- Access to a test or lab environment for safe execution
|
||||
- Python 3.8+ with required dependencies installed
|
||||
- Appropriate authorization for any testing activities
|
||||
|
||||
## Instructions
|
||||
|
||||
Parse API gateway access logs to identify attack patterns including broken object
|
||||
level authorization (BOLA), excessive data exposure, and injection attempts.
|
||||
|
||||
```python
|
||||
import pandas as pd
|
||||
|
||||
df = pd.read_json("api_gateway_logs.json", lines=True)
|
||||
# Detect BOLA: same user accessing many different resource IDs
|
||||
bola = df.groupby(["user_id", "endpoint"]).agg(
|
||||
unique_ids=("resource_id", "nunique")).reset_index()
|
||||
suspicious = bola[bola["unique_ids"] > 50]
|
||||
```
|
||||
|
||||
Key detection patterns:
|
||||
1. BOLA/IDOR: sequential resource ID enumeration
|
||||
2. Rate limit bypass via header manipulation
|
||||
3. Credential scanning (401 surges from single source)
|
||||
4. SQL/NoSQL injection in query parameters
|
||||
5. Unusual HTTP methods (DELETE, PATCH) on read-only endpoints
|
||||
|
||||
## Examples
|
||||
|
||||
```python
|
||||
# Detect 401 surges indicating credential scanning
|
||||
auth_failures = df[df["status_code"] == 401]
|
||||
scanner_ips = auth_failures.groupby("source_ip").size()
|
||||
scanners = scanner_ips[scanner_ips > 100]
|
||||
```
|
||||
@@ -0,0 +1,58 @@
|
||||
# API Reference: Analyzing API Gateway Access Logs
|
||||
|
||||
## AWS API Gateway Log Fields
|
||||
|
||||
```json
|
||||
{
|
||||
"requestId": "abc-123",
|
||||
"ip": "203.0.113.50",
|
||||
"httpMethod": "GET",
|
||||
"resourcePath": "/api/users/{id}",
|
||||
"status": 200,
|
||||
"requestTime": "2025-03-15T14:00:00Z",
|
||||
"responseLength": 1024
|
||||
}
|
||||
```
|
||||
|
||||
## Pandas Log Analysis
|
||||
|
||||
```python
|
||||
import pandas as pd
|
||||
|
||||
df = pd.read_json("access_logs.json", lines=True)
|
||||
|
||||
# BOLA detection
|
||||
df.groupby("user_id")["resource_id"].nunique()
|
||||
|
||||
# Auth failure surge
|
||||
df[df["status_code"] == 401].groupby("source_ip").size()
|
||||
|
||||
# Request velocity
|
||||
df.set_index("timestamp").resample("1min").size()
|
||||
```
|
||||
|
||||
## OWASP API Top 10 Patterns
|
||||
|
||||
| Risk | Detection Pattern |
|
||||
|------|-------------------|
|
||||
| BOLA (API1) | User accessing > 50 unique resource IDs |
|
||||
| Broken Auth (API2) | > 100 401/403 from single IP |
|
||||
| Excessive Data (API3) | Response size > 10x average |
|
||||
| Rate Limit (API4) | > 100 req/min from single IP |
|
||||
| BFLA (API5) | DELETE/PUT on read-only endpoints |
|
||||
| Injection (API8) | SQL/NoSQL patterns in params |
|
||||
|
||||
## Injection Regex Patterns
|
||||
|
||||
```python
|
||||
sql = r"union\s+select|drop\s+table|'\s*or\s+'1'"
|
||||
nosql = r"\$ne|\$gt|\$regex|\$where"
|
||||
xss = r"<script|javascript:|onerror="
|
||||
path_traversal = r"\.\./\.\./|/etc/passwd"
|
||||
```
|
||||
|
||||
### References
|
||||
|
||||
- OWASP API Security Top 10: https://owasp.org/API-Security/
|
||||
- AWS API Gateway logging: https://docs.aws.amazon.com/apigateway/latest/developerguide/
|
||||
- pandas: https://pandas.pydata.org/docs/
|
||||
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Agent for analyzing API Gateway access logs for security threats."""
|
||||
|
||||
import re
|
||||
import json
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def load_api_logs(log_path):
|
||||
"""Load API gateway logs from JSON lines or CSV."""
|
||||
if log_path.endswith(".csv"):
|
||||
return pd.read_csv(log_path, parse_dates=["timestamp"])
|
||||
return pd.read_json(log_path, lines=True)
|
||||
|
||||
|
||||
def detect_bola_attacks(df, threshold=50):
|
||||
"""Detect Broken Object Level Authorization (BOLA/IDOR) attacks."""
|
||||
findings = []
|
||||
if "resource_id" not in df.columns:
|
||||
path_col = "request_path" if "request_path" in df.columns else "path"
|
||||
df["resource_id"] = df[path_col].str.extract(r'/(\d+)(?:/|$|\?)')
|
||||
df_with_ids = df.dropna(subset=["resource_id"])
|
||||
if df_with_ids.empty:
|
||||
return findings
|
||||
user_col = "user_id" if "user_id" in df.columns else "source_ip"
|
||||
grouped = df_with_ids.groupby([user_col]).agg(
|
||||
unique_resources=("resource_id", "nunique"),
|
||||
total_requests=("resource_id", "count"),
|
||||
).reset_index()
|
||||
bola_suspects = grouped[grouped["unique_resources"] >= threshold]
|
||||
for _, row in bola_suspects.iterrows():
|
||||
findings.append({
|
||||
"user": row[user_col],
|
||||
"unique_resources_accessed": int(row["unique_resources"]),
|
||||
"total_requests": int(row["total_requests"]),
|
||||
"type": "BOLA/IDOR",
|
||||
"severity": "CRITICAL",
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
def detect_auth_scanning(df, threshold=100):
|
||||
"""Detect credential scanning via 401/403 response surges."""
|
||||
findings = []
|
||||
auth_failures = df[df["status_code"].isin([401, 403])]
|
||||
if auth_failures.empty:
|
||||
return findings
|
||||
ip_col = "source_ip" if "source_ip" in df.columns else "client_ip"
|
||||
ip_failures = auth_failures.groupby(ip_col).agg(
|
||||
failure_count=("status_code", "count"),
|
||||
unique_endpoints=("request_path", "nunique") if "request_path" in df.columns
|
||||
else ("path", "nunique"),
|
||||
).reset_index()
|
||||
scanners = ip_failures[ip_failures["failure_count"] >= threshold]
|
||||
for _, row in scanners.iterrows():
|
||||
findings.append({
|
||||
"source_ip": row[ip_col],
|
||||
"auth_failures": int(row["failure_count"]),
|
||||
"endpoints_probed": int(row["unique_endpoints"]),
|
||||
"type": "credential_scanning",
|
||||
"severity": "HIGH",
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
def detect_injection_attempts(df):
|
||||
"""Detect SQL/NoSQL injection attempts in request parameters."""
|
||||
injection_patterns = [
|
||||
r"(?:union\s+select|select\s+.*\s+from|drop\s+table|insert\s+into)",
|
||||
r"(?:'\s*or\s+'1'\s*=\s*'1|'\s*or\s+1\s*=\s*1)",
|
||||
r'(?:\$ne|\$gt|\$lt|\$regex|\$where)',
|
||||
r'(?:<script|javascript:|onerror=|onload=)',
|
||||
r'(?:\.\./\.\./|/etc/passwd|/proc/self)',
|
||||
]
|
||||
findings = []
|
||||
path_col = "request_path" if "request_path" in df.columns else "path"
|
||||
query_col = "query_string" if "query_string" in df.columns else path_col
|
||||
for _, row in df.iterrows():
|
||||
request_str = str(row.get(query_col, "")) + str(row.get("request_body", ""))
|
||||
for pattern in injection_patterns:
|
||||
if re.search(pattern, request_str, re.IGNORECASE):
|
||||
findings.append({
|
||||
"source_ip": row.get("source_ip", row.get("client_ip", "")),
|
||||
"path": row.get(path_col, ""),
|
||||
"pattern_matched": pattern,
|
||||
"type": "injection_attempt",
|
||||
"severity": "HIGH",
|
||||
})
|
||||
break
|
||||
return findings[:500]
|
||||
|
||||
|
||||
def detect_rate_limit_bypass(df, window="1min", threshold=100):
|
||||
"""Detect rate limit bypass attempts."""
|
||||
findings = []
|
||||
ip_col = "source_ip" if "source_ip" in df.columns else "client_ip"
|
||||
df_copy = df.copy()
|
||||
df_copy["timestamp"] = pd.to_datetime(df_copy["timestamp"])
|
||||
df_copy = df_copy.set_index("timestamp")
|
||||
for ip, group in df_copy.groupby(ip_col):
|
||||
resampled = group.resample(window).size()
|
||||
bursts = resampled[resampled > threshold]
|
||||
if len(bursts) > 0:
|
||||
findings.append({
|
||||
"source_ip": ip,
|
||||
"max_requests_per_min": int(resampled.max()),
|
||||
"burst_periods": len(bursts),
|
||||
"type": "rate_limit_bypass",
|
||||
"severity": "MEDIUM",
|
||||
})
|
||||
return sorted(findings, key=lambda x: x["max_requests_per_min"], reverse=True)[:50]
|
||||
|
||||
|
||||
def detect_unusual_methods(df):
|
||||
"""Detect unusual HTTP methods on typically read-only endpoints."""
|
||||
findings = []
|
||||
dangerous_methods = {"DELETE", "PUT", "PATCH"}
|
||||
method_col = "method" if "method" in df.columns else "http_method"
|
||||
path_col = "request_path" if "request_path" in df.columns else "path"
|
||||
unusual = df[df[method_col].str.upper().isin(dangerous_methods)]
|
||||
for _, row in unusual.iterrows():
|
||||
findings.append({
|
||||
"source_ip": row.get("source_ip", row.get("client_ip", "")),
|
||||
"method": row[method_col],
|
||||
"path": row[path_col],
|
||||
"status_code": int(row.get("status_code", 0)),
|
||||
"type": "unusual_method",
|
||||
"severity": "MEDIUM",
|
||||
})
|
||||
return findings[:200]
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="API Gateway Log Analysis Agent")
|
||||
parser.add_argument("--log-file", required=True, help="API gateway log file")
|
||||
parser.add_argument("--output", default="api_gateway_report.json")
|
||||
parser.add_argument("--action", choices=[
|
||||
"bola", "auth_scan", "injection", "rate_limit", "full_analysis"
|
||||
], default="full_analysis")
|
||||
args = parser.parse_args()
|
||||
|
||||
df = load_api_logs(args.log_file)
|
||||
report = {"generated_at": datetime.utcnow().isoformat(), "total_requests": len(df),
|
||||
"findings": {}}
|
||||
print(f"[+] Loaded {len(df)} API requests")
|
||||
|
||||
if args.action in ("bola", "full_analysis"):
|
||||
findings = detect_bola_attacks(df)
|
||||
report["findings"]["bola"] = findings
|
||||
print(f"[+] BOLA suspects: {len(findings)}")
|
||||
|
||||
if args.action in ("auth_scan", "full_analysis"):
|
||||
findings = detect_auth_scanning(df)
|
||||
report["findings"]["auth_scanning"] = findings
|
||||
print(f"[+] Auth scanners: {len(findings)}")
|
||||
|
||||
if args.action in ("injection", "full_analysis"):
|
||||
findings = detect_injection_attempts(df)
|
||||
report["findings"]["injection_attempts"] = findings
|
||||
print(f"[+] Injection attempts: {len(findings)}")
|
||||
|
||||
if args.action in ("rate_limit", "full_analysis"):
|
||||
findings = detect_rate_limit_bypass(df)
|
||||
report["findings"]["rate_limit_bypass"] = findings
|
||||
print(f"[+] Rate limit bypasses: {len(findings)}")
|
||||
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2, default=str)
|
||||
print(f"[+] Report saved to {args.output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,285 @@
|
||||
---
|
||||
name: analyzing-apt-group-with-mitre-navigator
|
||||
description: Analyze advanced persistent threat (APT) group techniques using MITRE ATT&CK Navigator to create layered heatmaps
|
||||
of adversary TTPs for detection gap analysis and threat-informed defense.
|
||||
domain: cybersecurity
|
||||
subdomain: threat-intelligence
|
||||
tags:
|
||||
- mitre-attack
|
||||
- navigator
|
||||
- apt
|
||||
- threat-actor
|
||||
- ttp-analysis
|
||||
- heatmap
|
||||
- detection-gap
|
||||
- threat-intelligence
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
d3fend_techniques:
|
||||
- Executable Denylisting
|
||||
- Execution Isolation
|
||||
- File Metadata Consistency Validation
|
||||
- Content Format Conversion
|
||||
- File Content Analysis
|
||||
nist_csf:
|
||||
- ID.RA-01
|
||||
- ID.RA-05
|
||||
- DE.CM-01
|
||||
- DE.AE-02
|
||||
---
|
||||
# Analyzing APT Group with MITRE ATT&CK Navigator
|
||||
|
||||
## Overview
|
||||
|
||||
MITRE ATT&CK Navigator is a web-based tool for annotating and exploring ATT&CK matrices, enabling analysts to visualize threat actor technique coverage, compare multiple APT groups, identify detection gaps, and build threat-informed defense strategies. This skill covers querying ATT&CK data programmatically, mapping APT group TTPs to Navigator layers, creating multi-layer overlays for gap analysis, and generating actionable intelligence reports for detection engineering teams.
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing apt group with mitre navigator
|
||||
- When building detection rules or threat hunting queries for this domain
|
||||
- When SOC analysts need structured procedures for this analysis type
|
||||
- When validating security monitoring coverage for related attack techniques
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.9+ with `attackcti`, `mitreattack-python`, `stix2`, `requests` libraries
|
||||
- ATT&CK Navigator (https://mitre-attack.github.io/attack-navigator/) or local deployment
|
||||
- Understanding of ATT&CK Enterprise matrix: 14 Tactics, 200+ Techniques, Sub-techniques
|
||||
- Access to threat intelligence reports or MISP/OpenCTI for threat actor data
|
||||
- Familiarity with STIX 2.1 Intrusion Set and Attack Pattern objects
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### ATT&CK Navigator Layers
|
||||
|
||||
Navigator layers are JSON files that annotate ATT&CK techniques with scores, colors, comments, and metadata. Each layer can represent a single APT group's technique usage, a detection capability map, or a combined overlay. Layer version 4.5 supports enterprise-attack, mobile-attack, and ics-attack domains with filtering by platform (Windows, Linux, macOS, Cloud, Azure AD, Office 365, SaaS).
|
||||
|
||||
### APT Group Profiles in ATT&CK
|
||||
|
||||
ATT&CK catalogs over 140 threat groups with documented technique usage. Each group profile includes aliases, targeted sectors, associated campaigns, software used, and technique mappings with procedure-level detail. Groups are identified by G-codes (e.g., G0016 for APT29, G0007 for APT28, G0032 for Lazarus Group).
|
||||
|
||||
### Multi-Layer Analysis
|
||||
|
||||
The Navigator supports loading multiple layers simultaneously, allowing analysts to overlay threat actor TTPs against detection coverage to identify gaps, compare multiple APT groups to find common techniques worth prioritizing, and track technique coverage changes over time.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Query ATT&CK Data for APT Group
|
||||
|
||||
```python
|
||||
from attackcti import attack_client
|
||||
import json
|
||||
|
||||
lift = attack_client()
|
||||
|
||||
# Get all threat groups
|
||||
groups = lift.get_groups()
|
||||
print(f"Total ATT&CK groups: {len(groups)}")
|
||||
|
||||
# Find APT29 (Cozy Bear / Midnight Blizzard)
|
||||
apt29 = next((g for g in groups if g.get('name') == 'APT29'), None)
|
||||
if apt29:
|
||||
print(f"Group: {apt29['name']}")
|
||||
print(f"Aliases: {apt29.get('aliases', [])}")
|
||||
print(f"Description: {apt29.get('description', '')[:300]}")
|
||||
|
||||
# Get techniques used by APT29 (G0016)
|
||||
techniques = lift.get_techniques_used_by_group("G0016")
|
||||
print(f"APT29 uses {len(techniques)} techniques")
|
||||
|
||||
technique_map = {}
|
||||
for tech in techniques:
|
||||
tech_id = ""
|
||||
for ref in tech.get("external_references", []):
|
||||
if ref.get("source_name") == "mitre-attack":
|
||||
tech_id = ref.get("external_id", "")
|
||||
break
|
||||
if tech_id:
|
||||
tactics = [p.get("phase_name", "") for p in tech.get("kill_chain_phases", [])]
|
||||
technique_map[tech_id] = {
|
||||
"name": tech.get("name", ""),
|
||||
"tactics": tactics,
|
||||
"description": tech.get("description", "")[:500],
|
||||
"platforms": tech.get("x_mitre_platforms", []),
|
||||
"data_sources": tech.get("x_mitre_data_sources", []),
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Generate Navigator Layer JSON
|
||||
|
||||
```python
|
||||
def create_navigator_layer(group_name, technique_map, color="#ff6666"):
|
||||
techniques_list = []
|
||||
for tech_id, info in technique_map.items():
|
||||
for tactic in info["tactics"]:
|
||||
techniques_list.append({
|
||||
"techniqueID": tech_id,
|
||||
"tactic": tactic,
|
||||
"color": color,
|
||||
"comment": info["name"],
|
||||
"enabled": True,
|
||||
"score": 100,
|
||||
"metadata": [
|
||||
{"name": "group", "value": group_name},
|
||||
{"name": "platforms", "value": ", ".join(info["platforms"])},
|
||||
],
|
||||
})
|
||||
|
||||
layer = {
|
||||
"name": f"{group_name} TTP Coverage",
|
||||
"versions": {"attack": "16.1", "navigator": "5.1.0", "layer": "4.5"},
|
||||
"domain": "enterprise-attack",
|
||||
"description": f"Techniques attributed to {group_name}",
|
||||
"filters": {
|
||||
"platforms": ["Linux", "macOS", "Windows", "Cloud",
|
||||
"Azure AD", "Office 365", "SaaS", "Google Workspace"]
|
||||
},
|
||||
"sorting": 0,
|
||||
"layout": {
|
||||
"layout": "side", "aggregateFunction": "average",
|
||||
"showID": True, "showName": True,
|
||||
"showAggregateScores": False, "countUnscored": False,
|
||||
},
|
||||
"hideDisabled": False,
|
||||
"techniques": techniques_list,
|
||||
"gradient": {"colors": ["#ffffff", color], "minValue": 0, "maxValue": 100},
|
||||
"legendItems": [
|
||||
{"label": f"Used by {group_name}", "color": color},
|
||||
{"label": "Not observed", "color": "#ffffff"},
|
||||
],
|
||||
"showTacticRowBackground": True,
|
||||
"tacticRowBackground": "#dddddd",
|
||||
"selectTechniquesAcrossTactics": True,
|
||||
"selectSubtechniquesWithParent": False,
|
||||
"selectVisibleTechniques": False,
|
||||
}
|
||||
return layer
|
||||
|
||||
layer = create_navigator_layer("APT29", technique_map)
|
||||
with open("apt29_layer.json", "w") as f:
|
||||
json.dump(layer, f, indent=2)
|
||||
print("[+] Layer saved: apt29_layer.json")
|
||||
```
|
||||
|
||||
### Step 3: Compare Multiple APT Groups
|
||||
|
||||
```python
|
||||
groups_to_compare = {"G0016": "APT29", "G0007": "APT28", "G0032": "Lazarus Group"}
|
||||
group_techniques = {}
|
||||
|
||||
for gid, gname in groups_to_compare.items():
|
||||
techs = lift.get_techniques_used_by_group(gid)
|
||||
tech_ids = set()
|
||||
for t in techs:
|
||||
for ref in t.get("external_references", []):
|
||||
if ref.get("source_name") == "mitre-attack":
|
||||
tech_ids.add(ref.get("external_id", ""))
|
||||
group_techniques[gname] = tech_ids
|
||||
|
||||
common_to_all = set.intersection(*group_techniques.values())
|
||||
print(f"Techniques common to all groups: {len(common_to_all)}")
|
||||
for tid in sorted(common_to_all):
|
||||
print(f" {tid}")
|
||||
|
||||
for gname, techs in group_techniques.items():
|
||||
others = set.union(*[t for n, t in group_techniques.items() if n != gname])
|
||||
unique = techs - others
|
||||
print(f"\nUnique to {gname}: {len(unique)} techniques")
|
||||
```
|
||||
|
||||
### Step 4: Detection Gap Analysis with Layer Overlay
|
||||
|
||||
```python
|
||||
# Define your current detection capabilities
|
||||
detected_techniques = {
|
||||
"T1059", "T1059.001", "T1071", "T1071.001", "T1566", "T1566.001",
|
||||
"T1547", "T1547.001", "T1053", "T1053.005", "T1078", "T1027",
|
||||
}
|
||||
|
||||
actor_techniques = set(technique_map.keys())
|
||||
covered = actor_techniques.intersection(detected_techniques)
|
||||
gaps = actor_techniques - detected_techniques
|
||||
|
||||
print(f"=== Detection Gap Analysis for APT29 ===")
|
||||
print(f"Actor techniques: {len(actor_techniques)}")
|
||||
print(f"Detected: {len(covered)} ({len(covered)/len(actor_techniques)*100:.0f}%)")
|
||||
print(f"Gaps: {len(gaps)} ({len(gaps)/len(actor_techniques)*100:.0f}%)")
|
||||
|
||||
# Create gap layer (red = undetected, green = detected)
|
||||
gap_techniques = []
|
||||
for tech_id in actor_techniques:
|
||||
info = technique_map.get(tech_id, {})
|
||||
for tactic in info.get("tactics", [""]):
|
||||
color = "#66ff66" if tech_id in detected_techniques else "#ff3333"
|
||||
gap_techniques.append({
|
||||
"techniqueID": tech_id,
|
||||
"tactic": tactic,
|
||||
"color": color,
|
||||
"comment": f"{'DETECTED' if tech_id in detected_techniques else 'GAP'}: {info.get('name', '')}",
|
||||
"enabled": True,
|
||||
"score": 100 if tech_id in detected_techniques else 0,
|
||||
})
|
||||
|
||||
gap_layer = {
|
||||
"name": "APT29 Detection Gap Analysis",
|
||||
"versions": {"attack": "16.1", "navigator": "5.1.0", "layer": "4.5"},
|
||||
"domain": "enterprise-attack",
|
||||
"description": "Green = detected, Red = gap",
|
||||
"techniques": gap_techniques,
|
||||
"gradient": {"colors": ["#ff3333", "#66ff66"], "minValue": 0, "maxValue": 100},
|
||||
"legendItems": [
|
||||
{"label": "Detected", "color": "#66ff66"},
|
||||
{"label": "Detection Gap", "color": "#ff3333"},
|
||||
],
|
||||
}
|
||||
with open("apt29_gap_layer.json", "w") as f:
|
||||
json.dump(gap_layer, f, indent=2)
|
||||
```
|
||||
|
||||
### Step 5: Tactic Breakdown Analysis
|
||||
|
||||
```python
|
||||
from collections import defaultdict
|
||||
|
||||
tactic_breakdown = defaultdict(list)
|
||||
for tech_id, info in technique_map.items():
|
||||
for tactic in info["tactics"]:
|
||||
tactic_breakdown[tactic].append({"id": tech_id, "name": info["name"]})
|
||||
|
||||
tactic_order = [
|
||||
"reconnaissance", "resource-development", "initial-access",
|
||||
"execution", "persistence", "privilege-escalation",
|
||||
"defense-evasion", "credential-access", "discovery",
|
||||
"lateral-movement", "collection", "command-and-control",
|
||||
"exfiltration", "impact",
|
||||
]
|
||||
|
||||
print("\n=== APT29 Tactic Breakdown ===")
|
||||
for tactic in tactic_order:
|
||||
techs = tactic_breakdown.get(tactic, [])
|
||||
if techs:
|
||||
print(f"\n{tactic.upper()} ({len(techs)} techniques):")
|
||||
for t in techs:
|
||||
print(f" {t['id']}: {t['name']}")
|
||||
```
|
||||
|
||||
## Validation Criteria
|
||||
|
||||
- ATT&CK data queried successfully via TAXII server
|
||||
- APT group mapped to all documented techniques with procedure examples
|
||||
- Navigator layer JSON validates and renders correctly in ATT&CK Navigator
|
||||
- Multi-layer overlay shows threat actor vs. detection coverage
|
||||
- Detection gap analysis identifies unmonitored techniques with data source recommendations
|
||||
- Cross-group comparison reveals shared and unique TTPs
|
||||
- Output is actionable for detection engineering prioritization
|
||||
|
||||
## References
|
||||
|
||||
- [MITRE ATT&CK Navigator](https://mitre-attack.github.io/attack-navigator/)
|
||||
- [ATT&CK Groups](https://attack.mitre.org/groups/)
|
||||
- [attackcti Python Library](https://github.com/OTRF/ATTACK-Python-Client)
|
||||
- [Navigator Layer Format v4.5](https://github.com/mitre-attack/attack-navigator/blob/master/layers/LAYERFORMATv4_5.md)
|
||||
- [CISA Best Practices for MITRE ATT&CK Mapping](https://www.cisa.gov/sites/default/files/2023-01/Best%20Practices%20for%20MITRE%20ATTCK%20Mapping.pdf)
|
||||
- [Picus: Leverage MITRE ATT&CK for Threat Intelligence](https://www.picussecurity.com/how-to-leverage-the-mitre-attack-framework-for-threat-intelligence)
|
||||
@@ -0,0 +1,97 @@
|
||||
# API Reference: MITRE ATT&CK Navigator APT Analysis
|
||||
|
||||
## ATT&CK Navigator Layer Format
|
||||
|
||||
### Layer JSON Structure
|
||||
```json
|
||||
{
|
||||
"name": "APT29 - TTPs",
|
||||
"versions": {"attack": "14", "navigator": "4.9.1", "layer": "4.5"},
|
||||
"domain": "enterprise-attack",
|
||||
"techniques": [
|
||||
{
|
||||
"techniqueID": "T1566.001",
|
||||
"tactic": "initial-access",
|
||||
"color": "#ff6666",
|
||||
"score": 100,
|
||||
"comment": "Used by APT29",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"gradient": {"colors": ["#ffffff", "#ff6666"], "minValue": 0, "maxValue": 100}
|
||||
}
|
||||
```
|
||||
|
||||
## ATT&CK STIX Data Access
|
||||
|
||||
### Download Enterprise ATT&CK Bundle
|
||||
```bash
|
||||
curl -o enterprise-attack.json \
|
||||
https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json
|
||||
```
|
||||
|
||||
### STIX Object Types
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `intrusion-set` | APT groups / threat actors |
|
||||
| `attack-pattern` | Techniques and sub-techniques |
|
||||
| `relationship` | Links groups to techniques (`uses`) |
|
||||
| `malware` | Malware families |
|
||||
| `tool` | Legitimate tools used by adversaries |
|
||||
|
||||
## mitreattack-python Library
|
||||
|
||||
### Installation
|
||||
```bash
|
||||
pip install mitreattack-python
|
||||
```
|
||||
|
||||
### Query Group Techniques
|
||||
```python
|
||||
from mitreattack.stix20 import MitreAttackData
|
||||
|
||||
attack = MitreAttackData("enterprise-attack.json")
|
||||
groups = attack.get_groups()
|
||||
for g in groups:
|
||||
techs = attack.get_techniques_used_by_group(g)
|
||||
print(f"{g.name}: {len(techs)} techniques")
|
||||
```
|
||||
|
||||
### Get Technique Details
|
||||
```python
|
||||
technique = attack.get_object_by_attack_id("T1566.001", "attack-pattern")
|
||||
print(technique.name) # Spearphishing Attachment
|
||||
print(technique.x_mitre_platforms) # ['Windows', 'macOS', 'Linux']
|
||||
```
|
||||
|
||||
## Navigator CLI (attack-navigator)
|
||||
|
||||
### Export Layer to SVG
|
||||
```bash
|
||||
npx attack-navigator-export \
|
||||
--layer layer.json \
|
||||
--output output.svg \
|
||||
--theme dark
|
||||
```
|
||||
|
||||
## ATT&CK API (TAXII)
|
||||
```python
|
||||
from stix2 import TAXIICollectionSource, Filter
|
||||
from taxii2client.v20 import Collection
|
||||
|
||||
collection = Collection(
|
||||
"https://cti-taxii.mitre.org/stix/collections/95ecc380-afe9-11e4-9b6c-751b66dd541e/"
|
||||
)
|
||||
tc_source = TAXIICollectionSource(collection)
|
||||
groups = tc_source.query([Filter("type", "=", "intrusion-set")])
|
||||
```
|
||||
|
||||
## Key APT Groups Reference
|
||||
| ID | Name | Known Aliases |
|
||||
|----|------|--------------|
|
||||
| G0016 | APT29 | Cozy Bear, The Dukes, NOBELIUM |
|
||||
| G0007 | APT28 | Fancy Bear, Sofacy, Strontium |
|
||||
| G0022 | APT3 | Gothic Panda, UPS |
|
||||
| G0032 | Lazarus Group | HIDDEN COBRA, Zinc |
|
||||
| G0074 | Dragonfly 2.0 | Energetic Bear, Berserk Bear |
|
||||
| G0010 | Turla | Waterbug, Venomous Bear |
|
||||
@@ -0,0 +1,244 @@
|
||||
#!/usr/bin/env python3
|
||||
"""APT group analysis agent using MITRE ATT&CK Navigator layers.
|
||||
|
||||
Queries ATT&CK data, maps APT techniques to Navigator layers,
|
||||
performs detection gap analysis, and generates threat-informed reports.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from collections import Counter
|
||||
|
||||
try:
|
||||
import requests
|
||||
HAS_REQUESTS = True
|
||||
except ImportError:
|
||||
HAS_REQUESTS = False
|
||||
|
||||
ATTACK_ENTERPRISE_URL = "https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json"
|
||||
|
||||
NAVIGATOR_LAYER_TEMPLATE = {
|
||||
"name": "",
|
||||
"versions": {"attack": "14", "navigator": "4.9.1", "layer": "4.5"},
|
||||
"domain": "enterprise-attack",
|
||||
"description": "",
|
||||
"filters": {"platforms": ["Windows", "Linux", "macOS", "Cloud"]},
|
||||
"sorting": 0,
|
||||
"layout": {"layout": "side", "aggregateFunction": "average", "showID": False,
|
||||
"showName": True, "showAggregateScores": False, "countUnscored": False},
|
||||
"hideDisabled": False,
|
||||
"techniques": [],
|
||||
"gradient": {"colors": ["#ffffff", "#ff6666"], "minValue": 0, "maxValue": 100},
|
||||
"legendItems": [],
|
||||
"metadata": [],
|
||||
"links": [],
|
||||
"showTacticRowBackground": False,
|
||||
"tacticRowBackground": "#dddddd",
|
||||
"selectTechniquesAcrossTactics": True,
|
||||
"selectSubtechniquesWithParent": False,
|
||||
"selectVisibleTechniques": False,
|
||||
}
|
||||
|
||||
|
||||
def load_attack_data(filepath=None):
|
||||
"""Load ATT&CK STIX bundle from file or download."""
|
||||
if filepath and os.path.exists(filepath):
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
if HAS_REQUESTS:
|
||||
print("[*] Downloading ATT&CK Enterprise data...")
|
||||
resp = requests.get(ATTACK_ENTERPRISE_URL, timeout=60)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
return None
|
||||
|
||||
|
||||
def extract_groups(bundle):
|
||||
"""Extract intrusion-set (APT group) objects from STIX bundle."""
|
||||
groups = {}
|
||||
for obj in bundle.get("objects", []):
|
||||
if obj.get("type") == "intrusion-set":
|
||||
name = obj.get("name", "Unknown")
|
||||
aliases = obj.get("aliases", [])
|
||||
ext_refs = obj.get("external_references", [])
|
||||
attack_id = ""
|
||||
for ref in ext_refs:
|
||||
if ref.get("source_name") == "mitre-attack":
|
||||
attack_id = ref.get("external_id", "")
|
||||
break
|
||||
groups[obj["id"]] = {
|
||||
"name": name, "id": attack_id, "aliases": aliases,
|
||||
"description": obj.get("description", "")[:200],
|
||||
}
|
||||
return groups
|
||||
|
||||
|
||||
def extract_techniques(bundle):
|
||||
"""Extract attack-pattern (technique) objects from STIX bundle."""
|
||||
techniques = {}
|
||||
for obj in bundle.get("objects", []):
|
||||
if obj.get("type") == "attack-pattern" and not obj.get("revoked", False):
|
||||
ext_refs = obj.get("external_references", [])
|
||||
attack_id = ""
|
||||
for ref in ext_refs:
|
||||
if ref.get("source_name") == "mitre-attack":
|
||||
attack_id = ref.get("external_id", "")
|
||||
break
|
||||
if attack_id:
|
||||
tactics = [p["phase_name"] for p in obj.get("kill_chain_phases", [])]
|
||||
techniques[obj["id"]] = {
|
||||
"id": attack_id, "name": obj.get("name", ""),
|
||||
"tactics": tactics, "platforms": obj.get("x_mitre_platforms", []),
|
||||
}
|
||||
return techniques
|
||||
|
||||
|
||||
def map_group_techniques(bundle, group_stix_id, techniques):
|
||||
"""Map techniques used by a specific group via relationship objects."""
|
||||
group_techniques = []
|
||||
for obj in bundle.get("objects", []):
|
||||
if (obj.get("type") == "relationship" and
|
||||
obj.get("relationship_type") == "uses" and
|
||||
obj.get("source_ref") == group_stix_id and
|
||||
obj.get("target_ref", "").startswith("attack-pattern--")):
|
||||
tech_id = obj["target_ref"]
|
||||
if tech_id in techniques:
|
||||
group_techniques.append(techniques[tech_id])
|
||||
return group_techniques
|
||||
|
||||
|
||||
def build_navigator_layer(group_name, group_techniques, color="#ff6666", score=100):
|
||||
"""Build ATT&CK Navigator JSON layer for a group's techniques."""
|
||||
layer = json.loads(json.dumps(NAVIGATOR_LAYER_TEMPLATE))
|
||||
layer["name"] = f"{group_name} - TTPs"
|
||||
layer["description"] = f"ATT&CK techniques attributed to {group_name}"
|
||||
for tech in group_techniques:
|
||||
entry = {
|
||||
"techniqueID": tech["id"],
|
||||
"tactic": tech["tactics"][0] if tech["tactics"] else "",
|
||||
"color": color,
|
||||
"comment": f"Used by {group_name}",
|
||||
"enabled": True,
|
||||
"metadata": [],
|
||||
"links": [],
|
||||
"showSubtechniques": False,
|
||||
"score": score,
|
||||
}
|
||||
layer["techniques"].append(entry)
|
||||
return layer
|
||||
|
||||
|
||||
def detection_gap_analysis(group_techniques, detection_rules):
|
||||
"""Compare group TTPs against existing detection rules to find gaps."""
|
||||
covered = set()
|
||||
for rule in detection_rules:
|
||||
tech_id = rule.get("technique_id", "")
|
||||
if tech_id:
|
||||
covered.add(tech_id)
|
||||
gaps = []
|
||||
for tech in group_techniques:
|
||||
if tech["id"] not in covered:
|
||||
gaps.append({
|
||||
"technique_id": tech["id"],
|
||||
"technique_name": tech["name"],
|
||||
"tactics": tech["tactics"],
|
||||
"status": "NO DETECTION",
|
||||
})
|
||||
coverage_pct = (len(covered & {t["id"] for t in group_techniques}) /
|
||||
len(group_techniques) * 100) if group_techniques else 0
|
||||
return gaps, round(coverage_pct, 1)
|
||||
|
||||
|
||||
def tactic_heatmap(group_techniques):
|
||||
"""Generate tactic-level heatmap showing technique distribution."""
|
||||
tactic_counts = Counter()
|
||||
for tech in group_techniques:
|
||||
for tactic in tech["tactics"]:
|
||||
tactic_counts[tactic] += 1
|
||||
return dict(tactic_counts.most_common())
|
||||
|
||||
|
||||
def compare_groups(group_a_techs, group_b_techs):
|
||||
"""Compare two groups' technique sets for overlap analysis."""
|
||||
set_a = {t["id"] for t in group_a_techs}
|
||||
set_b = {t["id"] for t in group_b_techs}
|
||||
overlap = set_a & set_b
|
||||
only_a = set_a - set_b
|
||||
only_b = set_b - set_a
|
||||
jaccard = len(overlap) / len(set_a | set_b) if (set_a | set_b) else 0
|
||||
return {
|
||||
"overlap_count": len(overlap), "overlap_ids": sorted(overlap),
|
||||
"only_group_a": len(only_a), "only_group_b": len(only_b),
|
||||
"jaccard_similarity": round(jaccard, 4),
|
||||
}
|
||||
|
||||
|
||||
def save_layer(layer, output_path):
|
||||
"""Save Navigator layer to JSON file."""
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
json.dump(layer, f, indent=2)
|
||||
print(f"[+] Layer saved: {output_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("APT Group Analysis Agent - MITRE ATT&CK Navigator")
|
||||
print("TTP mapping, detection gap analysis, group comparison")
|
||||
print("=" * 60)
|
||||
|
||||
group_name = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
attack_file = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
|
||||
bundle = load_attack_data(attack_file)
|
||||
if not bundle:
|
||||
print("\n[!] Cannot load ATT&CK data. Provide STIX bundle path or install requests.")
|
||||
print("[DEMO] Usage:")
|
||||
print(" python agent.py APT29 enterprise-attack.json")
|
||||
print(" python agent.py APT28 # downloads from GitHub")
|
||||
sys.exit(1)
|
||||
|
||||
groups = extract_groups(bundle)
|
||||
techniques = extract_techniques(bundle)
|
||||
print(f"[*] Loaded {len(groups)} groups, {len(techniques)} techniques")
|
||||
|
||||
if not group_name:
|
||||
print("\n--- Available APT Groups (sample) ---")
|
||||
for gid, g in list(groups.items())[:20]:
|
||||
print(f" {g['id']:8s} {g['name']:30s} aliases={g['aliases'][:3]}")
|
||||
sys.exit(0)
|
||||
|
||||
target_group = None
|
||||
for gid, g in groups.items():
|
||||
if (g["name"].lower() == group_name.lower() or
|
||||
g["id"].lower() == group_name.lower() or
|
||||
group_name.lower() in [a.lower() for a in g["aliases"]]):
|
||||
target_group = (gid, g)
|
||||
break
|
||||
|
||||
if not target_group:
|
||||
print(f"[!] Group '{group_name}' not found")
|
||||
sys.exit(1)
|
||||
|
||||
gid, ginfo = target_group
|
||||
print(f"\n[*] Group: {ginfo['name']} ({ginfo['id']})")
|
||||
print(f" Aliases: {', '.join(ginfo['aliases'][:5])}")
|
||||
|
||||
group_techs = map_group_techniques(bundle, gid, techniques)
|
||||
print(f" Techniques: {len(group_techs)}")
|
||||
|
||||
heatmap = tactic_heatmap(group_techs)
|
||||
print("\n--- Tactic Heatmap ---")
|
||||
for tactic, count in heatmap.items():
|
||||
bar = "#" * count
|
||||
print(f" {tactic:35s} {count:3d} {bar}")
|
||||
|
||||
layer = build_navigator_layer(ginfo["name"], group_techs)
|
||||
out_file = f"{ginfo['name'].replace(' ', '_')}_layer.json"
|
||||
save_layer(layer, out_file)
|
||||
|
||||
sample_rules = [{"technique_id": t["id"]} for t in group_techs[:len(group_techs)//2]]
|
||||
gaps, coverage = detection_gap_analysis(group_techs, sample_rules)
|
||||
print(f"\n--- Detection Gap Analysis (demo: {coverage}% coverage) ---")
|
||||
for gap in gaps[:10]:
|
||||
print(f" [GAP] {gap['technique_id']:12s} {gap['technique_name']}")
|
||||
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
name: analyzing-azure-activity-logs-for-threats
|
||||
description: 'Queries Azure Monitor activity logs and sign-in logs via azure-monitor-query to detect suspicious administrative
|
||||
operations, impossible travel, privilege escalation, and resource modifications. Builds KQL queries for threat hunting in
|
||||
Azure environments. Use when investigating suspicious Azure tenant activity or building cloud SIEM detections.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: security-operations
|
||||
tags:
|
||||
- analyzing
|
||||
- azure
|
||||
- activity
|
||||
- logs
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- DE.CM-01
|
||||
- RS.MA-01
|
||||
- GV.OV-01
|
||||
- DE.AE-02
|
||||
---
|
||||
|
||||
# Analyzing Azure Activity Logs for Threats
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing azure activity logs for threats
|
||||
- When building detection rules or threat hunting queries for this domain
|
||||
- When SOC analysts need structured procedures for this analysis type
|
||||
- When validating security monitoring coverage for related attack techniques
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Familiarity with security operations concepts and tools
|
||||
- Access to a test or lab environment for safe execution
|
||||
- Python 3.8+ with required dependencies installed
|
||||
- Appropriate authorization for any testing activities
|
||||
|
||||
## Instructions
|
||||
|
||||
Use azure-monitor-query to execute KQL queries against Azure Log Analytics workspaces,
|
||||
detecting suspicious admin operations and sign-in anomalies.
|
||||
|
||||
```python
|
||||
from azure.identity import DefaultAzureCredential
|
||||
from azure.monitor.query import LogsQueryClient
|
||||
from datetime import timedelta
|
||||
|
||||
credential = DefaultAzureCredential()
|
||||
client = LogsQueryClient(credential)
|
||||
|
||||
response = client.query_workspace(
|
||||
workspace_id="WORKSPACE_ID",
|
||||
query="AzureActivity | where OperationNameValue has 'MICROSOFT.AUTHORIZATION/ROLEASSIGNMENTS/WRITE' | take 10",
|
||||
timespan=timedelta(hours=24),
|
||||
)
|
||||
```
|
||||
|
||||
Key detection queries:
|
||||
1. Role assignment changes (privilege escalation)
|
||||
2. Resource group and subscription modifications
|
||||
3. Key vault secret access from new IPs
|
||||
4. Network security group rule changes
|
||||
5. Conditional access policy modifications
|
||||
|
||||
## Examples
|
||||
|
||||
```python
|
||||
# Detect new Global Admin role assignments
|
||||
query = '''
|
||||
AuditLogs
|
||||
| where OperationName == "Add member to role"
|
||||
| where TargetResources[0].modifiedProperties[0].newValue has "Global Administrator"
|
||||
'''
|
||||
```
|
||||
@@ -0,0 +1,54 @@
|
||||
# API Reference: Analyzing Azure Activity Logs for Threats
|
||||
|
||||
## azure-monitor-query
|
||||
|
||||
```python
|
||||
from azure.identity import DefaultAzureCredential
|
||||
from azure.monitor.query import LogsQueryClient, LogsQueryStatus
|
||||
from datetime import timedelta
|
||||
|
||||
credential = DefaultAzureCredential()
|
||||
client = LogsQueryClient(credential)
|
||||
|
||||
response = client.query_workspace(
|
||||
workspace_id="WORKSPACE_ID",
|
||||
query="AzureActivity | take 10",
|
||||
timespan=timedelta(hours=24),
|
||||
)
|
||||
if response.status == LogsQueryStatus.SUCCESS:
|
||||
for table in response.tables:
|
||||
columns = [col.name for col in table.columns]
|
||||
for row in table.rows:
|
||||
print(dict(zip(columns, row)))
|
||||
```
|
||||
|
||||
## Key Azure Log Tables
|
||||
|
||||
| Table | Content |
|
||||
|-------|---------|
|
||||
| `AzureActivity` | Control plane operations (ARM) |
|
||||
| `SigninLogs` | Azure AD sign-in events |
|
||||
| `AuditLogs` | Azure AD audit trail |
|
||||
| `AzureDiagnostics` | Resource diagnostics (Key Vault, NSG) |
|
||||
| `SecurityAlert` | Defender for Cloud alerts |
|
||||
|
||||
## Threat Detection KQL Patterns
|
||||
|
||||
```kql
|
||||
// Privilege escalation
|
||||
AzureActivity | where OperationNameValue has "ROLEASSIGNMENTS/WRITE"
|
||||
|
||||
// Impossible travel
|
||||
SigninLogs | where ResultType == 0
|
||||
| extend Distance = geo_distance_2points(...)
|
||||
|
||||
// Mass deletion
|
||||
AzureActivity | where OperationNameValue endswith "/DELETE"
|
||||
| summarize count() by Caller, bin(TimeGenerated, 1h)
|
||||
```
|
||||
|
||||
### References
|
||||
|
||||
- azure-monitor-query: https://pypi.org/project/azure-monitor-query/
|
||||
- KQL reference: https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/
|
||||
- Azure Activity Log schema: https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log-schema
|
||||
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Agent for analyzing Azure activity logs for threat detection."""
|
||||
|
||||
import os
|
||||
import json
|
||||
import argparse
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from azure.identity import DefaultAzureCredential, ClientSecretCredential
|
||||
from azure.monitor.query import LogsQueryClient, LogsQueryStatus
|
||||
|
||||
|
||||
def get_credential(tenant_id=None, client_id=None, client_secret=None):
|
||||
"""Get Azure credential."""
|
||||
if client_id and client_secret and tenant_id:
|
||||
return ClientSecretCredential(tenant_id, client_id, client_secret)
|
||||
return DefaultAzureCredential()
|
||||
|
||||
|
||||
def run_kql(credential, workspace_id, query, hours=24):
|
||||
"""Execute KQL query against Log Analytics workspace."""
|
||||
client = LogsQueryClient(credential)
|
||||
response = client.query_workspace(
|
||||
workspace_id, query, timespan=timedelta(hours=hours)
|
||||
)
|
||||
rows = []
|
||||
if response.status == LogsQueryStatus.SUCCESS:
|
||||
for table in response.tables:
|
||||
columns = [col.name for col in table.columns]
|
||||
for row in table.rows:
|
||||
rows.append(dict(zip(columns, row)))
|
||||
return rows
|
||||
|
||||
|
||||
def detect_privilege_escalation(credential, workspace_id):
|
||||
"""Detect role assignment changes indicating privilege escalation."""
|
||||
query = """
|
||||
AzureActivity
|
||||
| where OperationNameValue has_any (
|
||||
"MICROSOFT.AUTHORIZATION/ROLEASSIGNMENTS/WRITE",
|
||||
"MICROSOFT.AUTHORIZATION/ROLEDEFINITIONS/WRITE"
|
||||
)
|
||||
| where ActivityStatusValue == "Success"
|
||||
| project TimeGenerated, Caller, CallerIpAddress,
|
||||
OperationNameValue, ResourceGroup, Properties_d
|
||||
| order by TimeGenerated desc
|
||||
"""
|
||||
return run_kql(credential, workspace_id, query)
|
||||
|
||||
|
||||
def detect_nsg_changes(credential, workspace_id):
|
||||
"""Detect Network Security Group rule modifications."""
|
||||
query = """
|
||||
AzureActivity
|
||||
| where OperationNameValue has_any (
|
||||
"MICROSOFT.NETWORK/NETWORKSECURITYGROUPS/SECURITYRULES/WRITE",
|
||||
"MICROSOFT.NETWORK/NETWORKSECURITYGROUPS/SECURITYRULES/DELETE"
|
||||
)
|
||||
| where ActivityStatusValue == "Success"
|
||||
| project TimeGenerated, Caller, CallerIpAddress,
|
||||
OperationNameValue, ResourceGroup
|
||||
| order by TimeGenerated desc
|
||||
"""
|
||||
return run_kql(credential, workspace_id, query)
|
||||
|
||||
|
||||
def detect_keyvault_access(credential, workspace_id):
|
||||
"""Detect Key Vault secret access from unusual sources."""
|
||||
query = """
|
||||
AzureDiagnostics
|
||||
| where ResourceProvider == "MICROSOFT.KEYVAULT"
|
||||
| where OperationName in ("SecretGet", "SecretList", "SecretSet")
|
||||
| summarize AccessCount = count(), DistinctIPs = dcount(CallerIPAddress),
|
||||
IPList = make_set(CallerIPAddress, 10)
|
||||
by identity_claim_upn_s, OperationName, Resource
|
||||
| where DistinctIPs > 2 or AccessCount > 50
|
||||
| order by AccessCount desc
|
||||
"""
|
||||
return run_kql(credential, workspace_id, query)
|
||||
|
||||
|
||||
def detect_impossible_travel(credential, workspace_id):
|
||||
"""Detect sign-ins from geographically distant locations in short time."""
|
||||
query = """
|
||||
SigninLogs
|
||||
| where ResultType == 0
|
||||
| project TimeGenerated, UserPrincipalName, IPAddress,
|
||||
Lat = toreal(LocationDetails.geoCoordinates.latitude),
|
||||
Lon = toreal(LocationDetails.geoCoordinates.longitude)
|
||||
| sort by UserPrincipalName asc, TimeGenerated asc
|
||||
| extend PrevLat = prev(Lat), PrevLon = prev(Lon),
|
||||
PrevTime = prev(TimeGenerated), PrevUser = prev(UserPrincipalName)
|
||||
| where UserPrincipalName == PrevUser
|
||||
| extend TimeDiffMin = datetime_diff('minute', TimeGenerated, PrevTime)
|
||||
| where TimeDiffMin < 60 and TimeDiffMin > 0
|
||||
| extend DistKm = geo_distance_2points(Lon, Lat, PrevLon, PrevLat) / 1000
|
||||
| where DistKm > 500
|
||||
| project TimeGenerated, UserPrincipalName, IPAddress, DistKm, TimeDiffMin
|
||||
"""
|
||||
return run_kql(credential, workspace_id, query)
|
||||
|
||||
|
||||
def detect_resource_deletion(credential, workspace_id):
|
||||
"""Detect mass resource deletion events."""
|
||||
query = """
|
||||
AzureActivity
|
||||
| where OperationNameValue endswith "/DELETE"
|
||||
| where ActivityStatusValue == "Success"
|
||||
| summarize DeleteCount = count(), Resources = make_set(Resource, 20)
|
||||
by Caller, bin(TimeGenerated, 1h)
|
||||
| where DeleteCount > 10
|
||||
| order by DeleteCount desc
|
||||
"""
|
||||
return run_kql(credential, workspace_id, query)
|
||||
|
||||
|
||||
def detect_conditional_access_changes(credential, workspace_id):
|
||||
"""Detect modifications to Conditional Access policies."""
|
||||
query = """
|
||||
AuditLogs
|
||||
| where OperationName has_any (
|
||||
"Update conditional access policy",
|
||||
"Delete conditional access policy"
|
||||
)
|
||||
| project TimeGenerated, InitiatedBy, OperationName,
|
||||
TargetResources, Result
|
||||
| order by TimeGenerated desc
|
||||
"""
|
||||
return run_kql(credential, workspace_id, query)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Azure Activity Log Threat Detection Agent")
|
||||
parser.add_argument("--workspace-id", default=os.getenv("AZURE_WORKSPACE_ID"))
|
||||
parser.add_argument("--tenant-id", default=os.getenv("AZURE_TENANT_ID"))
|
||||
parser.add_argument("--client-id", default=os.getenv("AZURE_CLIENT_ID"))
|
||||
parser.add_argument("--client-secret", default=os.getenv("AZURE_CLIENT_SECRET"))
|
||||
parser.add_argument("--output", default="azure_threat_report.json")
|
||||
parser.add_argument("--action", choices=[
|
||||
"privesc", "nsg", "keyvault", "travel", "deletion", "full_hunt"
|
||||
], default="full_hunt")
|
||||
args = parser.parse_args()
|
||||
|
||||
cred = get_credential(args.tenant_id, args.client_id, args.client_secret)
|
||||
report = {"generated_at": datetime.utcnow().isoformat(), "findings": {}}
|
||||
|
||||
if args.action in ("privesc", "full_hunt"):
|
||||
results = detect_privilege_escalation(cred, args.workspace_id)
|
||||
report["findings"]["privilege_escalation"] = results
|
||||
print(f"[+] Privilege escalation events: {len(results)}")
|
||||
|
||||
if args.action in ("nsg", "full_hunt"):
|
||||
results = detect_nsg_changes(cred, args.workspace_id)
|
||||
report["findings"]["nsg_changes"] = results
|
||||
print(f"[+] NSG changes: {len(results)}")
|
||||
|
||||
if args.action in ("keyvault", "full_hunt"):
|
||||
results = detect_keyvault_access(cred, args.workspace_id)
|
||||
report["findings"]["keyvault_anomalies"] = results
|
||||
print(f"[+] Key Vault anomalies: {len(results)}")
|
||||
|
||||
if args.action in ("travel", "full_hunt"):
|
||||
results = detect_impossible_travel(cred, args.workspace_id)
|
||||
report["findings"]["impossible_travel"] = results
|
||||
print(f"[+] Impossible travel: {len(results)}")
|
||||
|
||||
if args.action in ("deletion", "full_hunt"):
|
||||
results = detect_resource_deletion(cred, args.workspace_id)
|
||||
report["findings"]["mass_deletion"] = results
|
||||
print(f"[+] Mass deletion events: {len(results)}")
|
||||
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2, default=str)
|
||||
print(f"[+] Report saved to {args.output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,354 @@
|
||||
---
|
||||
name: analyzing-bootkit-and-rootkit-samples
|
||||
description: 'Analyzes bootkit and advanced rootkit malware that infects the Master Boot Record (MBR), Volume Boot Record
|
||||
(VBR), or UEFI firmware to gain persistence below the operating system. Covers boot sector analysis, UEFI module inspection,
|
||||
and anti-rootkit detection techniques. Activates for requests involving bootkit analysis, MBR malware investigation, UEFI
|
||||
persistence analysis, or pre-OS malware detection.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- bootkit
|
||||
- rootkit
|
||||
- UEFI
|
||||
- MBR-analysis
|
||||
version: 1.0.0
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- DE.AE-02
|
||||
- RS.AN-03
|
||||
- ID.RA-01
|
||||
- DE.CM-01
|
||||
---
|
||||
|
||||
# Analyzing Bootkit and Rootkit Samples
|
||||
|
||||
## When to Use
|
||||
|
||||
- A system shows signs of compromise that persist through OS reinstallation
|
||||
- Antivirus and EDR are unable to detect malware despite clear evidence of compromise
|
||||
- UEFI Secure Boot has been disabled or shows integrity violations
|
||||
- Memory forensics reveals rootkit behavior (hidden processes, hooked system calls)
|
||||
- Investigating nation-state level threats known to deploy bootkits (APT28, APT41, Equation Group)
|
||||
|
||||
**Do not use** for standard user-mode malware; bootkits and rootkits operate at a fundamentally different level requiring specialized analysis techniques.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Disk imaging tools (dd, FTK Imager) for acquiring MBR/VBR sectors
|
||||
- UEFITool for UEFI firmware volume analysis and module extraction
|
||||
- chipsec for hardware-level firmware security assessment
|
||||
- Ghidra with x86 real-mode and 16-bit support for MBR code analysis
|
||||
- Volatility 3 for kernel-level rootkit artifact detection
|
||||
- Bootable Linux live USB for offline system analysis
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Acquire Boot Sectors and Firmware
|
||||
|
||||
Extract MBR, VBR, and UEFI firmware for offline analysis:
|
||||
|
||||
```bash
|
||||
# Acquire MBR (first 512 bytes of disk)
|
||||
dd if=/dev/sda of=mbr.bin bs=512 count=1
|
||||
|
||||
# Acquire first track (usually contains bootkit code beyond MBR)
|
||||
dd if=/dev/sda of=first_track.bin bs=512 count=63
|
||||
|
||||
# Acquire VBR (Volume Boot Record - first sector of partition)
|
||||
dd if=/dev/sda1 of=vbr.bin bs=512 count=1
|
||||
|
||||
# Acquire UEFI System Partition
|
||||
mkdir /mnt/efi
|
||||
mount /dev/sda1 /mnt/efi
|
||||
cp -r /mnt/efi/EFI /analysis/efi_backup/
|
||||
|
||||
# Dump UEFI firmware (requires chipsec or flashrom)
|
||||
# Using chipsec:
|
||||
python chipsec_util.py spi dump firmware.rom
|
||||
|
||||
# Using flashrom:
|
||||
flashrom -p internal -r firmware.rom
|
||||
|
||||
# Verify firmware dump integrity
|
||||
sha256sum firmware.rom
|
||||
```
|
||||
|
||||
### Step 2: Analyze MBR/VBR for Bootkit Code
|
||||
|
||||
Examine boot sector code for malicious modifications:
|
||||
|
||||
```bash
|
||||
# Disassemble MBR code (16-bit real mode)
|
||||
ndisasm -b16 mbr.bin > mbr_disasm.txt
|
||||
|
||||
# Compare MBR with known-good Windows MBR
|
||||
# Standard Windows MBR begins with: EB 5A 90 (JMP 0x5C, NOP)
|
||||
# Standard Windows 10 MBR: 33 C0 8E D0 BC 00 7C (XOR AX,AX; MOV SS,AX; MOV SP,7C00h)
|
||||
|
||||
python3 << 'PYEOF'
|
||||
with open("mbr.bin", "rb") as f:
|
||||
mbr = f.read()
|
||||
|
||||
# Check MBR signature (bytes 510-511 should be 0x55AA)
|
||||
if mbr[510:512] == b'\x55\xAA':
|
||||
print("[*] Valid MBR signature (0x55AA)")
|
||||
else:
|
||||
print("[!] Invalid MBR signature")
|
||||
|
||||
# Check for known bootkit signatures
|
||||
bootkit_sigs = {
|
||||
b'\xE8\x00\x00\x5E\x81\xEE': "TDL4/Alureon bootkit",
|
||||
b'\xFA\x33\xC0\x8E\xD0\xBC\x00\x7C\x8B\xF4\x50\x07': "Standard Windows MBR (clean)",
|
||||
b'\xEB\x5A\x90\x4E\x54\x46\x53': "Standard NTFS VBR (clean)",
|
||||
}
|
||||
|
||||
for sig, name in bootkit_sigs.items():
|
||||
if sig in mbr:
|
||||
print(f"[{'!' if 'clean' not in name else '*'}] Signature match: {name}")
|
||||
|
||||
# Check partition table entries
|
||||
print("\nPartition Table:")
|
||||
for i in range(4):
|
||||
offset = 446 + (i * 16)
|
||||
entry = mbr[offset:offset+16]
|
||||
if entry != b'\x00' * 16:
|
||||
boot_flag = "Active" if entry[0] == 0x80 else "Inactive"
|
||||
part_type = entry[4]
|
||||
start_lba = int.from_bytes(entry[8:12], 'little')
|
||||
size_lba = int.from_bytes(entry[12:16], 'little')
|
||||
print(f" Partition {i+1}: Type=0x{part_type:02X} {boot_flag} Start=LBA {start_lba} Size={size_lba} sectors")
|
||||
PYEOF
|
||||
```
|
||||
|
||||
### Step 3: Analyze UEFI Firmware for Implants
|
||||
|
||||
Inspect UEFI firmware volumes for unauthorized modules:
|
||||
|
||||
```bash
|
||||
# Extract UEFI firmware components with UEFITool
|
||||
# GUI: Open firmware.rom -> Inspect firmware volumes
|
||||
# CLI:
|
||||
UEFIExtract firmware.rom all
|
||||
|
||||
# List all DXE drivers (most common target for UEFI implants)
|
||||
find firmware.rom.dump -name "*.efi" -exec file {} \;
|
||||
|
||||
# Compare against known-good firmware module list
|
||||
# Each UEFI module has a GUID - compare against vendor baseline
|
||||
|
||||
# Verify Secure Boot configuration
|
||||
python chipsec_main.py -m common.secureboot.variables
|
||||
|
||||
# Check SPI flash write protection
|
||||
python chipsec_main.py -m common.bios_wp
|
||||
|
||||
# Check for known UEFI malware patterns
|
||||
yara -r uefi_malware.yar firmware.rom
|
||||
```
|
||||
|
||||
```
|
||||
Known UEFI Bootkit Detection Points:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
LoJax (APT28):
|
||||
- Modified SPI flash
|
||||
- Added DXE driver that drops agent to Windows
|
||||
- Persists through OS reinstall and disk replacement
|
||||
|
||||
BlackLotus:
|
||||
- Exploits CVE-2022-21894 to bypass Secure Boot
|
||||
- Modifies EFI System Partition bootloader
|
||||
- Installs kernel driver during boot
|
||||
|
||||
CosmicStrand:
|
||||
- Modifies CORE_DXE firmware module
|
||||
- Hooks kernel initialization during boot
|
||||
- Drops shellcode into Windows kernel memory
|
||||
|
||||
MoonBounce:
|
||||
- SPI flash implant in CORE_DXE module
|
||||
- Modified GetVariable() function
|
||||
- Deploys user-mode implant through boot chain
|
||||
|
||||
ESPecter:
|
||||
- Modifies Windows Boot Manager on ESP
|
||||
- Patches winload.efi to disable DSE
|
||||
- Loads unsigned kernel driver
|
||||
```
|
||||
|
||||
### Step 4: Detect Kernel-Level Rootkit Behavior
|
||||
|
||||
Analyze the running system for rootkit artifacts:
|
||||
|
||||
```bash
|
||||
# Memory forensics for rootkit detection
|
||||
# SSDT hook detection
|
||||
vol3 -f memory.dmp windows.ssdt | grep -v "ntoskrnl\|win32k"
|
||||
|
||||
# Hidden processes (DKOM)
|
||||
vol3 -f memory.dmp windows.psscan > psscan.txt
|
||||
vol3 -f memory.dmp windows.pslist > pslist.txt
|
||||
# Diff to find hidden processes
|
||||
|
||||
# Kernel callback registration (rootkits register callbacks for filtering)
|
||||
vol3 -f memory.dmp windows.callbacks
|
||||
|
||||
# Driver analysis
|
||||
vol3 -f memory.dmp windows.driverscan
|
||||
vol3 -f memory.dmp windows.modules
|
||||
|
||||
# Check for unsigned drivers
|
||||
vol3 -f memory.dmp windows.driverscan | while read line; do
|
||||
driver_path=$(echo "$line" | awk '{print $NF}')
|
||||
if [ -f "$driver_path" ]; then
|
||||
sigcheck -nobanner "$driver_path" 2>/dev/null | grep "Unsigned"
|
||||
fi
|
||||
done
|
||||
|
||||
# IDT hook detection
|
||||
vol3 -f memory.dmp windows.idt
|
||||
```
|
||||
|
||||
### Step 5: Boot Process Integrity Verification
|
||||
|
||||
Verify the integrity of the entire boot chain:
|
||||
|
||||
```bash
|
||||
# Verify Windows Boot Manager signature
|
||||
sigcheck -a C:\Windows\Boot\EFI\bootmgfw.efi
|
||||
|
||||
# Verify winload.efi
|
||||
sigcheck -a C:\Windows\System32\winload.efi
|
||||
|
||||
# Verify ntoskrnl.exe
|
||||
sigcheck -a C:\Windows\System32\ntoskrnl.exe
|
||||
|
||||
# Check Measured Boot logs (if TPM is available)
|
||||
# Windows: BCDEdit /enum firmware
|
||||
bcdedit /enum firmware
|
||||
|
||||
# Verify Secure Boot state
|
||||
Confirm-SecureBootUEFI # PowerShell cmdlet
|
||||
|
||||
# Check boot configuration for tampering
|
||||
bcdedit /v
|
||||
|
||||
# Look for boot configuration changes
|
||||
# testsigning: should be No
|
||||
# nointegritychecks: should be No
|
||||
# debug: should be No
|
||||
bcdedit | findstr /i "testsigning nointegritychecks debug"
|
||||
```
|
||||
|
||||
### Step 6: Document Bootkit/Rootkit Analysis
|
||||
|
||||
Compile comprehensive analysis findings:
|
||||
|
||||
```
|
||||
Analysis should document:
|
||||
- Boot sector (MBR/VBR) integrity status with hex comparison
|
||||
- UEFI firmware module inventory and integrity verification
|
||||
- Secure Boot status and any bypass mechanisms detected
|
||||
- Kernel-level hooks (SSDT, IDT, IRP, inline) identified
|
||||
- Hidden processes, drivers, and files discovered
|
||||
- Persistence mechanism (SPI flash, ESP, MBR, kernel driver)
|
||||
- Boot chain integrity verification results
|
||||
- Attribution to known bootkit families if possible
|
||||
- Remediation steps (reflash firmware, rebuild MBR, replace hardware)
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **Bootkit** | Malware that infects the boot process (MBR, VBR, UEFI) to execute before the operating system loads, gaining persistent low-level control |
|
||||
| **MBR (Master Boot Record)** | First 512 bytes of a disk containing bootstrap code and partition table; MBR bootkits replace this code with malicious loaders |
|
||||
| **UEFI (Unified Extensible Firmware Interface)** | Modern firmware interface replacing BIOS; UEFI bootkits implant malicious modules in firmware volumes or modify the ESP |
|
||||
| **Secure Boot** | UEFI security feature verifying digital signatures of boot components; bootkits like BlackLotus exploit vulnerabilities to bypass it |
|
||||
| **SPI Flash** | Flash memory chip storing UEFI firmware; advanced bootkits like LoJax and MoonBounce modify SPI flash for firmware-level persistence |
|
||||
| **DKOM (Direct Kernel Object Manipulation)** | Rootkit technique modifying kernel structures to hide processes, files, and network connections without hooking functions |
|
||||
| **Driver Signature Enforcement (DSE)** | Windows security feature requiring kernel drivers to be digitally signed; bootkits disable DSE during boot to load unsigned rootkit drivers |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **UEFITool**: Open-source UEFI firmware image editor and parser for inspecting firmware volumes, drivers, and modules
|
||||
- **chipsec**: Intel hardware security assessment framework for verifying SPI flash protection, Secure Boot, and UEFI configuration
|
||||
- **Volatility**: Memory forensics framework with SSDT, IDT, callback, and driver analysis plugins for kernel rootkit detection
|
||||
- **GMER**: Windows rootkit detection tool scanning for SSDT hooks, IDT hooks, hidden processes, and modified kernel modules
|
||||
- **Bootkits Analyzer**: Specialized tool for analyzing MBR/VBR code including disassembly and comparison against known-good baselines
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Investigating Persistent Compromise Surviving OS Reinstallation
|
||||
|
||||
**Context**: An organization reimaged a compromised workstation, but the same C2 beaconing resumed within hours. Standard disk forensics finds no malware. UEFI bootkit is suspected.
|
||||
|
||||
**Approach**:
|
||||
1. Boot from a Linux live USB to avoid executing any compromised OS components
|
||||
2. Dump the SPI flash firmware using chipsec or flashrom for offline analysis
|
||||
3. Dump the MBR and VBR sectors with dd for boot sector analysis
|
||||
4. Copy the EFI System Partition for bootloader integrity verification
|
||||
5. Open the SPI dump in UEFITool and compare module GUIDs against vendor-provided firmware
|
||||
6. Look for additional or modified DXE drivers that should not be present
|
||||
7. Analyze any suspicious modules with Ghidra (x86_64 UEFI module format)
|
||||
8. Verify Secure Boot configuration and check for exploit-based bypasses
|
||||
|
||||
**Pitfalls**:
|
||||
- Analyzing the system while the compromised OS is running (rootkit may hide from live analysis)
|
||||
- Not checking SPI flash (only analyzing disk-based boot components misses firmware-level implants)
|
||||
- Assuming Secure Boot prevents all bootkits (known bypasses exist, e.g., CVE-2022-21894)
|
||||
- Not preserving the original firmware dump before reflashing (critical evidence for attribution)
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
BOOTKIT / ROOTKIT ANALYSIS REPORT
|
||||
====================================
|
||||
System: Dell OptiPlex 7090 (UEFI, TPM 2.0)
|
||||
Firmware Version: 1.15.0 (Dell)
|
||||
Secure Boot: ENABLED (but bypassed)
|
||||
Capture Method: Linux Live USB + chipsec SPI dump
|
||||
|
||||
MBR/VBR ANALYSIS
|
||||
MBR Signature: Valid (0x55AA)
|
||||
MBR Code: MATCHES standard Windows 10 MBR (clean)
|
||||
VBR Code: MATCHES standard NTFS VBR (clean)
|
||||
|
||||
UEFI FIRMWARE ANALYSIS
|
||||
Total Modules: 287
|
||||
Vendor Expected: 285
|
||||
Extra Modules: 2 UNAUTHORIZED
|
||||
[!] DXE Driver GUID: {ABCD1234-...} "SmmAccessDxe_mod" (MODIFIED)
|
||||
Original Size: 12,288 bytes
|
||||
Current Size: 45,056 bytes (32KB ADDED)
|
||||
Entropy: 7.82 (HIGH - encrypted payload)
|
||||
|
||||
[!] DXE Driver GUID: {EFGH5678-...} "UefiPayloadDxe" (NEW - not in vendor firmware)
|
||||
Size: 28,672 bytes
|
||||
Function: Drops persistence agent during boot
|
||||
|
||||
BOOT CHAIN INTEGRITY
|
||||
bootmgfw.efi: MODIFIED (hash mismatch, Secure Boot bypass via CVE-2022-21894)
|
||||
winload.efi: MODIFIED (DSE disabled at load time)
|
||||
ntoskrnl.exe: CLEAN (but unsigned driver loaded after boot)
|
||||
|
||||
KERNEL ROOTKIT COMPONENTS
|
||||
Driver: C:\Windows\System32\drivers\null_mod.sys (unsigned, hidden)
|
||||
SSDT Hooks: 3 (NtQuerySystemInformation, NtQueryDirectoryFile, NtDeviceIoControlFile)
|
||||
Hidden Processes: 2 (PID 6784: beacon.exe, PID 6812: keylog.exe)
|
||||
Hidden Files: C:\Windows\System32\drivers\null_mod.sys
|
||||
|
||||
ATTRIBUTION
|
||||
Family: BlackLotus variant
|
||||
Confidence: HIGH (CVE-2022-21894 exploit, ESP modification pattern matches)
|
||||
|
||||
REMEDIATION
|
||||
1. Reflash SPI firmware with clean vendor image via hardware programmer
|
||||
2. Rebuild EFI System Partition from clean Windows installation media
|
||||
3. Reinstall OS from verified media
|
||||
4. Enable all firmware write protections
|
||||
5. Update firmware to latest version (patches CVE-2022-21894)
|
||||
```
|
||||
@@ -0,0 +1,97 @@
|
||||
# API Reference: Bootkit and Rootkit Analysis Tools
|
||||
|
||||
## dd - Boot Sector Extraction
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
dd if=/dev/sda of=mbr.bin bs=512 count=1 # MBR
|
||||
dd if=/dev/sda of=first_track.bin bs=512 count=63 # First track
|
||||
dd if=/dev/sda1 of=vbr.bin bs=512 count=1 # VBR
|
||||
```
|
||||
|
||||
## ndisasm - 16-bit Disassembly
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
ndisasm -b16 mbr.bin > mbr_disasm.txt
|
||||
ndisasm -b16 -o 0x7C00 mbr.bin # Set origin to MBR load address
|
||||
```
|
||||
|
||||
### Key Flags
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-b16` | 16-bit real-mode disassembly |
|
||||
| `-b32` | 32-bit protected-mode |
|
||||
| `-o` | Origin address offset |
|
||||
|
||||
## UEFITool - Firmware Analysis
|
||||
|
||||
### CLI Syntax
|
||||
```bash
|
||||
UEFIExtract firmware.rom all # Extract all modules
|
||||
UEFIExtract firmware.rom <GUID> body # Extract specific module body
|
||||
```
|
||||
|
||||
### Output
|
||||
Extracts firmware volumes into a directory tree with each DXE driver, PEI module, and option ROM as separate files identified by GUID.
|
||||
|
||||
## chipsec - Hardware Security Assessment
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
python chipsec_main.py -m common.secureboot.variables # Check Secure Boot
|
||||
python chipsec_main.py -m common.bios_wp # SPI write protection
|
||||
python chipsec_main.py -m common.spi_lock # SPI lock status
|
||||
python chipsec_util.py spi dump firmware.rom # Dump SPI flash
|
||||
```
|
||||
|
||||
### Key Modules
|
||||
| Module | Purpose |
|
||||
|--------|---------|
|
||||
| `common.secureboot.variables` | Verify Secure Boot configuration |
|
||||
| `common.bios_wp` | Check BIOS write protection |
|
||||
| `common.spi_lock` | Verify SPI flash lock bits |
|
||||
| `common.smm` | SMM protection verification |
|
||||
|
||||
## Volatility 3 - Rootkit Detection Plugins
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
vol3 -f memory.dmp <plugin>
|
||||
```
|
||||
|
||||
### Rootkit Detection Plugins
|
||||
| Plugin | Purpose |
|
||||
|--------|---------|
|
||||
| `windows.ssdt` | System Service Descriptor Table hooks |
|
||||
| `windows.callbacks` | Kernel callback registrations |
|
||||
| `windows.driverscan` | Scan for driver objects |
|
||||
| `windows.modules` | List loaded kernel modules |
|
||||
| `windows.psscan` | Pool-tag scan for processes (finds hidden) |
|
||||
| `windows.pslist` | Active process list (DKOM-affected) |
|
||||
| `windows.idt` | Interrupt Descriptor Table hooks |
|
||||
|
||||
### Output Format
|
||||
```
|
||||
Offset Order Module Section Owner
|
||||
------- ----- ------ ------- -----
|
||||
0x... 0 ntoskrnl.exe .text ntoskrnl.exe
|
||||
0x... 73 UNKNOWN - rootkit.sys ← suspicious
|
||||
```
|
||||
|
||||
## flashrom - SPI Flash Dumping
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
flashrom -p internal -r firmware.rom # Read/dump
|
||||
flashrom -p internal -w clean.rom # Write/reflash
|
||||
flashrom -p internal --verify clean.rom # Verify flash contents
|
||||
```
|
||||
|
||||
## YARA - Firmware Pattern Scanning
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
yara -r uefi_malware.yar firmware.rom
|
||||
yara -s -r rules.yar firmware.rom # Show matching strings
|
||||
```
|
||||
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Bootkit and rootkit analysis agent for MBR/VBR/UEFI inspection and rootkit detection."""
|
||||
|
||||
import struct
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import math
|
||||
from collections import Counter
|
||||
|
||||
|
||||
def read_mbr(disk_path_or_file):
|
||||
"""Read and parse the first 512 bytes (MBR) from a disk image or device."""
|
||||
with open(disk_path_or_file, "rb") as f:
|
||||
mbr = f.read(512)
|
||||
return mbr
|
||||
|
||||
|
||||
def validate_mbr_signature(mbr_data):
|
||||
"""Check the MBR boot signature at bytes 510-511 (should be 0x55AA)."""
|
||||
sig = mbr_data[510:512]
|
||||
valid = sig == b"\x55\xAA"
|
||||
return valid, sig.hex()
|
||||
|
||||
|
||||
def parse_partition_table(mbr_data):
|
||||
"""Parse the four 16-byte partition table entries starting at offset 446."""
|
||||
partitions = []
|
||||
for i in range(4):
|
||||
offset = 446 + (i * 16)
|
||||
entry = mbr_data[offset:offset + 16]
|
||||
if entry == b"\x00" * 16:
|
||||
continue
|
||||
boot_flag = entry[0]
|
||||
part_type = entry[4]
|
||||
start_lba = struct.unpack_from("<I", entry, 8)[0]
|
||||
size_lba = struct.unpack_from("<I", entry, 12)[0]
|
||||
partitions.append({
|
||||
"index": i + 1,
|
||||
"active": boot_flag == 0x80,
|
||||
"type_id": f"0x{part_type:02X}",
|
||||
"start_lba": start_lba,
|
||||
"size_sectors": size_lba,
|
||||
"size_mb": round(size_lba * 512 / (1024 * 1024), 1),
|
||||
})
|
||||
return partitions
|
||||
|
||||
|
||||
BOOTKIT_SIGNATURES = {
|
||||
b"\xE8\x00\x00\x5E\x81\xEE": "TDL4/Alureon bootkit",
|
||||
b"\xFA\x33\xC0\x8E\xD0\xBC\x00\x7C\x8B\xF4\x50\x07": "Standard Windows MBR (clean)",
|
||||
b"\xEB\x5A\x90\x4E\x54\x46\x53": "Standard NTFS VBR (clean)",
|
||||
b"\xEB\x52\x90\x4E\x54\x46\x53": "NTFS VBR variant (clean)",
|
||||
b"\x33\xC0\x8E\xD0\xBC\x00\x7C": "Windows 10 MBR (clean)",
|
||||
}
|
||||
|
||||
|
||||
def scan_bootkit_signatures(data):
|
||||
"""Scan boot sector data against known bootkit signatures."""
|
||||
matches = []
|
||||
for sig, name in BOOTKIT_SIGNATURES.items():
|
||||
if sig in data:
|
||||
offset = data.find(sig)
|
||||
matches.append({"signature": name, "offset": offset, "clean": "clean" in name})
|
||||
return matches
|
||||
|
||||
|
||||
def calculate_entropy(data):
|
||||
"""Calculate Shannon entropy of binary data."""
|
||||
if not data:
|
||||
return 0.0
|
||||
counter = Counter(data)
|
||||
length = len(data)
|
||||
entropy = -sum(
|
||||
(count / length) * math.log2(count / length)
|
||||
for count in counter.values()
|
||||
)
|
||||
return round(entropy, 4)
|
||||
|
||||
|
||||
def read_first_track(disk_path, num_sectors=63):
|
||||
"""Read the first track (typically 63 sectors) for extended bootkit code."""
|
||||
with open(disk_path, "rb") as f:
|
||||
data = f.read(num_sectors * 512)
|
||||
return data
|
||||
|
||||
|
||||
def analyze_boot_code(mbr_data):
|
||||
"""Analyze MBR bootstrap code (bytes 0-445) for suspicious patterns."""
|
||||
boot_code = mbr_data[:446]
|
||||
entropy = calculate_entropy(boot_code)
|
||||
sha256 = hashlib.sha256(boot_code).hexdigest()
|
||||
suspicious_patterns = []
|
||||
# Check for INT 13h hooking (common bootkit technique)
|
||||
if b"\xCD\x13" in boot_code:
|
||||
count = boot_code.count(b"\xCD\x13")
|
||||
suspicious_patterns.append(f"INT 13h calls: {count}")
|
||||
# Check for far jumps to unusual addresses
|
||||
if b"\xEA" in boot_code:
|
||||
suspicious_patterns.append("Far JMP instruction found")
|
||||
# Check for self-modifying code patterns
|
||||
if b"\xF3\xA4" in boot_code or b"\xF3\xA5" in boot_code:
|
||||
suspicious_patterns.append("REP MOVSB/MOVSW (memory copy, possible code relocation)")
|
||||
return {
|
||||
"entropy": entropy,
|
||||
"sha256": sha256,
|
||||
"high_entropy": entropy > 6.5,
|
||||
"suspicious_patterns": suspicious_patterns,
|
||||
}
|
||||
|
||||
|
||||
def run_volatility_rootkit_scan(memory_dump, plugin):
|
||||
"""Run a Volatility 3 plugin for rootkit detection via subprocess."""
|
||||
result = subprocess.run(
|
||||
["vol3", "-f", memory_dump, plugin],
|
||||
capture_output=True, text=True,
|
||||
timeout=120,
|
||||
)
|
||||
return result.stdout, result.stderr, result.returncode
|
||||
|
||||
|
||||
def detect_kernel_rootkit(memory_dump):
|
||||
"""Run multiple Volatility plugins to detect kernel-level rootkit artifacts."""
|
||||
plugins = [
|
||||
"windows.ssdt",
|
||||
"windows.callbacks",
|
||||
"windows.driverscan",
|
||||
"windows.modules",
|
||||
"windows.psscan",
|
||||
"windows.pslist",
|
||||
]
|
||||
results = {}
|
||||
for plugin in plugins:
|
||||
stdout, stderr, rc = run_volatility_rootkit_scan(memory_dump, plugin)
|
||||
results[plugin] = {"output": stdout, "error": stderr, "return_code": rc}
|
||||
return results
|
||||
|
||||
|
||||
def compare_process_lists(pslist_output, psscan_output):
|
||||
"""Compare pslist and psscan output to find hidden processes (DKOM)."""
|
||||
pslist_pids = set()
|
||||
psscan_pids = set()
|
||||
for line in pslist_output.splitlines():
|
||||
parts = line.split()
|
||||
if len(parts) >= 2 and parts[1].isdigit():
|
||||
pslist_pids.add(int(parts[1]))
|
||||
for line in psscan_output.splitlines():
|
||||
parts = line.split()
|
||||
if len(parts) >= 2 and parts[1].isdigit():
|
||||
psscan_pids.add(int(parts[1]))
|
||||
hidden = psscan_pids - pslist_pids
|
||||
return hidden
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Bootkit & Rootkit Analysis Agent")
|
||||
print("MBR/VBR inspection, UEFI firmware analysis, rootkit detection")
|
||||
print("=" * 60)
|
||||
|
||||
# Demo with a sample MBR file if available
|
||||
demo_mbr = "mbr.bin"
|
||||
if len(sys.argv) > 1:
|
||||
demo_mbr = sys.argv[1]
|
||||
|
||||
if os.path.exists(demo_mbr):
|
||||
print(f"\n[*] Analyzing: {demo_mbr}")
|
||||
mbr = read_mbr(demo_mbr)
|
||||
valid, sig_hex = validate_mbr_signature(mbr)
|
||||
print(f"[*] MBR Signature: 0x{sig_hex.upper()} ({'Valid' if valid else 'INVALID'})")
|
||||
|
||||
partitions = parse_partition_table(mbr)
|
||||
print(f"[*] Partition entries: {len(partitions)}")
|
||||
for p in partitions:
|
||||
active = "Active" if p["active"] else "Inactive"
|
||||
print(f" Part {p['index']}: Type={p['type_id']} {active} "
|
||||
f"Start=LBA {p['start_lba']} Size={p['size_mb']} MB")
|
||||
|
||||
sigs = scan_bootkit_signatures(mbr)
|
||||
for s in sigs:
|
||||
tag = "[*]" if s["clean"] else "[!]"
|
||||
print(f"{tag} Signature match: {s['signature']} at offset {s['offset']}")
|
||||
|
||||
analysis = analyze_boot_code(mbr)
|
||||
print(f"[*] Boot code entropy: {analysis['entropy']}"
|
||||
f" ({'HIGH - possible encryption' if analysis['high_entropy'] else 'Normal'})")
|
||||
print(f"[*] Boot code SHA-256: {analysis['sha256']}")
|
||||
for pat in analysis["suspicious_patterns"]:
|
||||
print(f"[!] {pat}")
|
||||
else:
|
||||
print(f"\n[DEMO] No MBR file provided. Usage: {sys.argv[0]} <mbr.bin | /dev/sda>")
|
||||
print("[DEMO] Provide a 512-byte MBR dump or disk device for analysis.")
|
||||
print("\n[*] Supported analysis:")
|
||||
print(" - MBR/VBR signature validation and bootkit detection")
|
||||
print(" - Partition table parsing and anomaly detection")
|
||||
print(" - Boot code entropy and pattern analysis")
|
||||
print(" - Volatility-based kernel rootkit detection (SSDT, callbacks, DKOM)")
|
||||
print(" - UEFI firmware module inspection via chipsec subprocess")
|
||||
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,296 @@
|
||||
---
|
||||
name: analyzing-browser-forensics-with-hindsight
|
||||
description: Analyze Chromium-based browser artifacts using Hindsight to extract browsing history, downloads, cookies, cached
|
||||
content, autofill data, saved passwords, and browser extensions from Chrome, Edge, Brave, and Opera for forensic investigation.
|
||||
domain: cybersecurity
|
||||
subdomain: digital-forensics
|
||||
tags:
|
||||
- browser-forensics
|
||||
- hindsight
|
||||
- chrome-forensics
|
||||
- chromium
|
||||
- edge
|
||||
- browsing-history
|
||||
- cookies
|
||||
- downloads
|
||||
- cache
|
||||
- web-artifacts
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- RS.AN-01
|
||||
- RS.AN-03
|
||||
- DE.AE-02
|
||||
- RS.MA-01
|
||||
---
|
||||
|
||||
# Analyzing Browser Forensics with Hindsight
|
||||
|
||||
## Overview
|
||||
|
||||
Hindsight is an open-source browser forensics tool designed to parse artifacts from Google Chrome and other Chromium-based browsers (Microsoft Edge, Brave, Opera, Vivaldi). It extracts and correlates data from multiple browser database files to create a unified timeline of web activity. Hindsight can parse URLs, download history, cache records, bookmarks, autofill records, saved passwords, preferences, browser extensions, HTTP cookies, Local Storage (HTML5 cookies), login data, and session/tab information. The tool produces chronological timelines in multiple output formats (XLSX, JSON, SQLite) that enable investigators to reconstruct user web activity for incident response, insider threat investigations, and criminal cases.
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing browser forensics with hindsight
|
||||
- When building detection rules or threat hunting queries for this domain
|
||||
- When SOC analysts need structured procedures for this analysis type
|
||||
- When validating security monitoring coverage for related attack techniques
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.8+ with Hindsight installed (`pip install pyhindsight`)
|
||||
- Access to browser profile directories from forensic image
|
||||
- Browser profile data (not encrypted with OS-level encryption)
|
||||
- Timeline Explorer or spreadsheet application for analysis
|
||||
|
||||
## Browser Profile Locations
|
||||
|
||||
| Browser | Windows Profile Path |
|
||||
|---------|---------------------|
|
||||
| Chrome | %LOCALAPPDATA%\Google\Chrome\User Data\Default\ |
|
||||
| Edge | %LOCALAPPDATA%\Microsoft\Edge\User Data\Default\ |
|
||||
| Brave | %LOCALAPPDATA%\BraveSoftware\Brave-Browser\User Data\Default\ |
|
||||
| Opera | %APPDATA%\Opera Software\Opera Stable\ |
|
||||
| Vivaldi | %LOCALAPPDATA%\Vivaldi\User Data\Default\ |
|
||||
| Chrome (macOS) | ~/Library/Application Support/Google/Chrome/Default/ |
|
||||
| Chrome (Linux) | ~/.config/google-chrome/Default/ |
|
||||
|
||||
## Key Artifact Files
|
||||
|
||||
| File | Contents |
|
||||
|------|----------|
|
||||
| History | URL visits, downloads, keyword searches |
|
||||
| Cookies | HTTP cookies with domain, expiry, values |
|
||||
| Web Data | Autofill entries, saved credit cards |
|
||||
| Login Data | Saved usernames/passwords (encrypted) |
|
||||
| Bookmarks | JSON bookmark tree |
|
||||
| Preferences | Browser configuration and extensions |
|
||||
| Local Storage/ | HTML5 Local Storage per domain |
|
||||
| Session Storage/ | Session-specific storage per domain |
|
||||
| Network Action Predictor | Previously typed URLs |
|
||||
| Shortcuts | Omnibox shortcuts and predictions |
|
||||
| Top Sites | Frequently visited sites |
|
||||
|
||||
## Running Hindsight
|
||||
|
||||
### Command Line
|
||||
|
||||
```bash
|
||||
# Basic analysis of a Chrome profile
|
||||
hindsight.exe -i "C:\Evidence\Users\suspect\AppData\Local\Google\Chrome\User Data\Default" -o C:\Output\chrome_analysis
|
||||
|
||||
# Specify browser type
|
||||
hindsight.exe -i "/path/to/profile" -o /output/analysis -b Chrome
|
||||
|
||||
# JSON output format
|
||||
hindsight.exe -i "C:\Evidence\Chrome\Default" -o C:\Output\chrome --format jsonl
|
||||
|
||||
# With cache parsing (slower but more complete)
|
||||
hindsight.exe -i "C:\Evidence\Chrome\Default" -o C:\Output\chrome --cache
|
||||
```
|
||||
|
||||
### Web UI
|
||||
|
||||
```bash
|
||||
# Start Hindsight web interface
|
||||
hindsight_gui.exe
|
||||
# Navigate to http://localhost:8080
|
||||
# Upload or point to browser profile directory
|
||||
# Configure output format and analysis options
|
||||
# Generate and download report
|
||||
```
|
||||
|
||||
## Artifact Analysis Details
|
||||
|
||||
### URL History and Visits
|
||||
|
||||
```sql
|
||||
-- Chrome History database schema (key tables)
|
||||
-- urls table: id, url, title, visit_count, typed_count, last_visit_time
|
||||
-- visits table: id, url, visit_time, from_visit, transition, segment_id
|
||||
|
||||
-- Timestamps are Chrome/WebKit format: microseconds since 1601-01-01
|
||||
-- Convert: datetime((visit_time/1000000)-11644473600, 'unixepoch')
|
||||
```
|
||||
|
||||
### Download History
|
||||
|
||||
```sql
|
||||
-- downloads table: id, current_path, target_path, start_time, end_time,
|
||||
-- received_bytes, total_bytes, state, danger_type, interrupt_reason,
|
||||
-- url, referrer, tab_url, mime_type, original_mime_type
|
||||
```
|
||||
|
||||
### Cookie Analysis
|
||||
|
||||
```sql
|
||||
-- cookies table: creation_utc, host_key, name, value, encrypted_value,
|
||||
-- path, expires_utc, is_secure, is_httponly, last_access_utc,
|
||||
-- has_expires, is_persistent, priority, samesite
|
||||
```
|
||||
|
||||
## Python Analysis Script
|
||||
|
||||
```python
|
||||
import sqlite3
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
CHROME_EPOCH = datetime(1601, 1, 1)
|
||||
|
||||
|
||||
def chrome_time_to_datetime(chrome_ts: int):
|
||||
"""Convert Chrome timestamp to datetime."""
|
||||
if chrome_ts == 0:
|
||||
return None
|
||||
try:
|
||||
return CHROME_EPOCH + timedelta(microseconds=chrome_ts)
|
||||
except (OverflowError, OSError):
|
||||
return None
|
||||
|
||||
|
||||
def analyze_chrome_history(profile_path: str, output_dir: str) -> dict:
|
||||
"""Analyze Chrome History database for forensic evidence."""
|
||||
history_db = os.path.join(profile_path, "History")
|
||||
if not os.path.exists(history_db):
|
||||
return {"error": "History database not found"}
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
conn = sqlite3.connect(f"file:{history_db}?mode=ro", uri=True)
|
||||
|
||||
# URL visits with timestamps
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT u.url, u.title, v.visit_time, u.visit_count,
|
||||
v.transition & 0xFF as transition_type
|
||||
FROM visits v JOIN urls u ON v.url = u.id
|
||||
ORDER BY v.visit_time DESC LIMIT 5000
|
||||
""")
|
||||
visits = [{
|
||||
"url": r[0], "title": r[1],
|
||||
"visit_time": str(chrome_time_to_datetime(r[2])),
|
||||
"total_visits": r[3], "transition": r[4]
|
||||
} for r in cursor.fetchall()]
|
||||
|
||||
# Downloads
|
||||
cursor.execute("""
|
||||
SELECT target_path, tab_url, start_time, end_time,
|
||||
received_bytes, total_bytes, mime_type, state
|
||||
FROM downloads ORDER BY start_time DESC LIMIT 1000
|
||||
""")
|
||||
downloads = [{
|
||||
"path": r[0], "source_url": r[1],
|
||||
"start_time": str(chrome_time_to_datetime(r[2])),
|
||||
"end_time": str(chrome_time_to_datetime(r[3])),
|
||||
"received_bytes": r[4], "total_bytes": r[5],
|
||||
"mime_type": r[6], "state": r[7]
|
||||
} for r in cursor.fetchall()]
|
||||
|
||||
# Keyword searches
|
||||
cursor.execute("""
|
||||
SELECT k.term, u.url, k.url_id
|
||||
FROM keyword_search_terms k JOIN urls u ON k.url_id = u.id
|
||||
ORDER BY u.last_visit_time DESC LIMIT 1000
|
||||
""")
|
||||
searches = [{"term": r[0], "url": r[1]} for r in cursor.fetchall()]
|
||||
|
||||
conn.close()
|
||||
|
||||
report = {
|
||||
"analysis_timestamp": datetime.now().isoformat(),
|
||||
"profile_path": profile_path,
|
||||
"total_visits": len(visits),
|
||||
"total_downloads": len(downloads),
|
||||
"total_searches": len(searches),
|
||||
"visits": visits,
|
||||
"downloads": downloads,
|
||||
"searches": searches
|
||||
}
|
||||
|
||||
report_path = os.path.join(output_dir, "browser_forensics.json")
|
||||
with open(report_path, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python process.py <chrome_profile_path> <output_dir>")
|
||||
sys.exit(1)
|
||||
analyze_chrome_history(sys.argv[1], sys.argv[2])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- Hindsight GitHub: https://github.com/obsidianforensics/hindsight
|
||||
- Chrome Forensics Guide: https://allenace.medium.com/hindsight-chrome-forensics-made-simple-425db99fa5ed
|
||||
- Browser Forensics Tools: https://www.cyberforensicacademy.com/blog/browser-forensics-tools-how-to-extract-user-activity
|
||||
- Chromium Source (History): https://source.chromium.org/chromium/chromium/src/+/main:components/history/
|
||||
|
||||
## Example Output
|
||||
|
||||
```text
|
||||
$ python hindsight.py -i /evidence/chrome-profile -o /analysis/hindsight_output
|
||||
|
||||
Hindsight v2024.01 - Chrome/Chromium Browser Forensic Analysis
|
||||
================================================================
|
||||
|
||||
Profile: /evidence/chrome-profile (Chrome 120.0.6099.130)
|
||||
OS: Windows 10
|
||||
|
||||
[+] Parsing History database...
|
||||
URL records: 12,456
|
||||
Download records: 234
|
||||
Search terms: 567
|
||||
|
||||
[+] Parsing Cookies database...
|
||||
Cookie records: 8,923
|
||||
Encrypted cookies: 6,712
|
||||
|
||||
[+] Parsing Web Data (Autofill)...
|
||||
Autofill entries: 1,234
|
||||
Credit card entries: 2 (encrypted)
|
||||
|
||||
[+] Parsing Login Data...
|
||||
Saved credentials: 45 (encrypted)
|
||||
|
||||
[+] Parsing Bookmarks...
|
||||
Bookmark entries: 189
|
||||
|
||||
--- Browsing History (Last 10 Entries) ---
|
||||
Timestamp (UTC) | URL | Title | Visit Count
|
||||
2024-01-15 14:32:05.123 | https://mail.corporate.com/inbox | Corporate Mail | 45
|
||||
2024-01-15 14:33:12.456 | https://drive.google.com/file/d/1aBcDe... | Q4_Financial_Report.xlsx | 1
|
||||
2024-01-15 14:35:44.789 | https://mega.nz/folder/xYz123 | MEGA - Secure Cloud | 3
|
||||
2024-01-15 14:36:01.234 | https://mega.nz/folder/xYz123#upload | MEGA - Upload | 8
|
||||
2024-01-15 14:42:15.567 | https://pastebin.com/raw/kL9mN2pQ | Pastebin (raw) | 1
|
||||
2024-01-15 15:01:33.890 | https://192.168.1.50:8443/admin | Admin Panel | 12
|
||||
2024-01-15 15:15:22.111 | https://transfer.sh/upload | transfer.sh | 2
|
||||
2024-01-15 15:30:45.222 | https://vpn-gateway.corporate.com | VPN Login | 5
|
||||
2024-01-15 16:00:00.333 | https://whatismyipaddress.com | What Is My IP | 1
|
||||
2024-01-15 16:05:12.444 | https://protonmail.com/inbox | ProtonMail | 3
|
||||
|
||||
--- Downloads (Suspicious) ---
|
||||
Timestamp (UTC) | Filename | URL Source | Size
|
||||
2024-01-15 14:33:15.000 | Q4_Financial_Report.xlsm | https://phish-domain.com/docs/report | 245 KB
|
||||
2024-01-15 14:34:02.000 | update_client.exe | https://cdn.evil-updates.com/client.exe | 1.2 MB
|
||||
|
||||
--- Cookies (Session Tokens) ---
|
||||
Domain | Name | Expires | Secure | HttpOnly
|
||||
.corporate.com | SESSION_ID | 2024-01-16 14:32 | Yes | Yes
|
||||
.mega.nz | session | Session | Yes | Yes
|
||||
.protonmail.com | AUTH-TOKEN | 2024-02-15 00:00 | Yes | Yes
|
||||
|
||||
Report saved to: /analysis/hindsight_output/Hindsight_Report.xlsx
|
||||
```
|
||||
@@ -0,0 +1,22 @@
|
||||
# Browser Forensics Report
|
||||
## Case Info
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Case Number | |
|
||||
| Browser | |
|
||||
| Profile Path | |
|
||||
## Activity Summary
|
||||
| Metric | Count |
|
||||
|--------|-------|
|
||||
| URL Visits | |
|
||||
| Downloads | |
|
||||
| Saved Passwords | |
|
||||
| Cookies | |
|
||||
## Notable URLs
|
||||
| Timestamp | URL | Title |
|
||||
|-----------|-----|-------|
|
||||
| | | |
|
||||
## Downloads
|
||||
| Timestamp | File | Source URL | Size |
|
||||
|-----------|------|-----------|------|
|
||||
| | | | |
|
||||
@@ -0,0 +1,92 @@
|
||||
# API Reference: Browser Forensics with Hindsight
|
||||
|
||||
## Hindsight CLI
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
hindsight.py -i <profile_path> # Analyze Chrome profile
|
||||
hindsight.py -i <path> -o <output_dir> # Save results
|
||||
hindsight.py -i <path> -f xlsx # Export as Excel
|
||||
hindsight.py -i <path> -f sqlite # Export as SQLite
|
||||
hindsight.py -i <path> -b <browser_type> # Specify browser type
|
||||
```
|
||||
|
||||
### Browser Types
|
||||
| Flag | Browser |
|
||||
|------|---------|
|
||||
| `Chrome` | Google Chrome |
|
||||
| `Edge` | Microsoft Edge (Chromium) |
|
||||
| `Brave` | Brave Browser |
|
||||
| `Opera` | Opera (Chromium) |
|
||||
|
||||
### Output Artifacts
|
||||
| Table | Description |
|
||||
|-------|-------------|
|
||||
| `urls` | Browsing history with visit counts |
|
||||
| `downloads` | File downloads with source URLs |
|
||||
| `cookies` | Cookie values, domains, expiry |
|
||||
| `autofill` | Form autofill entries |
|
||||
| `bookmarks` | Saved bookmarks |
|
||||
| `preferences` | Browser configuration |
|
||||
| `local_storage` | Site local storage data |
|
||||
| `login_data` | Saved credential metadata |
|
||||
| `extensions` | Installed extensions with permissions |
|
||||
|
||||
## Chrome SQLite Databases
|
||||
|
||||
### History Database
|
||||
```sql
|
||||
-- Browsing history
|
||||
SELECT u.url, u.title, v.visit_time, v.transition
|
||||
FROM visits v JOIN urls u ON v.url = u.id
|
||||
ORDER BY v.visit_time DESC;
|
||||
|
||||
-- Downloads
|
||||
SELECT target_path, tab_url, total_bytes, start_time, danger_type, mime_type
|
||||
FROM downloads ORDER BY start_time DESC;
|
||||
```
|
||||
|
||||
### Cookies Database
|
||||
```sql
|
||||
SELECT host_key, name, value, creation_utc, expires_utc, is_secure, is_httponly
|
||||
FROM cookies ORDER BY creation_utc DESC;
|
||||
```
|
||||
|
||||
### Web Data Database (Autofill)
|
||||
```sql
|
||||
SELECT name, value, count, date_created, date_last_used
|
||||
FROM autofill ORDER BY date_last_used DESC;
|
||||
```
|
||||
|
||||
## Chrome Timestamp Conversion
|
||||
|
||||
### Format
|
||||
Microseconds since January 1, 1601 (Windows FILETIME base)
|
||||
|
||||
### Python Conversion
|
||||
```python
|
||||
import datetime
|
||||
def chrome_to_datetime(chrome_time):
|
||||
epoch = datetime.datetime(1601, 1, 1)
|
||||
return epoch + datetime.timedelta(microseconds=chrome_time)
|
||||
```
|
||||
|
||||
## Browser Profile Paths
|
||||
|
||||
| OS | Browser | Default Path |
|
||||
|----|---------|-------------|
|
||||
| Windows | Chrome | `%LOCALAPPDATA%\Google\Chrome\User Data\Default` |
|
||||
| Windows | Edge | `%LOCALAPPDATA%\Microsoft\Edge\User Data\Default` |
|
||||
| Linux | Chrome | `~/.config/google-chrome/Default` |
|
||||
| macOS | Chrome | `~/Library/Application Support/Google/Chrome/Default` |
|
||||
|
||||
## Transition Types (visit_transition & 0xFF)
|
||||
| Value | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| 0 | LINK | Clicked a link |
|
||||
| 1 | TYPED | Typed URL in address bar |
|
||||
| 2 | AUTO_BOOKMARK | Via bookmark |
|
||||
| 3 | AUTO_SUBFRAME | Subframe navigation |
|
||||
| 5 | GENERATED | Generated (e.g., search) |
|
||||
| 7 | FORM_SUBMIT | Form submission |
|
||||
| 8 | RELOAD | Page reload |
|
||||
@@ -0,0 +1,15 @@
|
||||
# Standards - Browser Forensics with Hindsight
|
||||
## Tools
|
||||
- Hindsight: https://github.com/obsidianforensics/hindsight
|
||||
- DB Browser for SQLite: Chrome database inspection
|
||||
- ChromeCacheView (NirSoft): Cache analysis
|
||||
## Browser Databases
|
||||
- History: URL visits, downloads, keyword searches
|
||||
- Cookies: HTTP cookies per domain
|
||||
- Web Data: Autofill, credit cards
|
||||
- Login Data: Saved credentials (encrypted)
|
||||
- Bookmarks: JSON bookmark tree
|
||||
## Timestamp Formats
|
||||
- Chrome/WebKit: microseconds since 1601-01-01 UTC
|
||||
- Firefox/Mozilla: microseconds since Unix epoch
|
||||
- Safari/Mac: seconds since 2001-01-01 UTC
|
||||
@@ -0,0 +1,19 @@
|
||||
# Workflows - Browser Forensics
|
||||
## Workflow: Chrome Profile Analysis
|
||||
```
|
||||
Locate browser profile directory
|
||||
|
|
||||
Run Hindsight against profile path
|
||||
|
|
||||
Review generated timeline (XLSX/JSON)
|
||||
|
|
||||
Analyze URL history for suspicious sites
|
||||
|
|
||||
Check downloads for malware/exfiltrated data
|
||||
|
|
||||
Review cookies for session hijacking evidence
|
||||
|
|
||||
Examine autofill and saved credentials
|
||||
|
|
||||
Correlate browser activity with system timeline
|
||||
```
|
||||
@@ -0,0 +1,254 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Browser forensics analysis agent using Hindsight concepts.
|
||||
|
||||
Parses Chromium-based browser artifacts (Chrome, Edge, Brave) including
|
||||
history, downloads, cookies, autofill, and extensions from SQLite databases.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import sqlite3
|
||||
import datetime
|
||||
|
||||
|
||||
def chrome_time_to_datetime(chrome_time):
|
||||
"""Convert Chrome timestamp (microseconds since 1601-01-01) to datetime."""
|
||||
if not chrome_time or chrome_time == 0:
|
||||
return None
|
||||
try:
|
||||
epoch = datetime.datetime(1601, 1, 1)
|
||||
delta = datetime.timedelta(microseconds=chrome_time)
|
||||
return (epoch + delta).isoformat() + "Z"
|
||||
except (OverflowError, OSError):
|
||||
return None
|
||||
|
||||
|
||||
def find_browser_profiles(base_path=None):
|
||||
"""Locate Chromium-based browser profile directories."""
|
||||
if base_path and os.path.isdir(base_path):
|
||||
return [base_path]
|
||||
profiles = []
|
||||
home = os.path.expanduser("~")
|
||||
candidates = [
|
||||
os.path.join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Default"),
|
||||
os.path.join(home, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default"),
|
||||
os.path.join(home, "AppData", "Local", "BraveSoftware", "Brave-Browser", "User Data", "Default"),
|
||||
os.path.join(home, ".config", "google-chrome", "Default"),
|
||||
os.path.join(home, ".config", "chromium", "Default"),
|
||||
os.path.join(home, ".config", "microsoft-edge", "Default"),
|
||||
]
|
||||
for path in candidates:
|
||||
if os.path.isdir(path):
|
||||
profiles.append(path)
|
||||
return profiles
|
||||
|
||||
|
||||
def parse_history(profile_path):
|
||||
"""Parse browsing history from History SQLite database."""
|
||||
db_path = os.path.join(profile_path, "History")
|
||||
if not os.path.exists(db_path):
|
||||
return []
|
||||
entries = []
|
||||
try:
|
||||
conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT u.url, u.title, v.visit_time, v.transition, u.visit_count
|
||||
FROM visits v JOIN urls u ON v.url = u.id
|
||||
ORDER BY v.visit_time DESC LIMIT 5000
|
||||
""")
|
||||
for url, title, visit_time, transition, count in cursor.fetchall():
|
||||
entries.append({
|
||||
"url": url, "title": title or "",
|
||||
"visit_time": chrome_time_to_datetime(visit_time),
|
||||
"transition": transition & 0xFF,
|
||||
"visit_count": count,
|
||||
})
|
||||
conn.close()
|
||||
except sqlite3.Error as e:
|
||||
entries.append({"error": str(e)})
|
||||
return entries
|
||||
|
||||
|
||||
def parse_downloads(profile_path):
|
||||
"""Parse download history from History database."""
|
||||
db_path = os.path.join(profile_path, "History")
|
||||
if not os.path.exists(db_path):
|
||||
return []
|
||||
downloads = []
|
||||
try:
|
||||
conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT target_path, tab_url, total_bytes, start_time, end_time,
|
||||
danger_type, interrupt_reason, mime_type
|
||||
FROM downloads ORDER BY start_time DESC LIMIT 1000
|
||||
""")
|
||||
for row in cursor.fetchall():
|
||||
downloads.append({
|
||||
"target_path": row[0], "source_url": row[1],
|
||||
"total_bytes": row[2],
|
||||
"start_time": chrome_time_to_datetime(row[3]),
|
||||
"end_time": chrome_time_to_datetime(row[4]),
|
||||
"danger_type": row[5], "interrupt_reason": row[6],
|
||||
"mime_type": row[7],
|
||||
})
|
||||
conn.close()
|
||||
except sqlite3.Error as e:
|
||||
downloads.append({"error": str(e)})
|
||||
return downloads
|
||||
|
||||
|
||||
def parse_cookies(profile_path):
|
||||
"""Parse cookies from Cookies database."""
|
||||
db_path = os.path.join(profile_path, "Cookies")
|
||||
if not os.path.exists(db_path):
|
||||
db_path = os.path.join(profile_path, "Network", "Cookies")
|
||||
if not os.path.exists(db_path):
|
||||
return []
|
||||
cookies = []
|
||||
try:
|
||||
conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT host_key, name, path, creation_utc, expires_utc,
|
||||
is_secure, is_httponly, samesite
|
||||
FROM cookies ORDER BY creation_utc DESC LIMIT 2000
|
||||
""")
|
||||
for row in cursor.fetchall():
|
||||
cookies.append({
|
||||
"host": row[0], "name": row[1], "path": row[2],
|
||||
"created": chrome_time_to_datetime(row[3]),
|
||||
"expires": chrome_time_to_datetime(row[4]),
|
||||
"secure": bool(row[5]), "httponly": bool(row[6]),
|
||||
"samesite": row[7],
|
||||
})
|
||||
conn.close()
|
||||
except sqlite3.Error as e:
|
||||
cookies.append({"error": str(e)})
|
||||
return cookies
|
||||
|
||||
|
||||
def parse_autofill(profile_path):
|
||||
"""Parse autofill data from Web Data database."""
|
||||
db_path = os.path.join(profile_path, "Web Data")
|
||||
if not os.path.exists(db_path):
|
||||
return []
|
||||
entries = []
|
||||
try:
|
||||
conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT name, value, count, date_created, date_last_used
|
||||
FROM autofill ORDER BY date_last_used DESC LIMIT 500
|
||||
""")
|
||||
for row in cursor.fetchall():
|
||||
entries.append({
|
||||
"field_name": row[0], "value": row[1][:50] + "..." if len(row[1]) > 50 else row[1],
|
||||
"usage_count": row[2],
|
||||
"created": chrome_time_to_datetime(row[3] * 1000000 if row[3] else 0),
|
||||
"last_used": chrome_time_to_datetime(row[4] * 1000000 if row[4] else 0),
|
||||
})
|
||||
conn.close()
|
||||
except sqlite3.Error as e:
|
||||
entries.append({"error": str(e)})
|
||||
return entries
|
||||
|
||||
|
||||
def parse_extensions(profile_path):
|
||||
"""Parse installed browser extensions."""
|
||||
ext_dir = os.path.join(profile_path, "Extensions")
|
||||
extensions = []
|
||||
if not os.path.isdir(ext_dir):
|
||||
return extensions
|
||||
for ext_id in os.listdir(ext_dir):
|
||||
ext_path = os.path.join(ext_dir, ext_id)
|
||||
if os.path.isdir(ext_path):
|
||||
versions = sorted(os.listdir(ext_path))
|
||||
manifest_path = os.path.join(ext_path, versions[-1], "manifest.json") if versions else None
|
||||
name = ext_id
|
||||
if manifest_path and os.path.exists(manifest_path):
|
||||
try:
|
||||
with open(manifest_path, "r", encoding="utf-8") as f:
|
||||
manifest = json.load(f)
|
||||
name = manifest.get("name", ext_id)
|
||||
extensions.append({
|
||||
"id": ext_id, "name": name,
|
||||
"version": manifest.get("version", "?"),
|
||||
"permissions": manifest.get("permissions", [])[:10],
|
||||
})
|
||||
except (json.JSONDecodeError, IOError):
|
||||
extensions.append({"id": ext_id, "name": name, "version": "unknown"})
|
||||
return extensions
|
||||
|
||||
|
||||
def detect_suspicious_activity(history, downloads):
|
||||
"""Flag suspicious browsing and download patterns."""
|
||||
findings = []
|
||||
suspicious_domains = ["pastebin.com", "ngrok.io", "raw.githubusercontent.com",
|
||||
"transfer.sh", "file.io", "temp.sh", "anonfiles.com"]
|
||||
for entry in history:
|
||||
url = entry.get("url", "").lower()
|
||||
for domain in suspicious_domains:
|
||||
if domain in url:
|
||||
findings.append({
|
||||
"type": "suspicious_url", "url": entry["url"],
|
||||
"domain": domain, "time": entry.get("visit_time"),
|
||||
})
|
||||
dangerous_mimes = ["application/x-msdownload", "application/x-msdos-program",
|
||||
"application/x-executable", "application/vnd.ms-excel.sheet.macroEnabled"]
|
||||
for dl in downloads:
|
||||
if dl.get("danger_type", 0) > 0:
|
||||
findings.append({
|
||||
"type": "dangerous_download", "path": dl.get("target_path"),
|
||||
"source": dl.get("source_url"), "danger_type": dl.get("danger_type"),
|
||||
})
|
||||
if dl.get("mime_type", "") in dangerous_mimes:
|
||||
findings.append({
|
||||
"type": "suspicious_mime", "mime": dl.get("mime_type"),
|
||||
"path": dl.get("target_path"),
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Browser Forensics Analysis Agent")
|
||||
print("Chromium history, downloads, cookies, extensions")
|
||||
print("=" * 60)
|
||||
|
||||
target = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
profiles = find_browser_profiles(target)
|
||||
|
||||
if not profiles:
|
||||
print("\n[!] No browser profiles found.")
|
||||
print("[DEMO] Usage: python agent.py <profile_path>")
|
||||
print(" e.g. python agent.py ~/AppData/Local/Google/Chrome/User\\ Data/Default")
|
||||
sys.exit(0)
|
||||
|
||||
for profile in profiles:
|
||||
print(f"\n[*] Profile: {profile}")
|
||||
|
||||
history = parse_history(profile)
|
||||
print(f" History entries: {len(history)}")
|
||||
for h in history[:5]:
|
||||
print(f" {h.get('visit_time', '?')} | {h.get('title', '')[:50]} | {h.get('url', '')[:60]}")
|
||||
|
||||
downloads = parse_downloads(profile)
|
||||
print(f" Downloads: {len(downloads)}")
|
||||
for d in downloads[:5]:
|
||||
print(f" {d.get('start_time', '?')} | {d.get('mime_type', '?')} | {os.path.basename(d.get('target_path', ''))}")
|
||||
|
||||
cookies = parse_cookies(profile)
|
||||
print(f" Cookies: {len(cookies)}")
|
||||
|
||||
extensions = parse_extensions(profile)
|
||||
print(f" Extensions: {len(extensions)}")
|
||||
for ext in extensions[:5]:
|
||||
print(f" {ext.get('name', '?')} v{ext.get('version', '?')} [{ext.get('id', '')[:20]}]")
|
||||
|
||||
findings = detect_suspicious_activity(history, downloads)
|
||||
print(f"\n --- Suspicious Activity: {len(findings)} findings ---")
|
||||
for f in findings[:10]:
|
||||
print(f" [{f['type']}] {f.get('url', f.get('path', ''))}")
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Browser Forensics Analyzer - Parses Chrome History SQLite for investigation."""
|
||||
import sqlite3, json, os, sys
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
CHROME_EPOCH = datetime(1601, 1, 1)
|
||||
|
||||
def chrome_ts(ts):
|
||||
if not ts: return None
|
||||
try: return str(CHROME_EPOCH + timedelta(microseconds=ts))
|
||||
except: return None
|
||||
|
||||
def analyze_chrome(profile: str, output_dir: str) -> str:
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
history_db = os.path.join(profile, "History")
|
||||
conn = sqlite3.connect(f"file:{history_db}?mode=ro", uri=True)
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT u.url, u.title, v.visit_time, u.visit_count FROM visits v JOIN urls u ON v.url=u.id ORDER BY v.visit_time DESC LIMIT 2000")
|
||||
visits = [{"url": r[0], "title": r[1], "time": chrome_ts(r[2]), "count": r[3]} for r in c.fetchall()]
|
||||
c.execute("SELECT target_path, tab_url, start_time, total_bytes, mime_type FROM downloads ORDER BY start_time DESC LIMIT 500")
|
||||
downloads = [{"path": r[0], "url": r[1], "time": chrome_ts(r[2]), "size": r[3], "mime": r[4]} for r in c.fetchall()]
|
||||
conn.close()
|
||||
report = {"visits": len(visits), "downloads": len(downloads), "visit_data": visits, "download_data": downloads}
|
||||
out = os.path.join(output_dir, "browser_forensics.json")
|
||||
with open(out, "w") as f: json.dump(report, f, indent=2)
|
||||
print(f"[*] Visits: {len(visits)}, Downloads: {len(downloads)}")
|
||||
return out
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 3: print("Usage: process.py <chrome_profile> <output>"); sys.exit(1)
|
||||
analyze_chrome(sys.argv[1], sys.argv[2])
|
||||
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,237 @@
|
||||
---
|
||||
name: analyzing-campaign-attribution-evidence
|
||||
description: Campaign attribution analysis involves systematically evaluating evidence to determine which threat actor or
|
||||
group is responsible for a cyber operation. This skill covers collecting and weighting attr
|
||||
domain: cybersecurity
|
||||
subdomain: threat-intelligence
|
||||
tags:
|
||||
- threat-intelligence
|
||||
- cti
|
||||
- ioc
|
||||
- mitre-attack
|
||||
- stix
|
||||
- attribution
|
||||
- campaign-analysis
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- ID.RA-01
|
||||
- ID.RA-05
|
||||
- DE.CM-01
|
||||
- DE.AE-02
|
||||
---
|
||||
# Analyzing Campaign Attribution Evidence
|
||||
|
||||
## Overview
|
||||
|
||||
Campaign attribution analysis involves systematically evaluating evidence to determine which threat actor or group is responsible for a cyber operation. This skill covers collecting and weighting attribution indicators using the Diamond Model and ACH (Analysis of Competing Hypotheses), analyzing infrastructure overlaps, TTP consistency, malware code similarities, operational timing patterns, and language artifacts to build confidence-weighted attribution assessments.
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing campaign attribution evidence
|
||||
- When building detection rules or threat hunting queries for this domain
|
||||
- When SOC analysts need structured procedures for this analysis type
|
||||
- When validating security monitoring coverage for related attack techniques
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.9+ with `attackcti`, `stix2`, `networkx` libraries
|
||||
- Access to threat intelligence platforms (MISP, OpenCTI)
|
||||
- Understanding of Diamond Model of Intrusion Analysis
|
||||
- Familiarity with MITRE ATT&CK threat group profiles
|
||||
- Knowledge of malware analysis and infrastructure tracking techniques
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Attribution Evidence Categories
|
||||
1. **Infrastructure Overlap**: Shared C2 servers, domains, IP ranges, hosting providers
|
||||
2. **TTP Consistency**: Matching ATT&CK techniques and sub-techniques across campaigns
|
||||
3. **Malware Code Similarity**: Shared code bases, compilers, PDB paths, encryption routines
|
||||
4. **Operational Patterns**: Timing (working hours, time zones), targeting patterns, operational tempo
|
||||
5. **Language Artifacts**: Embedded strings, variable names, error messages in specific languages
|
||||
6. **Victimology**: Target sector, geography, and organizational profile consistency
|
||||
|
||||
### Confidence Levels
|
||||
- **High Confidence**: Multiple independent evidence categories converge on same actor
|
||||
- **Moderate Confidence**: Several evidence categories match, some ambiguity remains
|
||||
- **Low Confidence**: Limited evidence, possible false flags or shared tooling
|
||||
|
||||
### Analysis of Competing Hypotheses (ACH)
|
||||
Structured analytical method that evaluates evidence against multiple competing hypotheses. Each piece of evidence is scored as consistent, inconsistent, or neutral with respect to each hypothesis. The hypothesis with the least inconsistent evidence is favored.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Collect Attribution Evidence
|
||||
|
||||
```python
|
||||
from stix2 import MemoryStore, Filter
|
||||
from collections import defaultdict
|
||||
|
||||
class AttributionAnalyzer:
|
||||
def __init__(self):
|
||||
self.evidence = []
|
||||
self.hypotheses = {}
|
||||
|
||||
def add_evidence(self, category, description, value, confidence):
|
||||
self.evidence.append({
|
||||
"category": category,
|
||||
"description": description,
|
||||
"value": value,
|
||||
"confidence": confidence,
|
||||
"timestamp": None,
|
||||
})
|
||||
|
||||
def add_hypothesis(self, actor_name, actor_id=""):
|
||||
self.hypotheses[actor_name] = {
|
||||
"actor_id": actor_id,
|
||||
"consistent_evidence": [],
|
||||
"inconsistent_evidence": [],
|
||||
"neutral_evidence": [],
|
||||
"score": 0,
|
||||
}
|
||||
|
||||
def evaluate_evidence(self, evidence_idx, actor_name, assessment):
|
||||
"""Assess evidence against a hypothesis: consistent/inconsistent/neutral."""
|
||||
if assessment == "consistent":
|
||||
self.hypotheses[actor_name]["consistent_evidence"].append(evidence_idx)
|
||||
self.hypotheses[actor_name]["score"] += self.evidence[evidence_idx]["confidence"]
|
||||
elif assessment == "inconsistent":
|
||||
self.hypotheses[actor_name]["inconsistent_evidence"].append(evidence_idx)
|
||||
self.hypotheses[actor_name]["score"] -= self.evidence[evidence_idx]["confidence"] * 2
|
||||
else:
|
||||
self.hypotheses[actor_name]["neutral_evidence"].append(evidence_idx)
|
||||
|
||||
def rank_hypotheses(self):
|
||||
"""Rank hypotheses by attribution score."""
|
||||
ranked = sorted(
|
||||
self.hypotheses.items(),
|
||||
key=lambda x: x[1]["score"],
|
||||
reverse=True,
|
||||
)
|
||||
return [
|
||||
{
|
||||
"actor": name,
|
||||
"score": data["score"],
|
||||
"consistent": len(data["consistent_evidence"]),
|
||||
"inconsistent": len(data["inconsistent_evidence"]),
|
||||
"confidence": self._score_to_confidence(data["score"]),
|
||||
}
|
||||
for name, data in ranked
|
||||
]
|
||||
|
||||
def _score_to_confidence(self, score):
|
||||
if score >= 80:
|
||||
return "HIGH"
|
||||
elif score >= 40:
|
||||
return "MODERATE"
|
||||
else:
|
||||
return "LOW"
|
||||
```
|
||||
|
||||
### Step 2: Infrastructure Overlap Analysis
|
||||
|
||||
```python
|
||||
def analyze_infrastructure_overlap(campaign_a_infra, campaign_b_infra):
|
||||
"""Compare infrastructure between two campaigns for attribution."""
|
||||
overlap = {
|
||||
"shared_ips": set(campaign_a_infra.get("ips", [])).intersection(
|
||||
campaign_b_infra.get("ips", [])
|
||||
),
|
||||
"shared_domains": set(campaign_a_infra.get("domains", [])).intersection(
|
||||
campaign_b_infra.get("domains", [])
|
||||
),
|
||||
"shared_asns": set(campaign_a_infra.get("asns", [])).intersection(
|
||||
campaign_b_infra.get("asns", [])
|
||||
),
|
||||
"shared_registrars": set(campaign_a_infra.get("registrars", [])).intersection(
|
||||
campaign_b_infra.get("registrars", [])
|
||||
),
|
||||
}
|
||||
|
||||
overlap_score = 0
|
||||
if overlap["shared_ips"]:
|
||||
overlap_score += 30
|
||||
if overlap["shared_domains"]:
|
||||
overlap_score += 25
|
||||
if overlap["shared_asns"]:
|
||||
overlap_score += 15
|
||||
if overlap["shared_registrars"]:
|
||||
overlap_score += 10
|
||||
|
||||
return {
|
||||
"overlap": {k: list(v) for k, v in overlap.items()},
|
||||
"overlap_score": overlap_score,
|
||||
"assessment": "STRONG" if overlap_score >= 40 else "MODERATE" if overlap_score >= 20 else "WEAK",
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: TTP Comparison Across Campaigns
|
||||
|
||||
```python
|
||||
from attackcti import attack_client
|
||||
|
||||
def compare_campaign_ttps(campaign_techniques, known_actor_techniques):
|
||||
"""Compare campaign TTPs against known threat actor profiles."""
|
||||
campaign_set = set(campaign_techniques)
|
||||
actor_set = set(known_actor_techniques)
|
||||
|
||||
common = campaign_set.intersection(actor_set)
|
||||
unique_campaign = campaign_set - actor_set
|
||||
unique_actor = actor_set - campaign_set
|
||||
|
||||
jaccard = len(common) / len(campaign_set.union(actor_set)) if campaign_set.union(actor_set) else 0
|
||||
|
||||
return {
|
||||
"common_techniques": sorted(common),
|
||||
"common_count": len(common),
|
||||
"unique_to_campaign": sorted(unique_campaign),
|
||||
"unique_to_actor": sorted(unique_actor),
|
||||
"jaccard_similarity": round(jaccard, 3),
|
||||
"overlap_percentage": round(len(common) / len(campaign_set) * 100, 1) if campaign_set else 0,
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Generate Attribution Report
|
||||
|
||||
```python
|
||||
def generate_attribution_report(analyzer):
|
||||
"""Generate structured attribution assessment report."""
|
||||
rankings = analyzer.rank_hypotheses()
|
||||
|
||||
report = {
|
||||
"assessment_date": "2026-02-23",
|
||||
"total_evidence_items": len(analyzer.evidence),
|
||||
"hypotheses_evaluated": len(analyzer.hypotheses),
|
||||
"rankings": rankings,
|
||||
"primary_attribution": rankings[0] if rankings else None,
|
||||
"evidence_summary": [
|
||||
{
|
||||
"index": i,
|
||||
"category": e["category"],
|
||||
"description": e["description"],
|
||||
"confidence": e["confidence"],
|
||||
}
|
||||
for i, e in enumerate(analyzer.evidence)
|
||||
],
|
||||
}
|
||||
|
||||
return report
|
||||
```
|
||||
|
||||
## Validation Criteria
|
||||
|
||||
- Evidence collection covers all six attribution categories
|
||||
- ACH matrix properly evaluates evidence against competing hypotheses
|
||||
- Infrastructure overlap analysis identifies shared indicators
|
||||
- TTP comparison uses ATT&CK technique IDs for precision
|
||||
- Attribution confidence levels are properly justified
|
||||
- Report includes alternative hypotheses and false flag considerations
|
||||
|
||||
## References
|
||||
|
||||
- [Diamond Model of Intrusion Analysis](https://www.activeresponse.org/wp-content/uploads/2013/07/diamond.pdf)
|
||||
- [MITRE ATT&CK Groups](https://attack.mitre.org/groups/)
|
||||
- [Analysis of Competing Hypotheses](https://www.cia.gov/static/9a5f1162fd0932c29e985f0159f56c07/Tradecraft-Primer-apr09.pdf)
|
||||
- [Threat Attribution Framework](https://www.mandiant.com/resources/reports)
|
||||
@@ -0,0 +1,39 @@
|
||||
# Campaign Attribution Analysis Report Template
|
||||
|
||||
## Report Metadata
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Report ID | CTI-YYYY-NNNN |
|
||||
| Date | YYYY-MM-DD |
|
||||
| Classification | TLP:AMBER |
|
||||
| Analyst | [Name] |
|
||||
| Confidence | High/Moderate/Low |
|
||||
|
||||
## Executive Summary
|
||||
[Brief overview of key findings and their significance]
|
||||
|
||||
## Key Findings
|
||||
1. [Finding 1 with supporting evidence]
|
||||
2. [Finding 2 with supporting evidence]
|
||||
3. [Finding 3 with supporting evidence]
|
||||
|
||||
## Detailed Analysis
|
||||
### Finding 1
|
||||
- **Evidence**: [Description of evidence]
|
||||
- **Confidence**: High/Moderate/Low
|
||||
- **MITRE ATT&CK**: [Relevant technique IDs]
|
||||
- **Impact Assessment**: [Potential impact to organization]
|
||||
|
||||
## Indicators of Compromise
|
||||
| Type | Value | Context | Confidence |
|
||||
|------|-------|---------|-----------|
|
||||
| | | | |
|
||||
|
||||
## Recommendations
|
||||
1. **Immediate**: [Actions requiring immediate attention]
|
||||
2. **Short-term**: [Actions within 1-2 weeks]
|
||||
3. **Long-term**: [Strategic improvements]
|
||||
|
||||
## References
|
||||
- [Source 1]
|
||||
- [Source 2]
|
||||
@@ -0,0 +1,110 @@
|
||||
# API Reference: Campaign Attribution Evidence Analysis
|
||||
|
||||
## Diamond Model of Intrusion Analysis
|
||||
|
||||
### Four Core Features
|
||||
| Feature | Description | Attribution Value |
|
||||
|---------|-------------|-------------------|
|
||||
| Adversary | Threat actor identity | Direct attribution |
|
||||
| Capability | Malware, exploits, tools | Indirect - shared tooling |
|
||||
| Infrastructure | C2, domains, IPs | Strong - operational overlap |
|
||||
| Victim | Targets, sectors, regions | Contextual - targeting pattern |
|
||||
|
||||
### Pivot Analysis
|
||||
```
|
||||
Adversary ←→ Capability ←→ Infrastructure ←→ Victim
|
||||
↕ ↕ ↕ ↕
|
||||
(HUMINT) (Malware DB) (WHOIS/DNS) (Victimology)
|
||||
```
|
||||
|
||||
## Analysis of Competing Hypotheses (ACH)
|
||||
|
||||
### Matrix Format
|
||||
```
|
||||
Evidence \ Hypothesis | APT28 | APT29 | Lazarus | Unknown
|
||||
-----------------------------------------------------------------
|
||||
Infrastructure overlap | ++ | - | - | N
|
||||
TTP consistency | ++ | ++ | - | N
|
||||
Malware similarity | + | - | - | N
|
||||
Timing (UTC+3) | ++ | ++ | - | N
|
||||
Language (Russian) | ++ | ++ | - | N
|
||||
```
|
||||
|
||||
### Scoring
|
||||
| Symbol | Meaning | Weight |
|
||||
|--------|---------|--------|
|
||||
| `++` | Strongly consistent | +2 |
|
||||
| `+` | Consistent | +1 |
|
||||
| `N` | Neutral | 0 |
|
||||
| `-` | Inconsistent | -1 |
|
||||
| `--` | Strongly inconsistent | -2 |
|
||||
|
||||
## MITRE ATT&CK Group Queries
|
||||
|
||||
### Python (mitreattack-python)
|
||||
```python
|
||||
from mitreattack.stix20 import MitreAttackData
|
||||
attack = MitreAttackData("enterprise-attack.json")
|
||||
group = attack.get_group_by_alias("APT29")
|
||||
techniques = attack.get_techniques_used_by_group(group.id)
|
||||
```
|
||||
|
||||
### STIX2 Relationship Query
|
||||
```python
|
||||
from stix2 import Filter
|
||||
relationships = src.query([
|
||||
Filter("type", "=", "relationship"),
|
||||
Filter("source_ref", "=", group_id),
|
||||
Filter("relationship_type", "=", "uses"),
|
||||
])
|
||||
```
|
||||
|
||||
## Infrastructure Overlap Tools
|
||||
|
||||
### PassiveTotal / RiskIQ
|
||||
```bash
|
||||
# WHOIS history
|
||||
curl -u user:key "https://api.passivetotal.org/v2/whois?query=domain.com"
|
||||
|
||||
# Passive DNS
|
||||
curl -u user:key "https://api.passivetotal.org/v2/dns/passive?query=1.2.3.4"
|
||||
```
|
||||
|
||||
### VirusTotal Relations
|
||||
```bash
|
||||
curl -H "x-apikey: KEY" \
|
||||
"https://www.virustotal.com/api/v3/domains/example.com/communicating_files"
|
||||
```
|
||||
|
||||
## Confidence Assessment Framework
|
||||
|
||||
| Level | Score Range | Criteria |
|
||||
|-------|------------|---------|
|
||||
| HIGH | 0.8-1.0 | Multiple independent evidence types converge |
|
||||
| MEDIUM | 0.5-0.8 | Significant evidence with some gaps |
|
||||
| LOW | 0.2-0.5 | Limited evidence, alternative hypotheses remain |
|
||||
| NEGLIGIBLE | 0.0-0.2 | Insufficient evidence for attribution |
|
||||
|
||||
## STIX Attribution Objects
|
||||
|
||||
### Campaign Object
|
||||
```json
|
||||
{
|
||||
"type": "campaign",
|
||||
"name": "Operation DarkShadow",
|
||||
"first_seen": "2024-01-15T00:00:00Z",
|
||||
"last_seen": "2024-03-20T00:00:00Z",
|
||||
"objective": "Espionage targeting defense sector"
|
||||
}
|
||||
```
|
||||
|
||||
### Attribution Relationship
|
||||
```json
|
||||
{
|
||||
"type": "relationship",
|
||||
"relationship_type": "attributed-to",
|
||||
"source_ref": "campaign--abc123",
|
||||
"target_ref": "intrusion-set--def456",
|
||||
"confidence": 75
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
# Standards and Frameworks Reference
|
||||
|
||||
## Applicable Standards
|
||||
- **STIX 2.1**: Structured Threat Information eXpression for CTI data representation
|
||||
- **TAXII 2.1**: Transport protocol for sharing CTI over HTTPS
|
||||
- **MITRE ATT&CK**: Adversary tactics, techniques, and procedures taxonomy
|
||||
- **Diamond Model**: Intrusion analysis framework (Adversary, Capability, Infrastructure, Victim)
|
||||
- **Traffic Light Protocol (TLP)**: Information sharing classification (CLEAR, GREEN, AMBER, RED)
|
||||
|
||||
## MITRE ATT&CK Relevance
|
||||
- Technique mapping for threat actor behavior classification
|
||||
- Data sources for detection capability assessment
|
||||
- Mitigation strategies linked to specific techniques
|
||||
|
||||
## Industry Frameworks
|
||||
- NIST Cybersecurity Framework (CSF) 2.0 - Identify function
|
||||
- ISO 27001:2022 - A.5.7 Threat Intelligence
|
||||
- FIRST Standards - TLP, CSIRT, vulnerability coordination
|
||||
|
||||
## References
|
||||
- [STIX 2.1 Specification](https://docs.oasis-open.org/cti/stix/v2.1/stix-v2.1.html)
|
||||
- [MITRE ATT&CK](https://attack.mitre.org/)
|
||||
- [Diamond Model Paper](https://www.activeresponse.org/wp-content/uploads/2013/07/diamond.pdf)
|
||||
- [NIST CSF 2.0](https://www.nist.gov/cyberframework)
|
||||
@@ -0,0 +1,31 @@
|
||||
# Campaign Attribution Analysis Workflows
|
||||
|
||||
## Workflow 1: Collection and Analysis
|
||||
```
|
||||
[Intelligence Sources] --> [Data Collection] --> [Analysis] --> [Reporting]
|
||||
| | | |
|
||||
v v v v
|
||||
OSINT/HUMINT/SIGINT Normalize/Enrich Assess/Correlate Disseminate
|
||||
```
|
||||
|
||||
### Steps:
|
||||
1. **Planning**: Define intelligence requirements and collection priorities
|
||||
2. **Collection**: Gather data from relevant sources
|
||||
3. **Processing**: Normalize data formats and filter noise
|
||||
4. **Analysis**: Apply analytical frameworks and correlate findings
|
||||
5. **Production**: Generate intelligence products and reports
|
||||
6. **Dissemination**: Share with stakeholders via appropriate channels
|
||||
7. **Feedback**: Collect consumer feedback to refine future collection
|
||||
|
||||
## Workflow 2: Continuous Monitoring
|
||||
```
|
||||
[Watchlist] --> [Automated Monitoring] --> [Change Detection] --> [Alert/Update]
|
||||
```
|
||||
|
||||
### Steps:
|
||||
1. **Define Watchlist**: Identify indicators, actors, and topics to monitor
|
||||
2. **Configure Monitoring**: Set up automated collection from relevant sources
|
||||
3. **Change Detection**: Identify new or changed intelligence
|
||||
4. **Assessment**: Evaluate significance of changes
|
||||
5. **Alerting**: Notify stakeholders of significant intelligence updates
|
||||
6. **Archive**: Store intelligence for historical analysis and trending
|
||||
@@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Campaign attribution analysis agent using Diamond Model and ACH methodology.
|
||||
|
||||
Evaluates attribution evidence including infrastructure overlaps, TTP consistency,
|
||||
malware code similarity, timing patterns, and language artifacts.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
DIAMOND_DIMENSIONS = {
|
||||
"adversary": "Threat actor identity, group attribution",
|
||||
"capability": "Malware, exploits, tools used",
|
||||
"infrastructure": "C2 servers, domains, IP addresses",
|
||||
"victim": "Targeted sectors, regions, organizations",
|
||||
}
|
||||
|
||||
EVIDENCE_WEIGHTS = {
|
||||
"infrastructure_overlap": 0.25,
|
||||
"ttp_consistency": 0.30,
|
||||
"malware_code_similarity": 0.25,
|
||||
"timing_pattern": 0.10,
|
||||
"language_artifact": 0.10,
|
||||
}
|
||||
|
||||
CONFIDENCE_LEVELS = {
|
||||
(0.8, 1.0): "HIGH - Strong attribution confidence",
|
||||
(0.5, 0.8): "MEDIUM - Moderate attribution, further analysis recommended",
|
||||
(0.2, 0.5): "LOW - Weak attribution, insufficient evidence",
|
||||
(0.0, 0.2): "NEGLIGIBLE - No meaningful attribution possible",
|
||||
}
|
||||
|
||||
|
||||
def diamond_model_analysis(adversary=None, capability=None, infrastructure=None, victim=None):
|
||||
"""Structure evidence using the Diamond Model of Intrusion Analysis."""
|
||||
model = {
|
||||
"adversary": {
|
||||
"identified": adversary is not None,
|
||||
"details": adversary or "Unknown",
|
||||
},
|
||||
"capability": {
|
||||
"tools": capability.get("tools", []) if capability else [],
|
||||
"exploits": capability.get("exploits", []) if capability else [],
|
||||
"malware": capability.get("malware", []) if capability else [],
|
||||
},
|
||||
"infrastructure": {
|
||||
"c2_servers": infrastructure.get("c2", []) if infrastructure else [],
|
||||
"domains": infrastructure.get("domains", []) if infrastructure else [],
|
||||
"ip_addresses": infrastructure.get("ips", []) if infrastructure else [],
|
||||
},
|
||||
"victim": {
|
||||
"sectors": victim.get("sectors", []) if victim else [],
|
||||
"regions": victim.get("regions", []) if victim else [],
|
||||
},
|
||||
"pivot_opportunities": [],
|
||||
}
|
||||
if infrastructure and infrastructure.get("c2"):
|
||||
model["pivot_opportunities"].append("Pivot from C2 infrastructure to related campaigns")
|
||||
if capability and capability.get("malware"):
|
||||
model["pivot_opportunities"].append("Pivot from malware samples to shared infrastructure")
|
||||
return model
|
||||
|
||||
|
||||
def evaluate_infrastructure_overlap(campaign_infra, known_actor_infra):
|
||||
"""Score infrastructure overlap between campaign and known actor."""
|
||||
campaign_set = set(campaign_infra)
|
||||
known_set = set(known_actor_infra)
|
||||
if not campaign_set or not known_set:
|
||||
return 0.0, []
|
||||
overlap = campaign_set & known_set
|
||||
score = len(overlap) / max(len(campaign_set), len(known_set))
|
||||
return round(score, 4), sorted(overlap)
|
||||
|
||||
|
||||
def evaluate_ttp_consistency(campaign_ttps, actor_ttps):
|
||||
"""Score TTP consistency using MITRE ATT&CK technique overlap."""
|
||||
campaign_set = set(campaign_ttps)
|
||||
actor_set = set(actor_ttps)
|
||||
if not campaign_set or not actor_set:
|
||||
return 0.0, []
|
||||
overlap = campaign_set & actor_set
|
||||
jaccard = len(overlap) / len(campaign_set | actor_set)
|
||||
return round(jaccard, 4), sorted(overlap)
|
||||
|
||||
|
||||
def evaluate_malware_similarity(sample_features, known_features):
|
||||
"""Score malware code similarity based on feature comparison."""
|
||||
if not sample_features or not known_features:
|
||||
return 0.0
|
||||
matches = 0
|
||||
total = max(len(sample_features), len(known_features))
|
||||
for feature in sample_features:
|
||||
if feature in known_features:
|
||||
matches += 1
|
||||
return round(matches / total, 4) if total > 0 else 0.0
|
||||
|
||||
|
||||
def evaluate_timing_pattern(campaign_timestamps, actor_timezone_offset=None):
|
||||
"""Analyze operational timing to infer timezone/working hours."""
|
||||
if not campaign_timestamps:
|
||||
return {"score": 0.0, "working_hours": None, "timezone_guess": None}
|
||||
hours = []
|
||||
for ts in campaign_timestamps:
|
||||
try:
|
||||
if isinstance(ts, str):
|
||||
dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
||||
else:
|
||||
dt = ts
|
||||
adjusted = dt.hour + (actor_timezone_offset or 0)
|
||||
hours.append(adjusted % 24)
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
if not hours:
|
||||
return {"score": 0.0}
|
||||
work_hours = sum(1 for h in hours if 8 <= h <= 18)
|
||||
work_ratio = work_hours / len(hours)
|
||||
avg_hour = sum(hours) / len(hours)
|
||||
return {
|
||||
"score": round(work_ratio, 4),
|
||||
"average_hour_utc": round(avg_hour, 1),
|
||||
"work_hour_ratio": round(work_ratio, 4),
|
||||
"sample_size": len(hours),
|
||||
}
|
||||
|
||||
|
||||
def evaluate_language_artifacts(strings_list):
|
||||
"""Detect language artifacts in malware strings or documents."""
|
||||
language_indicators = {
|
||||
"Russian": [r"[а-яА-Я]{3,}", r"codepage.*1251", r"locale.*ru"],
|
||||
"Chinese": [r"[\u4e00-\u9fff]{2,}", r"codepage.*936", r"GB2312"],
|
||||
"Korean": [r"[\uac00-\ud7af]{2,}", r"codepage.*949", r"EUC-KR"],
|
||||
"Farsi": [r"[\u0600-\u06ff]{3,}", r"codepage.*1256"],
|
||||
"English": [r"\b(the|and|for|with)\b"],
|
||||
}
|
||||
detections = defaultdict(int)
|
||||
for s in strings_list:
|
||||
for lang, patterns in language_indicators.items():
|
||||
for pattern in patterns:
|
||||
if re.search(pattern, s, re.IGNORECASE):
|
||||
detections[lang] += 1
|
||||
total = sum(detections.values()) or 1
|
||||
scored = {lang: round(count / total, 4) for lang, count in detections.items()}
|
||||
return scored
|
||||
|
||||
|
||||
def ach_analysis(hypotheses, evidence_items):
|
||||
"""Analysis of Competing Hypotheses (ACH) for attribution."""
|
||||
matrix = {}
|
||||
for hyp in hypotheses:
|
||||
hyp_name = hyp["name"]
|
||||
matrix[hyp_name] = {"consistent": 0, "inconsistent": 0, "neutral": 0, "score": 0}
|
||||
for evidence in evidence_items:
|
||||
ev_name = evidence["name"]
|
||||
consistency = evidence.get("hypotheses", {}).get(hyp_name, "neutral")
|
||||
if consistency == "consistent":
|
||||
matrix[hyp_name]["consistent"] += evidence.get("weight", 1)
|
||||
elif consistency == "inconsistent":
|
||||
matrix[hyp_name]["inconsistent"] += evidence.get("weight", 1)
|
||||
else:
|
||||
matrix[hyp_name]["neutral"] += evidence.get("weight", 1)
|
||||
c = matrix[hyp_name]["consistent"]
|
||||
i = matrix[hyp_name]["inconsistent"]
|
||||
matrix[hyp_name]["score"] = round((c - i) / (c + i + 0.01), 4)
|
||||
return matrix
|
||||
|
||||
|
||||
def compute_attribution_score(scores):
|
||||
"""Compute weighted attribution confidence score."""
|
||||
total = 0.0
|
||||
for evidence_type, weight in EVIDENCE_WEIGHTS.items():
|
||||
score = scores.get(evidence_type, 0.0)
|
||||
total += score * weight
|
||||
confidence = "UNKNOWN"
|
||||
for (low, high), label in CONFIDENCE_LEVELS.items():
|
||||
if low <= total < high:
|
||||
confidence = label
|
||||
break
|
||||
return round(total, 4), confidence
|
||||
|
||||
|
||||
def generate_attribution_report(campaign_name, candidate_actor, evidence):
|
||||
"""Generate structured attribution assessment report."""
|
||||
scores = {}
|
||||
details = {}
|
||||
|
||||
infra_score, infra_overlap = evaluate_infrastructure_overlap(
|
||||
evidence.get("campaign_infra", []), evidence.get("actor_infra", []))
|
||||
scores["infrastructure_overlap"] = infra_score
|
||||
details["infrastructure_overlap"] = infra_overlap
|
||||
|
||||
ttp_score, ttp_overlap = evaluate_ttp_consistency(
|
||||
evidence.get("campaign_ttps", []), evidence.get("actor_ttps", []))
|
||||
scores["ttp_consistency"] = ttp_score
|
||||
details["ttp_consistency"] = ttp_overlap
|
||||
|
||||
malware_score = evaluate_malware_similarity(
|
||||
evidence.get("sample_features", []), evidence.get("known_features", []))
|
||||
scores["malware_code_similarity"] = malware_score
|
||||
|
||||
timing = evaluate_timing_pattern(
|
||||
evidence.get("timestamps", []), evidence.get("tz_offset"))
|
||||
scores["timing_pattern"] = timing.get("score", 0.0)
|
||||
details["timing"] = timing
|
||||
|
||||
lang = evaluate_language_artifacts(evidence.get("strings", []))
|
||||
scores["language_artifact"] = max(lang.values()) if lang else 0.0
|
||||
details["language_artifacts"] = lang
|
||||
|
||||
total_score, confidence = compute_attribution_score(scores)
|
||||
|
||||
return {
|
||||
"campaign": campaign_name,
|
||||
"candidate_actor": candidate_actor,
|
||||
"attribution_score": total_score,
|
||||
"confidence_level": confidence,
|
||||
"evidence_scores": scores,
|
||||
"evidence_details": details,
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Campaign Attribution Evidence Analysis Agent")
|
||||
print("Diamond Model, ACH, TTP/infrastructure/malware scoring")
|
||||
print("=" * 60)
|
||||
|
||||
demo_evidence = {
|
||||
"campaign_infra": ["185.220.101.1", "evil-domain.com", "c2.attacker.net"],
|
||||
"actor_infra": ["185.220.101.1", "c2.attacker.net", "other-domain.org"],
|
||||
"campaign_ttps": ["T1566.001", "T1059.001", "T1053.005", "T1071.001", "T1041"],
|
||||
"actor_ttps": ["T1566.001", "T1059.001", "T1053.005", "T1071.001", "T1021.001", "T1003.001"],
|
||||
"sample_features": ["xor_0x55", "mutex_Global\\QWE", "ua_Mozilla5", "rc4_key"],
|
||||
"known_features": ["xor_0x55", "mutex_Global\\QWE", "ua_Mozilla5", "aes_cbc"],
|
||||
"timestamps": ["2024-03-15T06:30:00Z", "2024-03-15T07:15:00Z",
|
||||
"2024-03-16T08:00:00Z", "2024-03-16T09:45:00Z"],
|
||||
"tz_offset": 3,
|
||||
"strings": ["Привет мир", "connect to server", "upload file"],
|
||||
}
|
||||
|
||||
report = generate_attribution_report("Operation DarkShadow", "APT29", demo_evidence)
|
||||
|
||||
print(f"\n[*] Campaign: {report['campaign']}")
|
||||
print(f"[*] Candidate: {report['candidate_actor']}")
|
||||
print(f"[*] Attribution Score: {report['attribution_score']}")
|
||||
print(f"[*] Confidence: {report['confidence_level']}")
|
||||
print("\n--- Evidence Scores ---")
|
||||
for ev, score in report["evidence_scores"].items():
|
||||
weight = EVIDENCE_WEIGHTS.get(ev, 0)
|
||||
print(f" {ev:30s} score={score:.4f} weight={weight}")
|
||||
print(f"\n[*] Full report:\n{json.dumps(report, indent=2, default=str)}")
|
||||
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Campaign Attribution Evidence Analysis Script
|
||||
|
||||
Implements structured attribution analysis:
|
||||
- Analysis of Competing Hypotheses (ACH) matrix
|
||||
- Infrastructure overlap scoring
|
||||
- TTP similarity comparison using ATT&CK
|
||||
- Evidence weighting and confidence assessment
|
||||
|
||||
Requirements:
|
||||
pip install attackcti stix2 requests
|
||||
|
||||
Usage:
|
||||
python process.py --evidence evidence.json --hypotheses actors.json --output report.json
|
||||
python process.py --compare-ttps --campaign campaign_techs.json --actor APT29
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
class AttributionEngine:
|
||||
"""Structured attribution analysis using ACH methodology."""
|
||||
|
||||
def __init__(self):
|
||||
self.evidence = []
|
||||
self.hypotheses = {}
|
||||
|
||||
def load_evidence(self, filepath):
|
||||
with open(filepath) as f:
|
||||
self.evidence = json.load(f)
|
||||
|
||||
def add_evidence(self, category, description, value, confidence):
|
||||
self.evidence.append({
|
||||
"id": len(self.evidence),
|
||||
"category": category,
|
||||
"description": description,
|
||||
"value": value,
|
||||
"confidence": confidence,
|
||||
})
|
||||
|
||||
def add_hypothesis(self, actor_name, supporting_info=""):
|
||||
self.hypotheses[actor_name] = {
|
||||
"info": supporting_info,
|
||||
"assessments": {},
|
||||
"score": 0,
|
||||
}
|
||||
|
||||
def evaluate(self, evidence_id, actor_name, assessment):
|
||||
"""Evaluate evidence against hypothesis: C=consistent, I=inconsistent, N=neutral."""
|
||||
weight = self.evidence[evidence_id]["confidence"]
|
||||
self.hypotheses[actor_name]["assessments"][evidence_id] = assessment
|
||||
|
||||
if assessment == "C":
|
||||
self.hypotheses[actor_name]["score"] += weight
|
||||
elif assessment == "I":
|
||||
self.hypotheses[actor_name]["score"] -= weight * 2
|
||||
|
||||
def generate_ach_matrix(self):
|
||||
matrix = {"evidence": [], "hypotheses": {}}
|
||||
for e in self.evidence:
|
||||
matrix["evidence"].append({
|
||||
"id": e["id"],
|
||||
"category": e["category"],
|
||||
"description": e["description"],
|
||||
})
|
||||
|
||||
for actor, data in self.hypotheses.items():
|
||||
matrix["hypotheses"][actor] = {
|
||||
"assessments": data["assessments"],
|
||||
"score": data["score"],
|
||||
"consistent": sum(1 for a in data["assessments"].values() if a == "C"),
|
||||
"inconsistent": sum(1 for a in data["assessments"].values() if a == "I"),
|
||||
"neutral": sum(1 for a in data["assessments"].values() if a == "N"),
|
||||
}
|
||||
|
||||
return matrix
|
||||
|
||||
def rank(self):
|
||||
ranked = sorted(
|
||||
self.hypotheses.items(), key=lambda x: x[1]["score"], reverse=True
|
||||
)
|
||||
results = []
|
||||
for name, data in ranked:
|
||||
incon = sum(1 for a in data["assessments"].values() if a == "I")
|
||||
confidence = "HIGH" if data["score"] >= 80 and incon == 0 else \
|
||||
"MODERATE" if data["score"] >= 40 else "LOW"
|
||||
results.append({
|
||||
"actor": name,
|
||||
"score": data["score"],
|
||||
"confidence": confidence,
|
||||
"inconsistent_count": incon,
|
||||
})
|
||||
return results
|
||||
|
||||
|
||||
def compare_ttp_similarity(campaign_techs, actor_techs):
|
||||
campaign_set = set(campaign_techs)
|
||||
actor_set = set(actor_techs)
|
||||
common = campaign_set & actor_set
|
||||
|
||||
jaccard = len(common) / len(campaign_set | actor_set) if (campaign_set | actor_set) else 0
|
||||
return {
|
||||
"common": sorted(common),
|
||||
"jaccard_similarity": round(jaccard, 3),
|
||||
"campaign_coverage": round(len(common) / len(campaign_set) * 100, 1) if campaign_set else 0,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Campaign Attribution Analysis")
|
||||
parser.add_argument("--evidence", help="Evidence JSON file")
|
||||
parser.add_argument("--hypotheses", help="Hypotheses JSON file")
|
||||
parser.add_argument("--compare-ttps", action="store_true")
|
||||
parser.add_argument("--campaign", help="Campaign techniques JSON")
|
||||
parser.add_argument("--actor", help="Actor name for ATT&CK lookup")
|
||||
parser.add_argument("--output", default="attribution_report.json")
|
||||
|
||||
args = parser.parse_args()
|
||||
engine = AttributionEngine()
|
||||
|
||||
if args.evidence and args.hypotheses:
|
||||
engine.load_evidence(args.evidence)
|
||||
with open(args.hypotheses) as f:
|
||||
hyps = json.load(f)
|
||||
for h in hyps:
|
||||
engine.add_hypothesis(h["name"], h.get("info", ""))
|
||||
for eid, assessment in h.get("evaluations", {}).items():
|
||||
engine.evaluate(int(eid), h["name"], assessment)
|
||||
|
||||
matrix = engine.generate_ach_matrix()
|
||||
rankings = engine.rank()
|
||||
report = {"ach_matrix": matrix, "rankings": rankings}
|
||||
print(json.dumps(report, indent=2))
|
||||
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
|
||||
elif args.compare_ttps and args.campaign:
|
||||
with open(args.campaign) as f:
|
||||
campaign_techs = json.load(f)
|
||||
|
||||
if args.actor:
|
||||
try:
|
||||
from attackcti import attack_client
|
||||
lift = attack_client()
|
||||
groups = lift.get_groups()
|
||||
group = next(
|
||||
(g for g in groups if args.actor.lower() in g.get("name", "").lower()),
|
||||
None,
|
||||
)
|
||||
if group:
|
||||
gid = group["external_references"][0]["external_id"]
|
||||
techs = lift.get_techniques_used_by_group(gid)
|
||||
actor_techs = [
|
||||
t["external_references"][0]["external_id"]
|
||||
for t in techs if t.get("external_references")
|
||||
]
|
||||
result = compare_ttp_similarity(campaign_techs, actor_techs)
|
||||
print(json.dumps(result, indent=2))
|
||||
except ImportError:
|
||||
print("[-] attackcti not installed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,336 @@
|
||||
---
|
||||
name: analyzing-certificate-transparency-for-phishing
|
||||
description: Monitor Certificate Transparency logs using crt.sh and Certstream to detect phishing domains, lookalike certificates,
|
||||
and unauthorized certificate issuance targeting your organization.
|
||||
domain: cybersecurity
|
||||
subdomain: threat-intelligence
|
||||
tags:
|
||||
- certificate-transparency
|
||||
- ct-logs
|
||||
- phishing
|
||||
- crt-sh
|
||||
- certstream
|
||||
- ssl
|
||||
- domain-monitoring
|
||||
- threat-intelligence
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
atlas_techniques:
|
||||
- AML.T0052
|
||||
nist_csf:
|
||||
- ID.RA-01
|
||||
- ID.RA-05
|
||||
- DE.CM-01
|
||||
- DE.AE-02
|
||||
---
|
||||
# Analyzing Certificate Transparency for Phishing
|
||||
|
||||
## Overview
|
||||
|
||||
Certificate Transparency (CT) is an Internet security standard that creates a public, append-only log of all issued SSL/TLS certificates. Monitoring CT logs enables early detection of phishing domains that register certificates mimicking legitimate brands, unauthorized certificate issuance for owned domains, and certificate-based attack infrastructure. This skill covers querying CT logs via crt.sh, real-time monitoring with Certstream, building automated alerting for suspicious certificates, and integrating findings into threat intelligence workflows.
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing certificate transparency for phishing
|
||||
- When building detection rules or threat hunting queries for this domain
|
||||
- When SOC analysts need structured procedures for this analysis type
|
||||
- When validating security monitoring coverage for related attack techniques
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.9+ with `requests`, `certstream`, `tldextract`, `Levenshtein` libraries
|
||||
- Access to crt.sh (https://crt.sh/) for historical CT log queries
|
||||
- Certstream (https://certstream.calidog.io/) for real-time monitoring
|
||||
- List of organization domains and brand keywords to monitor
|
||||
- Understanding of SSL/TLS certificate structure and issuance process
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Certificate Transparency Logs
|
||||
|
||||
CT logs are cryptographically assured, publicly auditable, append-only records of TLS certificate issuance. Major CAs (Let's Encrypt, DigiCert, Sectigo, Google Trust Services) submit all issued certificates to multiple CT logs. As of 2025, Chrome and Safari require CT for all publicly trusted certificates.
|
||||
|
||||
### Phishing Detection via CT
|
||||
|
||||
Attackers register lookalike domains and obtain free certificates (often from Let's Encrypt) to make phishing sites appear legitimate with HTTPS. CT monitoring detects these early because the certificate appears in logs before the phishing campaign launches, providing a window for proactive blocking.
|
||||
|
||||
### crt.sh Database
|
||||
|
||||
crt.sh is a free web interface and PostgreSQL database operated by Sectigo that indexes CT logs. It supports wildcard searches (`%.example.com`), direct SQL queries, and JSON API responses. It tracks certificate issuance, expiration, and revocation across all major CT logs.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Query crt.sh for Certificate History
|
||||
|
||||
```python
|
||||
import requests
|
||||
import json
|
||||
from datetime import datetime
|
||||
import tldextract
|
||||
|
||||
class CTLogMonitor:
|
||||
CRT_SH_URL = "https://crt.sh"
|
||||
|
||||
def __init__(self, monitored_domains, brand_keywords):
|
||||
self.monitored_domains = monitored_domains
|
||||
self.brand_keywords = [k.lower() for k in brand_keywords]
|
||||
|
||||
def query_crt_sh(self, domain, include_expired=False):
|
||||
"""Query crt.sh for certificates matching a domain."""
|
||||
params = {
|
||||
"q": f"%.{domain}",
|
||||
"output": "json",
|
||||
}
|
||||
if not include_expired:
|
||||
params["exclude"] = "expired"
|
||||
|
||||
resp = requests.get(self.CRT_SH_URL, params=params, timeout=30)
|
||||
if resp.status_code == 200:
|
||||
certs = resp.json()
|
||||
print(f"[+] crt.sh: {len(certs)} certificates for *.{domain}")
|
||||
return certs
|
||||
return []
|
||||
|
||||
def find_suspicious_certs(self, domain):
|
||||
"""Find certificates that may be phishing attempts."""
|
||||
certs = self.query_crt_sh(domain)
|
||||
suspicious = []
|
||||
|
||||
for cert in certs:
|
||||
common_name = cert.get("common_name", "").lower()
|
||||
name_value = cert.get("name_value", "").lower()
|
||||
issuer = cert.get("issuer_name", "")
|
||||
not_before = cert.get("not_before", "")
|
||||
not_after = cert.get("not_after", "")
|
||||
|
||||
# Check for exact domain matches (legitimate)
|
||||
extracted = tldextract.extract(common_name)
|
||||
cert_domain = f"{extracted.domain}.{extracted.suffix}"
|
||||
if cert_domain == domain:
|
||||
continue # Legitimate certificate
|
||||
|
||||
# Flag suspicious patterns
|
||||
flags = []
|
||||
if domain.replace(".", "") in common_name.replace(".", ""):
|
||||
flags.append("contains target domain string")
|
||||
if any(kw in common_name for kw in self.brand_keywords):
|
||||
flags.append("contains brand keyword")
|
||||
if "let's encrypt" in issuer.lower():
|
||||
flags.append("free CA (Let's Encrypt)")
|
||||
|
||||
if flags:
|
||||
suspicious.append({
|
||||
"common_name": cert.get("common_name", ""),
|
||||
"name_value": cert.get("name_value", ""),
|
||||
"issuer": issuer,
|
||||
"not_before": not_before,
|
||||
"not_after": not_after,
|
||||
"serial": cert.get("serial_number", ""),
|
||||
"flags": flags,
|
||||
"crt_sh_id": cert.get("id", ""),
|
||||
"crt_sh_url": f"https://crt.sh/?id={cert.get('id', '')}",
|
||||
})
|
||||
|
||||
print(f"[+] Found {len(suspicious)} suspicious certificates")
|
||||
return suspicious
|
||||
|
||||
monitor = CTLogMonitor(
|
||||
monitored_domains=["mycompany.com", "mycompany.org"],
|
||||
brand_keywords=["mycompany", "mybrand", "myproduct"],
|
||||
)
|
||||
suspicious = monitor.find_suspicious_certs("mycompany.com")
|
||||
for cert in suspicious[:5]:
|
||||
print(f" [{cert['common_name']}] Flags: {cert['flags']}")
|
||||
```
|
||||
|
||||
### Step 2: Real-Time Monitoring with Certstream
|
||||
|
||||
```python
|
||||
import certstream
|
||||
import Levenshtein
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
class CertstreamMonitor:
|
||||
def __init__(self, watched_domains, brand_keywords, similarity_threshold=0.8):
|
||||
self.watched_domains = [d.lower() for d in watched_domains]
|
||||
self.brand_keywords = [k.lower() for k in brand_keywords]
|
||||
self.threshold = similarity_threshold
|
||||
self.alerts = []
|
||||
|
||||
def start_monitoring(self, max_alerts=100):
|
||||
"""Start real-time CT log monitoring."""
|
||||
print("[*] Starting Certstream monitoring...")
|
||||
print(f" Watching: {self.watched_domains}")
|
||||
print(f" Keywords: {self.brand_keywords}")
|
||||
|
||||
def callback(message, context):
|
||||
if message["message_type"] == "certificate_update":
|
||||
data = message["data"]
|
||||
leaf = data.get("leaf_cert", {})
|
||||
all_domains = leaf.get("all_domains", [])
|
||||
|
||||
for domain in all_domains:
|
||||
domain_lower = domain.lower().strip("*.")
|
||||
if self._is_suspicious(domain_lower):
|
||||
alert = {
|
||||
"domain": domain,
|
||||
"all_domains": all_domains,
|
||||
"issuer": leaf.get("issuer", {}).get("O", ""),
|
||||
"fingerprint": leaf.get("fingerprint", ""),
|
||||
"not_before": leaf.get("not_before", ""),
|
||||
"detected_at": datetime.now().isoformat(),
|
||||
"reason": self._get_reason(domain_lower),
|
||||
}
|
||||
self.alerts.append(alert)
|
||||
print(f" [ALERT] {domain} - {alert['reason']}")
|
||||
|
||||
if len(self.alerts) >= max_alerts:
|
||||
raise KeyboardInterrupt
|
||||
|
||||
try:
|
||||
certstream.listen_for_events(callback, url="wss://certstream.calidog.io/")
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n[+] Monitoring stopped. {len(self.alerts)} alerts collected.")
|
||||
return self.alerts
|
||||
|
||||
def _is_suspicious(self, domain):
|
||||
"""Check if domain is suspicious relative to watched domains."""
|
||||
for watched in self.watched_domains:
|
||||
# Exact keyword match
|
||||
watched_base = watched.split(".")[0]
|
||||
if watched_base in domain and domain != watched:
|
||||
return True
|
||||
|
||||
# Levenshtein distance (typosquatting detection)
|
||||
domain_base = tldextract.extract(domain).domain
|
||||
similarity = Levenshtein.ratio(watched_base, domain_base)
|
||||
if similarity >= self.threshold and domain_base != watched_base:
|
||||
return True
|
||||
|
||||
# Brand keyword match
|
||||
for keyword in self.brand_keywords:
|
||||
if keyword in domain:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _get_reason(self, domain):
|
||||
"""Determine why domain was flagged."""
|
||||
reasons = []
|
||||
for watched in self.watched_domains:
|
||||
watched_base = watched.split(".")[0]
|
||||
if watched_base in domain:
|
||||
reasons.append(f"contains '{watched_base}'")
|
||||
domain_base = tldextract.extract(domain).domain
|
||||
similarity = Levenshtein.ratio(watched_base, domain_base)
|
||||
if similarity >= self.threshold and domain_base != watched_base:
|
||||
reasons.append(f"similar to '{watched}' ({similarity:.0%})")
|
||||
for kw in self.brand_keywords:
|
||||
if kw in domain:
|
||||
reasons.append(f"brand keyword '{kw}'")
|
||||
return "; ".join(reasons) if reasons else "unknown"
|
||||
|
||||
cs_monitor = CertstreamMonitor(
|
||||
watched_domains=["mycompany.com"],
|
||||
brand_keywords=["mycompany", "mybrand"],
|
||||
similarity_threshold=0.75,
|
||||
)
|
||||
alerts = cs_monitor.start_monitoring(max_alerts=50)
|
||||
```
|
||||
|
||||
### Step 3: Enumerate Subdomains from CT Logs
|
||||
|
||||
```python
|
||||
def enumerate_subdomains_ct(domain):
|
||||
"""Discover all subdomains from Certificate Transparency logs."""
|
||||
params = {"q": f"%.{domain}", "output": "json"}
|
||||
resp = requests.get("https://crt.sh", params=params, timeout=30)
|
||||
|
||||
if resp.status_code != 200:
|
||||
return []
|
||||
|
||||
certs = resp.json()
|
||||
subdomains = set()
|
||||
for cert in certs:
|
||||
name_value = cert.get("name_value", "")
|
||||
for name in name_value.split("\n"):
|
||||
name = name.strip().lower()
|
||||
if name.endswith(f".{domain}") or name == domain:
|
||||
name = name.lstrip("*.")
|
||||
subdomains.add(name)
|
||||
|
||||
sorted_subs = sorted(subdomains)
|
||||
print(f"[+] CT subdomain enumeration for {domain}: {len(sorted_subs)} subdomains")
|
||||
return sorted_subs
|
||||
|
||||
subdomains = enumerate_subdomains_ct("example.com")
|
||||
for sub in subdomains[:20]:
|
||||
print(f" {sub}")
|
||||
```
|
||||
|
||||
### Step 4: Generate CT Intelligence Report
|
||||
|
||||
```python
|
||||
def generate_ct_report(suspicious_certs, certstream_alerts, domain):
|
||||
report = f"""# Certificate Transparency Intelligence Report
|
||||
## Target Domain: {domain}
|
||||
## Generated: {datetime.now().isoformat()}
|
||||
|
||||
## Summary
|
||||
- Suspicious certificates found: {len(suspicious_certs)}
|
||||
- Real-time alerts triggered: {len(certstream_alerts)}
|
||||
|
||||
## Suspicious Certificates (crt.sh)
|
||||
| Common Name | Issuer | Flags | crt.sh Link |
|
||||
|------------|--------|-------|-------------|
|
||||
"""
|
||||
for cert in suspicious_certs[:20]:
|
||||
flags = "; ".join(cert.get("flags", []))
|
||||
report += (f"| {cert['common_name']} | {cert['issuer'][:30]} "
|
||||
f"| {flags} | [View]({cert['crt_sh_url']}) |\n")
|
||||
|
||||
report += f"""
|
||||
## Real-Time Certstream Alerts
|
||||
| Domain | Issuer | Reason | Detected |
|
||||
|--------|--------|--------|----------|
|
||||
"""
|
||||
for alert in certstream_alerts[:20]:
|
||||
report += (f"| {alert['domain']} | {alert['issuer']} "
|
||||
f"| {alert['reason']} | {alert['detected_at'][:19]} |\n")
|
||||
|
||||
report += """
|
||||
## Recommendations
|
||||
1. Add flagged domains to DNS sinkhole / web proxy blocklist
|
||||
2. Submit takedown requests for confirmed phishing domains
|
||||
3. Monitor CT logs continuously for new certificate registrations
|
||||
4. Implement CAA DNS records to restrict certificate issuance for your domains
|
||||
5. Deploy DMARC to prevent email spoofing from lookalike domains
|
||||
"""
|
||||
with open(f"ct_report_{domain.replace('.','_')}.md", "w") as f:
|
||||
f.write(report)
|
||||
print(f"[+] CT report saved")
|
||||
return report
|
||||
|
||||
generate_ct_report(suspicious, alerts if 'alerts' in dir() else [], "mycompany.com")
|
||||
```
|
||||
|
||||
## Validation Criteria
|
||||
|
||||
- crt.sh queries return certificate data for target domains
|
||||
- Suspicious certificates identified based on lookalike patterns
|
||||
- Certstream real-time monitoring detects new phishing certificates
|
||||
- Subdomain enumeration produces comprehensive list from CT logs
|
||||
- Alerts generated with reason classification
|
||||
- CT intelligence report created with actionable recommendations
|
||||
|
||||
## References
|
||||
|
||||
- [crt.sh Certificate Search](https://crt.sh/)
|
||||
- [Certstream Real-Time CT Monitor](https://certstream.calidog.io/)
|
||||
- [River Security: CT Logs for Attack Surface Discovery](https://riversecurity.eu/finding-attack-surface-and-fraudulent-domains-via-certificate-transparency-logs/)
|
||||
- [Let's Encrypt: Certificate Transparency Logs](https://letsencrypt.org/docs/ct-logs/)
|
||||
- [SSLMate Cert Spotter](https://sslmate.com/certspotter/)
|
||||
- [CyberSierra: CT Logs as Early Warning System](https://cybersierra.co/blog/ssl-certificate-transparency-logs/)
|
||||
@@ -0,0 +1,97 @@
|
||||
# API Reference: Certificate Transparency Phishing Detection
|
||||
|
||||
## crt.sh API
|
||||
|
||||
### Search Certificates
|
||||
```bash
|
||||
# JSON output
|
||||
curl "https://crt.sh/?q=%.example.com&output=json"
|
||||
|
||||
# Exclude expired
|
||||
curl "https://crt.sh/?q=%.example.com&output=json&exclude=expired"
|
||||
|
||||
# Exact match
|
||||
curl "https://crt.sh/?q=example.com&output=json"
|
||||
```
|
||||
|
||||
### Response Fields
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `id` | Certificate ID in crt.sh database |
|
||||
| `common_name` | Certificate CN |
|
||||
| `name_value` | All SANs (newline-separated) |
|
||||
| `issuer_name` | Certificate Authority |
|
||||
| `not_before` | Validity start |
|
||||
| `not_after` | Validity end |
|
||||
| `serial_number` | Certificate serial |
|
||||
|
||||
## Certstream - Real-time CT Monitoring
|
||||
|
||||
### Python Client
|
||||
```python
|
||||
import certstream
|
||||
|
||||
def callback(message, context):
|
||||
if message["message_type"] == "certificate_update":
|
||||
data = message["data"]
|
||||
domains = data["leaf_cert"]["all_domains"]
|
||||
for domain in domains:
|
||||
if "example" in domain:
|
||||
print(f"[ALERT] {domain}")
|
||||
|
||||
certstream.listen_for_events(callback, url="wss://certstream.calidog.io/")
|
||||
```
|
||||
|
||||
### Message Fields
|
||||
| Field | Path |
|
||||
|-------|------|
|
||||
| Domains | `data.leaf_cert.all_domains` |
|
||||
| Issuer | `data.leaf_cert.issuer.O` |
|
||||
| Subject | `data.leaf_cert.subject.CN` |
|
||||
| Fingerprint | `data.leaf_cert.fingerprint` |
|
||||
| Source | `data.source.name` |
|
||||
|
||||
## CT Log Servers
|
||||
|
||||
| Log | Operator | URL |
|
||||
|-----|----------|-----|
|
||||
| Argon | Google | `ct.googleapis.com/logs/argon2024` |
|
||||
| Xenon | Google | `ct.googleapis.com/logs/xenon2024` |
|
||||
| Nimbus | Cloudflare | `ct.cloudflare.com/logs/nimbus2024` |
|
||||
| Oak | Let's Encrypt | `oak.ct.letsencrypt.org/2024h1` |
|
||||
| Yeti | DigiCert | `yeti2024.ct.digicert.com/log` |
|
||||
|
||||
## Phishing Detection Techniques
|
||||
|
||||
### Homoglyph / IDN Attacks
|
||||
| Original | Lookalike | Technique |
|
||||
|----------|-----------|-----------|
|
||||
| example.com | examp1e.com | Character substitution (l→1) |
|
||||
| google.com | gооgle.com | Cyrillic о (U+043E) |
|
||||
| paypal.com | paypa1.com | l→1 substitution |
|
||||
| microsoft.com | mіcrosoft.com | Cyrillic і (U+0456) |
|
||||
|
||||
### dnstwist Integration
|
||||
```bash
|
||||
dnstwist -r -f json example.com # Generate and resolve permutations
|
||||
dnstwist -w wordlist.txt example.com # Dictionary-based
|
||||
```
|
||||
|
||||
## Certificate Details Lookup
|
||||
```bash
|
||||
# Get full certificate from crt.sh
|
||||
curl "https://crt.sh/?d=<cert_id>"
|
||||
|
||||
# OpenSSL inspection
|
||||
openssl s_client -connect domain.com:443 -servername domain.com </dev/null 2>/dev/null | \
|
||||
openssl x509 -noout -text
|
||||
```
|
||||
|
||||
## Suspicious Indicators
|
||||
| Pattern | Risk Level |
|
||||
|---------|-----------|
|
||||
| Free CA + new domain + brand keyword | HIGH |
|
||||
| Wildcard cert on recently registered domain | HIGH |
|
||||
| Multiple certs for slight domain variants | MEDIUM |
|
||||
| IDN/punycode domain mimicking brand | HIGH |
|
||||
| Cert issued same day as domain registration | MEDIUM |
|
||||
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Certificate Transparency monitoring agent for phishing detection.
|
||||
|
||||
Queries crt.sh for certificates matching target domains, detects lookalike
|
||||
certificates, and identifies potential phishing infrastructure.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
try:
|
||||
import requests
|
||||
HAS_REQUESTS = True
|
||||
except ImportError:
|
||||
HAS_REQUESTS = False
|
||||
|
||||
|
||||
def query_crtsh(domain, wildcard=True, expired=False):
|
||||
"""Query crt.sh for certificates matching a domain."""
|
||||
if not HAS_REQUESTS:
|
||||
return []
|
||||
query = f"%.{domain}" if wildcard else domain
|
||||
params = {"q": query, "output": "json"}
|
||||
if not expired:
|
||||
params["exclude"] = "expired"
|
||||
try:
|
||||
resp = requests.get("https://crt.sh/", params=params, timeout=30)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
except (requests.RequestException, json.JSONDecodeError) as e:
|
||||
return [{"error": str(e)}]
|
||||
|
||||
|
||||
def find_lookalike_domains(target_domain, ct_results):
|
||||
"""Identify certificates for domains that look similar to the target."""
|
||||
base = target_domain.split(".")[0].lower()
|
||||
lookalikes = []
|
||||
for cert in ct_results:
|
||||
cn = cert.get("common_name", "").lower()
|
||||
names = cert.get("name_value", "").lower().split("\n")
|
||||
for name in [cn] + names:
|
||||
name = name.strip()
|
||||
if not name or name == target_domain:
|
||||
continue
|
||||
similarity = calculate_similarity(base, name.split(".")[0])
|
||||
if similarity > 0.6 and name != target_domain:
|
||||
lookalikes.append({
|
||||
"domain": name,
|
||||
"similarity": round(similarity, 3),
|
||||
"issuer": cert.get("issuer_name", ""),
|
||||
"not_before": cert.get("not_before", ""),
|
||||
"not_after": cert.get("not_after", ""),
|
||||
"cert_id": cert.get("id"),
|
||||
})
|
||||
seen = set()
|
||||
unique = []
|
||||
for l in sorted(lookalikes, key=lambda x: -x["similarity"]):
|
||||
if l["domain"] not in seen:
|
||||
seen.add(l["domain"])
|
||||
unique.append(l)
|
||||
return unique
|
||||
|
||||
|
||||
def calculate_similarity(s1, s2):
|
||||
"""Calculate string similarity using Levenshtein-like ratio."""
|
||||
if s1 == s2:
|
||||
return 1.0
|
||||
len1, len2 = len(s1), len(s2)
|
||||
if len1 == 0 or len2 == 0:
|
||||
return 0.0
|
||||
matrix = [[0] * (len2 + 1) for _ in range(len1 + 1)]
|
||||
for i in range(len1 + 1):
|
||||
matrix[i][0] = i
|
||||
for j in range(len2 + 1):
|
||||
matrix[0][j] = j
|
||||
for i in range(1, len1 + 1):
|
||||
for j in range(1, len2 + 1):
|
||||
cost = 0 if s1[i-1] == s2[j-1] else 1
|
||||
matrix[i][j] = min(matrix[i-1][j] + 1, matrix[i][j-1] + 1,
|
||||
matrix[i-1][j-1] + cost)
|
||||
distance = matrix[len1][len2]
|
||||
return 1.0 - distance / max(len1, len2)
|
||||
|
||||
|
||||
HOMOGLYPH_MAP = {
|
||||
"a": ["а", "@", "4"], "e": ["е", "3"], "o": ["о", "0"],
|
||||
"i": ["і", "1", "l"], "l": ["1", "i", "I"],
|
||||
"s": ["5", "$"], "t": ["7"], "g": ["9", "q"],
|
||||
}
|
||||
|
||||
|
||||
def detect_homoglyph_domains(target_domain, ct_results):
|
||||
"""Detect domains using homoglyph/IDN attacks against target."""
|
||||
findings = []
|
||||
base = target_domain.split(".")[0].lower()
|
||||
for cert in ct_results:
|
||||
names = cert.get("name_value", "").lower().split("\n")
|
||||
for name in names:
|
||||
name = name.strip()
|
||||
if not name or name == target_domain:
|
||||
continue
|
||||
name_base = name.split(".")[0]
|
||||
if len(name_base) == len(base):
|
||||
diffs = sum(1 for a, b in zip(base, name_base) if a != b)
|
||||
if 0 < diffs <= 2:
|
||||
findings.append({
|
||||
"domain": name,
|
||||
"char_differences": diffs,
|
||||
"cert_id": cert.get("id"),
|
||||
"issuer": cert.get("issuer_name", ""),
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
def analyze_issuer_patterns(ct_results):
|
||||
"""Analyze certificate issuer patterns for anomalies."""
|
||||
issuer_counts = defaultdict(int)
|
||||
free_cas = ["Let's Encrypt", "ZeroSSL", "Buypass"]
|
||||
for cert in ct_results:
|
||||
issuer = cert.get("issuer_name", "Unknown")
|
||||
issuer_counts[issuer] += 1
|
||||
free_ca_certs = sum(
|
||||
count for issuer, count in issuer_counts.items()
|
||||
if any(ca.lower() in issuer.lower() for ca in free_cas)
|
||||
)
|
||||
return {
|
||||
"issuers": dict(issuer_counts),
|
||||
"total_certs": len(ct_results),
|
||||
"free_ca_count": free_ca_certs,
|
||||
"free_ca_ratio": round(free_ca_certs / max(len(ct_results), 1), 3),
|
||||
}
|
||||
|
||||
|
||||
def detect_wildcard_abuse(ct_results):
|
||||
"""Detect suspicious wildcard certificate patterns."""
|
||||
wildcards = []
|
||||
for cert in ct_results:
|
||||
cn = cert.get("common_name", "")
|
||||
if cn.startswith("*."):
|
||||
wildcards.append({
|
||||
"domain": cn,
|
||||
"issuer": cert.get("issuer_name", ""),
|
||||
"not_before": cert.get("not_before", ""),
|
||||
})
|
||||
return wildcards
|
||||
|
||||
|
||||
def generate_report(target_domain, ct_results):
|
||||
"""Generate comprehensive CT monitoring report."""
|
||||
lookalikes = find_lookalike_domains(target_domain, ct_results)
|
||||
homoglyphs = detect_homoglyph_domains(target_domain, ct_results)
|
||||
issuer_analysis = analyze_issuer_patterns(ct_results)
|
||||
wildcards = detect_wildcard_abuse(ct_results)
|
||||
|
||||
risk_score = 0
|
||||
risk_score += min(len(lookalikes) * 10, 40)
|
||||
risk_score += min(len(homoglyphs) * 15, 30)
|
||||
risk_score += 20 if issuer_analysis["free_ca_ratio"] > 0.8 else 0
|
||||
risk_score = min(risk_score, 100)
|
||||
|
||||
return {
|
||||
"target_domain": target_domain,
|
||||
"total_certificates": len(ct_results),
|
||||
"lookalike_domains": lookalikes[:20],
|
||||
"homoglyph_domains": homoglyphs[:20],
|
||||
"issuer_analysis": issuer_analysis,
|
||||
"wildcard_certs": wildcards[:10],
|
||||
"risk_score": risk_score,
|
||||
"risk_level": "HIGH" if risk_score >= 60 else "MEDIUM" if risk_score >= 30 else "LOW",
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Certificate Transparency Phishing Detection Agent")
|
||||
print("crt.sh queries, lookalike detection, homoglyph analysis")
|
||||
print("=" * 60)
|
||||
|
||||
domain = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
|
||||
if not domain:
|
||||
print("\n[DEMO] Usage: python agent.py <target_domain>")
|
||||
print(" e.g. python agent.py example.com")
|
||||
sys.exit(0)
|
||||
|
||||
if not HAS_REQUESTS:
|
||||
print("[!] Install requests: pip install requests")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"\n[*] Querying crt.sh for: {domain}")
|
||||
results = query_crtsh(domain)
|
||||
print(f"[*] Found {len(results)} certificates")
|
||||
|
||||
report = generate_report(domain, results)
|
||||
|
||||
print(f"\n--- Lookalike Domains ({len(report['lookalike_domains'])}) ---")
|
||||
for l in report["lookalike_domains"][:10]:
|
||||
print(f" [{l['similarity']:.3f}] {l['domain']} (issuer: {l['issuer'][:40]})")
|
||||
|
||||
print(f"\n--- Homoglyph Domains ({len(report['homoglyph_domains'])}) ---")
|
||||
for h in report["homoglyph_domains"][:10]:
|
||||
print(f" [diff={h['char_differences']}] {h['domain']}")
|
||||
|
||||
print(f"\n--- Issuer Analysis ---")
|
||||
for issuer, count in sorted(report["issuer_analysis"]["issuers"].items(),
|
||||
key=lambda x: -x[1])[:5]:
|
||||
print(f" {count:4d} | {issuer[:60]}")
|
||||
|
||||
print(f"\n[*] Risk Score: {report['risk_score']}/100 ({report['risk_level']})")
|
||||
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
name: analyzing-cloud-storage-access-patterns
|
||||
description: Detect abnormal access patterns in AWS S3, GCS, and Azure Blob Storage by analyzing CloudTrail Data Events, GCS
|
||||
audit logs, and Azure Storage Analytics. Identifies after-hours bulk downloads, access from new IP addresses, unusual API
|
||||
calls (GetObject spikes), and potential data exfiltration using statistical baselines and time-series anomaly detection.
|
||||
domain: cybersecurity
|
||||
subdomain: cloud-security
|
||||
tags:
|
||||
- analyzing
|
||||
- cloud
|
||||
- storage
|
||||
- access
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
atlas_techniques:
|
||||
- AML.T0024
|
||||
- AML.T0056
|
||||
nist_ai_rmf:
|
||||
- MEASURE-2.7
|
||||
- MAP-5.1
|
||||
- MANAGE-2.4
|
||||
nist_csf:
|
||||
- PR.IR-01
|
||||
- ID.AM-08
|
||||
- GV.SC-06
|
||||
- DE.CM-01
|
||||
---
|
||||
|
||||
|
||||
# Analyzing Cloud Storage Access Patterns
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing cloud storage access patterns
|
||||
- When building detection rules or threat hunting queries for this domain
|
||||
- When SOC analysts need structured procedures for this analysis type
|
||||
- When validating security monitoring coverage for related attack techniques
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Familiarity with cloud security concepts and tools
|
||||
- Access to a test or lab environment for safe execution
|
||||
- Python 3.8+ with required dependencies installed
|
||||
- Appropriate authorization for any testing activities
|
||||
|
||||
## Instructions
|
||||
|
||||
1. Install dependencies: `pip install boto3 requests`
|
||||
2. Query CloudTrail for S3 Data Events using AWS CLI or boto3.
|
||||
3. Build access baselines: hourly request volume, per-user object counts, source IP history.
|
||||
4. Detect anomalies:
|
||||
- After-hours access (outside 8am-6pm local time)
|
||||
- Bulk downloads: >100 GetObject calls from single principal in 1 hour
|
||||
- New source IPs not seen in the prior 30 days
|
||||
- ListBucket enumeration spikes (reconnaissance indicator)
|
||||
5. Generate prioritized findings report.
|
||||
|
||||
```bash
|
||||
python scripts/agent.py --bucket my-sensitive-data --hours-back 24 --output s3_access_report.json
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### CloudTrail S3 Data Event
|
||||
```json
|
||||
{"eventName": "GetObject", "requestParameters": {"bucketName": "sensitive-data", "key": "financials/q4.xlsx"},
|
||||
"sourceIPAddress": "203.0.113.50", "userIdentity": {"arn": "arn:aws:iam::123456789012:user/analyst"}}
|
||||
```
|
||||
@@ -0,0 +1,49 @@
|
||||
# API Reference: Cloud Storage Access Pattern Analysis
|
||||
|
||||
## AWS CLI - CloudTrail Lookup
|
||||
```bash
|
||||
aws cloudtrail lookup-events \
|
||||
--lookup-attributes AttributeKey=ResourceType,AttributeValue=AWS::S3::Object \
|
||||
--start-time 2024-01-15T00:00:00Z \
|
||||
--output json
|
||||
```
|
||||
|
||||
## CloudTrail S3 Data Event Structure
|
||||
```json
|
||||
{
|
||||
"EventTime": "2024-01-15T10:30:00Z",
|
||||
"EventName": "GetObject",
|
||||
"Username": "analyst",
|
||||
"CloudTrailEvent": "{\"sourceIPAddress\":\"10.0.0.1\",\"userAgent\":\"aws-cli\",\"requestParameters\":{\"bucketName\":\"data\",\"key\":\"file.csv\"},\"userIdentity\":{\"arn\":\"arn:aws:iam::123:user/analyst\"}}"
|
||||
}
|
||||
```
|
||||
|
||||
## Key S3 Event Names
|
||||
| Event | Meaning |
|
||||
|-------|---------|
|
||||
| GetObject | Object download |
|
||||
| PutObject | Object upload |
|
||||
| DeleteObject | Object deletion |
|
||||
| ListBucket / ListObjectsV2 | Bucket enumeration |
|
||||
| GetBucketPolicy | Policy read |
|
||||
| PutBucketPolicy | Policy modification |
|
||||
|
||||
## Detection Thresholds
|
||||
| Anomaly | Threshold | Severity |
|
||||
|---------|-----------|----------|
|
||||
| Bulk download | >100 GetObject/hr per user | Critical |
|
||||
| After-hours | Access outside 08:00-18:00 UTC | Medium |
|
||||
| New source IP | IP not in 30-day baseline | High |
|
||||
| Enumeration | >20 ListBucket per user | High |
|
||||
|
||||
## boto3 CloudTrail Client (alternative)
|
||||
```python
|
||||
import boto3
|
||||
client = boto3.client("cloudtrail")
|
||||
response = client.lookup_events(
|
||||
LookupAttributes=[{"AttributeKey":"ResourceType","AttributeValue":"AWS::S3::Object"}],
|
||||
StartTime=datetime(2024,1,15),
|
||||
MaxResults=50
|
||||
)
|
||||
events = response["Events"]
|
||||
```
|
||||
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Cloud Storage Access Pattern Analyzer - Detects abnormal S3/GCS/Azure Blob access via CloudTrail."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import argparse
|
||||
import subprocess
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def query_cloudtrail_s3_events(bucket_name, hours_back=24):
|
||||
"""Query CloudTrail for S3 data events on a specific bucket."""
|
||||
start_time = (datetime.utcnow() - timedelta(hours=hours_back)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
cmd = [
|
||||
"aws", "cloudtrail", "lookup-events",
|
||||
"--lookup-attributes", f"AttributeKey=ResourceType,AttributeValue=AWS::S3::Object",
|
||||
"--start-time", start_time,
|
||||
"--output", "json",
|
||||
]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
||||
if result.returncode != 0:
|
||||
logger.error("CloudTrail query failed: %s", result.stderr[:200])
|
||||
return []
|
||||
events = json.loads(result.stdout).get("Events", [])
|
||||
s3_events = []
|
||||
for event in events:
|
||||
ct_event = json.loads(event.get("CloudTrailEvent", "{}"))
|
||||
req_params = ct_event.get("requestParameters", {})
|
||||
if req_params.get("bucketName") == bucket_name or not bucket_name:
|
||||
s3_events.append({
|
||||
"timestamp": event.get("EventTime", ""),
|
||||
"event_name": event.get("EventName", ""),
|
||||
"username": event.get("Username", ""),
|
||||
"source_ip": ct_event.get("sourceIPAddress", ""),
|
||||
"user_agent": ct_event.get("userAgent", ""),
|
||||
"bucket": req_params.get("bucketName", ""),
|
||||
"key": req_params.get("key", ""),
|
||||
"user_arn": ct_event.get("userIdentity", {}).get("arn", ""),
|
||||
})
|
||||
logger.info("Retrieved %d S3 events for bucket '%s'", len(s3_events), bucket_name or "all")
|
||||
return s3_events
|
||||
|
||||
|
||||
def detect_bulk_downloads(events, threshold=100):
|
||||
"""Detect bulk GetObject operations from a single principal."""
|
||||
user_downloads = defaultdict(list)
|
||||
for event in events:
|
||||
if event["event_name"] == "GetObject":
|
||||
user_downloads[event["user_arn"]].append(event)
|
||||
alerts = []
|
||||
for user_arn, downloads in user_downloads.items():
|
||||
if len(downloads) >= threshold:
|
||||
keys = [d["key"] for d in downloads]
|
||||
alerts.append({
|
||||
"user_arn": user_arn,
|
||||
"download_count": len(downloads),
|
||||
"unique_keys": len(set(keys)),
|
||||
"source_ips": list({d["source_ip"] for d in downloads}),
|
||||
"first_access": downloads[0]["timestamp"],
|
||||
"last_access": downloads[-1]["timestamp"],
|
||||
"severity": "critical",
|
||||
"indicator": "Bulk download (potential exfiltration)",
|
||||
})
|
||||
logger.info("Found %d bulk download alerts", len(alerts))
|
||||
return alerts
|
||||
|
||||
|
||||
def detect_after_hours_access(events, business_start=8, business_end=18):
|
||||
"""Detect access outside business hours."""
|
||||
after_hours = []
|
||||
for event in events:
|
||||
try:
|
||||
ts = event["timestamp"]
|
||||
if isinstance(ts, str):
|
||||
dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
||||
else:
|
||||
dt = ts
|
||||
hour = dt.hour
|
||||
if hour < business_start or hour >= business_end:
|
||||
event["indicator"] = f"After-hours access at {hour:02d}:00 UTC"
|
||||
event["severity"] = "medium"
|
||||
after_hours.append(event)
|
||||
except (ValueError, AttributeError):
|
||||
continue
|
||||
logger.info("Found %d after-hours access events", len(after_hours))
|
||||
return after_hours
|
||||
|
||||
|
||||
def detect_new_source_ips(events, known_ips=None):
|
||||
"""Detect access from IP addresses not in the known baseline."""
|
||||
if known_ips is None:
|
||||
known_ips = set()
|
||||
new_ip_events = []
|
||||
for event in events:
|
||||
ip = event["source_ip"]
|
||||
if ip and ip not in known_ips and not ip.startswith("AWS Internal"):
|
||||
event["indicator"] = f"New source IP: {ip}"
|
||||
event["severity"] = "high"
|
||||
new_ip_events.append(event)
|
||||
unique_new = len({e["source_ip"] for e in new_ip_events})
|
||||
logger.info("Found %d events from %d new source IPs", len(new_ip_events), unique_new)
|
||||
return new_ip_events
|
||||
|
||||
|
||||
def detect_enumeration(events, threshold=20):
|
||||
"""Detect ListBucket/ListObjects enumeration patterns."""
|
||||
user_listings = defaultdict(int)
|
||||
for event in events:
|
||||
if event["event_name"] in ("ListBucket", "ListObjects", "ListObjectsV2"):
|
||||
user_listings[event["user_arn"]] += 1
|
||||
alerts = []
|
||||
for user_arn, count in user_listings.items():
|
||||
if count >= threshold:
|
||||
alerts.append({
|
||||
"user_arn": user_arn,
|
||||
"list_count": count,
|
||||
"severity": "high",
|
||||
"indicator": "Bucket enumeration spike (reconnaissance)",
|
||||
})
|
||||
return alerts
|
||||
|
||||
|
||||
def build_access_baseline(events):
|
||||
"""Build statistical baseline of normal access patterns."""
|
||||
hourly_counts = defaultdict(int)
|
||||
user_counts = defaultdict(int)
|
||||
ip_set = set()
|
||||
for event in events:
|
||||
try:
|
||||
ts = event["timestamp"]
|
||||
if isinstance(ts, str):
|
||||
dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
||||
hourly_counts[dt.hour] += 1
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
user_counts[event["user_arn"]] += 1
|
||||
if event["source_ip"]:
|
||||
ip_set.add(event["source_ip"])
|
||||
return {
|
||||
"hourly_distribution": dict(hourly_counts),
|
||||
"user_request_counts": dict(user_counts),
|
||||
"known_ips": list(ip_set),
|
||||
"total_events": len(events),
|
||||
}
|
||||
|
||||
|
||||
def generate_report(events, bulk_alerts, after_hours, new_ips, enum_alerts, baseline):
|
||||
"""Generate cloud storage access analysis report."""
|
||||
report = {
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"total_events_analyzed": len(events),
|
||||
"bulk_download_alerts": bulk_alerts,
|
||||
"after_hours_access": len(after_hours),
|
||||
"new_source_ip_events": len(new_ips),
|
||||
"enumeration_alerts": enum_alerts,
|
||||
"baseline_summary": {
|
||||
"known_ips": len(baseline.get("known_ips", [])),
|
||||
"total_baseline_events": baseline.get("total_events", 0),
|
||||
},
|
||||
"sample_after_hours": after_hours[:10],
|
||||
"sample_new_ips": new_ips[:10],
|
||||
}
|
||||
total_alerts = len(bulk_alerts) + len(enum_alerts) + (1 if new_ips else 0)
|
||||
print(f"CLOUD STORAGE REPORT: {len(events)} events, {total_alerts} alerts")
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Cloud Storage Access Pattern Analyzer")
|
||||
parser.add_argument("--bucket", default="", help="S3 bucket name to analyze")
|
||||
parser.add_argument("--hours-back", type=int, default=24)
|
||||
parser.add_argument("--bulk-threshold", type=int, default=100)
|
||||
parser.add_argument("--known-ips-file", help="File with known IP baselines")
|
||||
parser.add_argument("--output", default="s3_access_report.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
events = query_cloudtrail_s3_events(args.bucket, args.hours_back)
|
||||
baseline = build_access_baseline(events)
|
||||
known_ips = set(baseline.get("known_ips", []))
|
||||
if args.known_ips_file:
|
||||
with open(args.known_ips_file) as f:
|
||||
known_ips.update(line.strip() for line in f if line.strip())
|
||||
|
||||
bulk_alerts = detect_bulk_downloads(events, args.bulk_threshold)
|
||||
after_hours = detect_after_hours_access(events)
|
||||
new_ips = detect_new_source_ips(events, known_ips)
|
||||
enum_alerts = detect_enumeration(events)
|
||||
|
||||
report = generate_report(events, bulk_alerts, after_hours, new_ips, enum_alerts, baseline)
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2, default=str)
|
||||
logger.info("Report saved to %s", args.output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,381 @@
|
||||
---
|
||||
name: analyzing-cobalt-strike-beacon-configuration
|
||||
description: Extract and analyze Cobalt Strike beacon configuration from PE files and memory dumps to identify C2 infrastructure,
|
||||
malleable profiles, and operator tradecraft.
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- cobalt-strike
|
||||
- beacon
|
||||
- c2
|
||||
- malware-analysis
|
||||
- config-extraction
|
||||
- threat-hunting
|
||||
- red-team-tools
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- DE.AE-02
|
||||
- RS.AN-03
|
||||
- ID.RA-01
|
||||
- DE.CM-01
|
||||
---
|
||||
# Analyzing Cobalt Strike Beacon Configuration
|
||||
|
||||
## Overview
|
||||
|
||||
Cobalt Strike is a commercial adversary simulation tool widely abused by threat actors for post-exploitation operations. Beacon payloads contain embedded configuration data that reveals C2 server addresses, communication protocols, sleep intervals, jitter values, malleable C2 profile settings, watermark identifiers, and encryption keys. Extracting this configuration from PE files, shellcode, or memory dumps is critical for incident responders to map attacker infrastructure and attribute campaigns. The beacon configuration is XOR-encoded using a single byte (0x69 for version 3, 0x2e for version 4) and stored in a Type-Length-Value (TLV) format within the .data section.
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing cobalt strike beacon configuration
|
||||
- When building detection rules or threat hunting queries for this domain
|
||||
- When SOC analysts need structured procedures for this analysis type
|
||||
- When validating security monitoring coverage for related attack techniques
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.9+ with `dissect.cobaltstrike`, `pefile`, `yara-python`
|
||||
- SentinelOne CobaltStrikeParser (`parse_beacon_config.py`)
|
||||
- Hex editor (010 Editor, HxD) for manual inspection
|
||||
- Understanding of PE file format and XOR encoding
|
||||
- Memory dump acquisition tools (Volatility3, WinDbg)
|
||||
- Network analysis tools (Wireshark) for C2 traffic correlation
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Beacon Configuration Structure
|
||||
|
||||
Cobalt Strike beacons store their configuration as a blob of TLV (Type-Length-Value) entries within the .data section of the PE. Stageless beacons XOR the entire beacon code with a 4-byte key. The configuration blob itself uses a single-byte XOR key. Each TLV entry contains a 2-byte type identifier (e.g., 0x0001 for BeaconType, 0x0008 for C2Server), a 2-byte length, and variable-length data.
|
||||
|
||||
### Malleable C2 Profiles
|
||||
|
||||
The beacon configuration encodes the malleable C2 profile that dictates HTTP request/response transformations, including URI paths, headers, metadata encoding (Base64, NetBIOS), and data transforms. Analyzing these settings reveals how the beacon disguises its traffic to blend with legitimate web traffic.
|
||||
|
||||
### Watermark and License Identification
|
||||
|
||||
Each Cobalt Strike license embeds a unique watermark (4-byte integer) into generated beacons. Extracting the watermark can link multiple beacons to the same operator or cracked license. Known watermark databases maintained by threat intelligence providers map watermarks to specific threat actors or leaked license keys.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Extract Configuration with CobaltStrikeParser
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""Extract Cobalt Strike beacon config from PE or memory dump."""
|
||||
import sys
|
||||
import json
|
||||
|
||||
# Using SentinelOne's CobaltStrikeParser
|
||||
# pip install dissect.cobaltstrike
|
||||
from dissect.cobaltstrike.beacon import BeaconConfig
|
||||
|
||||
def extract_beacon_config(filepath):
|
||||
"""Parse beacon configuration from file."""
|
||||
configs = list(BeaconConfig.from_path(filepath))
|
||||
|
||||
if not configs:
|
||||
print(f"[-] No beacon configuration found in {filepath}")
|
||||
return None
|
||||
|
||||
for i, config in enumerate(configs):
|
||||
print(f"\n[+] Beacon Configuration #{i+1}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
settings = config.as_dict()
|
||||
|
||||
# Critical fields for incident response
|
||||
critical_fields = [
|
||||
"SETTING_C2_REQUEST",
|
||||
"SETTING_C2_RECOVER",
|
||||
"SETTING_PUBKEY",
|
||||
"SETTING_DOMAINS",
|
||||
"SETTING_BEACONTYPE",
|
||||
"SETTING_PORT",
|
||||
"SETTING_SLEEPTIME",
|
||||
"SETTING_JITTER",
|
||||
"SETTING_MAXGET",
|
||||
"SETTING_SPAWNTO_X86",
|
||||
"SETTING_SPAWNTO_X64",
|
||||
"SETTING_PIPENAME",
|
||||
"SETTING_WATERMARK",
|
||||
"SETTING_C2_VERB_GET",
|
||||
"SETTING_C2_VERB_POST",
|
||||
"SETTING_USERAGENT",
|
||||
"SETTING_PROTOCOL",
|
||||
]
|
||||
|
||||
for field in critical_fields:
|
||||
value = settings.get(field, "N/A")
|
||||
print(f" {field}: {value}")
|
||||
|
||||
return settings
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def extract_c2_indicators(config):
|
||||
"""Extract actionable C2 indicators from beacon config."""
|
||||
indicators = {
|
||||
"c2_domains": [],
|
||||
"c2_ips": [],
|
||||
"c2_urls": [],
|
||||
"user_agent": "",
|
||||
"named_pipes": [],
|
||||
"spawn_processes": [],
|
||||
"watermark": "",
|
||||
}
|
||||
|
||||
if not config:
|
||||
return indicators
|
||||
|
||||
# Extract C2 domains
|
||||
domains = config.get("SETTING_DOMAINS", "")
|
||||
if domains:
|
||||
for domain in str(domains).split(","):
|
||||
domain = domain.strip().rstrip("/")
|
||||
if domain:
|
||||
indicators["c2_domains"].append(domain)
|
||||
|
||||
# Extract user agent
|
||||
indicators["user_agent"] = str(config.get("SETTING_USERAGENT", ""))
|
||||
|
||||
# Extract named pipes
|
||||
pipe = config.get("SETTING_PIPENAME", "")
|
||||
if pipe:
|
||||
indicators["named_pipes"].append(str(pipe))
|
||||
|
||||
# Extract spawn-to processes
|
||||
for arch in ["SETTING_SPAWNTO_X86", "SETTING_SPAWNTO_X64"]:
|
||||
proc = config.get(arch, "")
|
||||
if proc:
|
||||
indicators["spawn_processes"].append(str(proc))
|
||||
|
||||
# Extract watermark
|
||||
indicators["watermark"] = str(config.get("SETTING_WATERMARK", ""))
|
||||
|
||||
return indicators
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print(f"Usage: {sys.argv[0]} <beacon_file_or_dump>")
|
||||
sys.exit(1)
|
||||
|
||||
config = extract_beacon_config(sys.argv[1])
|
||||
if config:
|
||||
indicators = extract_c2_indicators(config)
|
||||
print(f"\n[+] Extracted C2 Indicators:")
|
||||
print(json.dumps(indicators, indent=2))
|
||||
```
|
||||
|
||||
### Step 2: Manual XOR Decryption of Beacon Config
|
||||
|
||||
```python
|
||||
import struct
|
||||
|
||||
def find_and_decrypt_config(data):
|
||||
"""Manually locate and decrypt beacon configuration."""
|
||||
# Cobalt Strike 4.x uses 0x2e as XOR key
|
||||
xor_keys = [0x2e, 0x69] # v4, v3
|
||||
|
||||
for xor_key in xor_keys:
|
||||
# Search for the config magic bytes after XOR
|
||||
# Config starts with 0x0001 (BeaconType) XOR'd with key
|
||||
magic = bytes([0x00 ^ xor_key, 0x01 ^ xor_key,
|
||||
0x00 ^ xor_key, 0x02 ^ xor_key])
|
||||
|
||||
offset = data.find(magic)
|
||||
if offset == -1:
|
||||
continue
|
||||
|
||||
print(f"[+] Found config at offset 0x{offset:x} (XOR key: 0x{xor_key:02x})")
|
||||
|
||||
# Decrypt the config blob (typically 4096 bytes)
|
||||
config_size = 4096
|
||||
encrypted = data[offset:offset + config_size]
|
||||
decrypted = bytes([b ^ xor_key for b in encrypted])
|
||||
|
||||
# Parse TLV entries
|
||||
entries = parse_tlv(decrypted)
|
||||
return entries
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def parse_tlv(data):
|
||||
"""Parse Type-Length-Value configuration entries."""
|
||||
entries = {}
|
||||
offset = 0
|
||||
|
||||
# TLV field type mapping
|
||||
field_names = {
|
||||
0x0001: "BeaconType",
|
||||
0x0002: "Port",
|
||||
0x0003: "SleepTime",
|
||||
0x0004: "MaxGetSize",
|
||||
0x0005: "Jitter",
|
||||
0x0006: "MaxDNS",
|
||||
0x0007: "Deprecated_PublicKey",
|
||||
0x0008: "C2Server",
|
||||
0x0009: "UserAgent",
|
||||
0x000a: "PostURI",
|
||||
0x000b: "Malleable_C2_Instructions",
|
||||
0x000c: "Deprecated_HttpGet_Metadata",
|
||||
0x000d: "SpawnTo_x86",
|
||||
0x000e: "SpawnTo_x64",
|
||||
0x000f: "CryptoScheme",
|
||||
0x001a: "Watermark",
|
||||
0x001d: "C2_HostHeader",
|
||||
0x0024: "PipeName",
|
||||
0x0025: "Year",
|
||||
0x0026: "Month",
|
||||
0x0027: "Day",
|
||||
0x0036: "ProxyHostname",
|
||||
}
|
||||
|
||||
while offset + 6 <= len(data):
|
||||
entry_type = struct.unpack(">H", data[offset:offset+2])[0]
|
||||
entry_len_type = struct.unpack(">H", data[offset+2:offset+4])[0]
|
||||
entry_len = struct.unpack(">H", data[offset+4:offset+6])[0]
|
||||
|
||||
if entry_type == 0:
|
||||
break
|
||||
|
||||
value_start = offset + 6
|
||||
value_end = value_start + entry_len
|
||||
value_data = data[value_start:value_end]
|
||||
|
||||
field_name = field_names.get(entry_type, f"Unknown_0x{entry_type:04x}")
|
||||
|
||||
if entry_len_type == 1: # Short
|
||||
value = struct.unpack(">H", value_data[:2])[0]
|
||||
elif entry_len_type == 2: # Int
|
||||
value = struct.unpack(">I", value_data[:4])[0]
|
||||
elif entry_len_type == 3: # String/Blob
|
||||
value = value_data.rstrip(b'\x00').decode('utf-8', errors='replace')
|
||||
else:
|
||||
value = value_data.hex()
|
||||
|
||||
entries[field_name] = value
|
||||
print(f" {field_name}: {value}")
|
||||
|
||||
offset = value_end
|
||||
|
||||
return entries
|
||||
```
|
||||
|
||||
### Step 3: YARA Rule for Beacon Detection
|
||||
|
||||
```python
|
||||
import yara
|
||||
|
||||
cobalt_strike_rule = """
|
||||
rule CobaltStrike_Beacon_Config {
|
||||
meta:
|
||||
description = "Detects Cobalt Strike beacon configuration"
|
||||
author = "Malware Analysis Team"
|
||||
date = "2025-01-01"
|
||||
|
||||
strings:
|
||||
// XOR'd config marker for CS 4.x (key 0x2e)
|
||||
$config_v4 = { 2e 2f 2e 2c }
|
||||
|
||||
// XOR'd config marker for CS 3.x (key 0x69)
|
||||
$config_v3 = { 69 68 69 6b }
|
||||
|
||||
// Common beacon strings
|
||||
$str_pipe = "\\\\.\\pipe\\" ascii wide
|
||||
$str_beacon = "beacon" ascii nocase
|
||||
$str_sleeptime = "sleeptime" ascii nocase
|
||||
|
||||
// Reflective loader pattern
|
||||
$reflective = { 4D 5A 41 52 55 48 89 E5 }
|
||||
|
||||
condition:
|
||||
($config_v4 or $config_v3) or
|
||||
(2 of ($str_*) and $reflective)
|
||||
}
|
||||
"""
|
||||
|
||||
def scan_for_beacons(filepath):
|
||||
"""Scan file with YARA rules for Cobalt Strike beacons."""
|
||||
rules = yara.compile(source=cobalt_strike_rule)
|
||||
matches = rules.match(filepath)
|
||||
|
||||
for match in matches:
|
||||
print(f"[+] YARA Match: {match.rule}")
|
||||
for string_match in match.strings:
|
||||
offset = string_match.instances[0].offset
|
||||
print(f" String: {string_match.identifier} at offset 0x{offset:x}")
|
||||
|
||||
return matches
|
||||
```
|
||||
|
||||
### Step 4: Network Traffic Correlation
|
||||
|
||||
```python
|
||||
from dissect.cobaltstrike.c2 import HttpC2Config
|
||||
|
||||
def analyze_c2_profile(beacon_config):
|
||||
"""Analyze malleable C2 profile from beacon configuration."""
|
||||
print("\n[+] Malleable C2 Profile Analysis")
|
||||
print("=" * 60)
|
||||
|
||||
# HTTP GET configuration
|
||||
get_verb = beacon_config.get("SETTING_C2_VERB_GET", "GET")
|
||||
get_uri = beacon_config.get("SETTING_C2_REQUEST", "")
|
||||
print(f"\n HTTP GET Request:")
|
||||
print(f" Verb: {get_verb}")
|
||||
print(f" URI: {get_uri}")
|
||||
|
||||
# HTTP POST configuration
|
||||
post_verb = beacon_config.get("SETTING_C2_VERB_POST", "POST")
|
||||
post_uri = beacon_config.get("SETTING_C2_POSTREQ", "")
|
||||
print(f"\n HTTP POST Request:")
|
||||
print(f" Verb: {post_verb}")
|
||||
print(f" URI: {post_uri}")
|
||||
|
||||
# User Agent
|
||||
ua = beacon_config.get("SETTING_USERAGENT", "")
|
||||
print(f"\n User-Agent: {ua}")
|
||||
|
||||
# Host header
|
||||
host = beacon_config.get("SETTING_C2_HOSTHEADER", "")
|
||||
print(f" Host Header: {host}")
|
||||
|
||||
# Sleep and jitter for traffic pattern
|
||||
sleep_ms = beacon_config.get("SETTING_SLEEPTIME", 60000)
|
||||
jitter = beacon_config.get("SETTING_JITTER", 0)
|
||||
print(f"\n Sleep Time: {sleep_ms}ms")
|
||||
print(f" Jitter: {jitter}%")
|
||||
|
||||
# Generate Suricata/Snort signatures
|
||||
print(f"\n[+] Suggested Network Signatures:")
|
||||
if ua:
|
||||
print(f' alert http any any -> any any (msg:"CS Beacon UA"; '
|
||||
f'content:"{ua}"; http_user_agent; sid:1000001; rev:1;)')
|
||||
if get_uri:
|
||||
print(f' alert http any any -> any any (msg:"CS Beacon URI"; '
|
||||
f'content:"{get_uri}"; http_uri; sid:1000002; rev:1;)')
|
||||
```
|
||||
|
||||
## Validation Criteria
|
||||
|
||||
- Beacon configuration successfully extracted from PE file or memory dump
|
||||
- C2 server domains/IPs correctly identified with port and protocol
|
||||
- Malleable C2 profile parameters decoded showing HTTP transforms
|
||||
- Watermark value extracted for attribution correlation
|
||||
- Sleep time and jitter values match observed network beacon intervals
|
||||
- YARA rules detect beacon in both packed and unpacked samples
|
||||
- Network signatures generated from extracted C2 profile
|
||||
|
||||
## References
|
||||
|
||||
- [SentinelOne CobaltStrikeParser](https://github.com/Sentinel-One/CobaltStrikeParser)
|
||||
- [dissect.cobaltstrike Library](https://github.com/fox-it/dissect.cobaltstrike)
|
||||
- [SentinelLabs Beacon Configuration Analysis](https://www.sentinelone.com/labs/the-anatomy-of-an-apt-attack-and-cobaltstrike-beacons-encoded-configuration/)
|
||||
- [Cobalt Strike Staging and Config Extraction](https://blog.securehat.co.uk/cobaltstrike/extracting-config-from-cobaltstrike-stager-shellcode)
|
||||
- [MITRE ATT&CK - Cobalt Strike S0154](https://attack.mitre.org/software/S0154/)
|
||||
@@ -0,0 +1,95 @@
|
||||
# Cobalt Strike Beacon Analysis Report Template
|
||||
|
||||
## Report Metadata
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Report ID | CS-BEACON-YYYY-NNNN |
|
||||
| Date | YYYY-MM-DD |
|
||||
| Sample Hash (SHA-256) | |
|
||||
| Classification | TLP:AMBER |
|
||||
| Analyst | |
|
||||
|
||||
## Beacon Configuration Summary
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| Beacon Type | HTTP / HTTPS / SMB / DNS |
|
||||
| C2 Server(s) | |
|
||||
| Port | |
|
||||
| Sleep Time | ms |
|
||||
| Jitter | % |
|
||||
| User-Agent | |
|
||||
| Watermark | |
|
||||
| SpawnTo (x86) | |
|
||||
| SpawnTo (x64) | |
|
||||
| Named Pipe | |
|
||||
| Host Header | |
|
||||
| Crypto Scheme | |
|
||||
|
||||
## C2 Infrastructure
|
||||
|
||||
| Indicator | Type | Value | Context |
|
||||
|-----------|------|-------|---------|
|
||||
| C2 Domain | domain | | Primary callback |
|
||||
| C2 IP | ip | | Resolved address |
|
||||
| URI Path (GET) | uri | | Beacon check-in |
|
||||
| URI Path (POST) | uri | | Data exfiltration |
|
||||
|
||||
## Malleable C2 Profile
|
||||
|
||||
### HTTP GET Configuration
|
||||
| Parameter | Value |
|
||||
|-----------|-------|
|
||||
| URI | |
|
||||
| Verb | |
|
||||
| Headers | |
|
||||
| Metadata Encoding | |
|
||||
|
||||
### HTTP POST Configuration
|
||||
| Parameter | Value |
|
||||
|-----------|-------|
|
||||
| URI | |
|
||||
| Verb | |
|
||||
| ID Encoding | |
|
||||
| Output Encoding | |
|
||||
|
||||
## Watermark Attribution
|
||||
|
||||
| Watermark | Known Association | Confidence |
|
||||
|-----------|------------------|------------|
|
||||
| | Cracked / Licensed / Threat Actor | High/Med/Low |
|
||||
|
||||
## Network Detection Signatures
|
||||
|
||||
```
|
||||
# Suricata signature for beacon C2 traffic
|
||||
alert http $HOME_NET any -> $EXTERNAL_NET any (
|
||||
msg:"Cobalt Strike Beacon C2 Communication";
|
||||
content:"[USER_AGENT]"; http_user_agent;
|
||||
content:"[URI_PATH]"; http_uri;
|
||||
sid:1000001; rev:1;
|
||||
)
|
||||
```
|
||||
|
||||
## YARA Detection Rule
|
||||
|
||||
```yara
|
||||
rule CobaltStrike_Beacon_[CAMPAIGN] {
|
||||
meta:
|
||||
description = "Detects Cobalt Strike beacon from [CAMPAIGN]"
|
||||
hash = "[SHA256]"
|
||||
strings:
|
||||
$c2 = "[C2_DOMAIN]" ascii
|
||||
$pipe = "[NAMED_PIPE]" ascii
|
||||
$ua = "[USER_AGENT]" ascii
|
||||
condition:
|
||||
2 of them
|
||||
}
|
||||
```
|
||||
|
||||
## Recommendations
|
||||
|
||||
1. **Block**: Add C2 domains/IPs to firewall deny lists
|
||||
2. **Hunt**: Search for named pipe and spawn-to process in endpoint logs
|
||||
3. **Detect**: Deploy YARA and network signatures to detection stack
|
||||
4. **Correlate**: Check watermark against threat intelligence databases
|
||||
@@ -0,0 +1,112 @@
|
||||
# API Reference: Cobalt Strike Beacon Configuration Analysis
|
||||
|
||||
## Beacon Config TLV Format
|
||||
|
||||
### Structure
|
||||
```
|
||||
[Field ID: 2 bytes][Type: 2 bytes][Value: variable]
|
||||
Type 1 = short (2 bytes), Type 2 = int (4 bytes), Type 3 = string/blob (2-byte length + data)
|
||||
```
|
||||
|
||||
### XOR Encoding
|
||||
| Version | XOR Key |
|
||||
|---------|---------|
|
||||
| CS 3.x | `0x69` |
|
||||
| CS 4.x | `0x2E` |
|
||||
|
||||
### Key Configuration Fields
|
||||
| ID | Name | Description |
|
||||
|----|------|-------------|
|
||||
| 1 | BeaconType | 0=HTTP, 1=Hybrid, 2=SMB, 8=HTTPS |
|
||||
| 2 | Port | C2 communication port |
|
||||
| 3 | SleepTime | Beacon interval (ms) |
|
||||
| 5 | Jitter | Random sleep variation (%) |
|
||||
| 7 | PublicKey | RSA public key for encryption |
|
||||
| 8 | C2Server | Command and control server(s) |
|
||||
| 9 | UserAgent | HTTP User-Agent string |
|
||||
| 10 | PostURI | POST callback URI |
|
||||
| 37 | Watermark | License watermark (operator ID) |
|
||||
| 54 | PipeName | Named pipe for SMB beacons |
|
||||
|
||||
## 1768.py (Didier Stevens) - Config Extractor
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
python 1768.py <beacon_file> # Extract config
|
||||
python 1768.py -j <beacon_file> # JSON output
|
||||
python 1768.py -r <beacon_file> # Raw config dump
|
||||
```
|
||||
|
||||
## CobaltStrikeParser (SentinelOne)
|
||||
|
||||
### Syntax
|
||||
```bash
|
||||
python parse_beacon_config.py <file>
|
||||
python parse_beacon_config.py --json <file>
|
||||
```
|
||||
|
||||
### Output Fields
|
||||
```
|
||||
BeaconType: HTTPS
|
||||
Port: 443
|
||||
SleepTime: 60000
|
||||
Jitter: 37
|
||||
C2Server: update.microsoft-cdn.com,/api/v2
|
||||
UserAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
|
||||
Watermark: 305419896
|
||||
SpawnToX86: %windir%\syswow64\dllhost.exe
|
||||
SpawnToX64: %windir%\sysnative\dllhost.exe
|
||||
```
|
||||
|
||||
## JARM Fingerprinting
|
||||
|
||||
### Cobalt Strike Default JARM
|
||||
```bash
|
||||
# Default CS JARM hash (pre-4.7)
|
||||
07d14d16d21d21d07c42d41d00041d24a458a375eef0c576d23a7bab9a9fb1
|
||||
|
||||
# Scan with JARM
|
||||
python jarm.py <target_ip> -p 443
|
||||
```
|
||||
|
||||
## Known Watermark Values
|
||||
| Watermark | Attribution |
|
||||
|-----------|------------|
|
||||
| 0 | Trial/cracked version |
|
||||
| 305419896 | Common cracked version |
|
||||
| 1359593325 | Known threat actor toolkit |
|
||||
| 1580103824 | Known APT usage |
|
||||
|
||||
## Detection Signatures
|
||||
|
||||
### Suricata
|
||||
```
|
||||
alert http $HOME_NET any -> $EXTERNAL_NET any (
|
||||
msg:"ET MALWARE Cobalt Strike Beacon";
|
||||
content:"/submit.php"; http_uri;
|
||||
content:"Cookie:"; http_header;
|
||||
pcre:"/Cookie:\s[A-Za-z0-9+/=]{60,}/H";
|
||||
sid:2028591; rev:1;)
|
||||
```
|
||||
|
||||
### YARA
|
||||
```yara
|
||||
rule CobaltStrike_Beacon {
|
||||
strings:
|
||||
$config_v3 = { 00 01 00 01 00 02 ?? ?? 00 01 00 02 }
|
||||
$magic = "MSSE-%d-server"
|
||||
$pipe = "\\\\.\\pipe\\msagent_"
|
||||
condition:
|
||||
uint16(0) == 0x5A4D and any of them
|
||||
}
|
||||
```
|
||||
|
||||
## Malleable C2 Profile Elements
|
||||
| Element | Description |
|
||||
|---------|-------------|
|
||||
| `http-get` | GET request profile (URI, headers, metadata transform) |
|
||||
| `http-post` | POST request profile (URI, body transform) |
|
||||
| `set sleeptime` | Default beacon interval |
|
||||
| `set jitter` | Randomization percentage |
|
||||
| `set useragent` | HTTP User-Agent |
|
||||
| `set pipename` | SMB named pipe name |
|
||||
@@ -0,0 +1,94 @@
|
||||
# Standards and Frameworks Reference
|
||||
|
||||
## Cobalt Strike Beacon Configuration Fields
|
||||
|
||||
### Configuration TLV Types
|
||||
| Type ID | Field Name | Data Type | Description |
|
||||
|---------|-----------|-----------|-------------|
|
||||
| 0x0001 | BeaconType | Short | 0=HTTP, 1=Hybrid HTTP/DNS, 8=HTTPS, 10=TCP Bind |
|
||||
| 0x0002 | Port | Short | C2 communication port |
|
||||
| 0x0003 | SleepTime | Int | Beacon callback interval in milliseconds |
|
||||
| 0x0005 | Jitter | Short | Percentage of sleep time randomization (0-99) |
|
||||
| 0x0008 | C2Server | String | Comma-separated C2 domains/IPs |
|
||||
| 0x0009 | UserAgent | String | HTTP User-Agent header value |
|
||||
| 0x000a | PostURI | String | URI for HTTP POST requests |
|
||||
| 0x000d | SpawnTo_x86 | String | 32-bit process to spawn for post-ex |
|
||||
| 0x000e | SpawnTo_x64 | String | 64-bit process to spawn for post-ex |
|
||||
| 0x001a | Watermark | Int | License watermark identifier |
|
||||
| 0x0024 | PipeName | String | Named pipe for SMB beacon |
|
||||
| 0x001d | HostHeader | String | HTTP Host header value |
|
||||
| 0x0032 | ProxyHostname | String | Proxy server address |
|
||||
|
||||
### XOR Encoding Scheme
|
||||
- **Cobalt Strike 3.x**: XOR key = 0x69
|
||||
- **Cobalt Strike 4.x**: XOR key = 0x2e
|
||||
- Configuration blob size: 4096 bytes (typical)
|
||||
- Encoding: Single-byte XOR across entire config blob
|
||||
|
||||
### Stageless Beacon Structure
|
||||
- PE with beacon code in .data section
|
||||
- 4-byte XOR key applied to .data section content
|
||||
- Configuration embedded after beacon code
|
||||
- Reflective DLL loader prepended to beacon
|
||||
|
||||
## MITRE ATT&CK Mappings
|
||||
|
||||
### Cobalt Strike Techniques (S0154)
|
||||
| Technique | ID | Description |
|
||||
|-----------|-----|------------|
|
||||
| Application Layer Protocol | T1071.001 | HTTP/HTTPS C2 communication |
|
||||
| Encrypted Channel | T1573.002 | AES-256 encrypted C2 |
|
||||
| Ingress Tool Transfer | T1105 | Download additional payloads |
|
||||
| Process Injection | T1055 | Inject into spawned processes |
|
||||
| Named Pipes | T1570 | SMB beacon lateral movement |
|
||||
| Service Execution | T1569.002 | PSExec-style lateral movement |
|
||||
| Reflective Code Loading | T1620 | In-memory beacon loading |
|
||||
|
||||
## Malleable C2 Profile Structure
|
||||
|
||||
### HTTP GET Block
|
||||
```
|
||||
http-get {
|
||||
set uri "/path";
|
||||
client {
|
||||
header "Accept" "text/html";
|
||||
metadata {
|
||||
base64url;
|
||||
prepend "session=";
|
||||
header "Cookie";
|
||||
}
|
||||
}
|
||||
server {
|
||||
header "Content-Type" "text/html";
|
||||
output {
|
||||
print;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP POST Block
|
||||
```
|
||||
http-post {
|
||||
set uri "/submit";
|
||||
client {
|
||||
id {
|
||||
uri-append;
|
||||
}
|
||||
output {
|
||||
base64;
|
||||
print;
|
||||
}
|
||||
}
|
||||
server {
|
||||
output {
|
||||
print;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## References
|
||||
- [Cobalt Strike Documentation](https://hstechdocs.helpsystems.com/manuals/cobaltstrike/)
|
||||
- [Malleable C2 Profile Reference](https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics/malleable-c2_main.htm)
|
||||
- [MITRE ATT&CK Cobalt Strike](https://attack.mitre.org/software/S0154/)
|
||||
@@ -0,0 +1,72 @@
|
||||
# Cobalt Strike Beacon Analysis Workflows
|
||||
|
||||
## Workflow 1: PE File Configuration Extraction
|
||||
|
||||
```
|
||||
[Suspicious PE] --> [Unpack if packed] --> [Locate .data section] --> [XOR Decrypt]
|
||||
|
|
||||
v
|
||||
[Parse TLV Config]
|
||||
|
|
||||
v
|
||||
[Extract C2 Indicators]
|
||||
```
|
||||
|
||||
### Steps:
|
||||
1. **Triage**: Identify file as potential Cobalt Strike beacon via YARA or AV detection
|
||||
2. **Unpacking**: If packed, unpack using appropriate tool (UPX, custom unpacker)
|
||||
3. **Section Analysis**: Locate .data section containing XOR'd beacon code
|
||||
4. **XOR Key Discovery**: Try known keys (0x2e, 0x69) or brute-force 4-byte key
|
||||
5. **Config Parsing**: Parse decrypted TLV entries for C2 and operational settings
|
||||
6. **IOC Extraction**: Extract domains, IPs, URIs, user agents, watermarks
|
||||
|
||||
## Workflow 2: Memory Dump Beacon Extraction
|
||||
|
||||
```
|
||||
[Memory Dump] --> [Volatility3 malfind] --> [Dump Injected Regions] --> [Parse Config]
|
||||
|
|
||||
v
|
||||
[C2 Infrastructure Map]
|
||||
```
|
||||
|
||||
### Steps:
|
||||
1. **Acquisition**: Capture memory dump from compromised system
|
||||
2. **Process Scan**: Use Volatility3 to identify suspicious processes
|
||||
3. **Injection Detection**: Use malfind to find RWX memory regions
|
||||
4. **Region Extraction**: Dump injected memory regions to files
|
||||
5. **Config Search**: Scan dumps for beacon configuration signatures
|
||||
6. **Infrastructure Mapping**: Correlate extracted C2 with network logs
|
||||
|
||||
## Workflow 3: Watermark Attribution
|
||||
|
||||
```
|
||||
[Multiple Beacons] --> [Extract Watermarks] --> [Cluster by Watermark] --> [Attribution]
|
||||
|
|
||||
v
|
||||
[Campaign Correlation]
|
||||
```
|
||||
|
||||
### Steps:
|
||||
1. **Collection**: Gather beacon samples from incident or threat intel feeds
|
||||
2. **Watermark Extraction**: Extract watermark value from each sample
|
||||
3. **Database Lookup**: Check watermark against known databases
|
||||
4. **Clustering**: Group beacons sharing the same watermark
|
||||
5. **Infrastructure Overlap**: Correlate C2 infrastructure across cluster
|
||||
6. **Attribution Assessment**: Link to known threat actor or cracked license
|
||||
|
||||
## Workflow 4: C2 Traffic Detection
|
||||
|
||||
```
|
||||
[Beacon Config] --> [Extract C2 Profile] --> [Generate Signatures] --> [Deploy to NIDS]
|
||||
|
|
||||
v
|
||||
[Monitor Network Traffic]
|
||||
```
|
||||
|
||||
### Steps:
|
||||
1. **Profile Extraction**: Parse malleable C2 profile from beacon config
|
||||
2. **Pattern Identification**: Identify unique HTTP headers, URIs, and encoding
|
||||
3. **Signature Creation**: Write Suricata/Snort rules matching C2 patterns
|
||||
4. **Deployment**: Deploy signatures to network detection infrastructure
|
||||
5. **Validation**: Test signatures against captured beacon traffic
|
||||
6. **Monitoring**: Alert on matching network flows for active beacons
|
||||
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Cobalt Strike beacon configuration extraction and analysis agent.
|
||||
|
||||
Extracts C2 configuration from beacon payloads including server addresses,
|
||||
communication settings, malleable C2 profile details, and watermark values.
|
||||
"""
|
||||
|
||||
import struct
|
||||
import os
|
||||
import sys
|
||||
import hashlib
|
||||
from collections import OrderedDict
|
||||
|
||||
# Cobalt Strike beacon configuration field IDs (Type-Length-Value format)
|
||||
BEACON_CONFIG_FIELDS = {
|
||||
1: ("BeaconType", "short"),
|
||||
2: ("Port", "short"),
|
||||
3: ("SleepTime", "int"),
|
||||
4: ("MaxGetSize", "int"),
|
||||
5: ("Jitter", "short"),
|
||||
7: ("PublicKey", "bytes"),
|
||||
8: ("C2Server", "str"),
|
||||
9: ("UserAgent", "str"),
|
||||
10: ("PostURI", "str"),
|
||||
11: ("Malleable_C2_Instructions", "bytes"),
|
||||
12: ("HttpGet_Metadata", "bytes"),
|
||||
13: ("HttpPost_Metadata", "bytes"),
|
||||
14: ("SpawnToX86", "str"),
|
||||
15: ("SpawnToX64", "str"),
|
||||
19: ("CryptoScheme", "short"),
|
||||
26: ("GetVerb", "str"),
|
||||
27: ("PostVerb", "str"),
|
||||
28: ("HttpPostChunk", "int"),
|
||||
29: ("Spawnto_x86", "str"),
|
||||
30: ("Spawnto_x64", "str"),
|
||||
31: ("CryptoScheme2", "str"),
|
||||
37: ("Watermark", "int"),
|
||||
38: ("StageCleanup", "short"),
|
||||
39: ("CFGCaution", "short"),
|
||||
43: ("DNS_Idle", "int"),
|
||||
44: ("DNS_Sleep", "int"),
|
||||
50: ("HostHeader", "str"),
|
||||
54: ("PipeName", "str"),
|
||||
}
|
||||
|
||||
BEACON_TYPES = {0: "HTTP", 1: "Hybrid HTTP/DNS", 2: "SMB", 4: "TCP", 8: "HTTPS", 16: "DNS over HTTPS"}
|
||||
|
||||
XOR_KEY_V3 = 0x69
|
||||
XOR_KEY_V4 = 0x2E
|
||||
|
||||
|
||||
def compute_hash(filepath):
|
||||
"""Compute SHA-256 hash of file."""
|
||||
sha256 = hashlib.sha256()
|
||||
with open(filepath, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(65536), b""):
|
||||
sha256.update(chunk)
|
||||
return sha256.hexdigest()
|
||||
|
||||
|
||||
def find_config_offset(data):
|
||||
"""Find the beacon configuration blob in PE data or shellcode."""
|
||||
# Look for XOR-encoded config patterns
|
||||
for xor_key in [XOR_KEY_V3, XOR_KEY_V4]:
|
||||
# Config starts with 0x0001 (BeaconType field ID) XOR-encoded
|
||||
encoded_marker = bytes([0x00 ^ xor_key, 0x01 ^ xor_key, 0x00 ^ xor_key, 0x01 ^ xor_key])
|
||||
offset = data.find(encoded_marker)
|
||||
if offset != -1:
|
||||
return offset, xor_key
|
||||
# Try unencoded
|
||||
for offset in range(len(data) - 100):
|
||||
if data[offset:offset+4] == b"\x00\x01\x00\x01":
|
||||
return offset, None
|
||||
return -1, None
|
||||
|
||||
|
||||
def xor_decode(data, key):
|
||||
"""XOR decode data with single byte key."""
|
||||
if key is None:
|
||||
return data
|
||||
return bytes(b ^ key for b in data)
|
||||
|
||||
|
||||
def parse_config_field(data, offset):
|
||||
"""Parse a single TLV config field."""
|
||||
if offset + 6 > len(data):
|
||||
return None, None, None, offset
|
||||
field_id = struct.unpack_from(">H", data, offset)[0]
|
||||
field_type = struct.unpack_from(">H", data, offset + 2)[0]
|
||||
if field_type == 1: # short
|
||||
value = struct.unpack_from(">H", data, offset + 4)[0]
|
||||
return field_id, "short", value, offset + 6
|
||||
elif field_type == 2: # int
|
||||
value = struct.unpack_from(">I", data, offset + 4)[0]
|
||||
return field_id, "int", value, offset + 8
|
||||
elif field_type == 3: # str/bytes
|
||||
length = struct.unpack_from(">H", data, offset + 4)[0]
|
||||
if offset + 6 + length > len(data):
|
||||
return None, None, None, offset
|
||||
value = data[offset + 6:offset + 6 + length]
|
||||
return field_id, "str", value, offset + 6 + length
|
||||
return None, None, None, offset + 2
|
||||
|
||||
|
||||
def extract_beacon_config(filepath):
|
||||
"""Extract and parse Cobalt Strike beacon configuration."""
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
config_offset, xor_key = find_config_offset(data)
|
||||
if config_offset == -1:
|
||||
return {"error": "No beacon configuration found", "file": filepath}
|
||||
|
||||
config_data = xor_decode(data[config_offset:config_offset + 4096], xor_key)
|
||||
config = OrderedDict()
|
||||
config["_meta"] = {
|
||||
"config_offset": f"0x{config_offset:08X}",
|
||||
"xor_key": f"0x{xor_key:02X}" if xor_key else "none",
|
||||
"version_guess": "4.x" if xor_key == XOR_KEY_V4 else "3.x" if xor_key == XOR_KEY_V3 else "unknown",
|
||||
}
|
||||
|
||||
offset = 0
|
||||
max_fields = 100
|
||||
parsed = 0
|
||||
while offset < len(config_data) - 4 and parsed < max_fields:
|
||||
field_id, field_type, value, new_offset = parse_config_field(config_data, offset)
|
||||
if field_id is None or new_offset == offset:
|
||||
break
|
||||
offset = new_offset
|
||||
parsed += 1
|
||||
|
||||
field_info = BEACON_CONFIG_FIELDS.get(field_id)
|
||||
if field_info:
|
||||
field_name, expected_type = field_info
|
||||
if isinstance(value, bytes):
|
||||
try:
|
||||
str_value = value.rstrip(b"\x00").decode("utf-8", errors="replace")
|
||||
config[field_name] = str_value
|
||||
except Exception:
|
||||
config[field_name] = value.hex()[:100]
|
||||
elif field_id == 1:
|
||||
config[field_name] = BEACON_TYPES.get(value, f"Unknown({value})")
|
||||
else:
|
||||
config[field_name] = value
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def extract_c2_indicators(config):
|
||||
"""Extract C2 indicators from parsed config for threat intelligence."""
|
||||
indicators = {"c2_servers": [], "user_agents": [], "uris": [],
|
||||
"pipes": [], "watermark": None, "dns": []}
|
||||
c2 = config.get("C2Server", "")
|
||||
if c2:
|
||||
for server in c2.split(","):
|
||||
server = server.strip().rstrip("/")
|
||||
if server:
|
||||
indicators["c2_servers"].append(server)
|
||||
ua = config.get("UserAgent", "")
|
||||
if ua:
|
||||
indicators["user_agents"].append(ua)
|
||||
for key in ["PostURI"]:
|
||||
uri = config.get(key, "")
|
||||
if uri:
|
||||
indicators["uris"].append(uri)
|
||||
pipe = config.get("PipeName", "")
|
||||
if pipe:
|
||||
indicators["pipes"].append(pipe)
|
||||
wm = config.get("Watermark")
|
||||
if wm:
|
||||
indicators["watermark"] = wm
|
||||
return indicators
|
||||
|
||||
|
||||
def assess_operator_opsec(config):
|
||||
"""Assess operator OPSEC based on beacon configuration."""
|
||||
findings = []
|
||||
sleep = config.get("SleepTime", 0)
|
||||
jitter = config.get("Jitter", 0)
|
||||
if sleep < 30000:
|
||||
findings.append({"level": "INFO", "detail": f"Low sleep time: {sleep}ms - high beacon frequency"})
|
||||
if jitter == 0:
|
||||
findings.append({"level": "WARN", "detail": "No jitter configured - predictable beacon interval"})
|
||||
ua = config.get("UserAgent", "")
|
||||
if "Mozilla" not in ua and ua:
|
||||
findings.append({"level": "WARN", "detail": f"Non-standard User-Agent: {ua[:60]}"})
|
||||
spawn86 = config.get("SpawnToX86", config.get("Spawnto_x86", ""))
|
||||
if "rundll32" in spawn86.lower():
|
||||
findings.append({"level": "INFO", "detail": "Default spawn-to process (rundll32) - easy to detect"})
|
||||
cleanup = config.get("StageCleanup", 0)
|
||||
if cleanup == 0:
|
||||
findings.append({"level": "INFO", "detail": "Stage cleanup disabled - beacon stub remains in memory"})
|
||||
return findings
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Cobalt Strike Beacon Configuration Extractor")
|
||||
print("C2 extraction, watermark analysis, OPSEC assessment")
|
||||
print("=" * 60)
|
||||
|
||||
target = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
|
||||
if not target or not os.path.exists(target):
|
||||
print("\n[DEMO] Usage: python agent.py <beacon_sample.exe>")
|
||||
print(" Extracts: C2 servers, sleep/jitter, watermark, malleable profile")
|
||||
sys.exit(0)
|
||||
|
||||
print(f"\n[*] Analyzing: {target}")
|
||||
print(f"[*] SHA-256: {compute_hash(target)}")
|
||||
print(f"[*] Size: {os.path.getsize(target)} bytes")
|
||||
|
||||
config = extract_beacon_config(target)
|
||||
|
||||
if "error" in config:
|
||||
print(f"\n[!] {config['error']}")
|
||||
sys.exit(1)
|
||||
|
||||
print("\n--- Beacon Configuration ---")
|
||||
for key, value in config.items():
|
||||
if key == "_meta":
|
||||
for mk, mv in value.items():
|
||||
print(f" {mk}: {mv}")
|
||||
else:
|
||||
print(f" {key}: {value}")
|
||||
|
||||
indicators = extract_c2_indicators(config)
|
||||
print("\n--- C2 Indicators ---")
|
||||
for c2 in indicators["c2_servers"]:
|
||||
print(f" [C2] {c2}")
|
||||
if indicators["watermark"]:
|
||||
print(f" [Watermark] {indicators['watermark']}")
|
||||
for pipe in indicators["pipes"]:
|
||||
print(f" [Pipe] {pipe}")
|
||||
|
||||
opsec = assess_operator_opsec(config)
|
||||
print("\n--- Operator OPSEC Assessment ---")
|
||||
for f in opsec:
|
||||
print(f" [{f['level']}] {f['detail']}")
|
||||
@@ -0,0 +1,337 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Cobalt Strike Beacon Configuration Analyzer
|
||||
|
||||
Extracts and analyzes beacon configurations from PE files, shellcode,
|
||||
and memory dumps using dissect.cobaltstrike and manual parsing.
|
||||
|
||||
Requirements:
|
||||
pip install dissect.cobaltstrike pefile yara-python
|
||||
|
||||
Usage:
|
||||
python process.py --file beacon.exe --output report.json
|
||||
python process.py --file memdump.bin --scan-memory
|
||||
python process.py --directory ./samples --batch
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from dissect.cobaltstrike.beacon import BeaconConfig
|
||||
except ImportError:
|
||||
print("ERROR: dissect.cobaltstrike not installed.")
|
||||
print("Run: pip install dissect.cobaltstrike")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# TLV field type mapping
|
||||
TLV_FIELDS = {
|
||||
0x0001: ("BeaconType", "short"),
|
||||
0x0002: ("Port", "short"),
|
||||
0x0003: ("SleepTime", "int"),
|
||||
0x0004: ("MaxGetSize", "int"),
|
||||
0x0005: ("Jitter", "short"),
|
||||
0x0006: ("MaxDNS", "short"),
|
||||
0x0008: ("C2Server", "str"),
|
||||
0x0009: ("UserAgent", "str"),
|
||||
0x000a: ("PostURI", "str"),
|
||||
0x000b: ("Malleable_C2_Instructions", "blob"),
|
||||
0x000d: ("SpawnTo_x86", "str"),
|
||||
0x000e: ("SpawnTo_x64", "str"),
|
||||
0x000f: ("CryptoScheme", "short"),
|
||||
0x001a: ("Watermark", "int"),
|
||||
0x001d: ("HostHeader", "str"),
|
||||
0x0024: ("PipeName", "str"),
|
||||
0x0025: ("Year", "short"),
|
||||
0x0026: ("Month", "short"),
|
||||
0x0027: ("Day", "short"),
|
||||
0x002c: ("ProxyHostname", "str"),
|
||||
0x002d: ("ProxyUsername", "str"),
|
||||
0x002e: ("ProxyPassword", "str"),
|
||||
}
|
||||
|
||||
BEACON_TYPES = {
|
||||
0: "HTTP",
|
||||
1: "Hybrid HTTP/DNS",
|
||||
2: "SMB",
|
||||
4: "TCP",
|
||||
8: "HTTPS",
|
||||
10: "TCP Bind",
|
||||
14: "External C2",
|
||||
}
|
||||
|
||||
|
||||
class BeaconAnalyzer:
|
||||
"""Analyze Cobalt Strike beacon configurations."""
|
||||
|
||||
def __init__(self):
|
||||
self.results = []
|
||||
|
||||
def analyze_file(self, filepath):
|
||||
"""Extract beacon config from a file."""
|
||||
filepath = Path(filepath)
|
||||
if not filepath.exists():
|
||||
print(f"[-] File not found: {filepath}")
|
||||
return None
|
||||
|
||||
print(f"[*] Analyzing: {filepath}")
|
||||
|
||||
# Try dissect.cobaltstrike first
|
||||
result = self._extract_with_dissect(filepath)
|
||||
|
||||
# Fall back to manual extraction
|
||||
if not result:
|
||||
result = self._extract_manual(filepath)
|
||||
|
||||
if result:
|
||||
result["source_file"] = str(filepath)
|
||||
result["analysis_time"] = datetime.now().isoformat()
|
||||
self.results.append(result)
|
||||
|
||||
return result
|
||||
|
||||
def _extract_with_dissect(self, filepath):
|
||||
"""Extract config using dissect.cobaltstrike library."""
|
||||
try:
|
||||
configs = list(BeaconConfig.from_path(filepath))
|
||||
if not configs:
|
||||
return None
|
||||
|
||||
config = configs[0]
|
||||
settings = config.as_dict()
|
||||
|
||||
result = {
|
||||
"method": "dissect.cobaltstrike",
|
||||
"config": {},
|
||||
"indicators": {},
|
||||
}
|
||||
|
||||
for key, value in settings.items():
|
||||
if value is not None:
|
||||
result["config"][key] = str(value)
|
||||
|
||||
result["indicators"] = self._extract_indicators(settings)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
print(f" [!] dissect extraction failed: {e}")
|
||||
return None
|
||||
|
||||
def _extract_manual(self, filepath):
|
||||
"""Manual XOR-based config extraction."""
|
||||
try:
|
||||
with open(filepath, "rb") as f:
|
||||
data = f.read()
|
||||
except Exception as e:
|
||||
print(f" [!] Read failed: {e}")
|
||||
return None
|
||||
|
||||
for xor_key in [0x2e, 0x69]:
|
||||
# Search for XOR'd config start marker
|
||||
magic = bytes([0x00 ^ xor_key, 0x01 ^ xor_key,
|
||||
0x00 ^ xor_key, 0x02 ^ xor_key])
|
||||
|
||||
offset = data.find(magic)
|
||||
if offset == -1:
|
||||
continue
|
||||
|
||||
print(f" [+] Config found at 0x{offset:x} (XOR key: 0x{xor_key:02x})")
|
||||
|
||||
config_blob = data[offset:offset + 4096]
|
||||
decrypted = bytes([b ^ xor_key for b in config_blob])
|
||||
|
||||
entries = self._parse_tlv(decrypted)
|
||||
if entries:
|
||||
return {
|
||||
"method": "manual_xor",
|
||||
"xor_key": f"0x{xor_key:02x}",
|
||||
"config_offset": f"0x{offset:x}",
|
||||
"config": entries,
|
||||
"indicators": self._extract_indicators(entries),
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
def _parse_tlv(self, data):
|
||||
"""Parse TLV configuration entries."""
|
||||
entries = {}
|
||||
offset = 0
|
||||
|
||||
while offset + 6 <= len(data):
|
||||
try:
|
||||
entry_type = struct.unpack(">H", data[offset:offset+2])[0]
|
||||
data_type = struct.unpack(">H", data[offset+2:offset+4])[0]
|
||||
entry_len = struct.unpack(">H", data[offset+4:offset+6])[0]
|
||||
except struct.error:
|
||||
break
|
||||
|
||||
if entry_type == 0 or entry_len > 4096:
|
||||
break
|
||||
|
||||
value_data = data[offset+6:offset+6+entry_len]
|
||||
field_info = TLV_FIELDS.get(entry_type)
|
||||
|
||||
if field_info:
|
||||
field_name, expected_type = field_info
|
||||
else:
|
||||
field_name = f"Unknown_0x{entry_type:04x}"
|
||||
expected_type = "blob"
|
||||
|
||||
if data_type == 1 and len(value_data) >= 2:
|
||||
value = struct.unpack(">H", value_data[:2])[0]
|
||||
elif data_type == 2 and len(value_data) >= 4:
|
||||
value = struct.unpack(">I", value_data[:4])[0]
|
||||
elif data_type == 3:
|
||||
value = value_data.rstrip(b'\x00').decode('utf-8', errors='replace')
|
||||
else:
|
||||
value = value_data.hex()
|
||||
|
||||
# Resolve beacon type names
|
||||
if field_name == "BeaconType" and isinstance(value, int):
|
||||
value = BEACON_TYPES.get(value, f"Unknown ({value})")
|
||||
|
||||
entries[field_name] = value
|
||||
offset += 6 + entry_len
|
||||
|
||||
return entries
|
||||
|
||||
def _extract_indicators(self, config):
|
||||
"""Extract IOCs from parsed configuration."""
|
||||
indicators = {
|
||||
"c2_servers": [],
|
||||
"user_agent": "",
|
||||
"named_pipes": [],
|
||||
"spawn_processes": [],
|
||||
"watermark": "",
|
||||
"beacon_type": "",
|
||||
"sleep_time_ms": 0,
|
||||
"jitter_pct": 0,
|
||||
}
|
||||
|
||||
# Handle both dissect dict keys and manual parse keys
|
||||
c2_keys = ["SETTING_DOMAINS", "C2Server"]
|
||||
for key in c2_keys:
|
||||
domains = config.get(key, "")
|
||||
if domains:
|
||||
for d in str(domains).split(","):
|
||||
d = d.strip().rstrip("/")
|
||||
if d:
|
||||
indicators["c2_servers"].append(d)
|
||||
|
||||
ua_keys = ["SETTING_USERAGENT", "UserAgent"]
|
||||
for key in ua_keys:
|
||||
ua = config.get(key, "")
|
||||
if ua:
|
||||
indicators["user_agent"] = str(ua)
|
||||
|
||||
pipe_keys = ["SETTING_PIPENAME", "PipeName"]
|
||||
for key in pipe_keys:
|
||||
pipe = config.get(key, "")
|
||||
if pipe:
|
||||
indicators["named_pipes"].append(str(pipe))
|
||||
|
||||
spawn_keys = [
|
||||
("SETTING_SPAWNTO_X86", "SpawnTo_x86"),
|
||||
("SETTING_SPAWNTO_X64", "SpawnTo_x64"),
|
||||
]
|
||||
for dissect_key, manual_key in spawn_keys:
|
||||
for key in [dissect_key, manual_key]:
|
||||
proc = config.get(key, "")
|
||||
if proc:
|
||||
indicators["spawn_processes"].append(str(proc))
|
||||
|
||||
wm_keys = ["SETTING_WATERMARK", "Watermark"]
|
||||
for key in wm_keys:
|
||||
wm = config.get(key, "")
|
||||
if wm:
|
||||
indicators["watermark"] = str(wm)
|
||||
|
||||
return indicators
|
||||
|
||||
def batch_analyze(self, directory):
|
||||
"""Analyze all files in a directory."""
|
||||
directory = Path(directory)
|
||||
extensions = {".exe", ".dll", ".bin", ".dmp", ".raw"}
|
||||
|
||||
for filepath in directory.rglob("*"):
|
||||
if filepath.suffix.lower() in extensions:
|
||||
self.analyze_file(filepath)
|
||||
|
||||
return self.results
|
||||
|
||||
def cluster_by_watermark(self):
|
||||
"""Cluster analyzed beacons by watermark."""
|
||||
clusters = defaultdict(list)
|
||||
|
||||
for result in self.results:
|
||||
wm = result.get("indicators", {}).get("watermark", "unknown")
|
||||
clusters[wm].append(result.get("source_file", "unknown"))
|
||||
|
||||
return dict(clusters)
|
||||
|
||||
def generate_report(self, output_path=None):
|
||||
"""Generate JSON analysis report."""
|
||||
report = {
|
||||
"analysis_date": datetime.now().isoformat(),
|
||||
"total_beacons": len(self.results),
|
||||
"watermark_clusters": self.cluster_by_watermark(),
|
||||
"all_c2_servers": list(set(
|
||||
server
|
||||
for r in self.results
|
||||
for server in r.get("indicators", {}).get("c2_servers", [])
|
||||
)),
|
||||
"results": self.results,
|
||||
}
|
||||
|
||||
if output_path:
|
||||
with open(output_path, "w") as f:
|
||||
json.dump(report, f, indent=2, default=str)
|
||||
print(f"[+] Report saved to {output_path}")
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Cobalt Strike Beacon Configuration Analyzer"
|
||||
)
|
||||
parser.add_argument("--file", help="Single file to analyze")
|
||||
parser.add_argument("--directory", help="Directory for batch analysis")
|
||||
parser.add_argument("--output", default="beacon_report.json",
|
||||
help="Output report path")
|
||||
parser.add_argument("--scan-memory", action="store_true",
|
||||
help="Treat input as raw memory dump")
|
||||
parser.add_argument("--batch", action="store_true",
|
||||
help="Batch analyze directory")
|
||||
|
||||
args = parser.parse_args()
|
||||
analyzer = BeaconAnalyzer()
|
||||
|
||||
if args.file:
|
||||
result = analyzer.analyze_file(args.file)
|
||||
if result:
|
||||
print(json.dumps(result, indent=2, default=str))
|
||||
|
||||
elif args.directory and args.batch:
|
||||
results = analyzer.batch_analyze(args.directory)
|
||||
print(f"\n[+] Analyzed {len(results)} beacons")
|
||||
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
report = analyzer.generate_report(args.output)
|
||||
print(f"\n[+] Total C2 servers found: {len(report['all_c2_servers'])}")
|
||||
for server in report["all_c2_servers"]:
|
||||
print(f" {server}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: analyzing-cobaltstrike-malleable-c2-profiles
|
||||
description: Parse and analyze Cobalt Strike Malleable C2 profiles using dissect.cobaltstrike and pyMalleableC2 to extract
|
||||
C2 indicators, detect evasion techniques, and generate network detection signatures.
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- cobalt-strike
|
||||
- malleable-c2
|
||||
- c2-detection
|
||||
- beacon-analysis
|
||||
- network-signatures
|
||||
- threat-hunting
|
||||
- red-team-tools
|
||||
version: '1.0'
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- DE.AE-02
|
||||
- RS.AN-03
|
||||
- ID.RA-01
|
||||
- DE.CM-01
|
||||
---
|
||||
# Analyzing CobaltStrike Malleable C2 Profiles
|
||||
|
||||
## Overview
|
||||
|
||||
Cobalt Strike Malleable C2 profiles are domain-specific language scripts that customize how Beacon communicates with the team server, defining HTTP request/response transformations, sleep intervals, jitter values, user agents, URI paths, and process injection behavior. Threat actors use malleable profiles to disguise C2 traffic as legitimate services (Amazon, Google, Slack). Analyzing these profiles reveals network indicators for detection: URI patterns, HTTP headers, POST/GET transforms, DNS settings, and process injection techniques. The `dissect.cobaltstrike` library can parse both profile files and extract configurations from beacon payloads, while `pyMalleableC2` provides AST-based parsing using Lark grammar for programmatic profile manipulation and validation.
|
||||
|
||||
|
||||
## When to Use
|
||||
|
||||
- When investigating security incidents that require analyzing cobaltstrike malleable c2 profiles
|
||||
- When building detection rules or threat hunting queries for this domain
|
||||
- When SOC analysts need structured procedures for this analysis type
|
||||
- When validating security monitoring coverage for related attack techniques
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.9+ with `dissect.cobaltstrike` and/or `pyMalleableC2`
|
||||
- Sample Malleable C2 profiles (available from public repositories)
|
||||
- Understanding of HTTP protocol and Cobalt Strike beacon communication model
|
||||
- Network monitoring tools (Suricata/Snort) for signature deployment
|
||||
- PCAP analysis tools for traffic validation
|
||||
|
||||
## Steps
|
||||
|
||||
1. Install libraries: `pip install dissect.cobaltstrike` or `pip install pyMalleableC2`
|
||||
2. Parse profile with `C2Profile.from_path("profile.profile")`
|
||||
3. Extract HTTP GET/POST block configurations (URIs, headers, parameters)
|
||||
4. Identify user agent strings and spoof targets
|
||||
5. Extract sleep time, jitter percentage, and DNS beacon settings
|
||||
6. Analyze process injection settings (spawn-to, allocation technique)
|
||||
7. Generate Suricata/Snort signatures from extracted network indicators
|
||||
8. Compare profile against known threat actor profile collections
|
||||
9. Extract staging URIs and payload delivery mechanisms
|
||||
10. Produce detection report with IOCs and recommended network signatures
|
||||
|
||||
## Expected Output
|
||||
|
||||
A JSON report containing extracted C2 URIs, HTTP headers, user agents, sleep/jitter settings, process injection config, spawned process paths, DNS settings, and generated Suricata-compatible detection rules.
|
||||
@@ -0,0 +1,95 @@
|
||||
# CobaltStrike Malleable C2 Profile Analysis API Reference
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install dissect.cobaltstrike
|
||||
pip install 'dissect.cobaltstrike[full]' # With PCAP support
|
||||
pip install pyMalleableC2 # Alternative parser
|
||||
```
|
||||
|
||||
## dissect.cobaltstrike API
|
||||
|
||||
### Parse Beacon Configuration
|
||||
```python
|
||||
from dissect.cobaltstrike.beacon import BeaconConfig
|
||||
|
||||
bconfig = BeaconConfig.from_path("beacon.bin")
|
||||
print(hex(bconfig.watermark)) # 0x5109bf6d
|
||||
print(bconfig.protocol) # https
|
||||
print(bconfig.version) # BeaconVersion(...)
|
||||
print(bconfig.settings) # Full config dict
|
||||
```
|
||||
|
||||
### Parse Malleable C2 Profile
|
||||
```python
|
||||
from dissect.cobaltstrike.c2profile import C2Profile
|
||||
|
||||
profile = C2Profile.from_path("amazon.profile")
|
||||
config = profile.as_dict()
|
||||
print(config["useragent"])
|
||||
print(config["http-get.uri"])
|
||||
print(config["sleeptime"])
|
||||
```
|
||||
|
||||
### PCAP Analysis
|
||||
```bash
|
||||
# Extract beacons from PCAP
|
||||
beacon-pcap --extract-beacons traffic.pcap
|
||||
|
||||
# Decrypt traffic with private key
|
||||
beacon-pcap -p team_server.pem traffic.pcap --beacon beacon.bin
|
||||
```
|
||||
|
||||
## pyMalleableC2 API
|
||||
|
||||
```python
|
||||
from malleableC2 import Profile
|
||||
|
||||
profile = Profile.from_file("amazon.profile")
|
||||
print(profile.sleeptime)
|
||||
print(profile.useragent)
|
||||
print(profile.http_get.uri)
|
||||
print(profile.http_post.uri)
|
||||
```
|
||||
|
||||
## Key Profile Settings
|
||||
|
||||
| Setting | Description | Detection Value |
|
||||
|---------|-------------|-----------------|
|
||||
| `sleeptime` | Callback interval (ms) | Low values = aggressive beaconing |
|
||||
| `jitter` | Sleep randomization % | Timing analysis evasion |
|
||||
| `useragent` | HTTP User-Agent string | Network signature |
|
||||
| `http-get.uri` | GET request URI path | URI-based detection |
|
||||
| `http-post.uri` | POST request URI path | URI-based detection |
|
||||
| `spawnto_x86` | 32-bit spawn process | Process creation detection |
|
||||
| `spawnto_x64` | 64-bit spawn process | Process creation detection |
|
||||
| `pipename` | Named pipe pattern | Named pipe monitoring |
|
||||
| `dns_idle` | DNS idle IP address | DNS beacon detection |
|
||||
| `watermark` | License watermark | Operator attribution |
|
||||
|
||||
## Suricata Rule Format
|
||||
|
||||
```
|
||||
alert http $HOME_NET any -> $EXTERNAL_NET any (
|
||||
msg:"MALWARE CobaltStrike C2 URI";
|
||||
flow:established,to_server;
|
||||
http.uri; content:"/api/v1/status";
|
||||
http.header; content:"User-Agent: Mozilla/5.0";
|
||||
sid:9000001; rev:1;
|
||||
)
|
||||
```
|
||||
|
||||
## CLI Usage
|
||||
|
||||
```bash
|
||||
python agent.py --input profile.profile --output report.json
|
||||
python agent.py --input parsed_config.json --output report.json
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- dissect.cobaltstrike: https://github.com/fox-it/dissect.cobaltstrike
|
||||
- pyMalleableC2: https://github.com/byt3bl33d3r/pyMalleableC2
|
||||
- Unit42 Analysis: https://unit42.paloaltonetworks.com/cobalt-strike-malleable-c2-profile/
|
||||
- Config Extractor: https://github.com/strozfriedberg/cobaltstrike-config-extractor
|
||||
@@ -0,0 +1,234 @@
|
||||
#!/usr/bin/env python3
|
||||
"""CobaltStrike Malleable C2 Profile Analyzer - parses profiles to extract C2 indicators, detection signatures, and evasion techniques"""
|
||||
# For authorized security research and defensive analysis only
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
from collections import Counter
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from dissect.cobaltstrike.c2profile import C2Profile
|
||||
HAS_DISSECT = True
|
||||
except ImportError:
|
||||
HAS_DISSECT = False
|
||||
|
||||
RUN_KEY_SUSPICIOUS = ["powershell", "cmd.exe", "mshta", "rundll32", "regsvr32", "wscript", "cscript"]
|
||||
|
||||
KNOWN_SPOOF_TARGETS = {
|
||||
"amazon": "Amazon CDN impersonation",
|
||||
"google": "Google services impersonation",
|
||||
"microsoft": "Microsoft services impersonation",
|
||||
"slack": "Slack API impersonation",
|
||||
"cloudfront": "CloudFront CDN impersonation",
|
||||
"jquery": "jQuery CDN impersonation",
|
||||
"outlook": "Outlook Web impersonation",
|
||||
"onedrive": "OneDrive impersonation",
|
||||
}
|
||||
|
||||
|
||||
def load_data(path):
|
||||
return json.loads(Path(path).read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def parse_profile_with_dissect(profile_path):
|
||||
"""Parse a .profile file using dissect.cobaltstrike C2Profile."""
|
||||
if not HAS_DISSECT:
|
||||
return None
|
||||
profile = C2Profile.from_path(profile_path)
|
||||
return profile.as_dict()
|
||||
|
||||
|
||||
def parse_profile_regex(content):
|
||||
"""Regex-based parser for malleable C2 profile when dissect is unavailable."""
|
||||
config = {}
|
||||
set_pattern = re.compile(r'set\s+(\w+)\s+"([^"]*)"', re.MULTILINE)
|
||||
for match in set_pattern.finditer(content):
|
||||
config[match.group(1)] = match.group(2)
|
||||
block_pattern = re.compile(r'(http-get|http-post|http-stager|https-certificate|dns-beacon|process-inject|post-ex)\s*\{', re.MULTILINE)
|
||||
for match in block_pattern.finditer(content):
|
||||
config.setdefault("blocks", []).append(match.group(1))
|
||||
uri_pattern = re.compile(r'set\s+uri\s+"([^"]*)"', re.MULTILINE)
|
||||
for match in uri_pattern.finditer(content):
|
||||
config.setdefault("uris", []).append(match.group(1))
|
||||
header_pattern = re.compile(r'header\s+"([^"]+)"\s+"([^"]*)"', re.MULTILINE)
|
||||
for match in header_pattern.finditer(content):
|
||||
config.setdefault("headers", []).append({"name": match.group(1), "value": match.group(2)})
|
||||
spawn_pattern = re.compile(r'set\s+spawnto_x(?:86|64)\s+"([^"]*)"', re.MULTILINE)
|
||||
for match in spawn_pattern.finditer(content):
|
||||
config.setdefault("spawn_to", []).append(match.group(1))
|
||||
return config
|
||||
|
||||
|
||||
def analyze_profile(config):
|
||||
"""Analyze parsed profile configuration for detection opportunities."""
|
||||
findings = []
|
||||
ua = config.get("useragent", config.get("user_agent", ""))
|
||||
if ua:
|
||||
findings.append({
|
||||
"type": "user_agent_identified",
|
||||
"severity": "info",
|
||||
"resource": "http-config",
|
||||
"detail": f"User-Agent: {ua[:100]}",
|
||||
"indicator": ua,
|
||||
})
|
||||
for target, desc in KNOWN_SPOOF_TARGETS.items():
|
||||
if target.lower() in ua.lower():
|
||||
findings.append({
|
||||
"type": "service_impersonation",
|
||||
"severity": "medium",
|
||||
"resource": "user-agent",
|
||||
"detail": f"{desc} detected in User-Agent string",
|
||||
})
|
||||
sleeptime = config.get("sleeptime", config.get("sleep_time", ""))
|
||||
jitter = config.get("jitter", "")
|
||||
if sleeptime:
|
||||
try:
|
||||
sleep_ms = int(sleeptime)
|
||||
if sleep_ms < 1000:
|
||||
findings.append({
|
||||
"type": "aggressive_beaconing",
|
||||
"severity": "high",
|
||||
"resource": "beacon-config",
|
||||
"detail": f"Very low sleep time: {sleep_ms}ms - aggressive C2 callback rate",
|
||||
})
|
||||
except ValueError:
|
||||
pass
|
||||
uris = config.get("uris", [])
|
||||
for uri in uris:
|
||||
findings.append({
|
||||
"type": "c2_uri",
|
||||
"severity": "high",
|
||||
"resource": "http-config",
|
||||
"detail": f"C2 URI path: {uri}",
|
||||
"indicator": uri,
|
||||
})
|
||||
headers = config.get("headers", [])
|
||||
for h in headers:
|
||||
name = h.get("name", "") if isinstance(h, dict) else str(h)
|
||||
value = h.get("value", "") if isinstance(h, dict) else ""
|
||||
if name.lower() in ("host", "cookie", "authorization"):
|
||||
findings.append({
|
||||
"type": "c2_header",
|
||||
"severity": "medium",
|
||||
"resource": "http-config",
|
||||
"detail": f"Custom header: {name}: {value[:60]}",
|
||||
})
|
||||
spawn_to = config.get("spawn_to", config.get("spawnto_x86", []))
|
||||
if isinstance(spawn_to, str):
|
||||
spawn_to = [spawn_to]
|
||||
for proc in spawn_to:
|
||||
findings.append({
|
||||
"type": "spawn_to_process",
|
||||
"severity": "high",
|
||||
"resource": "process-inject",
|
||||
"detail": f"Beacon spawns to: {proc}",
|
||||
"indicator": proc,
|
||||
})
|
||||
pipename = config.get("pipename", config.get("pipename_stager", ""))
|
||||
if pipename:
|
||||
findings.append({
|
||||
"type": "named_pipe",
|
||||
"severity": "high",
|
||||
"resource": "process-inject",
|
||||
"detail": f"Named pipe: {pipename}",
|
||||
"indicator": pipename,
|
||||
})
|
||||
dns_idle = config.get("dns_idle", "")
|
||||
if dns_idle:
|
||||
findings.append({
|
||||
"type": "dns_beacon_config",
|
||||
"severity": "medium",
|
||||
"resource": "dns-beacon",
|
||||
"detail": f"DNS idle IP: {dns_idle}",
|
||||
})
|
||||
watermark = config.get("watermark", "")
|
||||
if watermark:
|
||||
findings.append({
|
||||
"type": "watermark",
|
||||
"severity": "info",
|
||||
"resource": "beacon-config",
|
||||
"detail": f"Beacon watermark: {watermark}",
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
def generate_suricata_rules(findings, sid_start=9000001):
|
||||
"""Generate Suricata rules from extracted indicators."""
|
||||
rules = []
|
||||
sid = sid_start
|
||||
for f in findings:
|
||||
if f["type"] == "c2_uri" and f.get("indicator"):
|
||||
uri = f["indicator"].replace('"', '\\"')
|
||||
rules.append(
|
||||
f'alert http $HOME_NET any -> $EXTERNAL_NET any '
|
||||
f'(msg:"MALWARE CobaltStrike Malleable C2 URI {uri}"; '
|
||||
f'flow:established,to_server; '
|
||||
f'http.uri; content:"{uri}"; '
|
||||
f'sid:{sid}; rev:1;)'
|
||||
)
|
||||
sid += 1
|
||||
elif f["type"] == "named_pipe" and f.get("indicator"):
|
||||
pipe = f["indicator"]
|
||||
rules.append(
|
||||
f'# Named pipe detection requires endpoint monitoring: {pipe}'
|
||||
)
|
||||
return rules
|
||||
|
||||
|
||||
def analyze(data):
|
||||
if isinstance(data, str):
|
||||
config = parse_profile_regex(data)
|
||||
elif isinstance(data, dict):
|
||||
config = data
|
||||
else:
|
||||
config = data[0] if isinstance(data, list) and data else {}
|
||||
return analyze_profile(config)
|
||||
|
||||
|
||||
def generate_report(input_path):
|
||||
path = Path(input_path)
|
||||
if path.suffix in (".profile", ".txt"):
|
||||
content = path.read_text(encoding="utf-8")
|
||||
config = parse_profile_regex(content)
|
||||
findings = analyze_profile(config)
|
||||
else:
|
||||
data = load_data(input_path)
|
||||
if isinstance(data, list):
|
||||
findings = []
|
||||
for profile in data:
|
||||
findings.extend(analyze_profile(profile))
|
||||
else:
|
||||
findings = analyze_profile(data)
|
||||
sev = Counter(f["severity"] for f in findings)
|
||||
iocs = [f.get("indicator", "") for f in findings if f.get("indicator")]
|
||||
rules = generate_suricata_rules(findings)
|
||||
return {
|
||||
"report": "cobaltstrike_malleable_c2_analysis",
|
||||
"generated_at": datetime.utcnow().isoformat() + "Z",
|
||||
"total_findings": len(findings),
|
||||
"severity_summary": dict(sev),
|
||||
"extracted_iocs": iocs,
|
||||
"suricata_rules": rules,
|
||||
"findings": findings,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="CobaltStrike Malleable C2 Profile Analyzer")
|
||||
ap.add_argument("--input", required=True, help="Input .profile file or JSON with parsed config")
|
||||
ap.add_argument("--output", help="Output JSON report path")
|
||||
args = ap.parse_args()
|
||||
report = generate_report(args.input)
|
||||
out = json.dumps(report, indent=2)
|
||||
if args.output:
|
||||
Path(args.output).write_text(out, encoding="utf-8")
|
||||
print(f"Report written to {args.output}")
|
||||
else:
|
||||
print(out)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,201 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. Please do not remove or change
|
||||
the license header comment from a contributed file except when
|
||||
necessary.
|
||||
|
||||
Copyright 2026 mukul975
|
||||
|
||||
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
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,397 @@
|
||||
---
|
||||
name: analyzing-command-and-control-communication
|
||||
description: 'Analyzes malware command-and-control (C2) communication protocols to understand beacon patterns, command structures,
|
||||
data encoding, and infrastructure. Covers HTTP, HTTPS, DNS, and custom protocol C2 analysis for detection development and
|
||||
threat intelligence. Activates for requests involving C2 analysis, beacon detection, C2 protocol reverse engineering, or
|
||||
command-and-control infrastructure mapping.
|
||||
|
||||
'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- C2
|
||||
- command-and-control
|
||||
- beacon
|
||||
- protocol-analysis
|
||||
version: 1.0.0
|
||||
author: mahipal
|
||||
license: Apache-2.0
|
||||
nist_csf:
|
||||
- DE.AE-02
|
||||
- RS.AN-03
|
||||
- ID.RA-01
|
||||
- DE.CM-01
|
||||
---
|
||||
|
||||
# Analyzing Command-and-Control Communication
|
||||
|
||||
## When to Use
|
||||
|
||||
- Reverse engineering a malware sample has revealed network communication that needs protocol analysis
|
||||
- Building network-level detection signatures for a specific C2 framework (Cobalt Strike, Metasploit, Sliver)
|
||||
- Mapping C2 infrastructure including primary servers, fallback domains, and dead drops
|
||||
- Analyzing encrypted or encoded C2 traffic to understand the command set and data format
|
||||
- Attributing malware to a threat actor based on C2 infrastructure patterns and tooling
|
||||
|
||||
**Do not use** for general network anomaly detection; this is specifically for understanding known or suspected C2 protocols from malware analysis.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- PCAP capture of malware network traffic (from sandbox, network tap, or full packet capture)
|
||||
- Wireshark/tshark for packet-level analysis
|
||||
- Reverse engineering tools (Ghidra, dnSpy) for understanding C2 code in the malware binary
|
||||
- Python 3.8+ with `scapy`, `dpkt`, and `requests` for protocol analysis and replay
|
||||
- Threat intelligence databases for C2 infrastructure correlation (VirusTotal, Shodan, Censys)
|
||||
- JA3/JA3S fingerprint databases for TLS-based C2 identification
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Identify the C2 Channel
|
||||
|
||||
Determine the protocol and transport used for C2 communication:
|
||||
|
||||
```
|
||||
C2 Communication Channels:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
HTTP/HTTPS: Most common; uses standard web traffic to blend in
|
||||
Indicators: Regular POST/GET requests, specific URI patterns, custom headers
|
||||
|
||||
DNS: Tunneling data through DNS queries and responses
|
||||
Indicators: High-volume TXT queries, long subdomain names, high entropy
|
||||
|
||||
Custom TCP/UDP: Proprietary binary protocol on non-standard port
|
||||
Indicators: Non-HTTP traffic on high ports, unknown protocol
|
||||
|
||||
ICMP: Data encoded in ICMP echo/reply payloads
|
||||
Indicators: ICMP packets with large or non-standard payloads
|
||||
|
||||
WebSocket: Persistent bidirectional connection for real-time C2
|
||||
Indicators: WebSocket upgrade followed by binary frames
|
||||
|
||||
Cloud Services: Using legitimate APIs (Telegram, Discord, Slack, GitHub)
|
||||
Indicators: API calls to cloud services from unexpected processes
|
||||
|
||||
Email: SMTP/IMAP for C2 commands and data exfiltration
|
||||
Indicators: Automated email operations from non-email processes
|
||||
```
|
||||
|
||||
### Step 2: Analyze Beacon Pattern
|
||||
|
||||
Characterize the periodic communication pattern:
|
||||
|
||||
```python
|
||||
from scapy.all import rdpcap, IP, TCP
|
||||
from collections import defaultdict
|
||||
import statistics
|
||||
import json
|
||||
|
||||
packets = rdpcap("c2_traffic.pcap")
|
||||
|
||||
# Group TCP SYN packets by destination
|
||||
connections = defaultdict(list)
|
||||
for pkt in packets:
|
||||
if IP in pkt and TCP in pkt and (pkt[TCP].flags & 0x02):
|
||||
key = f"{pkt[IP].dst}:{pkt[TCP].dport}"
|
||||
connections[key].append(float(pkt.time))
|
||||
|
||||
# Analyze each destination for beaconing
|
||||
for dst, times in sorted(connections.items()):
|
||||
if len(times) < 3:
|
||||
continue
|
||||
|
||||
intervals = [times[i+1] - times[i] for i in range(len(times)-1)]
|
||||
avg_interval = statistics.mean(intervals)
|
||||
stdev = statistics.stdev(intervals) if len(intervals) > 1 else 0
|
||||
jitter_pct = (stdev / avg_interval * 100) if avg_interval > 0 else 0
|
||||
duration = times[-1] - times[0]
|
||||
|
||||
beacon_data = {
|
||||
"destination": dst,
|
||||
"connections": len(times),
|
||||
"duration_seconds": round(duration, 1),
|
||||
"avg_interval_seconds": round(avg_interval, 1),
|
||||
"stdev_seconds": round(stdev, 1),
|
||||
"jitter_percent": round(jitter_pct, 1),
|
||||
"is_beacon": 5 < avg_interval < 7200 and jitter_pct < 25,
|
||||
}
|
||||
|
||||
if beacon_data["is_beacon"]:
|
||||
print(f"[!] BEACON DETECTED: {dst}")
|
||||
print(f" Interval: {avg_interval:.0f}s +/- {stdev:.0f}s ({jitter_pct:.0f}% jitter)")
|
||||
print(f" Sessions: {len(times)} over {duration:.0f}s")
|
||||
```
|
||||
|
||||
### Step 3: Decode C2 Protocol Structure
|
||||
|
||||
Reverse engineer the message format from captured traffic:
|
||||
|
||||
```python
|
||||
# HTTP-based C2 protocol analysis
|
||||
import dpkt
|
||||
import base64
|
||||
|
||||
with open("c2_traffic.pcap", "rb") as f:
|
||||
pcap = dpkt.pcap.Reader(f)
|
||||
|
||||
for ts, buf in pcap:
|
||||
eth = dpkt.ethernet.Ethernet(buf)
|
||||
if not isinstance(eth.data, dpkt.ip.IP):
|
||||
continue
|
||||
ip = eth.data
|
||||
if not isinstance(ip.data, dpkt.tcp.TCP):
|
||||
continue
|
||||
tcp = ip.data
|
||||
|
||||
if tcp.dport == 80 or tcp.dport == 443:
|
||||
if len(tcp.data) > 0:
|
||||
try:
|
||||
http = dpkt.http.Request(tcp.data)
|
||||
print(f"\n--- C2 REQUEST ---")
|
||||
print(f"Method: {http.method}")
|
||||
print(f"URI: {http.uri}")
|
||||
print(f"Headers: {dict(http.headers)}")
|
||||
if http.body:
|
||||
print(f"Body ({len(http.body)} bytes):")
|
||||
# Try Base64 decode
|
||||
try:
|
||||
decoded = base64.b64decode(http.body)
|
||||
print(f" Decoded: {decoded[:200]}")
|
||||
except:
|
||||
print(f" Raw: {http.body[:200]}")
|
||||
except:
|
||||
pass
|
||||
```
|
||||
|
||||
### Step 4: Identify C2 Framework
|
||||
|
||||
Match observed patterns to known C2 frameworks:
|
||||
|
||||
```
|
||||
Known C2 Framework Signatures:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Cobalt Strike:
|
||||
- Default URIs: /pixel, /submit.php, /___utm.gif, /ca, /dpixel
|
||||
- Malleable C2 profiles customize all traffic characteristics
|
||||
- JA3: varies by profile, catalog at ja3er.com
|
||||
- Watermark in beacon config (unique per license)
|
||||
- Config extraction: use CobaltStrikeParser or 1768.py
|
||||
|
||||
Metasploit/Meterpreter:
|
||||
- Default staging URI patterns: random 4-char checksum
|
||||
- Reverse HTTP(S) handler patterns
|
||||
- Meterpreter TLV (Type-Length-Value) protocol structure
|
||||
|
||||
Sliver:
|
||||
- mTLS, HTTP, DNS, WireGuard transport options
|
||||
- Protobuf-encoded messages
|
||||
- Unique implant ID in communication
|
||||
|
||||
Covenant:
|
||||
- .NET-based C2 framework
|
||||
- HTTP with customizable profiles
|
||||
- Task-based command execution
|
||||
|
||||
PoshC2:
|
||||
- PowerShell/C# based
|
||||
- HTTP with encrypted payloads
|
||||
- Cookie-based session management
|
||||
```
|
||||
|
||||
```bash
|
||||
# Extract Cobalt Strike beacon configuration from PCAP or sample
|
||||
python3 << 'PYEOF'
|
||||
# Using CobaltStrikeParser (pip install cobalt-strike-parser)
|
||||
from cobalt_strike_parser import BeaconConfig
|
||||
|
||||
try:
|
||||
config = BeaconConfig.from_file("suspect.exe")
|
||||
print("Cobalt Strike Beacon Configuration:")
|
||||
for key, value in config.items():
|
||||
print(f" {key}: {value}")
|
||||
except Exception as e:
|
||||
print(f"Not a Cobalt Strike beacon or parse error: {e}")
|
||||
PYEOF
|
||||
```
|
||||
|
||||
### Step 5: Map C2 Infrastructure
|
||||
|
||||
Document the full C2 infrastructure and failover mechanisms:
|
||||
|
||||
```python
|
||||
# Infrastructure mapping
|
||||
import requests
|
||||
import json
|
||||
|
||||
c2_indicators = {
|
||||
"primary_c2": "185.220.101.42",
|
||||
"domains": ["update.malicious.com", "backup.evil.net"],
|
||||
"ports": [443, 8443],
|
||||
"failover_dns": ["ns1.malicious-dns.com"],
|
||||
}
|
||||
|
||||
# Enrich with Shodan
|
||||
def shodan_lookup(ip, api_key):
|
||||
resp = requests.get(f"https://api.shodan.io/shodan/host/{ip}?key={api_key}")
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
return {
|
||||
"ip": ip,
|
||||
"ports": data.get("ports", []),
|
||||
"os": data.get("os"),
|
||||
"org": data.get("org"),
|
||||
"asn": data.get("asn"),
|
||||
"country": data.get("country_code"),
|
||||
"hostnames": data.get("hostnames", []),
|
||||
"last_update": data.get("last_update"),
|
||||
}
|
||||
return None
|
||||
|
||||
# Enrich with passive DNS
|
||||
def pdns_lookup(domain):
|
||||
# Using VirusTotal passive DNS
|
||||
resp = requests.get(
|
||||
f"https://www.virustotal.com/api/v3/domains/{domain}/resolutions",
|
||||
headers={"x-apikey": VT_API_KEY}
|
||||
)
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
resolutions = []
|
||||
for r in data.get("data", []):
|
||||
resolutions.append({
|
||||
"ip": r["attributes"]["ip_address"],
|
||||
"date": r["attributes"]["date"],
|
||||
})
|
||||
return resolutions
|
||||
return []
|
||||
```
|
||||
|
||||
### Step 6: Create Network Detection Signatures
|
||||
|
||||
Build detection rules based on analyzed C2 characteristics:
|
||||
|
||||
```bash
|
||||
# Suricata rules for the analyzed C2
|
||||
cat << 'EOF' > c2_detection.rules
|
||||
# HTTP beacon pattern
|
||||
alert http $HOME_NET any -> $EXTERNAL_NET any (
|
||||
msg:"MALWARE MalwareX C2 HTTP Beacon";
|
||||
flow:established,to_server;
|
||||
http.method; content:"POST";
|
||||
http.uri; content:"/gate.php"; startswith;
|
||||
http.header; content:"User-Agent: Mozilla/5.0 (compatible; MSIE 10.0)";
|
||||
threshold:type threshold, track by_src, count 5, seconds 600;
|
||||
sid:9000010; rev:1;
|
||||
)
|
||||
|
||||
# JA3 fingerprint match
|
||||
alert tls $HOME_NET any -> $EXTERNAL_NET any (
|
||||
msg:"MALWARE MalwareX TLS JA3 Fingerprint";
|
||||
ja3.hash; content:"a0e9f5d64349fb13191bc781f81f42e1";
|
||||
sid:9000011; rev:1;
|
||||
)
|
||||
|
||||
# DNS beacon detection (high-entropy subdomain)
|
||||
alert dns $HOME_NET any -> any any (
|
||||
msg:"MALWARE Suspected DNS C2 Tunneling";
|
||||
dns.query; pcre:"/^[a-z0-9]{20,}\./";
|
||||
threshold:type threshold, track by_src, count 10, seconds 60;
|
||||
sid:9000012; rev:1;
|
||||
)
|
||||
|
||||
# Certificate-based detection
|
||||
alert tls $HOME_NET any -> $EXTERNAL_NET any (
|
||||
msg:"MALWARE MalwareX Self-Signed C2 Certificate";
|
||||
tls.cert_subject; content:"CN=update.malicious.com";
|
||||
sid:9000013; rev:1;
|
||||
)
|
||||
EOF
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **Beaconing** | Periodic check-in communication from malware to C2 server at regular intervals, often with jitter to avoid pattern detection |
|
||||
| **Jitter** | Randomization applied to beacon interval (e.g., 60s +/- 15%) to make the timing pattern less predictable and harder to detect |
|
||||
| **Malleable C2** | Cobalt Strike feature allowing operators to customize all aspects of C2 traffic (URIs, headers, encoding) to mimic legitimate services |
|
||||
| **Dead Drop** | Intermediate location (paste site, cloud storage, social media) where C2 commands are posted for the malware to retrieve |
|
||||
| **Domain Fronting** | Using a trusted CDN domain in the TLS SNI while routing to a different backend, making C2 traffic appear to go to a legitimate service |
|
||||
| **Fast Flux** | Rapidly changing DNS records for C2 domains to distribute across many IPs and resist takedown efforts |
|
||||
| **C2 Framework** | Software toolkit providing C2 server, implant generator, and operator interface (Cobalt Strike, Metasploit, Sliver, Covenant) |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **Wireshark**: Packet analyzer for detailed C2 protocol analysis at the packet level
|
||||
- **RITA (Real Intelligence Threat Analytics)**: Open-source tool analyzing Zeek logs for beacon detection and DNS tunneling
|
||||
- **CobaltStrikeParser**: Tool extracting Cobalt Strike beacon configuration from samples and memory dumps
|
||||
- **JA3/JA3S**: TLS fingerprinting method for identifying C2 frameworks by their TLS implementation characteristics
|
||||
- **Shodan/Censys**: Internet scanning platforms for mapping C2 infrastructure and identifying related servers
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Reverse Engineering a Custom C2 Protocol
|
||||
|
||||
**Context**: A malware sample communicates with its C2 server using an unknown binary protocol over TCP port 8443. The protocol needs to be decoded to understand the command set and build detection signatures.
|
||||
|
||||
**Approach**:
|
||||
1. Filter PCAP for TCP port 8443 conversations and extract the TCP streams
|
||||
2. Analyze the first few exchanges to identify the handshake/authentication mechanism
|
||||
3. Map the message structure (length prefix, type field, payload encoding)
|
||||
4. Cross-reference with Ghidra disassembly of the send/receive functions in the malware
|
||||
5. Identify the command dispatcher and document each command code's function
|
||||
6. Build a protocol decoder in Python for ongoing traffic analysis
|
||||
7. Create Suricata rules matching the protocol handshake or static header bytes
|
||||
|
||||
**Pitfalls**:
|
||||
- Assuming the protocol is static; some C2 frameworks negotiate encryption during the handshake
|
||||
- Not capturing enough traffic to see all command types (some commands are rare)
|
||||
- Missing fallback C2 channels (DNS, ICMP) that activate when the primary channel fails
|
||||
- Confusing encrypted payload data with the protocol framing structure
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
C2 COMMUNICATION ANALYSIS REPORT
|
||||
===================================
|
||||
Sample: malware.exe (SHA-256: e3b0c44...)
|
||||
C2 Framework: Cobalt Strike 4.9
|
||||
|
||||
BEACON CONFIGURATION
|
||||
C2 Server: hxxps://185.220.101[.]42/updates
|
||||
Beacon Type: HTTPS (reverse)
|
||||
Sleep: 60 seconds
|
||||
Jitter: 15%
|
||||
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
|
||||
URI (GET): /dpixel
|
||||
URI (POST): /submit.php
|
||||
Watermark: 1234567890
|
||||
|
||||
PROTOCOL ANALYSIS
|
||||
Transport: HTTPS (TLS 1.2)
|
||||
JA3 Hash: a0e9f5d64349fb13191bc781f81f42e1
|
||||
Certificate: CN=Microsoft Update (self-signed)
|
||||
Encoding: Base64 with XOR key 0x69
|
||||
Command Format: [4B length][4B command_id][payload]
|
||||
|
||||
COMMAND SET
|
||||
0x01 - Sleep Change beacon interval
|
||||
0x02 - Shell Execute cmd.exe command
|
||||
0x03 - Download Transfer file from C2
|
||||
0x04 - Upload Exfiltrate file to C2
|
||||
0x05 - Inject Process injection
|
||||
0x06 - Keylog Start keylogger
|
||||
0x07 - Screenshot Capture screen
|
||||
|
||||
INFRASTRUCTURE
|
||||
Primary: 185.220.101[.]42 (AS12345, Hosting Co, NL)
|
||||
Failover: 91.215.85[.]17 (AS67890, VPS Provider, RU)
|
||||
DNS: update.malicious[.]com -> 185.220.101[.]42
|
||||
Registrar: NameCheap
|
||||
Registration: 2025-09-01
|
||||
|
||||
DETECTION SIGNATURES
|
||||
SID 9000010: HTTP beacon pattern
|
||||
SID 9000011: JA3 TLS fingerprint
|
||||
SID 9000013: C2 certificate match
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user