Files
MASForensic/tools/registry.py
BattleTag 097d2ce472 Initial commit
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 17:36:26 +08:00

450 lines
17 KiB
Python

"""Windows registry parsing tools."""
from __future__ import annotations
import logging
import struct
from datetime import datetime, timedelta, timezone
logger = logging.getLogger(__name__)
# Suppress noisy regipy warnings (hive-type identification + binary encoding fallbacks)
logging.getLogger("regipy.registry").setLevel(logging.WARNING)
logging.getLogger("regipy.utils").setLevel(logging.ERROR)
async def parse_registry_key(hive_path: str, key_path: str = "") -> str:
"""Parse a registry hive and list subkeys/values at the given path.
Uses regipy for pure-Python registry parsing.
"""
try:
from regipy.registry import RegistryHive
except ImportError:
return "[Error: regipy not installed. Run: uv add regipy]"
try:
reg = RegistryHive(hive_path)
if key_path:
key = reg.get_key(key_path)
else:
key = reg.root_key()
lines = [f"Key: {key.path}", f"Timestamp: {key.header.last_modified}", ""]
# Subkeys
subkeys = list(key.iter_subkeys())
if subkeys:
lines.append(f"Subkeys ({len(subkeys)}):")
for sk in subkeys[:50]:
lines.append(f" {sk.name}")
if len(subkeys) > 50:
lines.append(f" ... ({len(subkeys) - 50} more)")
lines.append("")
# Values
values = list(key.iter_values())
if values:
lines.append(f"Values ({len(values)}):")
for v in values[:30]:
val_data = str(v.value)
if len(val_data) > 200:
val_data = val_data[:200] + "..."
lines.append(f" {v.name} ({v.value_type}) = {val_data}")
return "\n".join(lines)
except Exception as e:
return f"[Error parsing registry: {e}]"
async def list_installed_software(hive_path: str) -> str:
"""List installed software from a SOFTWARE registry hive."""
try:
from regipy.registry import RegistryHive
except ImportError:
return "[Error: regipy not installed]"
try:
reg = RegistryHive(hive_path)
uninstall_path = "\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
key = reg.get_key(uninstall_path)
programs = []
for sk in key.iter_subkeys():
name = sk.name
display_name = None
for v in sk.iter_values():
if v.name == "DisplayName":
display_name = v.value
break
programs.append(display_name or name)
lines = [f"Installed Software ({len(programs)} entries):", ""]
for p in sorted(programs):
lines.append(f" - {p}")
return "\n".join(lines)
except Exception as e:
return f"[Error listing software: {e}]"
async def get_user_activity(hive_path: str) -> str:
"""Extract user activity indicators from NTUSER.DAT."""
try:
from regipy.registry import RegistryHive
except ImportError:
return "[Error: regipy not installed]"
try:
reg = RegistryHive(hive_path)
lines = ["=== User Activity from NTUSER.DAT ===", ""]
# Recent documents
try:
key = reg.get_key("\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RecentDocs")
lines.append("Recent Documents:")
for v in key.iter_values():
if v.name != "MRUListEx":
lines.append(f" {v.name}")
lines.append("")
except Exception:
lines.append("Recent Documents: [not found]")
# Run MRU (commands typed in Run dialog)
try:
key = reg.get_key("\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU")
lines.append("Run Dialog MRU:")
for v in key.iter_values():
if v.name not in ("MRUList",):
lines.append(f" {v.name}: {v.value}")
lines.append("")
except Exception:
lines.append("Run MRU: [not found]")
# Typed URLs
try:
key = reg.get_key("\\Software\\Microsoft\\Internet Explorer\\TypedURLs")
lines.append("Typed URLs:")
for v in key.iter_values():
lines.append(f" {v.value}")
lines.append("")
except Exception:
lines.append("Typed URLs: [not found]")
return "\n".join(lines)
except Exception as e:
return f"[Error analyzing user activity: {e}]"
def _filetime_to_datetime(ft: int) -> str:
"""Convert a Windows FILETIME (100-nanosecond intervals since 1601-01-01) to ISO string."""
if ft <= 0:
return "(not set)"
try:
epoch = datetime(1601, 1, 1, tzinfo=timezone.utc)
dt = epoch + timedelta(microseconds=ft // 10)
return dt.strftime("%Y-%m-%d %H:%M:%S UTC")
except (ValueError, OverflowError):
return f"(invalid FILETIME: {ft})"
async def get_system_info(software_hive_path: str) -> str:
"""Extract OS version, install date, registered owner from SOFTWARE hive."""
try:
from regipy.registry import RegistryHive
except ImportError:
return "[Error: regipy not installed]"
try:
reg = RegistryHive(software_hive_path)
key = reg.get_key("\\Microsoft\\Windows NT\\CurrentVersion")
data = {}
for v in key.iter_values():
data[v.name] = v.value
lines = ["=== System Information (SOFTWARE hive) ==="]
lines.append(f"Product Name: {data.get('ProductName', 'N/A')}")
lines.append(f"Current Version: {data.get('CurrentVersion', 'N/A')}")
lines.append(f"Build Number: {data.get('CurrentBuildNumber', 'N/A')}")
lines.append(f"CSD Version (Service Pack): {data.get('CSDVersion', 'None')}")
lines.append(f"Registered Owner: {data.get('RegisteredOwner', 'N/A')}")
lines.append(f"Registered Organization: {data.get('RegisteredOrganization', 'N/A')}")
lines.append(f"Product ID: {data.get('ProductId', 'N/A')}")
lines.append(f"System Root: {data.get('SystemRoot', 'N/A')}")
install_epoch = data.get("InstallDate")
if install_epoch and isinstance(install_epoch, int):
install_dt = datetime.fromtimestamp(install_epoch, tz=timezone.utc)
lines.append(f"Install Date: {install_dt.strftime('%Y-%m-%d %H:%M:%S UTC')} (epoch: {install_epoch})")
else:
lines.append(f"Install Date: {install_epoch}")
return "\n".join(lines)
except Exception as e:
return f"[Error: {e}]"
async def get_timezone_info(system_hive_path: str) -> str:
"""Extract timezone settings from SYSTEM hive."""
try:
from regipy.registry import RegistryHive
except ImportError:
return "[Error: regipy not installed]"
try:
reg = RegistryHive(system_hive_path)
key = reg.get_key("\\ControlSet001\\Control\\TimeZoneInformation")
data = {}
for v in key.iter_values():
data[v.name] = v.value
lines = ["=== Timezone Information (SYSTEM hive) ==="]
lines.append(f"Standard Name: {data.get('StandardName', 'N/A')}")
lines.append(f"Daylight Name: {data.get('DaylightName', 'N/A')}")
bias = data.get("Bias", "N/A")
if isinstance(bias, int):
hours = bias // 60
lines.append(f"Bias: {bias} minutes (UTC{-hours:+d}:00)")
else:
lines.append(f"Bias: {bias}")
lines.append(f"Active Time Bias: {data.get('ActiveTimeBias', 'N/A')}")
return "\n".join(lines)
except Exception as e:
return f"[Error: {e}]"
async def get_computer_name(system_hive_path: str) -> str:
"""Extract computer name from SYSTEM hive."""
try:
from regipy.registry import RegistryHive
except ImportError:
return "[Error: regipy not installed]"
try:
reg = RegistryHive(system_hive_path)
lines = ["=== Computer Name (SYSTEM hive) ==="]
for path_label, path in [
("ComputerName", "\\ControlSet001\\Control\\ComputerName\\ComputerName"),
("ActiveComputerName", "\\ControlSet001\\Control\\ComputerName\\ActiveComputerName"),
]:
try:
key = reg.get_key(path)
for v in key.iter_values():
if v.name == "ComputerName":
lines.append(f"{path_label}: {v.value}")
except Exception:
pass
# Also try Tcpip hostname
try:
key = reg.get_key("\\ControlSet001\\Services\\Tcpip\\Parameters")
for v in key.iter_values():
if v.name in ("Hostname", "Domain", "NV Hostname"):
lines.append(f"TCP/IP {v.name}: {v.value}")
except Exception:
pass
return "\n".join(lines) if len(lines) > 1 else "Computer name not found in SYSTEM hive."
except Exception as e:
return f"[Error: {e}]"
async def get_shutdown_time(system_hive_path: str) -> str:
"""Extract last shutdown time from SYSTEM hive."""
try:
from regipy.registry import RegistryHive
except ImportError:
return "[Error: regipy not installed]"
try:
reg = RegistryHive(system_hive_path)
lines = ["=== Shutdown Time (SYSTEM hive) ==="]
try:
key = reg.get_key("\\ControlSet001\\Control\\Windows")
for v in key.iter_values():
if v.name == "ShutdownTime":
raw = v.value
if isinstance(raw, bytes) and len(raw) >= 8:
ft = struct.unpack("<Q", raw[:8])[0]
lines.append(f"Last Shutdown: {_filetime_to_datetime(ft)}")
elif isinstance(raw, int):
lines.append(f"Last Shutdown: {_filetime_to_datetime(raw)}")
elif isinstance(raw, str):
# regipy may return hex-encoded string for REG_BINARY
try:
raw_bytes = bytes.fromhex(raw)
ft = struct.unpack("<Q", raw_bytes[:8])[0]
lines.append(f"Last Shutdown: {_filetime_to_datetime(ft)}")
except (ValueError, struct.error):
lines.append(f"ShutdownTime (raw): {raw!r}")
else:
lines.append(f"ShutdownTime (raw): {raw!r}")
except Exception:
lines.append("ShutdownTime value not found at ControlSet001\\Control\\Windows")
# Also show all values from the Windows key for context
try:
key = reg.get_key("\\ControlSet001\\Control\\Windows")
lines.append("\nAll values at ControlSet001\\Control\\Windows:")
for v in key.iter_values():
lines.append(f" {v.name} = {v.value}")
except Exception:
pass
return "\n".join(lines)
except Exception as e:
return f"[Error: {e}]"
async def enumerate_users(sam_hive_path: str) -> str:
"""Enumerate all user accounts from SAM hive."""
try:
from regipy.registry import RegistryHive
except ImportError:
return "[Error: regipy not installed]"
try:
reg = RegistryHive(sam_hive_path)
key = reg.get_key("\\SAM\\Domains\\Account\\Users\\Names")
accounts = []
for sk in key.iter_subkeys():
accounts.append(sk.name)
lines = [f"=== User Accounts (SAM hive) — {len(accounts)} total ==="]
for acct in accounts:
lines.append(f" - {acct}")
# Try to get RIDs from the Users key
try:
users_key = reg.get_key("\\SAM\\Domains\\Account\\Users")
rid_entries = []
for sk in users_key.iter_subkeys():
if sk.name != "Names" and sk.name.startswith("0"):
rid = int(sk.name, 16)
rid_entries.append(f" RID {rid} (0x{sk.name})")
if rid_entries:
lines.append("\nUser RIDs:")
lines.extend(rid_entries)
except Exception:
pass
return "\n".join(lines)
except Exception as e:
return f"[Error: {e}]"
async def get_network_interfaces(system_hive_path: str) -> str:
"""Extract network adapter and TCP/IP configuration from SYSTEM hive."""
try:
from regipy.registry import RegistryHive
except ImportError:
return "[Error: regipy not installed]"
try:
reg = RegistryHive(system_hive_path)
lines = ["=== Network Interfaces (SYSTEM hive) ==="]
# Try TCP/IP interfaces
try:
key = reg.get_key("\\ControlSet001\\Services\\Tcpip\\Parameters\\Interfaces")
for sk in key.iter_subkeys():
lines.append(f"\nInterface: {sk.name}")
for v in sk.iter_values():
if v.name in (
"IPAddress", "SubnetMask", "DefaultGateway",
"DhcpIPAddress", "DhcpSubnetMask", "DhcpDefaultGateway",
"DhcpServer", "NameServer", "Domain", "EnableDHCP",
):
lines.append(f" {v.name} = {v.value}")
except Exception as e:
lines.append(f"TCP/IP Interfaces: {e}")
# Try network adapter class
adapter_class = "\\ControlSet001\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002bE10318}"
try:
key = reg.get_key(adapter_class)
lines.append("\nNetwork Adapters:")
for sk in key.iter_subkeys():
if sk.name == "Properties":
continue
desc = None
for v in sk.iter_values():
if v.name == "DriverDesc":
desc = v.value
if desc:
lines.append(f" [{sk.name}] {desc}")
except Exception as e:
lines.append(f"Network Adapters: {e}")
# Try NetworkCards
try:
key = reg.get_key("\\ControlSet001\\Control\\NetworkCards")
for sk in key.iter_subkeys():
for v in sk.iter_values():
if v.name == "Description":
lines.append(f" NetworkCard {sk.name}: {v.value}")
except Exception:
pass
return "\n".join(lines) if len(lines) > 1 else "No network interface data found in SYSTEM hive."
except Exception as e:
return f"[Error: {e}]"
async def get_email_config(ntuser_hive_path: str) -> str:
"""Extract email account configuration (SMTP, POP3, NNTP) from NTUSER.DAT."""
try:
from regipy.registry import RegistryHive
except ImportError:
return "[Error: regipy not installed]"
try:
reg = RegistryHive(ntuser_hive_path)
lines = ["=== Email Account Configuration (NTUSER.DAT) ==="]
try:
key = reg.get_key("\\Software\\Microsoft\\Internet Account Manager\\Accounts")
for sk in key.iter_subkeys():
lines.append(f"\n--- Account: {sk.name} ---")
for v in sk.iter_values():
# Skip binary password hash fields (but keep "Prompt for Password" flags)
if "Password" in v.name and "Prompt" not in v.name:
lines.append(f" {v.name} = [present, redacted]")
else:
lines.append(f" {v.name} = {v.value}")
except Exception as e:
lines.append(f"Internet Account Manager: {e}")
return "\n".join(lines)
except Exception as e:
return f"[Error: {e}]"
async def search_registry(hive_path: str, pattern: str) -> str:
"""Search for a pattern in registry key names and values."""
try:
from regipy.registry import RegistryHive
except ImportError:
return "[Error: regipy not installed]"
try:
reg = RegistryHive(hive_path)
pattern_lower = pattern.lower()
matches = []
for entry in reg.recurse_subkeys(as_json=True):
path = entry.path or ""
if pattern_lower in path.lower():
matches.append(f"KEY: {path}")
if hasattr(entry, "values") and entry.values:
for v in entry.values:
name = v.get("name", "")
value = str(v.get("value", ""))
if pattern_lower in name.lower() or pattern_lower in value.lower():
matches.append(f" {path}\\{name} = {value[:200]}")
if len(matches) >= 50:
matches.append(f"[Truncated: more than 50 matches for '{pattern}']")
break
if not matches:
return f"No registry entries matching '{pattern}' found."
return "\n".join(matches)
except Exception as e:
return f"[Error searching registry: {e}]"