#!/usr/bin/env python3 """Windows LNK file and Jump List artifact analysis agent. Parses Windows Shell Link (.lnk) files and Jump List artifacts to extract file access evidence, program execution history, and user activity timelines. Uses LnkParse3 for binary parsing and supports LECmd/JLECmd CSV output analysis. """ import struct import os import sys import json import hashlib import datetime import re import glob as glob_mod try: import LnkParse3 HAS_LNKPARSE = True except ImportError: HAS_LNKPARSE = False 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 parse_lnk_with_lnkparse3(filepath): """Parse LNK file using LnkParse3 library.""" if not HAS_LNKPARSE: return {"error": "LnkParse3 not installed. pip install LnkParse3"} with open(filepath, "rb") as f: lnk = LnkParse3.lnk_file(f) info = lnk.get_json() result = { "target_path": info.get("data", {}).get("relative_path", ""), "working_dir": info.get("data", {}).get("working_directory", ""), "arguments": info.get("data", {}).get("command_line_arguments", ""), "icon_location": info.get("data", {}).get("icon_location", ""), "description": info.get("data", {}).get("description", ""), } header = info.get("header", {}) result["creation_time"] = header.get("creation_time", "") result["access_time"] = header.get("access_time", "") result["write_time"] = header.get("write_time", "") result["file_size"] = header.get("file_size", 0) result["file_flags"] = header.get("file_attributes", "") link_info = info.get("link_info", {}) if link_info: result["local_base_path"] = link_info.get("local_base_path", "") result["volume_serial"] = link_info.get("volume_serial_number", "") result["volume_label"] = link_info.get("volume_label", "") result["drive_type"] = link_info.get("drive_type", "") extra = info.get("extra", {}) if extra: tracker = extra.get("DISTRIBUTED_LINK_TRACKER_BLOCK", {}) if tracker: result["machine_id"] = tracker.get("machine_id", "") result["mac_address"] = tracker.get("mac_address", "") result["droid_volume_id"] = tracker.get("droid_volume_identifier", "") result["droid_file_id"] = tracker.get("droid_file_identifier", "") return result def parse_lnk_header_raw(filepath): """Parse LNK file header manually from raw bytes.""" with open(filepath, "rb") as f: data = f.read() if len(data) < 76: return {"error": "File too small for LNK header"} # Shell Link Header (76 bytes) header_size = struct.unpack_from(" # Analyze single LNK") print(" python agent.py # Scan directory for LNK/JumpList") print(f"\n LnkParse3 available: {HAS_LNKPARSE}") sys.exit(0) if os.path.isfile(target) and target.lower().endswith(".lnk"): print(f"\n[*] Analyzing: {target}") print(f"[*] SHA-256: {compute_hash(target)}") if HAS_LNKPARSE: parsed = parse_lnk_with_lnkparse3(target) else: parsed = parse_lnk_header_raw(target) print("\n--- LNK Properties ---") for k, v in parsed.items(): print(f" {k}: {v}") suspicious = detect_suspicious_lnk(parsed) if suspicious: print("\n--- Suspicious Indicators ---") for s in suspicious: print(f" [!] {s['indicator']}") elif os.path.isdir(target): print(f"\n[*] Scanning directory: {target}") lnk_results = scan_lnk_directory(target) print(f"[*] Found {len(lnk_results)} LNK files") for r in lnk_results[:20]: print(f" {r['file']}: {r['parsed'].get('target_path', r['parsed'].get('local_base_path', '?'))}") for s in r.get("suspicious", []): print(f" [!] {s['indicator']}") jl_dir = os.path.join(target, "AutomaticDestinations") if not os.path.isdir(jl_dir): jl_dir = target jl_results = scan_jump_lists(jl_dir) if jl_results: print(f"\n--- Jump Lists ({len(jl_results)}) ---") for jl in jl_results: print(f" {jl['app_name']:30s} [{jl['type']}] {jl['app_id']}") print(f"\n{json.dumps({'lnk_count': len(lnk_results) if os.path.isdir(target) else 1}, indent=2)}")