"""Windows-Konten ueber die SAM-Hive bearbeiten (via chntpw). Verlaesslich moeglich: Passwort blanken, Konto entsperren/aktivieren, Benutzer zum Administrator machen. Ein *neues* Passwort direkt zu schreiben ist auf modernen Windows-Versionen unzuverlaessig (SYSKEY / geaenderte Hash-Formate), deshalb bietet dieses Tool das bewusst nicht an -- der sichere Weg ist blanken und das Passwort danach in Windows selbst neu zu vergeben. """ from __future__ import annotations import os import re import shutil import subprocess from dataclasses import dataclass @dataclass class WindowsUser: rid: str name: str is_admin: bool is_locked: bool is_disabled: bool pw_blank: bool @property def display(self) -> str: flags = [] if self.is_admin: flags.append("Admin") if self.is_locked: flags.append("gesperrt") if self.is_disabled: flags.append("deaktiviert") if self.pw_blank: flags.append("kein PW") tail = f" ({', '.join(flags)})" if flags else "" return f"{self.name} [RID {self.rid}]{tail}" def _config_dir(mnt: str) -> str: for cfg in ("Windows/System32/config", "WINDOWS/system32/config"): p = os.path.join(mnt, cfg) if os.path.exists(os.path.join(p, "SAM")): return p raise RuntimeError("SAM-Hive nicht gefunden (kein Windows auf dieser Partition?)") def _sam(mnt: str) -> str: return os.path.join(_config_dir(mnt), "SAM") def list_users(mnt: str) -> list[WindowsUser]: """Benutzer aus der SAM lesen (chntpw -l).""" sam = _sam(mnt) res = subprocess.run(["chntpw", "-l", sam], capture_output=True, text=True) users: list[WindowsUser] = [] # Zeilen: | 01f4 | Administrator | ADMIN | dis/lock | row = re.compile(r"\|\s*([0-9a-fA-F]+)\s*\|\s*(.+?)\s*\|\s*(ADMIN)?\s*\|\s*(.*?)\s*\|") for line in res.stdout.splitlines(): m = row.match(line) if not m: continue rid, name, admin, status = m.groups() status = (status or "").lower() users.append(WindowsUser( rid=rid, name=name, is_admin=bool(admin), is_locked="lock" in status, is_disabled="dis" in status, pw_blank="*blank*" in status or "blank" in status, )) if not users: raise RuntimeError("Keine Benutzer in der SAM gelesen.\n" + (res.stderr.strip() or res.stdout.strip())) return users def _drive(mnt: str, username: str, menu_choices: str) -> None: """chntpw interaktiv fuer einen Benutzer steuern. menu_choices: Folge der Menuepunkte (z.B. '1' blanken, '2' entsperren, '3' Admin). Anschliessend 'q' (Benutzer verlassen) und 'y' (Hive schreiben). """ sam = _sam(mnt) shutil.copy2(sam, sam + ".puk.bak") # chntpw liest pro Zeile eine Eingabe. Nach den Auswahlpunkten 'q' und # zum Schluss 'y' zum Speichern der Aenderungen. stdin = "".join(c + "\n" for c in menu_choices) + "q\ny\n" res = subprocess.run( ["chntpw", "-u", username, sam], input=stdin, capture_output=True, text=True, ) low = (res.stdout + res.stderr).lower() if "error" in low and "writing" in low: raise RuntimeError(res.stdout + res.stderr) # chntpw gibt bei Erfolg typ. "Hives that have changed:" / "writing sam" if res.returncode != 0 and "edit complete" not in low: raise RuntimeError(res.stdout + res.stderr or "chntpw fehlgeschlagen") def blank_password(mnt: str, username: str) -> None: _drive(mnt, username, "1") def unlock(mnt: str, username: str) -> None: _drive(mnt, username, "2") def promote_admin(mnt: str, username: str) -> None: _drive(mnt, username, "3")