Nakido's Blog

website·Indexed 31 days ago

https://nakido.me

A technical blog featuring guides on Windows bootkit creation using the Windows Platform Binary Table (WPBT), patching WDK 22H2 for legacy project support, customizing Hexo, and automating synchronization from Overleaf to GitHub using GitHub Actions.

Tokens
5.3K
Snippets
17
Records
24
Agent score
52%

What's inside Nakido

  1. Overview of Windows Platform Binary Table (WPBT) for bootkits

    The Windows Platform Binary Table (WPBT) is an ACPI table used by OEMs to run a program every time Windows 8 or later boots. For a developer or security researcher, this provides a non-invasive method to execute custom code during the boot sequence without needing to hook RuntimeServices, patch the bootloader, or modify the MBR. The payload is executed by the Session Manager (SMSS) after the kernel has started but before the Win32 subsystem, CSRSS, or Winlogon have initialized.
  2. Remove deprecation warnings for ExAllocatePool and ExAllocatePoolWithTag

    In WDK 10.0.22621, functions like ExAllocatePool are marked as deprecated. To remove these warnings, manually edit the WDM header file.

    File path: C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\km\wdm.h

    Action: Locate the definition of the function (e.g., ExAllocatePool) and delete the __declspec(deprecated(ExAllocatePool)) attribute and the subsequent __drv_preferredFunction(...) call.

  3. Define and inject a Windows Platform Binary Table (WPBT) via UEFI

    To achieve early code execution in Windows, a WPBT can be injected into the ACPI tables using a UEFI DXE module. This involves defining the WINDOWS_PLATFORM_BINARY_TABLE structure, locating the EFI_ACPI_TABLE_PROTOCOL, allocating memory for the payload, and installing the table into the system.
    #include <Include/IndustryStandard/Acpi.h>
    
    #pragma pack(push, 1)
    typedef struct {
        EFI_ACPI_DESCRIPTION_HEADER Header;
        unsigned long HandoffMemSize;
        EFI_PHYSICAL_ADDRESS HandoffMemLoc;
        unsigned char ContentLayout;
        unsigned char ContentType;
        unsigned short ArgLength;
    } WINDOWS_PLATFORM_BINARY_TABLE;
    #pragma pack(pop)
    
    // 1. Define the WPBT instance
    WINDOWS_PLATFORM_BINARY_TABLE windowsPlatformBinaryTable = {
        {
            SIGNATURE_32('W', 'P', 'B', 'T'),
            sizeof(WINDOWS_PLATFORM_BINARY_TABLE),
            EFI_ACPI_5_0_FIRMWARE_PERFORMANCE_DATA_TABLE_REVISION,
            0x00, // Checksum
            {'N', 'A', 'K', 'I', 'D'},
            0x01,
            0x01,
            SIGNATURE_32('N', 'A', 'K', 'I'),
            0x01,
        },
        sizeof(hexData),
        (EFI_PHYSICAL_ADDRESS)NULL,
        1,
        1,
        0
    };
    
    // 2. Locate the ACPI Table Protocol
    EFI_ACPI_TABLE_PROTOCOL *EfiAcpiTableProtocolIf;
    EFI_STATUS status = gBS->LocateProtocol(&gEfiAcpiTableProtocolGuid, NULL, &EfiAcpiTableProtocolIf);
    
    // 3. Allocate memory for payload and copy data
    EFI_PHYSICAL_ADDRESS wpbtBinAddress;
    status = gBS->AllocatePages(AllocateAnyPages, EfiACPIReclaimMemory, sizeof(hexData) / 4096 + 1, &wpbtBinAddress);
    if (!EFI_ERROR(status)) {
        gBS->CopyMem((void *)wpbtBinAddress, hexData, sizeof(hexData));
        windowsPlatformBinaryTable.HandoffMemLoc = wpbtBinAddress;
        
        // Calculate checksum
        windowsPlatformBinaryTable.Header.Checksum = CalculateCheckSum8(
            (UINT8 *)&windowsPlatformBinaryTable, 
            windowsPlatformBinaryTable.Header.Length
        );
    }
    
    // 4. Install the table
    EfiAcpiTableProtocolIf->InstallAcpiTable(
        EfiAcpiTableProtocolIf, 
        (void *)&windowsPlatformBinaryTable, 
        windowsPlatformBinaryTable.Header.Length, 
        &wpbtKey
    );
  4. Compile and test a WPBT-based bootkit

    The deployment process requires a chain of signed binaries. Because Windows loaders block unsigned drivers and WPBT programs on 64-bit systems, you must either use valid code signatures or disable Driver Signature Enforcement at boot.

    Compilation Workflow:

    1. Compile and sign the Payload Driver $\rightarrow$ convert to a char array $\rightarrow$ embed in the payload's hexData.
    2. Compile and sign the payload $\rightarrow$ embed into the AcpiWpbtInjectorDxe module.
    3. Use EDK2 to compile AcpiWpbtInjectorDxe into an EFI module or OVMF image.

    Testing can be performed using QEMU by passing the -pflash flag pointing to the compiled OVMF image.

  5. Generate ntdll.lib for Native Application linking

    The Windows SDK does not provide ntdll.lib. To link against the NT Native API, you must generate a library file from ntdll.dll using a tool like pexports (from MinGW) and the MSVC lib tool.
    # Export symbols from ntdll.dll to a def file
    pexports ntdll.dll > ntdll.def
    
    # Create the .lib file from the .def file
    lib.exe /def:ntdll.def /out:ntdll.lib
  6. Develop a Windows Native Application for WPBT payload

    A WPBT payload must be a 'Native Application' (Subsystem 1). Because it runs before the Win32 subsystem is initialized, it cannot use standard Win32 libraries (like kernel32.dll) or the UCRT/MSVCRT. It can only use the Windows NT Native API (syscalls).

    Key constraints:

    • No Win32 API access.
    • No standard C/C++ runtime libraries.
    • Debugging requires a WinDbg dual-machine kernel debugging environment or using NtDisplayString for basic logging.
    • The entry point must be specified as NtProcessStartup.
  7. Enable 32-bit and Windows 7 to 8.1 build targets in WDK 22H2

    Visual Studio 2022 and WDK 10.0.22621 (22H2) restrict several older build targets. These restrictions are implemented in the MSBuild scripts rather than the headers or libraries. To bypass these checks and support 32-bit or Windows 7/8.1 targets using the v14.3 toolset, patch the WindowsDriver.Common.targets file.

    File path: C:\Program Files (x86)\Windows Kits\10\build\10.0.22621.0\WindowsDriver.Common.targets (adjust if using a custom installation path).

    Action: Search for the string '$(DDKPlatform)' == 'x86' or '$(DDKPlatform)' == 'ARM') and remove the conditional logic that validates Platform and TargetVersion.

  8. Customize Hexo page elements using non-invasive CSS

    To customize Hexo theme elements without modifying the core theme files, add custom CSS to the theme configuration. Use CSS selectors to target specific elements and apply !important to override existing styles of the same priority.
    /* Hide specific sidebar elements */
    #sidebar #sidebar-menus .sidebar-site-data {
        display: none !important;
    }
    
    /* Customize footer colors for light and dark themes */
    [data-theme='light'] #footer {
        background-color: #72bdfc;
    }
    
    [data-theme='dark'] #footer {
        background-color: transparent;
    }
  9. Set up GitHub Action to sync Overleaf to GitHub

    Create a workflow file in .github/workflows/ to automate the retrieval of files from Overleaf. This workflow uses the subhamx/overleaf_sync_with_git action to fetch the latest project version and peter-evans/create-pull-request to propose changes via a Pull Request if differences are detected. The workflow is configured for manual trigger via workflow_dispatch but can be modified for scheduled runs.
    name: overleaf2git
    on:
      workflow_dispatch:
    permissions:
      contents: write
      pull-requests: write
    jobs:
      main:
        runs-on: ubuntu-latest
        steps:
        - name: Fetch the latest version from overleaf server
          uses: subhamx/overleaf_sync_with_git@main
          with:
            OVERLEAF_PROJECT_ID: ${{ secrets.OVERLEAF_PROJECT_ID }}
            OVERLEAF_COOKIE: ${{ secrets.OVERLEAF_COOKIE }}
        - name: Get GMT+8 time
          id: gmt8-time
          run: |
            GMT8_TIME=$(date -u -d '+8 hours' +'%Y-%m-%d %H:%M:%S')
            echo "time=$GMT8_TIME" >> $GITHUB_OUTPUT
        - uses: actions/checkout@v2
          with:
            path: repo/
        - name: Update workspace
          run: |
            cd repo/
            cp -r ../artifacts/* ./
            git config user.name "github-actions[bot]"
            git config user.email "github-actions[bot]@users.noreply.github.com"
            git add .
            if [[ $(git diff HEAD --stat) == '' ]]; then
              echo 'Working tree is clean'
            else
              git commit -m "sync from overleaf"
            fi
        - name: Create Pull Request
          uses: peter-evans/create-pull-request@v7
          with:
            path: repo/
            title: 'sync from overleaf at ${{ steps.gmt8-time.outputs.time }}'
  10. Implement a Windows Native Application entry point for bootkits

    Native applications loaded by SMSS (Session Manager Subsystem) do not use the standard CRT entry point. They require a specific signature: void NtProcessStartup(PPEB Argument). The PPEB (Process Environment Block) pointer is used to retrieve system versions, environment variables, and loader parameters. Because the loader does not create a heap for native processes, the heap must be initialized manually using RtlCreateHeap.
    void NtProcessStartup(PPEB Argument) {
        PVOID heap;
        RTL_HEAP_PARAMETERS heapParams = {0};
        heapParams.Length = sizeof(RTL_HEAP_PARAMETERS);
        heap = RtlCreateHeap(HEAP_GROWABLE, NULL, 0x100000, 0x1000, NULL, &heapParams);
        // ... payload logic ...
    }
  11. Synchronize Overleaf project changes to GitHub

    The synchronization process is unidirectional (Overleaf $\rightarrow$ GitHub).

    1. Initial Setup: If this is the first sync or if you have made changes directly in Git, you must manually upload the files from the GitHub repository to Overleaf first to avoid overwriting work.
    2. Execution: Navigate to the Actions tab in your GitHub repository, select the workflow (e.g., overleaf2git), and click Run workflow.
    3. Result: If changes are detected in Overleaf, the action will automatically create a Pull Request containing the latest updates.
  12. Configure an EDK2 INF file for ACPI Table Injection

    To inject an ACPI table (such as WPBT) before the OS bootstrap, a UEFI module should be developed in the DXE (Driver Execution Environment). The module must be defined as a DXE_RUNTIME_DRIVER to ensure the payload memory is preserved after the OS bootstrap. If integrating into the OVMF firmware, the module can be defined directly within OvmfPkg using an .inf declaration file.
    [Defines]
      INF_VERSION                    = 0x00010005
      BASE_NAME                      = AcpiWpbtInjectorDxe
      # Generate a unique GUID to avoid conflicts
      FILE_GUID                      = B709BBEF-0862-4170-81A5-D5B316AD88A3
      # Use DXE_RUNTIME_DRIVER so memory is preserved after OS Bootstrap
      MODULE_TYPE                    = DXE_RUNTIME_DRIVER
      VERSION_STRING                 = 0.1
      ENTRY_POINT                    = DxeMain
      UNLOAD_IMAGE                   = DxeUnload
    
    [Sources]
      main.c
    
    [Packages]
      MdePkg/MdePkg.dec
      MdeModulePkg/MdeModulePkg.dec
    
    [LibraryClasses]
      UefiLib
      UefiDriverEntryPoint
      UefiBootServicesTableLib
      UefiRuntimeServicesTableLib
      DebugLib
      BaseLib
    
    [Depex]
      gEfiSimpleTextOutProtocolGuid AND
      gEfiLoadedImageProtocolGuid AND
      gEfiVariableArchProtocolGuid AND
      gEfiVariableWriteArchProtocolGuid AND
      gEfiResetArchProtocolGuid AND
      gEfiBdsArchProtocolGuid AND
      gEfiRuntimeArchProtocolGuid
    
    [Protocols]
      gEfiAcpiTableProtocolGuid