64位模式下CR0寄存器WP位操作汇编代码正确性咨询
问题描述
向ntoskrnl.exe地址空间所属的内核态只读代码区域执行写入操作时,机器始终卡死锁死,必须重启才能恢复。参考《Rootkit Arsenal》中提供的WP位禁用、启用实现代码时,发现书中代码除个别特例外均面向32位CPU编写,因此尝试适配修改代码兼容64位CPU。
开发环境为Visual Studio 2019 Community,目标运行环境为64位Windows 8.1 Pro,编写的汇编过程如下:
DisableWP PROC PUBLIC CLI PUSH RBX MOV RBX, CR0 AND RBX, 0FFFFFFFFFFFEFFFFh MOV CR0, RBX POP RBX RET DisableWP ENDP EnableWP PROC PUBLIC PUSH RBX MOV RBX, CR0 OR RBX, 0000000000010000h MOV CR0, RBX POP RBX STI RET EnableWP ENDP
请判断上述代码是否正确,若存在错误,给出适配64位架构的正确实现方案。
解答
你的代码存在三处明确错误,是导致系统卡死的直接原因:
- 中断状态处理逻辑错误:
DisableWP中直接执行CLI关中断,但EnableWP中无条件执行STI开中断,若调用DisableWP前中断本身处于关闭状态,恢复时会强行打开中断,破坏内核原有执行状态,直接触发死锁或异常。 - 缺少指令串行化操作:x86/x64架构下修改CR0这类控制寄存器后,CPU不会自动刷新指令流水线,可能出现WP位尚未实际生效就提前执行写操作,触发页保护异常。
- 未保存CR0原始状态:直接通过按位或/按位与修改WP位,若出现嵌套调用WP开关的场景,会导致WP位状态错乱,引发不可预期的内核异常。
另外你代码中WP位对应的位掩码本身是正确的:WP位是CR0寄存器的第16位,对应值0x10000,清位使用的掩码0xFFFFFFFFFFFEFFFF没有错误。
适配64位Windows、遵循Microsoft x64调用约定的正确实现如下:
; 禁用WP位,返回值为原始CR0寄存器值,调用EnableWP时需传入该值 DisableWP PROC PUBLIC PUSHFQ ; 保存完整RFLAGS寄存器(含原始中断开关状态)到栈 CLI ; 临时关中断,避免写入操作被中断打断 PUSH RBX ; 遵循x64调用约定,保存被调用者负责维护的非易失寄存器RBX MOV RBX, CR0 MOV RAX, RBX ; 将原始CR0值存入RAX作为返回值 AND RBX, NOT 00010000h ; 清除WP位,使用NOT写法避免手动计算长掩码出错 MOV CR0, RBX JMP $+2 ; 短跳转实现指令串行化,确保CR0修改立即生效,刷新流水线 POP RBX ; 恢复RBX寄存器 RET DisableWP ENDP ; 启用WP位,第一个参数(RCX传入)为DisableWP返回的原始CR0值 EnableWP PROC PUBLIC PUSH RBX ; 保存非易失寄存器RBX MOV RBX, RCX ; 读取传入的原始CR0值 MOV CR0, RBX ; 恢复CR0到调用DisableWP前的状态 JMP $+2 ; 串行化确保CR0修改生效 POP RBX ; 恢复RBX寄存器 POPFQ ; 恢复原始RFLAGS,自动还原之前的中断开关状态,不强行开/关中断 RET EnableWP ENDP
使用上述代码时需要注意:
- 调用逻辑为:先调用
DisableWP拿到返回的原始CR0值,执行完内核只读区域写入操作后,把该值作为参数传入EnableWP完成恢复,不要直接无参调用。 - 关中断状态下的代码执行时间必须尽可能短,长时间关中断会导致硬件中断丢失,直接触发系统卡死。
- 该实现仅能绕过CPU的页级写保护,Windows 8.1 x64默认开启Kernel Patch Protection(PatchGuard),定期校验ntoskrnl等内核模块的代码完整性,直接修改ntoskrnl只读代码段会在短时间内触发PatchGuard防护导致系统蓝屏,无法通过仅关闭WP位绕过。
内容的提问来源于stack exchange,提问作者JbG
相关产品推荐
相关产品推荐

