Procesar el registro de Windows con Python

por Alberto Jódar
0 comentario 12 minutos lectura

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:

Artículos relacionados

Deja un comentario

* Al utilizar este formulario usted acepta el almacenamiento y tratamiento de sus datos por parte de este sitio web.

Este sitio web utiliza cookies para mejorar su experiencia Aceptar Leer más