Files
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

184 lines
6.0 KiB
Python

#!/usr/bin/env python3
"""Windows ShellBag artifact analysis agent.
Parses ShellBag registry artifacts to reconstruct folder access history,
directory browsing patterns, and evidence of accessed network shares.
"""
import os
import sys
import json
import struct
import hashlib
import datetime
from collections import defaultdict
try:
import Registry
HAS_REGISTRY = True
except ImportError:
try:
from regipy.registry import RegistryHive
HAS_REGIPY = True
HAS_REGISTRY = False
except ImportError:
HAS_REGISTRY = False
HAS_REGIPY = False
SHELLBAG_PATHS = {
'ntuser': [
r'Software\Microsoft\Windows\Shell\BagMRU',
r'Software\Microsoft\Windows\Shell\Bags',
r'Software\Microsoft\Windows\ShellNoRoam\BagMRU',
r'Software\Microsoft\Windows\ShellNoRoam\Bags',
],
'usrclass': [
r'Local Settings\Software\Microsoft\Windows\Shell\BagMRU',
r'Local Settings\Software\Microsoft\Windows\Shell\Bags',
],
}
def filetime_to_datetime(filetime):
if not filetime or filetime == 0:
return None
try:
epoch = datetime.datetime(1601, 1, 1)
delta = datetime.timedelta(microseconds=filetime // 10)
return (epoch + delta).isoformat() + 'Z'
except (OverflowError, OSError):
return None
def parse_shell_item(data):
if len(data) < 2:
return None
item_size = struct.unpack_from('<H', data, 0)[0]
if item_size < 2 or item_size > len(data):
return None
item_type = data[2] if len(data) > 2 else 0
result = {'size': item_size, 'type': hex(item_type)}
if item_type == 0x1F:
result['class'] = 'Root Folder (GUID)'
if len(data) >= 18:
guid = data[4:20].hex()
result['guid'] = guid
elif item_type in (0x31, 0x32, 0x35):
result['class'] = 'File Entry'
if len(data) > 14:
file_size = struct.unpack_from('<I', data, 4)[0]
result['file_size'] = file_size
name_offset = 14
if name_offset < len(data):
name_end = data.find(b'\x00', name_offset)
if name_end > name_offset:
result['short_name'] = data[name_offset:name_end].decode('ascii', errors='replace')
elif item_type in (0x41, 0x42, 0x46, 0x47):
result['class'] = 'Network Location'
if len(data) > 5:
name_start = 5
name_end = data.find(b'\x00', name_start)
if name_end > name_start:
result['network_path'] = data[name_start:name_end].decode('ascii', errors='replace')
elif item_type == 0x71:
result['class'] = 'Control Panel'
else:
result['class'] = 'Unknown'
return result
def parse_bagmru_value(data):
items = []
offset = 0
while offset < len(data) - 2:
item_size = struct.unpack_from('<H', data, offset)[0]
if item_size == 0:
break
item_data = data[offset:offset + item_size]
parsed = parse_shell_item(item_data)
if parsed:
items.append(parsed)
offset += item_size
return items
def analyze_shellbags_regipy(hive_path):
if not HAS_REGIPY:
return []
hive = RegistryHive(hive_path)
results = []
for path_group in SHELLBAG_PATHS.values():
for reg_path in path_group:
try:
key = hive.get_key(reg_path)
if key:
for value in key.iter_values():
if isinstance(value.value, bytes):
items = parse_bagmru_value(value.value)
for item in items:
item['registry_path'] = reg_path
item['value_name'] = value.name
results.append(item)
except Exception:
continue
return results
def detect_suspicious_paths(shellbag_entries):
findings = []
suspicious_indicators = [
('\\', 'UNC path access (network share)'),
('temp', 'Temp directory access'),
('appdata', 'AppData directory (persistence location)'),
('recycle', 'Recycle Bin access'),
('usb', 'USB device path'),
('removable', 'Removable media'),
('.tor', 'Tor browser directory'),
('sysinternals', 'Sysinternals tools directory'),
('mimikatz', 'Mimikatz tool directory'),
('powershell', 'PowerShell directory'),
]
for entry in shellbag_entries:
path = (entry.get('short_name', '') + ' ' + entry.get('network_path', '')).lower()
for pattern, description in suspicious_indicators:
if pattern in path:
findings.append({
'type': 'suspicious_path',
'path': entry.get('short_name', entry.get('network_path', '')),
'indicator': description,
'severity': 'HIGH' if 'mimikatz' in pattern else 'MEDIUM',
})
break
return findings
if __name__ == '__main__':
print('=' * 60)
print('Windows ShellBag Artifact Analysis Agent')
print('Registry parsing, folder history, network share detection')
print('=' * 60)
target = sys.argv[1] if len(sys.argv) > 1 else None
if not target or not os.path.exists(target):
print('\n[DEMO] Usage: python agent.py <NTUSER.DAT|UsrClass.dat>')
print(f' regipy available: {HAS_REGIPY if not HAS_REGISTRY else False}')
print(f' python-registry available: {HAS_REGISTRY}')
sys.exit(0)
print(f'\n[*] Analyzing: {target}')
entries = analyze_shellbags_regipy(target)
print(f'[*] ShellBag entries: {len(entries)}')
print('\n--- Folder Access History ---')
for e in entries[:20]:
name = e.get('short_name', e.get('network_path', e.get('guid', '?')))
print(f' [{e["class"]:20s}] {name}')
findings = detect_suspicious_paths(entries)
print(f'\n--- Suspicious Paths ({len(findings)}) ---')
for f in findings[:10]:
print(f' [{f["severity"]}] {f["indicator"]}: {f["path"]}')