Claude 24f816b6a3
Consolidate 22 sibling repos into layered organism structure
Place useful parts of the surrounding repos into sica-fondt by layer, per the
body model (Ada = membrane; brain/endocrine/capabilities/knowledge non-Ada):

- brain/        LLM reasoning + providers (dapr, hermes, MoMoA)
- capabilities/ REPRAG sidecars: hermes tools/skills, dapr tools, parallel
                dispatch, A51 channels, and the OSINT cluster
- knowledge/    LORAG corpus: 754 cyber-skills, agency personas, secure-coding,
                MITRE ATT&CK data
- reference/    defensive threat-reference (C3, shhbruh doc) + AdaYaml parser

License handling: AGPL sources (worldosint, advanced_evolution, mercury,
Reticulum) and GPL DeTTECT are SPEC-only clean-room/port descriptions — no
copyleft code copied. MIT/Apache/data parts copied as working trees.

Safety: shhbruh escape/persistence material and C3 covert-C2 kept as reference
only, not wired into the running organism. See CONSOLIDATION.md.

https://claude.ai/code/session_01UehUqEXXJJCsHoA4voCU5c
2026-06-10 06:53:01 +00:00

172 lines
6.5 KiB
Python

#!/usr/bin/env python3
"""Agent for analyzing NTFS slack space and file system artifacts."""
import os
import json
import struct
import argparse
import subprocess
from datetime import datetime, timedelta
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, timeout=120)
return output_csv
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, timeout=120)
return output_path
def search_slack_keywords(slack_path, keywords=None):
"""Search extracted slack space for forensic keywords."""
if keywords is None:
keywords = ["password", "secret", "confidential", "credit card", "ssn"]
hits = []
with open(slack_path, "rb") as f:
data = f.read()
for kw in keywords:
kw_bytes = kw.encode("utf-8")
start = 0
while True:
idx = data.find(kw_bytes, start)
if idx == -1:
break
context = data[max(0, idx - 20):idx + len(kw_bytes) + 20]
hits.append({
"keyword": kw,
"offset": idx,
"context": context.decode("utf-8", errors="replace"),
})
start = idx + 1
return hits
def parse_usn_journal(usn_path):
"""Parse NTFS USN Change Journal ($UsnJrnl:$J) records."""
REASON_FLAGS = {
0x01: "DATA_OVERWRITE", 0x02: "DATA_EXTEND", 0x04: "DATA_TRUNCATION",
0x100: "FILE_CREATE", 0x200: "FILE_DELETE", 0x400: "EA_CHANGE",
0x800: "SECURITY_CHANGE", 0x1000: "RENAME_OLD_NAME",
0x2000: "RENAME_NEW_NAME", 0x80000000: "CLOSE",
}
records = []
with open(usn_path, "rb") as f:
data = f.read()
offset = 0
while offset < len(data) - 8:
rec_len = struct.unpack_from("<I", data, offset)[0]
if rec_len < 56 or rec_len > 65536 or offset + rec_len > len(data):
offset += 8
continue
major = struct.unpack_from("<H", data, offset + 4)[0]
if major != 2:
offset += max(rec_len, 8)
continue
mft_ref = struct.unpack_from("<Q", data, offset + 8)[0] & 0xFFFFFFFFFFFF
timestamp = struct.unpack_from("<Q", data, offset + 32)[0]
reason = struct.unpack_from("<I", data, offset + 40)[0]
fn_len = struct.unpack_from("<H", data, offset + 56)[0]
fn_off = struct.unpack_from("<H", data, offset + 58)[0]
name = data[offset + fn_off:offset + fn_off + fn_len].decode("utf-16-le", errors="ignore")
ts = datetime(1601, 1, 1) + timedelta(microseconds=timestamp // 10)
reasons = [desc for flag, desc in REASON_FLAGS.items() if reason & flag]
records.append({
"timestamp": ts.strftime("%Y-%m-%d %H:%M:%S"),
"filename": name,
"mft_entry": mft_ref,
"reasons": "|".join(reasons),
})
offset += rec_len
return records
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, timeout=120)
ads_entries = [line for line in result.stdout.splitlines() if ":" in line]
return ads_entries
def detect_timestomping(mft_csv_path):
"""Detect timestomping by comparing $SI and $FN timestamps in MFT CSV output."""
import csv
suspicious = []
with open(mft_csv_path, "r", errors="ignore") as f:
reader = csv.DictReader(f)
for row in reader:
si_mod = row.get("SI_Modified", "")
fn_mod = row.get("FN_Modified", "")
if si_mod and fn_mod and si_mod != fn_mod:
suspicious.append({
"filename": row.get("Filename", ""),
"si_modified": si_mod,
"fn_modified": fn_mod,
})
return suspicious
def generate_report(results_data, case_id):
"""Generate a structured forensic report."""
report = {
"report_type": "File System Artifact Analysis",
"case_id": case_id,
"generated_at": datetime.utcnow().isoformat() + "Z",
"findings": results_data,
}
return json.dumps(report, indent=2, default=str)
def main():
parser = argparse.ArgumentParser(description="NTFS File System Artifact Analysis Agent")
parser.add_argument("--image", required=True, help="Path to forensic disk image")
parser.add_argument("--offset", type=int, default=2048, help="Partition offset in sectors")
parser.add_argument("--case-id", default="CASE-001", help="Case identifier")
parser.add_argument("--output-dir", default="./analysis", help="Output directory")
parser.add_argument("--action", choices=[
"extract_slack", "parse_usn", "find_ads", "search_slack",
"parse_mft", "detect_timestomping", "full_analysis"
], default="full_analysis")
parser.add_argument("--mft-path", help="Path to extracted $MFT file")
parser.add_argument("--usn-path", help="Path to extracted $UsnJrnl:$J file")
args = parser.parse_args()
os.makedirs(args.output_dir, exist_ok=True)
findings = {}
if args.action in ("extract_slack", "full_analysis"):
slack_path = os.path.join(args.output_dir, "slack_space.raw")
extract_slack_space(args.image, args.offset, slack_path)
hits = search_slack_keywords(slack_path)
findings["slack_keywords"] = hits
print(f"[+] Slack space: {len(hits)} keyword hits found")
if args.action in ("parse_usn", "full_analysis") and args.usn_path:
records = parse_usn_journal(args.usn_path)
deletions = [r for r in records if "FILE_DELETE" in r["reasons"]]
findings["usn_journal"] = {
"total_records": len(records),
"deletions": len(deletions),
"recent_deletions": deletions[-20:],
}
print(f"[+] USN Journal: {len(records)} records, {len(deletions)} deletions")
if args.action in ("find_ads", "full_analysis"):
ads = find_ads_in_image(args.image, args.offset)
findings["alternate_data_streams"] = ads
print(f"[+] Alternate Data Streams: {len(ads)} found")
print(generate_report(findings, args.case_id))
if __name__ == "__main__":
main()