Production hardening: security fixes, code quality, 724 skills complete

- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
This commit is contained in:
mukul975
2026-03-19 13:26:49 +01:00
parent 63b442d347
commit c47eed6a64
900 changed files with 23085 additions and 2720 deletions
@@ -1,17 +1,19 @@
#!/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 sys
import datetime
import json
def run_cmd(cmd, capture=True):
"""Execute a shell command and return output."""
result = subprocess.run(cmd, shell=True, capture_output=capture, text=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
@@ -65,16 +67,22 @@ def compute_hash(path, algorithm="sha256", block_size=65536):
def acquire_with_dd(source, destination, block_size=4096, log_file=None):
"""Acquire a forensic image using dd with error handling."""
cmd = (
f"dd if={source} of={destination} bs={block_size} "
f"conv=noerror,sync status=progress"
)
if log_file:
cmd += f" 2>&1 | tee {log_file}"
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()
_, stderr, rc = run_cmd(cmd, capture=False)
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
@@ -83,18 +91,21 @@ def acquire_with_dd(source, destination, block_size=4096, log_file=None):
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 = f"dcfldd if={source} of={destination} bs={block_size} conv=noerror,sync"
cmd += f" hash={hash_alg}"
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 += f" hashlog={hash_log}"
cmd += " hashwindow=1G"
cmd.append(f"hashlog={hash_log}")
if error_log:
cmd += f" errlog={error_log}"
cmd.append(f"errlog={error_log}")
if split_size:
cmd += f" split={split_size} splitformat=aa"
cmd.extend([f"split={split_size}", "splitformat=aa"])
print(f"[*] Starting dcfldd acquisition: {source} -> {destination}")
start = datetime.datetime.utcnow()
_, stderr, rc = run_cmd(cmd, capture=False)
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
@@ -9,6 +9,9 @@ author: mahipal
license: Apache-2.0
---
# 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.
@@ -4,11 +4,8 @@
import argparse
import json
import struct
import sys
from collections import defaultdict
from ldap3 import Server, Connection, ALL, NTLM, SUBTREE
from ldap3.protocol.formatters.formatters import format_sid
DANGEROUS_MASKS = {
@@ -1,15 +1,12 @@
#!/usr/bin/env python3
"""Agent for analyzing API Gateway access logs for security threats."""
import os
import re
import json
import argparse
from datetime import datetime
from collections import defaultdict
import pandas as pd
import numpy as np
def load_api_logs(log_path):
@@ -36,7 +36,7 @@ ATT&CK catalogs over 140 threat groups with documented technique usage. Each gro
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.
## Practical Steps
## Workflow
### Step 1: Query ATT&CK Data for APT Group
@@ -8,7 +8,6 @@ performs detection gap analysis, and generates threat-informed reports.
import json
import os
import sys
import hashlib
from collections import Counter
try:
@@ -112,8 +112,11 @@ def analyze_boot_code(mbr_data):
def run_volatility_rootkit_scan(memory_dump, plugin):
"""Run a Volatility 3 plugin for rootkit detection via subprocess."""
cmd = f"vol3 -f {memory_dump} {plugin}"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
result = subprocess.run(
["vol3", "-f", memory_dump, plugin],
capture_output=True, text=True,
timeout=120,
)
return result.stdout, result.stderr, result.returncode
@@ -10,8 +10,6 @@ import sys
import json
import sqlite3
import datetime
import hashlib
from collections import defaultdict
def chrome_time_to_datetime(chrome_time):
@@ -40,7 +40,7 @@ Campaign attribution analysis involves systematically evaluating evidence to det
### 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.
## Practical Steps
## Workflow
### Step 1: Collect Attribution Evidence
@@ -6,9 +6,6 @@ malware code similarity, timing patterns, and language artifacts.
"""
import json
import os
import sys
import hashlib
import re
from collections import defaultdict
from datetime import datetime
@@ -36,7 +36,7 @@ Attackers register lookalike domains and obtain free certificates (often from Le
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.
## Practical Steps
## Workflow
### Step 1: Query crt.sh for Certificate History
@@ -6,10 +6,7 @@ certificates, and identifies potential phishing infrastructure.
"""
import json
import os
import sys
import re
from datetime import datetime
from collections import defaultdict
try:
@@ -13,6 +13,9 @@ author: mahipal
license: Apache-2.0
---
# Analyzing Cloud Storage Access Patterns
## Instructions
1. Install dependencies: `pip install boto3 requests`
@@ -21,7 +21,7 @@ def query_cloudtrail_s3_events(bucket_name, hours_back=24):
"--start-time", start_time,
"--output", "json",
]
result = subprocess.run(cmd, capture_output=True, text=True)
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 []
@@ -37,7 +37,7 @@ The beacon configuration encodes the malleable C2 profile that dictates HTTP req
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.
## Practical Steps
## Workflow
### Step 1: Extract Configuration with CobaltStrikeParser
@@ -8,9 +8,7 @@ communication settings, malleable C2 profile details, and watermark values.
import struct
import os
import sys
import json
import hashlib
import re
from collections import OrderedDict
# Cobalt Strike beacon configuration field IDs (Type-Length-Value format)
@@ -5,7 +5,6 @@
import argparse
import json
import re
import sys
from collections import Counter
from datetime import datetime
from pathlib import Path
@@ -3,13 +3,12 @@
import statistics
import base64
import json
import os
import sys
from collections import defaultdict
try:
from scapy.all import rdpcap, IP, TCP, UDP, DNS, DNSQR, Raw
from scapy.all import rdpcap, IP, TCP, DNS, DNSQR
HAS_SCAPY = True
except ImportError:
HAS_SCAPY = False
@@ -1,9 +1,6 @@
#!/usr/bin/env python3
"""Cyber Kill Chain analysis agent for mapping incidents to Lockheed Martin kill chain phases."""
import json
import os
import sys
import datetime
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
"""Forensic disk image analysis agent using The Sleuth Kit (TSK) command-line tools."""
import shlex
import subprocess
import os
import sys
@@ -10,8 +11,10 @@ import datetime
def run_cmd(cmd):
"""Execute a shell command and return output."""
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
"""Execute a command and return output."""
if isinstance(cmd, str):
cmd = shlex.split(cmd)
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
return result.stdout.strip(), result.stderr.strip(), result.returncode
@@ -93,9 +96,15 @@ def list_deleted_files(image_path, offset):
def recover_file(image_path, offset, inode, output_path):
"""Recover a file by inode using icat."""
cmd = f"icat -o {offset} {image_path} {inode} > {output_path}"
_, _, rc = run_cmd(cmd)
return rc == 0
result = subprocess.run(
["icat", "-o", str(offset), image_path, str(inode)],
capture_output=True,
timeout=120,
)
if result.returncode == 0:
with open(output_path, "wb") as f:
f.write(result.stdout)
return result.returncode == 0
def get_file_metadata(image_path, offset, inode):
@@ -106,26 +115,40 @@ def get_file_metadata(image_path, offset, inode):
def create_bodyfile(image_path, offset, output_path):
"""Generate a TSK bodyfile for timeline creation."""
cmd = f'fls -r -m "/" -o {offset} {image_path} > {output_path}'
_, _, rc = run_cmd(cmd)
return rc == 0
result = subprocess.run(
["fls", "-r", "-m", "/", "-o", str(offset), image_path],
capture_output=True, text=True,
timeout=120,
)
if result.returncode == 0:
with open(output_path, "w") as f:
f.write(result.stdout)
return result.returncode == 0
def generate_timeline(bodyfile_path, output_csv, start_date=None, end_date=None):
"""Generate a timeline from a bodyfile using mactime."""
cmd = f"mactime -b {bodyfile_path} -d"
cmd = ["mactime", "-b", bodyfile_path, "-d"]
if start_date and end_date:
cmd += f" {start_date}..{end_date}"
cmd += f" > {output_csv}"
_, _, rc = run_cmd(cmd)
return rc == 0
cmd.append(f"{start_date}..{end_date}")
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode == 0:
with open(output_csv, "w") as f:
f.write(result.stdout)
return result.returncode == 0
def search_keywords(image_path, offset, keyword):
"""Search for keyword strings in the disk image."""
cmd = f'srch_strings -a -o {offset} {image_path} | grep -i "{keyword}"'
stdout, _, rc = run_cmd(cmd)
return stdout.splitlines() if rc == 0 else []
result = subprocess.run(
["srch_strings", "-a", "-o", str(offset), image_path],
capture_output=True, text=True,
timeout=120,
)
if result.returncode != 0 or not result.stdout:
return []
keyword_lower = keyword.lower()
return [line for line in result.stdout.splitlines() if keyword_lower in line.lower()]
def find_file_signature(image_path, offset, hex_signature):
@@ -179,7 +202,8 @@ if __name__ == "__main__":
if len(sys.argv) > 1:
image = sys.argv[1]
case = sys.argv[2] if len(sys.argv) > 2 else "/tmp/autopsy_case"
import tempfile
case = sys.argv[2] if len(sys.argv) > 2 else os.environ.get("AUTOPSY_CASE_DIR", os.path.join(tempfile.gettempdir(), "autopsy_case"))
if os.path.exists(image):
analyze_image(image, case)
else:
@@ -2,11 +2,6 @@
"""DNS exfiltration detection agent using entropy analysis and query pattern detection."""
import math
import os
import sys
import json
import csv
import datetime
from collections import Counter, defaultdict
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
"""Docker container forensics agent for investigating compromised containers."""
import shlex
import subprocess
import json
import os
@@ -10,8 +11,10 @@ import datetime
def run_cmd(cmd):
"""Execute a shell command and return output."""
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
"""Execute a command and return output."""
if isinstance(cmd, str):
cmd = shlex.split(cmd)
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
return result.stdout.strip(), result.stderr.strip(), result.returncode
@@ -134,9 +137,13 @@ def detect_suspicious_files(changes):
def export_container(container_id, output_path):
"""Export container filesystem as a tarball for offline analysis."""
cmd = f"docker export {container_id} > {output_path}"
_, _, rc = run_cmd(cmd)
if rc == 0 and os.path.exists(output_path):
with open(output_path, "wb") as out_f:
result = subprocess.run(
["docker", "export", container_id],
stdout=out_f, stderr=subprocess.PIPE,
timeout=120,
)
if result.returncode == 0 and os.path.exists(output_path):
sha256 = hashlib.sha256()
with open(output_path, "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
@@ -8,7 +8,6 @@ import hashlib
import os
import sys
import subprocess
import json
from email import policy
@@ -147,9 +146,10 @@ def extract_attachments(msg, output_dir=None):
def dns_lookup(domain, record_type="TXT"):
"""Perform DNS lookup for SPF/DKIM/DMARC records."""
cmd = f"dig {record_type} {domain} +short"
stdout, _, rc = subprocess.run(cmd, shell=True, capture_output=True, text=True,
timeout=10).stdout, "", 0
stdout, _, rc = subprocess.run(
["dig", record_type, domain, "+short"],
capture_output=True, text=True, timeout=10
).stdout, "", 0
return stdout.strip() if stdout else ""
@@ -5,7 +5,6 @@ import json
import argparse
import logging
import subprocess
import os
from collections import defaultdict
from datetime import datetime
@@ -37,7 +37,7 @@ Despite stripping symbol tables, Go binaries retain function names within the pc
Go's dependency management embeds module paths and version strings in the binary. Extracting these reveals the malware's third-party dependencies (HTTP libraries, encryption packages, C2 frameworks), which provides insight into capabilities without full reverse engineering.
## Practical Steps
## Workflow
### Step 1: Initial Binary Analysis
@@ -5,7 +5,6 @@ Analyzes Go binaries to extract function names, strings, build metadata,
package information, and detects common Go malware characteristics.
"""
import struct
import os
import sys
import json
@@ -3,9 +3,7 @@
import re
import os
import sys
import json
import hashlib
import datetime
try:
@@ -69,7 +67,7 @@ def is_private_ip(ip):
def query_virustotal_hash(sha256, api_key):
"""Query VirusTotal for a file hash."""
url = f"https://www.virustotal.com/api/v3/files/{sha256}"
resp = requests.get(url, headers={"x-apikey": api_key})
resp = requests.get(url, headers={"x-apikey": api_key}, timeout=30)
if resp.status_code == 200:
data = resp.json().get("data", {}).get("attributes", {})
stats = data.get("last_analysis_stats", {})
@@ -88,7 +86,7 @@ def query_virustotal_hash(sha256, api_key):
def query_virustotal_domain(domain, api_key):
"""Query VirusTotal for domain reputation."""
url = f"https://www.virustotal.com/api/v3/domains/{domain}"
resp = requests.get(url, headers={"x-apikey": api_key})
resp = requests.get(url, headers={"x-apikey": api_key}, timeout=30)
if resp.status_code == 200:
data = resp.json().get("data", {}).get("attributes", {})
stats = data.get("last_analysis_stats", {})
@@ -107,7 +105,7 @@ def query_abuseipdb(ip, api_key, max_age_days=90):
"""Query AbuseIPDB for IP reputation."""
url = "https://api.abuseipdb.com/api/v2/check"
resp = requests.get(url, headers={"Key": api_key, "Accept": "application/json"},
params={"ipAddress": ip, "maxAgeInDays": max_age_days})
params={"ipAddress": ip, "maxAgeInDays": max_age_days}, timeout=30)
if resp.status_code == 200:
data = resp.json().get("data", {})
return {
@@ -125,7 +123,7 @@ def query_abuseipdb(ip, api_key, max_age_days=90):
def query_malwarebazaar(sha256):
"""Query MalwareBazaar for file hash information."""
url = "https://mb-api.abuse.ch/api/v1/"
resp = requests.post(url, data={"query": "get_info", "hash": sha256})
resp = requests.post(url, data={"query": "get_info", "hash": sha256}, timeout=30)
if resp.status_code == 200:
result = resp.json()
if result.get("query_status") == "ok" and result.get("data"):
@@ -7,9 +7,7 @@ keychain dumping, filesystem inspection, and jailbreak detection bypass.
import subprocess
import json
import os
import sys
import re
def run_objection(command, app_id=None, timeout=30):
@@ -1,7 +1,6 @@
#!/usr/bin/env python3
"""Agent for analyzing Kubernetes audit logs for security threats."""
import os
import json
import argparse
from collections import defaultdict
@@ -7,7 +7,6 @@ unauthorized file access, suspicious syscalls, and process execution anomalies.
import argparse
import json
import os
import re
import sys
import datetime
@@ -6,12 +6,10 @@ import math
import os
import sys
import subprocess
import struct
from collections import Counter
try:
from elftools.elf.elffile import ELFFile
from elftools.elf.sections import SymbolTableSection
HAS_ELFTOOLS = True
except ImportError:
HAS_ELFTOOLS = False
@@ -85,9 +83,9 @@ def analyze_sections(filepath):
def extract_strings(filepath, min_length=6):
"""Extract ASCII strings from the binary and categorize by type."""
stdout, _, rc = subprocess.run(
f"strings -n {min_length} {filepath}", shell=True,
["strings", "-n", str(min_length), filepath],
capture_output=True, text=True
).stdout, "", 0
, timeout=120).stdout, "", 0
if not stdout:
return {}
all_strings = stdout.strip().splitlines()
@@ -126,8 +124,9 @@ def check_packing(filepath):
indicators.append("UPX packer detected (UPX! magic)")
if b"UPX0" in data or b"UPX1" in data:
indicators.append("UPX section names found")
stdout, _, _ = subprocess.run(f"upx -t {filepath} 2>&1", shell=True,
capture_output=True, text=True).stdout, "", 0
stdout, _, _ = subprocess.run(["upx", "-t", filepath],
capture_output=True, text=True,
stderr=subprocess.STDOUT, timeout=120).stdout, "", 0
if stdout and "packed" in stdout.lower():
indicators.append("UPX verification confirms packing")
return indicators
@@ -135,8 +134,8 @@ def check_packing(filepath):
def analyze_dynamic_linking(filepath):
"""Analyze dynamic linking information and imported functions."""
stdout, _, rc = subprocess.run(f"readelf -d {filepath}", shell=True,
capture_output=True, text=True).stdout, "", 0
stdout, _, rc = subprocess.run(["readelf", "-d", filepath],
capture_output=True, text=True, timeout=120).stdout, "", 0
dynamic_info = {"libraries": [], "rpath": None}
if stdout:
for line in stdout.splitlines():
@@ -146,10 +145,17 @@ def analyze_dynamic_linking(filepath):
if "RPATH" in line or "RUNPATH" in line:
dynamic_info["rpath"] = line.split("[")[-1].rstrip("]")
stdout2, _, _ = subprocess.run(
f"readelf -r {filepath} | grep -E 'socket|connect|exec|fork|open|write|bind|listen|send|recv'",
shell=True, capture_output=True, text=True
).stdout, "", 0
readelf_proc = subprocess.run(
["readelf", "-r", filepath],
capture_output=True, text=True,
timeout=120,
)
import re as _re
suspicious_funcs = _re.compile(r'socket|connect|exec|fork|open|write|bind|listen|send|recv')
stdout2 = "\n".join(
line for line in (readelf_proc.stdout or "").splitlines()
if suspicious_funcs.search(line)
)
dynamic_info["suspicious_imports"] = [
line.strip() for line in (stdout2 or "").splitlines() if line.strip()
]
@@ -6,7 +6,6 @@ import argparse
import logging
import subprocess
import os
from collections import defaultdict
from datetime import datetime
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
@@ -4,15 +4,15 @@
import os
import sys
import glob
import json
import re
import datetime
import shlex
import subprocess
def run_cmd(cmd):
"""Execute a shell command and return output."""
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
"""Execute a command and return output."""
if isinstance(cmd, str):
cmd = shlex.split(cmd)
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
return result.stdout.strip(), result.stderr.strip(), result.returncode
@@ -196,10 +196,12 @@ def check_ld_preload(evidence_root):
def find_suid_binaries(evidence_root):
"""Find SUID/SGID binaries (potential privilege escalation)."""
stdout, _, rc = run_cmd(
f"find {evidence_root} -perm -4000 -type f 2>/dev/null"
result = subprocess.run(
["find", evidence_root, "-perm", "-4000", "-type", "f"],
capture_output=True, text=True, timeout=30
)
return stdout.splitlines() if rc == 0 and stdout else []
stdout = result.stdout.strip()
return stdout.splitlines() if result.returncode == 0 and stdout else []
def find_suspicious_tmp_files(evidence_root):
@@ -5,12 +5,11 @@ import re
import os
import sys
import hashlib
import subprocess
import json
import zipfile
try:
from oletools.olevba import VBA_Parser, TYPE_OLE, TYPE_OpenXML
from oletools.olevba import VBA_Parser
from oletools import oleid
HAS_OLETOOLS = True
except ImportError:
@@ -37,9 +37,9 @@ def run_pdfid(filepath):
"""Run pdfid.py to triage PDF for suspicious keywords."""
cmd = ["python3", "-m", "pdfid", filepath]
alt_cmd = ["pdfid.py", filepath]
result = subprocess.run(cmd, capture_output=True, text=True)
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode != 0:
result = subprocess.run(alt_cmd, capture_output=True, text=True)
result = subprocess.run(alt_cmd, capture_output=True, text=True, timeout=120)
keywords = {}
for line in result.stdout.strip().split("\n"):
line = line.strip()
@@ -59,9 +59,9 @@ def run_peepdf_analysis(filepath):
"""Run peepdf for detailed PDF object analysis."""
cmd = ["peepdf", "-f", "-l", filepath]
alt_cmd = ["python3", "-m", "peepdf", "-f", "-l", filepath]
result = subprocess.run(cmd, capture_output=True, text=True)
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode != 0:
result = subprocess.run(alt_cmd, capture_output=True, text=True)
result = subprocess.run(alt_cmd, capture_output=True, text=True, timeout=120)
analysis = {
"versions": 0,
"objects": 0,
@@ -98,7 +98,7 @@ def run_pdf_parser(filepath, object_id=None):
cmd = ["pdf-parser.py", "-o", str(object_id), "-f", "-d", filepath]
else:
cmd = ["pdf-parser.py", "--stats", filepath]
result = subprocess.run(cmd, capture_output=True, text=True)
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
return result.stdout[:3000]
@@ -107,7 +107,7 @@ def extract_javascript(filepath, peepdf_analysis):
js_content = []
for obj_id in peepdf_analysis.get("js_objects", []):
cmd = ["pdf-parser.py", "-o", str(obj_id), "-f", "-w", filepath]
result = subprocess.run(cmd, capture_output=True, text=True)
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.stdout:
js_content.append({
"object_id": obj_id,
@@ -41,7 +41,7 @@ URLScan.io is a free service for scanning and analyzing suspicious URLs. It capt
- Data URIs or base64-encoded content
- JavaScript-heavy pages with minimal HTML
## Implementation Steps
## Workflow
### Step 1: Submit URL to URLScan
```
@@ -4,9 +4,7 @@
import json
import os
import sys
import subprocess
import hashlib
import datetime
try:
import requests
@@ -30,7 +28,7 @@ def submit_file(filepath, timeout=300, machine=None, package=None):
data["machine"] = machine
if package:
data["package"] = package
resp = requests.post(url, files=files, data=data)
resp = requests.post(url, files=files, data=data, timeout=30)
if resp.status_code == 200:
return resp.json().get("task_id")
return None
@@ -42,7 +40,7 @@ def submit_url(url_to_analyze, timeout=300):
return None
url = f"{CUCKOO_API}/tasks/create/url"
data = {"url": url_to_analyze, "timeout": timeout}
resp = requests.post(url, data=data)
resp = requests.post(url, data=data, timeout=30)
if resp.status_code == 200:
return resp.json().get("task_id")
return None
@@ -53,7 +51,7 @@ def get_task_status(task_id):
if not HAS_REQUESTS:
return None
url = f"{CUCKOO_API}/tasks/view/{task_id}"
resp = requests.get(url)
resp = requests.get(url, timeout=30)
if resp.status_code == 200:
return resp.json().get("task", {}).get("status")
return None
@@ -36,7 +36,7 @@ Malpedia uses the format `platform.family_name` (e.g., `win.emotet`, `elf.mirai`
Malware families have relationships including: parent-child (code reuse, forks), loader-payload (Emotet loads TrickBot loads Ryuk), shared authorship (same threat actor develops multiple tools), and infrastructure sharing (common C2 frameworks).
## Practical Steps
## Workflow
### Step 1: Query Malpedia API for Malware Families
@@ -22,7 +22,7 @@ Sysinternals Autoruns extracts data from hundreds of Auto-Start Extensibility Po
- VirusTotal API key for reputation checks
- Clean baseline export for comparison
## Practical Steps
## Workflow
### Step 1: Automated Persistence Scanning
@@ -3,7 +3,6 @@
import json
import csv
import os
import re
import logging
import argparse
@@ -4,7 +4,6 @@
import json
import argparse
from datetime import datetime
from collections import defaultdict
TIMING_APIS = {
"GetTickCount", "GetTickCount64", "QueryPerformanceCounter",
@@ -1,19 +1,18 @@
#!/usr/bin/env python3
"""Memory forensics agent using Volatility 3 for malware detection in RAM dumps."""
import shlex
import subprocess
import os
import sys
import json
import csv
import re
import io
def run_vol3(memory_dump, plugin, extra_args=""):
"""Execute a Volatility 3 plugin and return output."""
cmd = f"vol3 -f {memory_dump} {plugin} {extra_args}"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=300)
cmd = ["vol3", "-f", memory_dump, plugin]
if extra_args:
cmd.extend(shlex.split(extra_args))
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
return result.stdout.strip(), result.stderr.strip(), result.returncode
@@ -1,7 +1,6 @@
#!/usr/bin/env python3
"""Agent for Linux memory forensics using LiME acquisition and Volatility 3."""
import os
import json
import subprocess
import argparse
@@ -12,13 +11,13 @@ from pathlib import Path
def acquire_memory_lime(output_path, lime_format="lime"):
"""Acquire memory using LiME kernel module."""
kernel_version = subprocess.run(
["uname", "-r"], capture_output=True, text=True
["uname", "-r"], capture_output=True, text=True, timeout=120
).stdout.strip()
lime_module = f"lime-{kernel_version}.ko"
if not Path(lime_module).exists():
lime_module = "lime.ko"
cmd = ["insmod", lime_module, f"path={output_path}", f"format={lime_format}"]
result = subprocess.run(cmd, capture_output=True, text=True)
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
return {
"status": "success" if result.returncode == 0 else "failed",
"output_path": output_path,
@@ -22,7 +22,7 @@ Malware uses covert channels to disguise C2 communication and data exfiltration
- DNS query logging infrastructure
- Understanding of DNS, ICMP, HTTP protocols at packet level
## Practical Steps
## Workflow
### Step 1: DNS Tunneling Detection
@@ -9,11 +9,10 @@ import os
import sys
import json
import math
import hashlib
from collections import Counter, defaultdict
try:
from scapy.all import rdpcap, DNS, DNSQR, DNSRR, ICMP, IP, TCP, UDP, Raw
from scapy.all import rdpcap, DNS, DNSQR, ICMP, IP, TCP, Raw
HAS_SCAPY = True
except ImportError:
HAS_SCAPY = False
@@ -13,6 +13,9 @@ author: mahipal
license: Apache-2.0
---
# Analyzing Network Flow Data with Netflow
## Instructions
1. Install dependencies: `pip install netflow`
@@ -7,7 +7,7 @@ import argparse
from collections import defaultdict, Counter
from datetime import datetime
from scapy.all import rdpcap, IP, TCP, UDP, DNS, DNSQR, ICMP, Raw
from scapy.all import rdpcap, IP, TCP, UDP, DNS, DNSQR, ICMP
def load_pcap(filepath):
@@ -6,10 +6,10 @@ import os
import sys
import json
import statistics
from collections import defaultdict, Counter
from collections import defaultdict
try:
from scapy.all import rdpcap, IP, TCP, UDP, DNS, DNSQR, Raw, ARP
from scapy.all import rdpcap, IP, TCP, DNS
HAS_SCAPY = True
except ImportError:
HAS_SCAPY = False
@@ -17,9 +17,11 @@ except ImportError:
def run_tshark(pcap_path, display_filter, fields):
"""Run tshark with a display filter and extract specific fields."""
field_args = " ".join(f"-e {f}" for f in fields)
cmd = f'tshark -r {pcap_path} -Y "{display_filter}" -T fields {field_args} -E separator="|"'
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=120)
cmd = ["tshark", "-r", pcap_path, "-Y", display_filter, "-T", "fields"]
for f in fields:
cmd += ["-e", f]
cmd += ["-E", "separator=|"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
rows = []
if result.returncode == 0:
for line in result.stdout.strip().splitlines():
@@ -31,8 +33,8 @@ def run_tshark(pcap_path, display_filter, fields):
def get_pcap_summary(pcap_path):
"""Get high-level PCAP statistics."""
cmd = f"tshark -r {pcap_path} -q -z conv,ip"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=60)
cmd = ["tshark", "-r", pcap_path, "-q", "-z", "conv,ip"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
return result.stdout if result.returncode == 0 else ""
@@ -57,8 +59,8 @@ def detect_lateral_movement(pcap_path):
def detect_data_exfiltration(pcap_path, threshold_mb=10):
"""Detect potential data exfiltration based on outbound data volume."""
cmd = f'tshark -r {pcap_path} -q -z conv,ip'
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=60)
cmd = ["tshark", "-r", pcap_path, "-q", "-z", "conv,ip"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
suspects = []
if result.returncode == 0:
for line in result.stdout.splitlines():
@@ -120,10 +122,13 @@ def extract_dns_queries(pcap_path):
def detect_ids_alerts(pcap_path):
"""Run Suricata on the PCAP and extract alerts."""
cmd = f"suricata -r {pcap_path} -l /tmp/suricata_output -k none 2>/dev/null"
subprocess.run(cmd, shell=True, timeout=120)
import tempfile
suricata_output = os.environ.get("SURICATA_OUTPUT_DIR", os.path.join(tempfile.gettempdir(), "suricata_output"))
os.makedirs(suricata_output, exist_ok=True)
cmd = ["suricata", "-r", pcap_path, "-l", suricata_output, "-k", "none"]
subprocess.run(cmd, capture_output=True, timeout=120)
alerts = []
alert_file = "/tmp/suricata_output/fast.log"
alert_file = os.path.join(suricata_output, "fast.log")
if os.path.exists(alert_file):
with open(alert_file, "r") as f:
for line in f:
@@ -134,8 +139,8 @@ def detect_ids_alerts(pcap_path):
def extract_http_objects(pcap_path, output_dir):
"""Extract HTTP objects (files) from the PCAP."""
os.makedirs(output_dir, exist_ok=True)
cmd = f'tshark -r {pcap_path} --export-objects "http,{output_dir}"'
subprocess.run(cmd, shell=True, timeout=60)
cmd = ["tshark", "-r", pcap_path, "--export-objects", f"http,{output_dir}"]
subprocess.run(cmd, capture_output=True, timeout=60)
exported = []
if os.path.exists(output_dir):
for f in os.listdir(output_dir):
@@ -3,9 +3,7 @@
import os
import sys
import json
import math
import subprocess
from collections import defaultdict, Counter
try:
@@ -15,7 +13,7 @@ except ImportError:
HAS_DPKT = False
try:
from scapy.all import rdpcap, IP, TCP, UDP, DNS, DNSQR, Raw
from scapy.all import rdpcap, IP, TCP, DNS, DNSQR
HAS_SCAPY = True
except ImportError:
HAS_SCAPY = False
@@ -2,26 +2,24 @@
"""Wireshark/tshark packet analysis agent for network security investigations."""
import subprocess
import shlex
import os
import sys
import json
import re
from collections import defaultdict
def run_tshark(pcap_path, args):
"""Execute tshark with custom arguments."""
cmd = f"tshark -r {pcap_path} {args}"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=120)
cmd = ["tshark", "-r", pcap_path] + shlex.split(args)
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
return result.stdout.strip(), result.stderr.strip(), result.returncode
def capture_live(interface, output_path, duration=60, capture_filter=None):
"""Start a live packet capture using tshark."""
cmd = f"tshark -i {interface} -w {output_path} -a duration:{duration}"
cmd = ["tshark", "-i", interface, "-w", output_path, "-a", f"duration:{duration}"]
if capture_filter:
cmd += f' -f "{capture_filter}"'
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=duration + 10)
cmd += ["-f", capture_filter]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=duration + 10)
return result.returncode == 0
@@ -10,8 +10,6 @@ import sys
import json
import hashlib
import re
from datetime import datetime
from collections import defaultdict
try:
import pypff
@@ -5,7 +5,6 @@ import subprocess
import os
import sys
import hashlib
import struct
import math
from collections import Counter
@@ -129,8 +128,8 @@ def unpack_upx(filepath, output_path=None):
if output_path is None:
output_path = filepath + ".unpacked"
# First try standard UPX decompression
cmd = f"upx -d -o {output_path} {filepath}"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
cmd = ["upx", "-d", "-o", output_path, filepath]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode == 0:
return True, "Standard UPX unpack succeeded", output_path
@@ -5,9 +5,7 @@ import re
import os
import sys
import hashlib
import json
import zlib
import struct
def compute_hash(filepath):
@@ -44,7 +44,8 @@ def scan_crontabs():
findings.extend(_scan_cron_file(full_path))
user_crontabs = subprocess.run(
["bash", "-c", "for u in $(cut -d: -f1 /etc/passwd); do crontab -l -u $u 2>/dev/null && echo \"__USER:$u\"; done"],
capture_output=True, text=True
capture_output=True, text=True,
timeout=120,
)
if user_crontabs.returncode == 0:
current_user = None
@@ -112,7 +113,8 @@ def scan_systemd_units():
if re.search(pattern, ex, re.IGNORECASE):
risk = "critical"
dpkg_check = subprocess.run(
["dpkg", "-S", unit_file], capture_output=True, text=True
["dpkg", "-S", unit_file], capture_output=True, text=True,
timeout=120,
)
package_managed = dpkg_check.returncode == 0
if not package_managed:
@@ -141,7 +143,7 @@ def scan_ld_preload():
"libraries": content.splitlines(), "risk": "critical",
"mitre": "T1574.006",
})
env_check = subprocess.run(["env"], capture_output=True, text=True)
env_check = subprocess.run(["env"], capture_output=True, text=True, timeout=120)
for line in env_check.stdout.splitlines():
if line.startswith("LD_PRELOAD="):
findings.append({
@@ -174,7 +176,7 @@ def scan_shell_profiles():
continue
etc_profiles = glob.glob("/etc/profile.d/*.sh")
for filepath in etc_profiles:
dpkg = subprocess.run(["dpkg", "-S", filepath], capture_output=True, text=True)
dpkg = subprocess.run(["dpkg", "-S", filepath], capture_output=True, text=True, timeout=120)
if dpkg.returncode != 0:
findings.append({
"type": "etc_profile_d", "path": filepath,
@@ -7,12 +7,9 @@ suspicious routing, and phishing indicators.
import os
import sys
import json
import re
import email
import email.utils
from datetime import datetime
from collections import OrderedDict
def parse_email_file(filepath):
@@ -13,6 +13,9 @@ author: mahipal
license: Apache-2.0
---
# Analyzing PowerShell Script Block Logging
## Instructions
1. Install dependencies: `pip install python-evtx lxml`
@@ -4,7 +4,6 @@
import struct
import os
import sys
import hashlib
import datetime
import json
import glob
@@ -164,11 +163,11 @@ def build_execution_timeline(prefetch_results):
def run_pecmd(prefetch_path, output_dir=None):
"""Run Eric Zimmerman's PECmd for comprehensive prefetch parsing."""
cmd = f"PECmd.exe -f {prefetch_path}"
if output_dir:
cmd += f" --csv {output_dir}"
import subprocess
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
cmd = ["PECmd.exe", "-f", prefetch_path]
if output_dir:
cmd += ["--csv", output_dir]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
return result.stdout, result.returncode
@@ -7,11 +7,9 @@ and assesses decryption feasibility for ransomware samples and encrypted files.
import os
import sys
import struct
import hashlib
import math
import json
import re
from collections import Counter
@@ -36,7 +36,7 @@ Leak sites provide: victim identification (company name, sector, country), attac
Never directly access DLS sites in a production environment. Use purpose-built monitoring services (Ransomwatch, DarkFeed, KELA, Flashpoint), Tor-isolated research VMs, commercial threat intelligence platforms, or community-maintained datasets. All analysis should be conducted in isolated environments with proper authorization.
## Practical Steps
## Workflow
### Step 1: Ingest Ransomware Leak Site Data from Public Feeds
@@ -5,11 +5,8 @@ Monitors and analyzes ransomware group leak site data for threat intelligence,
victim tracking, and TTI (time-to-intelligence) reporting.
"""
import os
import sys
import json
import re
import hashlib
from datetime import datetime, timedelta
from collections import defaultdict, Counter
@@ -3,11 +3,10 @@
import json
import csv
import math
import argparse
import urllib.request
from datetime import datetime
from collections import defaultdict, Counter
from collections import defaultdict
from statistics import mean, stdev
TOR_EXIT_LIST_URL = "https://check.torproject.org/torbulkexitlist"
@@ -0,0 +1,163 @@
---
name: analyzing-ransomware-payment-wallets
description: >
Traces ransomware cryptocurrency payment flows using blockchain analysis tools
such as Chainalysis Reactor, WalletExplorer, and blockchain.com APIs. Identifies
wallet clusters, tracks fund movement through mixers and exchanges, and supports
law enforcement attribution. Activates for requests involving ransomware payment
tracing, bitcoin wallet analysis, cryptocurrency forensics, or blockchain
intelligence gathering.
domain: cybersecurity
subdomain: ransomware-defense
tags: [ransomware, blockchain, cryptocurrency, forensics, threat-intelligence, bitcoin]
version: 1.0.0
author: mahipal
license: Apache-2.0
---
# Analyzing Ransomware Payment Wallets
## When to Use
- An organization has been hit by ransomware and the ransom note contains a Bitcoin or cryptocurrency wallet address that needs investigation
- Law enforcement or incident responders need to trace where ransom payments flowed after the victim paid
- Threat intelligence analysts are attributing ransomware campaigns by clustering payment infrastructure across incidents
- Investigators need to determine if a ransomware group is reusing wallet infrastructure across multiple victims
- Compliance or legal teams need evidence of fund flows for prosecution, sanctions enforcement, or insurance claims
**Do not use** this skill for live payment interception or to interact directly with ransomware operators. All analysis should be passive and read-only against public blockchain data.
## Prerequisites
- Python 3.8+ with `requests`, `json`, and `hashlib` libraries
- Access to blockchain explorer APIs (blockchain.com, WalletExplorer.com, Blockstream.info)
- Familiarity with Bitcoin transaction model (UTXOs, inputs, outputs, change addresses)
- Understanding of common obfuscation techniques (mixers, tumblers, peel chains, cross-chain swaps)
- Optional: Chainalysis Reactor license for enterprise-grade cluster analysis
- Optional: OXT.me for advanced transaction graph visualization
## Workflow
### Step 1: Extract Wallet Address from Ransom Note
Parse the ransom note to identify the payment address(es):
```
Common address formats:
Bitcoin (P2PKH): 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa (starts with 1)
Bitcoin (P2SH): 3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy (starts with 3)
Bitcoin (Bech32): bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq (starts with bc1)
Monero: 4... (95 characters, much harder to trace)
Ethereum: 0x... (40 hex chars)
```
### Step 2: Query Blockchain Explorer for Transaction History
Retrieve all transactions associated with the wallet:
```python
import requests
def get_wallet_transactions(address):
"""Query blockchain.com API for address transactions."""
url = f"https://blockchain.info/rawaddr/{address}"
resp = requests.get(url, timeout=30)
resp.raise_for_status()
data = resp.json()
return {
"address": address,
"n_tx": data.get("n_tx", 0),
"total_received_satoshi": data.get("total_received", 0),
"total_sent_satoshi": data.get("total_sent", 0),
"final_balance_satoshi": data.get("final_balance", 0),
"transactions": data.get("txs", []),
}
```
### Step 3: Map Fund Flow and Identify Clusters
Trace outputs from the ransom wallet to downstream addresses:
```
Fund Flow Analysis:
━━━━━━━━━━━━━━━━━━
Victim Payment ──► Ransom Wallet ──► Consolidation Wallet
├─► Mixer/Tumbler Service
├─► Exchange Deposit Address
└─► Peel Chain (sequential small outputs)
Key indicators:
- Consolidation: Multiple ransom payments aggregated into one wallet
- Peel chains: Sequential transactions with diminishing outputs
- Mixer usage: Funds sent to known mixer addresses (Wasabi, Samourai, ChipMixer)
- Exchange cashout: Deposits to known exchange wallets (Binance, Kraken hot wallets)
```
### Step 4: Cross-Reference with Known Wallet Databases
Check addresses against known ransomware infrastructure:
```python
# Check WalletExplorer for entity identification
def check_wallet_explorer(address):
url = f"https://www.walletexplorer.com/api/1/address?address={address}&caller=research"
resp = requests.get(url, timeout=30)
data = resp.json()
return {
"wallet_id": data.get("wallet_id"),
"label": data.get("label", "Unknown"),
"is_exchange": data.get("is_exchange", False),
}
```
### Step 5: Generate Attribution Report
Compile findings into a structured intelligence report:
```
RANSOMWARE WALLET ANALYSIS REPORT
====================================
Ransom Address: bc1q...xyz
Family Attribution: LockBit 3.0 (based on ransom note format)
Total Received: 4.25 BTC ($178,500 at time of payment)
Total Sent: 4.25 BTC (wallet fully drained)
Number of Payments: 3 (likely 3 separate victims)
FUND FLOW:
Payment 1: 1.5 BTC → Consolidation wallet → Binance deposit
Payment 2: 1.0 BTC → Wasabi Mixer → Unknown
Payment 3: 1.75 BTC → Peel chain (12 hops) → OKX deposit
CLUSTER ANALYSIS:
Related wallets: 47 addresses identified in same cluster
Total cluster volume: 156.3 BTC ($6.5M USD)
First activity: 2024-01-15
Last activity: 2024-09-22
```
## Verification
- Confirm wallet address format is valid before querying APIs
- Cross-reference transaction timestamps with known incident timelines
- Validate cluster associations by checking common-input-ownership heuristic
- Compare findings against OFAC SDN list for sanctioned addresses
- Verify exchange attribution against multiple sources (WalletExplorer, OXT, Chainalysis)
## Key Concepts
| Term | Definition |
|------|------------|
| **UTXO** | Unspent Transaction Output; the fundamental unit of Bitcoin that tracks ownership through a chain of transactions |
| **Cluster Analysis** | Grouping multiple Bitcoin addresses believed to be controlled by the same entity using common-input-ownership and change-address heuristics |
| **Peel Chain** | A laundering pattern where funds are sent through many sequential transactions, each peeling off a small amount to a new address |
| **CoinJoin/Mixer** | Privacy techniques that combine multiple users' transactions to obscure the link between sender and receiver |
| **Common Input Ownership** | Heuristic that assumes all inputs to a single transaction are controlled by the same entity |
## Tools & Systems
- **Chainalysis Reactor**: Enterprise blockchain investigation platform with entity attribution and cross-chain tracing
- **WalletExplorer**: Free tool that clusters Bitcoin addresses and labels known services (exchanges, mixers, markets)
- **OXT.me**: Advanced Bitcoin transaction visualization with UTXO graph analysis
- **Blockstream.info**: Open-source Bitcoin block explorer with full API access
- **blockchain.com API**: Free API for querying Bitcoin address balances and transaction histories
- **OFAC SDN List**: U.S. Treasury sanctioned address list for compliance checking
@@ -0,0 +1,97 @@
# API Reference: Ransomware Payment Wallet Analysis
## blockchain.com API
### Get Address Information
```
GET https://blockchain.info/rawaddr/{address}?limit=50
```
Returns transaction history, balance, and UTXO data for a Bitcoin address.
### Response Fields
| Field | Type | Description |
|-------|------|-------------|
| `address` | string | Bitcoin address |
| `n_tx` | int | Total number of transactions |
| `total_received` | int | Total satoshis received |
| `total_sent` | int | Total satoshis sent |
| `final_balance` | int | Current balance in satoshis |
| `txs` | array | Array of transaction objects |
### Get Single Transaction
```
GET https://blockchain.info/rawtx/{tx_hash}
```
### Get Unspent Outputs
```
GET https://blockchain.info/unspent?active={address}
```
## Blockstream.info API
### Get Address Stats
```
GET https://blockstream.info/api/address/{address}
```
### Response Fields
| Field | Type | Description |
|-------|------|-------------|
| `chain_stats.funded_txo_count` | int | Number of funding transactions |
| `chain_stats.spent_txo_count` | int | Number of spending transactions |
| `chain_stats.funded_txo_sum` | int | Total satoshis funded |
| `chain_stats.spent_txo_sum` | int | Total satoshis spent |
### Get Address Transactions
```
GET https://blockstream.info/api/address/{address}/txs
```
## WalletExplorer API
### Look Up Address
```
GET https://www.walletexplorer.com/api/1/address?address={address}&caller=research
```
### Response Fields
| Field | Type | Description |
|-------|------|-------------|
| `wallet_id` | string | Cluster wallet identifier |
| `label` | string | Known entity label (exchange, mixer, etc.) |
| `is_exchange` | bool | Whether address belongs to known exchange |
### Get Wallet Transactions
```
GET https://www.walletexplorer.com/api/1/wallet-addresses?wallet={wallet_id}&caller=research
```
## Bitcoin Address Formats
| Format | Prefix | Example | Notes |
|--------|--------|---------|-------|
| P2PKH (Legacy) | 1 | 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa | Original format |
| P2SH (SegWit compatible) | 3 | 3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy | Script hash |
| Bech32 (Native SegWit) | bc1q | bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq | Lower fees |
| Bech32m (Taproot) | bc1p | bc1p... | Newest format |
## Common Ransomware Wallet Indicators
| Pattern | Significance |
|---------|-------------|
| Single large inbound, rapid outbound | Ransom payment received, quickly laundered |
| Multiple small inbound from different addresses | Multiple victims paying same wallet |
| Outbound to known mixer addresses | Laundering through CoinJoin/mixer services |
| Peel chain (sequential diminishing outputs) | Structured laundering to evade detection |
| Transfer to exchange hot wallet | Cash-out attempt via cryptocurrency exchange |
## OFAC SDN Sanctions Check
```
Download list: https://www.treasury.gov/ofac/downloads/sdnlist.txt
Search API: https://sanctionssearch.ofac.treas.gov/
```
Check addresses against OFAC Specially Designated Nationals list for compliance.
@@ -0,0 +1,218 @@
#!/usr/bin/env python3
"""Ransomware payment wallet blockchain analysis agent.
Traces cryptocurrency payment flows from ransomware wallets using public
blockchain APIs. Identifies transaction patterns, cluster relationships,
and fund movement to exchanges or mixers.
"""
import json
import re
import sys
try:
import requests
except ImportError:
print("[!] 'requests' library required: pip install requests")
sys.exit(1)
BLOCKCHAIN_API = "https://blockchain.info"
BLOCKSTREAM_API = "https://blockstream.info/api"
BTC_ADDRESS_REGEX = re.compile(
r"^(1[a-km-zA-HJ-NP-Z1-9]{25,34}|3[a-km-zA-HJ-NP-Z1-9]{25,34}|bc1[a-z0-9]{39,59})$"
)
KNOWN_RANSOMWARE_WALLETS = {
"12t9YDPgwueZ9NyMgw519p7AA8isjr6SMw": "WannaCry",
"13AM4VW2dhxYgXeQepoHkHSQuy6NgaEb94": "WannaCry",
"115p7UMMngoj1pMvkpHijcRdfJNXj6LrLn": "WannaCry",
"1Mz7153HMuxXTuR2R1t78mGSdzaAtNbBWX": "DarkSide (Colonial Pipeline)",
"bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh": "DarkSide",
}
def validate_btc_address(address):
"""Validate Bitcoin address format."""
if BTC_ADDRESS_REGEX.match(address):
return True
return False
def query_address_info(address):
"""Query blockchain.info for address details."""
url = f"{BLOCKCHAIN_API}/rawaddr/{address}?limit=50"
resp = requests.get(url, timeout=30)
resp.raise_for_status()
data = resp.json()
return {
"address": address,
"total_received_btc": data.get("total_received", 0) / 1e8,
"total_sent_btc": data.get("total_sent", 0) / 1e8,
"final_balance_btc": data.get("final_balance", 0) / 1e8,
"n_tx": data.get("n_tx", 0),
"transactions": data.get("txs", []),
}
def query_blockstream_address(address):
"""Query Blockstream API for address stats (fallback)."""
url = f"{BLOCKSTREAM_API}/address/{address}"
resp = requests.get(url, timeout=30)
resp.raise_for_status()
data = resp.json()
chain = data.get("chain_stats", {})
return {
"address": address,
"funded_txo_count": chain.get("funded_txo_count", 0),
"spent_txo_count": chain.get("spent_txo_count", 0),
"funded_txo_sum_btc": chain.get("funded_txo_sum", 0) / 1e8,
"spent_txo_sum_btc": chain.get("spent_txo_sum", 0) / 1e8,
}
def extract_output_addresses(transactions, source_address):
"""Extract downstream addresses from transaction outputs."""
downstream = {}
for tx in transactions:
tx_hash = tx.get("hash", "unknown")
is_outgoing = any(
inp.get("prev_out", {}).get("addr") == source_address
for inp in tx.get("inputs", [])
)
if not is_outgoing:
continue
for out in tx.get("out", []):
addr = out.get("addr")
value = out.get("value", 0) / 1e8
if addr and addr != source_address:
if addr not in downstream:
downstream[addr] = {"total_btc": 0, "tx_count": 0, "tx_hashes": []}
downstream[addr]["total_btc"] += value
downstream[addr]["tx_count"] += 1
downstream[addr]["tx_hashes"].append(tx_hash[:16])
return downstream
def check_known_wallets(address):
"""Check if address matches known ransomware wallets."""
if address in KNOWN_RANSOMWARE_WALLETS:
return {"known": True, "family": KNOWN_RANSOMWARE_WALLETS[address]}
return {"known": False, "family": None}
def detect_peel_chain(transactions, address):
"""Detect peel chain pattern in outgoing transactions."""
outgoing_values = []
for tx in transactions:
is_outgoing = any(
inp.get("prev_out", {}).get("addr") == address
for inp in tx.get("inputs", [])
)
if is_outgoing:
outputs = [o.get("value", 0) / 1e8 for o in tx.get("out", []) if o.get("addr") != address]
outgoing_values.extend(outputs)
if len(outgoing_values) < 3:
return {"peel_chain_detected": False, "reason": "Insufficient transactions"}
decreasing = sum(1 for i in range(1, len(outgoing_values)) if outgoing_values[i] < outgoing_values[i - 1])
ratio = decreasing / (len(outgoing_values) - 1) if len(outgoing_values) > 1 else 0
return {
"peel_chain_detected": ratio > 0.6,
"decreasing_ratio": round(ratio, 3),
"num_outputs": len(outgoing_values),
}
def analyze_wallet(address):
"""Full analysis of a ransomware payment wallet."""
report = {"analysis_type": "Ransomware Payment Wallet Analysis", "address": address}
if not validate_btc_address(address):
report["error"] = f"Invalid Bitcoin address format: {address}"
return report
report["known_wallet_check"] = check_known_wallets(address)
try:
info = query_address_info(address)
report["wallet_info"] = {
"total_received_btc": info["total_received_btc"],
"total_sent_btc": info["total_sent_btc"],
"final_balance_btc": info["final_balance_btc"],
"transaction_count": info["n_tx"],
}
downstream = extract_output_addresses(info["transactions"], address)
report["downstream_addresses"] = {
"count": len(downstream),
"top_recipients": sorted(
[{"address": a, **d} for a, d in downstream.items()],
key=lambda x: x["total_btc"],
reverse=True,
)[:10],
}
for recipient in report["downstream_addresses"]["top_recipients"]:
match = check_known_wallets(recipient["address"])
recipient["known_entity"] = match["family"] if match["known"] else "Unknown"
report["peel_chain_analysis"] = detect_peel_chain(info["transactions"], address)
except requests.RequestException as e:
report["error"] = f"API query failed: {e}"
try:
fallback = query_blockstream_address(address)
report["wallet_info_blockstream"] = fallback
except requests.RequestException as e2:
report["fallback_error"] = f"Blockstream fallback also failed: {e2}"
return report
if __name__ == "__main__":
print("=" * 60)
print("Ransomware Payment Wallet Analysis Agent")
print("Blockchain tracing, cluster analysis, fund flow mapping")
print("=" * 60)
if len(sys.argv) < 2:
print("\nUsage:")
print(" python agent.py <bitcoin_address>")
print(" python agent.py <bitcoin_address> --deep")
print("\nExample:")
print(" python agent.py 12t9YDPgwueZ9NyMgw519p7AA8isjr6SMw")
sys.exit(0)
address = sys.argv[1]
print(f"\n[*] Analyzing wallet: {address}")
report = analyze_wallet(address)
known = report.get("known_wallet_check", {})
if known.get("known"):
print(f"[!] KNOWN RANSOMWARE WALLET: {known['family']}")
info = report.get("wallet_info", {})
if info:
print(f"\n--- Wallet Summary ---")
print(f" Total received: {info.get('total_received_btc', 0):.8f} BTC")
print(f" Total sent: {info.get('total_sent_btc', 0):.8f} BTC")
print(f" Balance: {info.get('final_balance_btc', 0):.8f} BTC")
print(f" Transactions: {info.get('transaction_count', 0)}")
ds = report.get("downstream_addresses", {})
if ds.get("count", 0) > 0:
print(f"\n--- Top Downstream Recipients ({ds['count']} total) ---")
for r in ds.get("top_recipients", [])[:5]:
entity = r.get("known_entity", "Unknown")
print(f" {r['address'][:20]}... {r['total_btc']:.8f} BTC [{entity}]")
peel = report.get("peel_chain_analysis", {})
if peel.get("peel_chain_detected"):
print(f"\n[!] Peel chain pattern detected (ratio: {peel['decreasing_ratio']})")
if report.get("error"):
print(f"\n[!] Error: {report['error']}")
print(f"\n[*] Full report:\n{json.dumps(report, indent=2, default=str)}")
@@ -2,11 +2,10 @@
"""Agent for analyzing security logs with Splunk using splunk-sdk."""
import os
import sys
import json
import time
import argparse
from datetime import datetime, timedelta
from datetime import datetime
import splunklib.client as client
import splunklib.results as results
@@ -2,7 +2,6 @@
"""Agent for analyzing NTFS slack space and file system artifacts."""
import os
import sys
import json
import struct
import argparse
@@ -14,7 +13,7 @@ from pathlib import Path
def parse_mft_with_analyzeMFT(mft_path, output_csv):
"""Parse MFT using analyzeMFT and return deleted/timestomped files."""
cmd = ["analyzeMFT.py", "-f", mft_path, "-o", output_csv, "-c"]
subprocess.run(cmd, check=True)
subprocess.run(cmd, check=True, timeout=120)
return output_csv
@@ -22,7 +21,7 @@ def extract_slack_space(image_path, offset, output_path):
"""Extract slack space from a disk image using blkls from The Sleuth Kit."""
cmd = ["blkls", "-s", "-o", str(offset), image_path]
with open(output_path, "wb") as out:
subprocess.run(cmd, stdout=out, check=True)
subprocess.run(cmd, stdout=out, check=True, timeout=120)
return output_path
@@ -92,7 +91,7 @@ def parse_usn_journal(usn_path):
def find_ads_in_image(image_path, offset):
"""List Alternate Data Streams using fls from The Sleuth Kit."""
cmd = ["fls", "-r", "-o", str(offset), image_path]
result = subprocess.run(cmd, capture_output=True, text=True)
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
ads_entries = [line for line in result.stdout.splitlines() if ":" in line]
return ads_entries
@@ -23,7 +23,7 @@ Supply chain attacks compromise legitimate software distribution channels to del
- Access to legitimate software versions for comparison
- Package repository monitoring (npm, PyPI, NuGet)
## Practical Steps
## Workflow
### Step 1: Binary Comparison Analysis
@@ -135,8 +135,7 @@ if __name__ == "__main__":
target = sys.argv[1] if len(sys.argv) > 1 else None
if not target:
print("
[DEMO] Usage:")
print("\n[DEMO] Usage:")
print(" python agent.py <package.json> # Analyze npm package")
print(" python agent.py npm:<package_name> # Check npm registry")
print(" python agent.py pypi:<package_name> # Check PyPI registry")
@@ -144,17 +143,14 @@ if __name__ == "__main__":
if target.startswith("npm:"):
pkg_name = target[4:]
print(f"
[*] Checking npm: {pkg_name}")
print(f"\n[*] Checking npm: {pkg_name}")
info = check_npm_package(pkg_name)
typos = detect_typosquat_packages(pkg_name)
print(json.dumps(info, indent=2))
print(f"
Potential typosquats: {typos[:10]}")
print(f"\n Potential typosquats: {typos[:10]}")
elif target.startswith("pypi:"):
pkg_name = target[5:]
print(f"
[*] Checking PyPI: {pkg_name}")
print(f"\n[*] Checking PyPI: {pkg_name}")
info = check_pypi_package(pkg_name)
print(json.dumps(info, indent=2))
elif os.path.exists(target):
@@ -36,7 +36,7 @@ ATT&CK catalogs over 140 threat groups (e.g., APT28, APT29, Lazarus Group, FIN7)
The ATT&CK Navigator is a web-based tool for creating custom ATT&CK matrix visualizations. Analysts create layers (JSON files) that annotate techniques with scores, colors, comments, and metadata to visualize threat actor coverage, detection capabilities, or risk assessments.
## Practical Steps
## Workflow
### Step 1: Query ATT&CK Data Programmatically
@@ -122,8 +122,7 @@ if __name__ == "__main__":
print(f"[*] Loaded {len(techniques)} techniques, {len(groups)} groups")
if not group_query:
print("
--- Available Groups (sample) ---")
print("\n--- Available Groups (sample) ---")
for gid, g in list(groups.items())[:15]:
print(f" {g['id']:8s} {g['name']}")
sys.exit(0)
@@ -133,23 +132,20 @@ if __name__ == "__main__":
print(f"[!] Group not found: {group_query}")
sys.exit(1)
print(f"
[*] Group: {ginfo['name']} ({ginfo['id']})")
print(f"\n[*] Group: {ginfo['name']} ({ginfo['id']})")
print(f" Aliases: {', '.join(ginfo['aliases'][:5])}")
ttps = map_group_techniques(bundle, gid, techniques)
print(f" Techniques: {len(ttps)}")
coverage = tactic_coverage(ttps)
print("
--- Tactic Coverage ---")
print("\n--- Tactic Coverage ---")
for tactic, info in sorted(coverage.items(), key=lambda x: -x[1]["count"]):
bar = "#" * info["count"]
print(f" {tactic:35s} {info['count']:3d} {bar}")
sample_detections = [t["id"] for t in ttps[:len(ttps)//2]]
gaps, pct = detection_gaps(ttps, sample_detections)
print(f"
--- Detection Gaps (demo: {pct}% coverage) ---")
print(f"\n--- Detection Gaps (demo: {pct}% coverage) ---")
for g in gaps[:10]:
print(f" [GAP] {g['id']:12s} {g['name']}")
@@ -4,11 +4,10 @@
import os
import json
import argparse
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from taxii2client.v21 import Server, Collection, as_pages
from stix2 import Filter, MemoryStore, Indicator, Relationship, Bundle
from stix2 import ThreatActor, Malware
from stix2 import Indicator, Bundle
def discover_taxii_server(url, user=None, password=None):
@@ -95,7 +94,7 @@ def score_feed_quality(indicators, known_good_iocs=None):
1 for i in indicators
if i.get("valid_from") and
datetime.fromisoformat(i["valid_from"].replace("Z", "+00:00"))
> datetime.now(tz=__import__("datetime").timezone.utc) - timedelta(days=90)
> datetime.now(tz=timezone.utc) - timedelta(days=90)
)
score = int(
(with_confidence / total * 25) +
@@ -14,6 +14,9 @@ author: mahipal
license: Apache-2.0
---
# Analyzing Threat Landscape with MISP
## Instructions
1. Install dependencies: `pip install pymisp`
@@ -1,7 +1,6 @@
#!/usr/bin/env python3
"""Agent for analyzing Certificate Transparency logs for phishing detection."""
import os
import json
import argparse
from datetime import datetime
@@ -36,7 +36,7 @@ DNSTwist uses ssdeep (locality-sensitive hash) to compare HTML content and pHash
The typical workflow is: generate domain permutations -> resolve DNS records -> check for registered domains -> compare web page similarity -> flag suspicious domains -> alert security team -> request takedown. For a typical corporate domain, dnstwist generates 5,000-10,000 permutations.
## Practical Steps
## Workflow
### Step 1: Basic Domain Permutation Scan
@@ -75,11 +75,9 @@ if __name__ == '__main__':
print('=' * 60)
domain = sys.argv[1] if len(sys.argv) > 1 else None
if not domain:
print('
[DEMO] Usage: python agent.py <domain.com>')
print('\n[DEMO] Usage: python agent.py <domain.com>')
sys.exit(0)
print(f'
[*] Target: {domain}')
print(f'\n[*] Target: {domain}')
dnstwist_results = run_dnstwist_cli(domain)
if dnstwist_results:
print(f'[*] dnstwist found {len(dnstwist_results)} permutations')
@@ -95,5 +93,4 @@ if __name__ == '__main__':
for r in resolved[:15]:
print(f' {r["domain"]:40s} {", ".join(r["ips"])}')
risk = 'HIGH' if len(resolved) > 20 else 'MEDIUM' if len(resolved) > 5 else 'LOW'
print(f'
[*] Risk: {risk}')
print(f'\n[*] Risk: {risk}')
@@ -5,7 +5,6 @@ import os
import json
import argparse
import csv
from datetime import datetime
from regipy.registry import RegistryHive
@@ -45,7 +44,6 @@ def parse_usbstor(system_hive_path):
def parse_mounted_devices(system_hive_path):
"""Parse MountedDevices to map drive letters to USB devices."""
import struct
reg = RegistryHive(system_hive_path)
mounted_key = reg.get_key("MountedDevices")
mappings = []
@@ -13,6 +13,9 @@ author: mahipal
license: Apache-2.0
---
# Analyzing Web Server Logs for Intrusion
## Instructions
1. Install dependencies: `pip install geoip2 user-agents`
@@ -7,10 +7,8 @@ file metadata, and device information using the regipy library.
import argparse
import json
import os
import sys
import datetime
import struct
try:
from regipy.registry import RegistryHive
@@ -6,7 +6,6 @@ import json
import csv
import argparse
from datetime import datetime
from pathlib import Path
import LnkParse3
@@ -7,7 +7,6 @@ import argparse
from datetime import datetime, timedelta
from azure.identity import DefaultAzureCredential, ClientSecretCredential
from azure.mgmt.authorization import AuthorizationManagementClient
import requests
@@ -21,7 +20,7 @@ def graph_get(token, endpoint, params=None):
"""Make an authenticated GET request to Microsoft Graph API."""
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
url = f"https://graph.microsoft.com/v1.0{endpoint}"
resp = requests.get(url, headers=headers, params=params)
resp = requests.get(url, headers=headers, params=params, timeout=30)
resp.raise_for_status()
return resp.json()
@@ -1,14 +1,12 @@
#!/usr/bin/env python3
"""Agent for auditing GCP IAM permissions using google-cloud libraries."""
import os
import json
import argparse
from datetime import datetime
from google.cloud import asset_v1
from google.cloud import resourcemanager_v3
from google.iam.v1 import iam_policy_pb2
def search_iam_policies(scope, query=""):

Some files were not shown because too many files have changed in this diff Show More