Initial commit
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
449
tools/registry.py
Normal file
449
tools/registry.py
Normal file
@@ -0,0 +1,449 @@
|
||||
"""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}]"
|
||||
Reference in New Issue
Block a user