Files
T
mukul975 c47eed6a64 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
2026-03-19 13:26:49 +01:00

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()