mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 06:04:56 +03:00
c47eed6a64
- 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
185 lines
6.5 KiB
Python
185 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for analyzing Windows LNK shortcut files for forensic artifacts."""
|
|
|
|
import os
|
|
import json
|
|
import csv
|
|
import argparse
|
|
from datetime import datetime
|
|
|
|
import LnkParse3
|
|
|
|
|
|
def parse_lnk_file(filepath):
|
|
"""Parse a single LNK file and extract forensic artifacts."""
|
|
with open(filepath, "rb") as f:
|
|
lnk = LnkParse3.lnk_file(f)
|
|
info = lnk.get_json()
|
|
|
|
parsed = {
|
|
"lnk_file": os.path.basename(filepath),
|
|
"target_path": "",
|
|
"working_dir": "",
|
|
"arguments": "",
|
|
"target_created": "",
|
|
"target_modified": "",
|
|
"target_accessed": "",
|
|
"file_size": "",
|
|
"drive_type": "",
|
|
"volume_serial": "",
|
|
"volume_label": "",
|
|
"machine_id": "",
|
|
"mac_address": "",
|
|
}
|
|
|
|
header = info.get("header", {})
|
|
parsed["target_created"] = str(header.get("creation_time", ""))
|
|
parsed["target_modified"] = str(header.get("modified_time", ""))
|
|
parsed["target_accessed"] = str(header.get("accessed_time", ""))
|
|
parsed["file_size"] = str(header.get("file_size", ""))
|
|
|
|
link_info = info.get("link_info", {})
|
|
if link_info:
|
|
local_path = link_info.get("local_base_path", "")
|
|
net_link = link_info.get("common_network_relative_link", {})
|
|
network_path = net_link.get("net_name", "") if net_link else ""
|
|
parsed["target_path"] = local_path or network_path
|
|
|
|
vol_info = link_info.get("volume_id", {})
|
|
if vol_info:
|
|
parsed["drive_type"] = str(vol_info.get("drive_type", ""))
|
|
parsed["volume_serial"] = str(vol_info.get("drive_serial_number", ""))
|
|
parsed["volume_label"] = str(vol_info.get("volume_label", ""))
|
|
|
|
string_data = info.get("string_data", {})
|
|
parsed["working_dir"] = str(string_data.get("working_dir", ""))
|
|
parsed["arguments"] = str(string_data.get("command_line_arguments", ""))
|
|
|
|
extra = info.get("extra", {})
|
|
tracker = extra.get("DISTRIBUTED_LINK_TRACKER_BLOCK", {})
|
|
if tracker:
|
|
parsed["machine_id"] = str(tracker.get("machine_id", ""))
|
|
parsed["mac_address"] = str(tracker.get("mac_address", ""))
|
|
|
|
return parsed
|
|
|
|
|
|
def parse_lnk_directory(directory):
|
|
"""Parse all LNK files in a directory."""
|
|
results = []
|
|
for filename in sorted(os.listdir(directory)):
|
|
if not filename.lower().endswith(".lnk"):
|
|
continue
|
|
filepath = os.path.join(directory, filename)
|
|
try:
|
|
parsed = parse_lnk_file(filepath)
|
|
results.append(parsed)
|
|
except Exception as e:
|
|
print(f" Error parsing {filename}: {e}")
|
|
return results
|
|
|
|
|
|
def filter_removable_media(results):
|
|
"""Filter LNK files that point to removable media."""
|
|
return [r for r in results if "removable" in r.get("drive_type", "").lower()]
|
|
|
|
|
|
def filter_network_shares(results):
|
|
"""Filter LNK files pointing to network shares."""
|
|
return [
|
|
r for r in results
|
|
if "network" in r.get("drive_type", "").lower()
|
|
or r.get("target_path", "").startswith("\\\\")
|
|
]
|
|
|
|
|
|
def detect_suspicious_startup(startup_dir):
|
|
"""Analyze Startup folder LNK files for potential persistence."""
|
|
suspicious = []
|
|
for filename in os.listdir(startup_dir):
|
|
if not filename.lower().endswith(".lnk"):
|
|
continue
|
|
filepath = os.path.join(startup_dir, filename)
|
|
try:
|
|
parsed = parse_lnk_file(filepath)
|
|
target = parsed["target_path"].lower()
|
|
args = parsed["arguments"].lower()
|
|
if any(s in target for s in ["temp", "appdata", "programdata", "public"]):
|
|
parsed["risk"] = "HIGH"
|
|
suspicious.append(parsed)
|
|
elif any(s in args for s in ["-enc", "powershell", "cmd /c", "wscript"]):
|
|
parsed["risk"] = "HIGH"
|
|
suspicious.append(parsed)
|
|
except Exception:
|
|
pass
|
|
return suspicious
|
|
|
|
|
|
def export_csv(results, output_path):
|
|
"""Export parsed LNK results to CSV."""
|
|
if not results:
|
|
return
|
|
with open(output_path, "w", newline="") as f:
|
|
writer = csv.DictWriter(f, fieldnames=results[0].keys())
|
|
writer.writeheader()
|
|
writer.writerows(results)
|
|
|
|
|
|
def extract_unique_machines(results):
|
|
"""Extract unique machine IDs and MAC addresses from LNK files."""
|
|
machines = {}
|
|
for r in results:
|
|
mid = r.get("machine_id", "")
|
|
mac = r.get("mac_address", "")
|
|
if mid:
|
|
machines[mid] = mac
|
|
return machines
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Windows LNK File Forensic Analysis Agent")
|
|
parser.add_argument("--lnk-dir", required=True, help="Directory containing LNK files")
|
|
parser.add_argument("--startup-dir", help="Startup folder to check for persistence")
|
|
parser.add_argument("--output-dir", default="./lnk_analysis")
|
|
parser.add_argument("--action", choices=[
|
|
"parse_all", "removable", "network", "startup", "machines", "full_analysis"
|
|
], default="full_analysis")
|
|
args = parser.parse_args()
|
|
|
|
os.makedirs(args.output_dir, exist_ok=True)
|
|
all_results = parse_lnk_directory(args.lnk_dir)
|
|
print(f"[+] Parsed {len(all_results)} LNK files")
|
|
|
|
if args.action in ("parse_all", "full_analysis"):
|
|
csv_path = os.path.join(args.output_dir, "lnk_analysis.csv")
|
|
export_csv(all_results, csv_path)
|
|
print(f"[+] Exported to {csv_path}")
|
|
|
|
if args.action in ("removable", "full_analysis"):
|
|
removable = filter_removable_media(all_results)
|
|
print(f"[+] Removable media files: {len(removable)}")
|
|
for r in removable:
|
|
print(f" {r['target_modified']} | {r['target_path']} | Vol: {r['volume_serial']}")
|
|
|
|
if args.action in ("network", "full_analysis"):
|
|
network = filter_network_shares(all_results)
|
|
print(f"[+] Network share files: {len(network)}")
|
|
|
|
if args.action in ("startup", "full_analysis") and args.startup_dir:
|
|
suspicious = detect_suspicious_startup(args.startup_dir)
|
|
print(f"[+] Suspicious startup LNK: {len(suspicious)}")
|
|
for s in suspicious:
|
|
print(f" [{s.get('risk')}] {s['lnk_file']} -> {s['target_path']}")
|
|
|
|
if args.action in ("machines", "full_analysis"):
|
|
machines = extract_unique_machines(all_results)
|
|
print(f"[+] Unique machines: {len(machines)}")
|
|
for mid, mac in machines.items():
|
|
print(f" Machine: {mid} | MAC: {mac}")
|
|
|
|
print(json.dumps({"total_lnk": len(all_results), "generated_at": datetime.utcnow().isoformat()}, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|