SkillAgentSearch skills...

KvcForensic

Windows LSA credential extractor for lsass.dmp minidumps. Targets Windows 11 24H2/25H2 and Windows Server 2025. Pure Win32, no DbgHelp, no dependencies. Extracts MSV, WDigest, Kerberos, CredMan, DPAPI. AES-CFB128 and 3DES-CBC decryption via BCrypt

Install / Use

/learn @wesmar/KvcForensic

README

KvcForensic

KvcForensic

Windows LSA credential parser for lsass.dmp minidumps. Active support targets: Windows 11 24H2 / 25H2 (builds 26100+) and Windows Server 2025. Template entries for older builds are present in KvcForensic.json but credential decryption is not implemented for them.

Built entirely on pure Win32 API. No runtime dependencies beyond the OS and the BCrypt primitive. No DbgHelp, no third-party libraries, no framework.

Binary size is under 300 KB when linked against the system CRT, and under 600 KB with the CRT statically embedded.


Requirements

KvcForensic.json must be placed in the same directory as KvcForensic.exe. It is loaded at startup before any analysis. The program will not run without it. If the file is missing, malformed, or fails schema validation, a startup error is shown and execution stops.


What it does

Parses a full-memory lsass.dmp minidump and extracts credentials from the following security packages:

| Package | Extracted data | |------------|---------------------------------------------------------| | MSV1_0 | NT hash, LM hash, SHA1, DPAPI blob | | WDigest | Cleartext password (when available) | | TSPKG | RDP-related credentials from tspkg.dll | | Kerberos | Username, domain, cleartext password, ticket list | | CredMan | Credential Manager stored entries | | DPAPI | MasterKey cache entries, decrypted masterkey, SHA1 |

Output formats: plain text report and/or structured JSON.


In progress (high priority)

The following items are currently in progress and are treated as high-priority work:

  1. TSPKG (Terminal Services Package) coverage hardening

    • Goal: reliable extraction from tspkg.dll, which may contain high-value cleartext RDP credentials.
    • Scope: stabilize tspkg_x64 template coverage across Windows build variants and improve walker robustness for list/sentinel edge cases.
    • Status: preliminary implementation is present (tspkg_x64 templates, TspkgWalker, and session integration), with ongoing tuning and validation.
  2. Kerberos ticket export (.kirbi / .ccache)

    • Goal: make ticket extraction operationally useful by exporting parsed tickets directly to standard formats.
    • Scope: improve export completeness, compatibility, and handling of malformed/partial ticket data.
    • Status: implementation exists and is being hardened for consistent cross-dump behavior.

Obtaining the dump

Standard tools (ProcDump, comsvcs.dll rundll32) fail against lsass.exe on modern Windows due to PPL (Protected Process Light) enforcement. To obtain a full-memory dump on Windows 11 24H2/25H2 with PPL active, use kvc, which bypasses PPL via kernel-level process protection manipulation:

kvc.exe dump lsass

The dump is written to the current user's Downloads folder by default. Pass it directly to KvcForensic:

KvcForensic.exe --analyze-dump --input "%USERPROFILE%\Downloads\lsass.dmp" --output result.txt --format both

Architecture

Memory model

The dump file is memory-mapped once via CreateFileMappingW / MapViewOfFile and exposed internally as a std::span<const std::byte>. Every subsequent read is a direct memcpy from a precomputed file offset. There is no ReadFile, no seeking, no intermediate heap allocation per read. VirtualMemory::VaToRva() translates virtual addresses from the dump's Memory64ListStream descriptors into file offsets by scanning the range table, which in practice resolves in a small number of comparisons per access.

lsass.dmp  ->  MapViewOfFile  ->  span<byte>
                                      |
                           VirtualMemory::VaToRva()
                                      |
                                memcpy to caller

Minidump parsing

The minidump format is parsed from scratch using only the public MINIDUMP_* structure definitions from the Windows SDK. DbgHelp is not used at any point. Stream discovery, module enumeration, and Memory64ListStream range building are all implemented directly. The build number read from SystemInfoStream drives template selection for every subsequent parsing step.

Cryptography

LSA encrypts in-memory credentials using either AES-CFB128 or 3DES-CBC, selected by a simple rule: if the encrypted blob length is not divisible by 8, AES is used; otherwise 3DES. This matches the logic observed in pypykatz.

The Windows BCrypt API exposes CFB only in 8-bit feedback (CFB8) mode. CFB128 is not available natively. KvcForensic implements CFB128 manually on top of BCryptEncrypt in ECB mode: the counter block is encrypted to produce a keystream block, which is XOR-ed with 16 bytes of ciphertext, then the counter is updated with those 16 ciphertext bytes before the next block. This matches the LSA behavior precisely.

3DES-CBC uses only the first 8 bytes of the IV.

Key material (AES-128/256, 3DES, IV) is located by scanning lsasrv.dll for the LSA_24H2_plus signature and following the KIWI_BCRYPT_HANDLE_KEY -> KIWI_BCRYPT_KEY81 pointer chain to the raw key bytes at a fixed structure offset.

Template system

All per-build configuration is stored in KvcForensic.json, which is loaded at startup from the executable's directory. The file must contain five mandatory top-level arrays: msv_x64, wdigest_x64, kerberos_x64, dpapi_x64, and lsa_secrets_x64. The tspkg_x64 array is optional. If any mandatory array is absent or fails validation, initialization fails and the program stops.

The JSON is parsed by a custom single-pass recursive-descent parser with no floating-point support. No third-party JSON library is used. The size of the file is capped at 4 MB before parsing begins.

Each template entry defines:

  • Build range (min_build / max_build)
  • Byte signature to locate the data structure within the loaded DLL image in memory
  • Offsets relative to the signature match
  • parser_support flag (MSV only): whether full session field parsing is implemented for that build range

Templates are selected at runtime by matching the build number from SystemInfoStream against each entry's range. The MSV array covers entries spanning Windows 7 (build 7600) through Windows 11 25H2 (build 26200+). WDigest has 2 entries, Kerberos 1, DPAPI 1, LSA secrets 1, and TSPKG templates are optional.

For MSV, multiple overlapping template entries are allowed for the same build range. KvcForensic evaluates all matching candidates and selects the best-scoring layout against live dump memory. Even when parser_support = true, a heuristic shift/fallback path can be activated when template offsets do not validate well on the analyzed dump. In that case, the GUI shows a red warning status (Heuristic mode / Heuristic fallback used) to indicate reduced confidence.

LSA key material decryption is only available for builds covered by the lsa_secrets_x64 template (26100+), so credential plaintext and NT hashes cannot be recovered from older dumps regardless of template presence.

MSV credential walk

FindMsvLogonList()        -- locate LogonSessionList via RIP-relative signature in lsasrv.dll
Walk()                    -- traverse doubly-linked list, one node per LogonSession
ExtractCredentials()      -- KIWI_MSV1_0_CREDENTIAL_LIST -> PRIMARY_CREDENTIAL_ENC
LsaSecretsExtractor       -- decrypt blob -> parse MSV1_0_PRIMARY_CREDENTIAL_11_H24_DEC

The decrypted MSV1_0_PRIMARY_CREDENTIAL_11_H24_DEC structure for 24H2/25H2 exists in two variants, distinguished by a format flag at offset 40. The flag reflects whether DPAPI protection is active for the credential:

Format A (isDPAPIProtected = 0):

| Offset | Field | Size | |--------|--------------------|---------| | +0 | LogonDomainName | 16 B | | +16 | UserName | 16 B | | +40 | isDPAPIProtected | 1 B | | +41 | isNtOwfPassword | 1 B | | +43 | isShaOwfPassword | 1 B | | +44 | isDPAPILimitedKey | 1 B | | +50 | DPAPILimitedKey | 20 B | | +70 | NtOwfPassword | 16 B | | +86 | LmOwfPassword | 16 B | | +102 | ShaOwfPassword | 20 B |

Format B (isDPAPIProtected = 1, NT hash relocated):

| Offset | Field | Size | |--------|--------------------|---------| | +50 | NtOwfPassword | 16 B | | +102 | ShaOwfPassword | 20 B | | +122 | DPAPILimitedKey | 20 B |

Both formats are handled transparently during extraction.

Kerberos AVL walk

Kerberos stores sessions in an RTL_AVL_TABLE. The tree is traversed with an iterative DFS using an explicit std::vector stack rather than recursion, eliminating any risk of stack overflow on deep or corrupted trees. Visited nodes are tracked in a std::unordered_set<uint64_t>. For each node, the OrderedPointer field at node+32 yields the session pointer. The LUID is read at session+64; if that value does not match any known session LUID, a fallback probe over offsets {56, 48, 72, 40, 32} is performed.

Kerberos ticket lists (three lists per session at offsets +280, +304, +328 in KIWI_KERBEROS_LOGON_SESSION_24H2) are extracted per session. Ticket metadata includes service name, target name, client name, flags, encryption type, kvno, and raw ticket bytes.

WDigest list walk

On some builds, WDigest maintains multiple adjacent list heads. KvcForensic probes four consecutive sentinel candidates to ensure complete coverage, deduplicating entries by virtual address so nodes reachable from multiple lists are not counted twice.

DPAPI master key walk

The DPAPI signature 48 89 4F 08 48 89 78 08 appears multiple times in lsasrv.dll. KvcForensic collects all occurrences in lsasrv.dll, dpapisrv.dll, and lsass.exe (with a f

View on GitHub
GitHub Stars6
CategoryDevelopment
Updated21d ago
Forks0

Languages

C++

Security Score

90/100

Audited on Mar 13, 2026

No findings