Una entrada sencilla de como tratar el registro de Windows con Python. Muy útil para comenzar a automatizar ciertas extracciones de datos cuando estamos haciendo el análisis forense. Es cierto que tenemos regripper incluso podemos hacer módulos personalizados. Pero os traigo otra manera de empezar a construir estas automatizaciones de un modo personalizado y usando todo el potencial que nos ofrece Python.
El registro
Cuando extraemos el registro de Windows de una máquina que queremos analizar, nos traemos el contenido de la carpeta:
C:\Windows\System32\config
--> SYSTEM → Hive de HKEY_LOCAL_MACHINE\SYSTEM
--> SOFTWARE → Hive de HKEY_LOCAL_MACHINE\SOFTWARE
--> SAM → Hive de HKEY_LOCAL_MACHINE\SAM
--> SECURITY → Hive de HKEY_LOCAL_MACHINE\SECURITY
--> DEFAULT → Hive de HKEY_USERS.DEFAULT
(Es cierto que también las carpetas del usuario tienen sus hive de registro, pero esto no es un artículo sobre el registro, sino sobre como tratarlo en Python, así que nos vamos a quedar solo con estos).
Subkeys y Values
El Registro de Windows es una base de datos jerárquica que centraliza la configuración del sistema, servicios y aplicaciones, organizada en “claves” (keys) y “valores” (values) dentro de cinco raíces lógicas como HKLM o HKCU
- Clave: contenedor equivalente a una carpeta. Puede contener:
- Subclaves: claves anidadas que heredan la estructura jerárquica y permiten segmentar la configuración por componentes, versiones o contextos
- Valores: entradas atómicas donde reside la configuración efectiva
El valor es donde está el dato. Cada valor posee un nombre, un tipo de dato (p. ej., REG_SZ, REG_DWORD, REG_BINARY, REG_MULTI_SZ) y un contenido; a diferencia de las claves, los valores no contienen otros elementos, solo datos.
ChatGPT nos lo quiere representar así (creo que se entiende el concepto). Las carpetas son las claves y subclaves creando una jerarquía donde almacenar los valores que son los que contienen datos.
Esto es importante a la hora de poder entender el código que veremos a continuación.
Como procesarlo con Python
Las dos librerías más usadas para procesar el registro son:
Para instalar cualquiera de ella ya sabéis:
python -m pip install regipy
python -m pip install python-registry
Vamos a centrarnos en regipy que bajo mi punto de vista es la más potente y sencilla de usar.
regipy
Esta es la librería que yo os recomiendo para la mayoría de las tareas. Veamos un ejemplo básico de como crear un lector de la clave CurrentVersion/Run que tanto usamos en forense:
from regipy.registry import RegistryHive
from pathlib import Path
regPath = Path("sampleData/windowsRegistry/PropiaVM/SOFTWARE")
reg = RegistryHive(str(regPath))
k = reg.get_key(r"Software\Microsoft\Windows\CurrentVersion\Run")
vals = list(k.iter_values())
for v in vals:
nombre = v.name or "(Default)"
print(f" (VAL) {nombre} = {v.value!r}")
Algo super sencillo. Evidentemente, no estamos haciendo control de errores y solo estamos leyendo los valores, no las subkeys. Podemos darle una vuelta de tuerca.
Con un par de funciones podemos hacer un lector de claves que imprima tanto las subclaves como los valores de una clave dada.
from regipy.registry import RegistryHive
from pathlib import Path
def print_key_values(reg: RegistryHive, key: str):
# Obtain key
try:
k = reg.get_key(key)
except Exception as e:
print(f"[NO EXISTE] SOFTWARE\\{key} -> {e}")
raise ValueError("key not found")
# Obtain values
vals = list(k.iter_values())
if vals:
for v in vals:
nombre = v.name or "(Default)"
print(f" (VAL) {nombre} = {v.value!r}")
else:
print(" (sin valores)")
def print_key_subkeys(reg: RegistryHive, key: str):
# Obtain key
try:
k = reg.get_key(key)
except Exception as e:
print(f"[NO EXISTE] SOFTWARE\\{key} -> {e}")
raise ValueError("key not found")
# Obtain subkeys
subs = list(k.iter_subkeys())
if subs:
for s in subs:
print(f" (KEY) {s.name}")
else:
print(" (sin subclaves)")
def print_key_all(reg: RegistryHive, key: str):
print("Subkeys for " + key)
print_key_subkeys(reg, key)
print("Values for " + key)
print_key_values(reg, key)
# MAIN for testing purpose
if __name__ == "__main__":
# Test with sample data - System DC Transgruma
regPath = Path("sampleData/windowsRegistry/PropiaVM/SOFTWARE")
reg = RegistryHive(str(regPath))
print_key_all(reg, r"Software\Microsoft\Windows\CurrentVersion\Run")
Una clase para importar
Os dejo una pequeña clase que cree en un proyecto mayor que me ayuda al proceso del registro:
- Carga los archivos del registro de Windows (hives) desde una carpeta.
- Usa Regipy para abrir y leer esos archivos.
- Permite buscar una clave del registro escribiéndola en formato normal, como HKLM\SYSTEM.…
- Limpia la clave para saber en qué hive está y navegar dentro de él.
- Muestra el contenido: valores y subclaves que hay en esa clave.
- Imprime una tabla con todos los hives cargados para ver qué se ha encontrado.
from pathlib import Path
from typing import Dict, Any, Optional
from regipy.registry import RegistryHive
from tabulate import tabulate
import re
class WindowsRegistry():
# ------ ATTRIBS ------
# Posible roots in registry keys
reg_roots = [
"HKEY_LOCAL_MACHINE", "HKLM",
"HKEY_CURRENT_USER", "HKCU",
"HKEY_CLASSES_ROOT", "HKCR",
"HKEY_USERS", "HKU",
"HKEY_CURRENT_CONFIG", "HKCC",
"HKEY_PERFORMANCE_DATA", "HKPD",
"HKEY_DYN_DATA", "HKDD",
]
# ------ INIT methods ------
def __init__(self, folder_hives: Optional[Path] = None):
# Initialize dict with registry hives - path + regipy object
self.hives: Dict[str, Dict[str, Any]] = {
"SYSTEM": {"path": None, "regipy": None},
"SECURITY": {"path": None, "regipy": None},
"SOFTWARE": {"path": None, "regipy": None},
"SAM": {"path": None, "regipy": None},
"DEFAULT": {"path": None, "regipy": None},
}
# If no folder --> Create empty object
if folder_hives is None:
return
# If folder --> Check
base_path = Path(folder_hives).expanduser().resolve()
if not base_path.exists():
raise ValueError(
f"Folder with hives does not exist: {base_path}")
if not base_path.is_dir():
raise ValueError(
f"Folder with hives is not a directory: {base_path}")
# here folder is defined and is ok
self.find_hives_in_folder(base_path)
# Create regipy objects
self.create_regipy()
# ------ Registry queries ------
def registry_query(self, key: str):
# Normalize key
norm_key, hive_name = self.normalize_key(key)
# TODO: Debug
print(f"[+] Search key '{norm_key}' in hive {hive_name}")
# Check if regipy is defined
if not self.hives[hive_name]["regipy"]:
raise ValueError(
f"Not exists or regipy not created for: {hive_name}")
# Search key
try:
query_res = self.hives[hive_name]["regipy"].get_key(norm_key)
except Exception as e:
# TODO: Debug
print(f"[ERROR] Key not found '{norm_key}' -> {e}")
print(f"[NO EXISTE] SOFTWARE\\{key} -> {e}")
raise ValueError("key not found")
# Print result
print(f"[+] Content of {norm_key}")
self.print_key(query_res)
def normalize_key(self, key: str):
"""Take a key and normalize it
Args:
key (str): Registry key
"""
# Procees key
# TODO: Debug
print(f"[+] Key to search '{key}'")
# Normalize key
norm_key = key.strip().strip('"').replace('/', '\\')
norm_key = norm_key.upper()
# Remove roots: HKLM and others...
pattern = re.compile(
r'^(?:' + '|'.join(map(re.escape, self.reg_roots)) + r')(?:\\|/)+',
flags=re.IGNORECASE)
norm_key = pattern.sub('', norm_key, count=1)
norm_key = norm_key.lstrip('\\')
# TODO: Debug
print(f"[+] Normalized key '{norm_key}'")
# Search wich hive
# Split by \\ and take first elemn
segments = [s for s in norm_key.split("\\") if s]
if not segments:
raise ValueError(f"Invalid key: {norm_key}")
hive_name = segments[0]
return norm_key, hive_name
# ------ Registry parsing ------
def create_regipy(self):
"""Create regipy object for the hives in Dict
"""
for hive_name, hive_data in self.hives.items():
path = hive_data["path"]
if path and Path(path).exists():
try:
hive_data["regipy"] = RegistryHive(str(path))
# TODO: Debug
print(f"[+] Hive '{hive_name}' loaded with Regipy")
except Exception as e:
# TODO: Debug
print(f"[!] Error loading hive '{hive_name}': {e}")
else:
# TODO: Debug
print(f"[-] Hive '{hive_name}' has invalid path")
# ------ Files and folder management ------
def find_hives_in_folder(self, folder_hives: Path):
"""Search in a folder for all registry hives based on hives dict
Args:
folder_hives (Path): Folder to search for hives
"""
# Check folder
if not folder_hives.is_dir():
raise ValueError(f"Is not a folder: {folder_hives}")
# For each file in folder
for file in folder_hives.iterdir():
if file.is_file() and file.name in self.hives.keys():
# TODO: Debug
print("Registry hive found: " + str(file))
self.hives[file.name]["path"] = file
# ------ PRINTING methods ------
def print_hives_table(self):
"""Print registry hives dict in table format
"""
table = []
for hive, datos in self.hives.items():
table.append([hive, datos["path"], datos["regipy"]])
print(tabulate(table, headers=["Hive", "Path", "Regipy"], tablefmt="fancy_grid"))
def print_key(self, key_res):
# Obtain values
vals = list(key_res.iter_values())
# Obtain subkeys
subs = list(key_res.iter_subkeys())
# PRINT VALS
print(" --> VALUES")
if vals:
for v in vals:
nombre = v.name or "(Default)"
print(f" (VAL) {nombre} = {v.value!r}")
else:
print(" (no values)")
# PRINT SUBKEYS
print(" --> SUBKEYS")
if subs:
for s in subs:
print(f" (SKEY) {s.name}")
else:
print(" (no subkeys)")
# MAIN for testing purpose
if __name__ == "__main__":
# Test with sample data
folder_hives = Path("[PATH A CARPETA CON HIVES]")
reg = WindowsRegistry(folder_hives=folder_hives)
reg.print_hives_table()
reg.registry_query("HKLM\\SYSTEM\\ControlSet001\\Services\\LanmanServer\\Shares")
Conclusiones
Espero que os sirva. A automatizar.
Os dejo una entrevista que me hicieron sobre el valor de la automatización en la respuesta ante incidentes: