Skip to content

关于HVM的下一步计划 #195

Description

@Felix3322

背景

当前 HVM 已经具备 resident VMM、EPT 4KB split、EPT Rule、EPT Event Ring、CLOAK / HOOK split view,以及多核下的 EPTP-switching 后端。

从底层能力看,现有 EPT Rule 已经可以通过移除页的 R/W/X 权限制造 EPT violation,事件结构里也已经能够记录:

  • guestPhysicalAddress

  • guestLinearAddress

  • guestRip

  • qualification

  • CPU group / number

  • access type

  • ruleId

  • timestamp / sequence

但这些能力目前仍然偏向 HVM 自身的验证、tripwire 和底层诊断,还没有形成一个真正面向 ARK 使用场景的功能。

目前最典型的问题是:

Ksword 能告诉用户“SSDT / DriverObject / Callback / 内核代码已经发生异常”,但很多情况下无法继续回答:是谁改的、从哪里改的、第一次修改发生在什么时候。

传统快照式检测通常只能得到:

Before: nt!Foo
After : suspicious.sys+0x1234

却无法证明中间的修改动作到底来自哪里。

HVM/EPT 的价值不应该只是“再实现一种 Hook”,而应该提供传统 R0 驱动很难稳定提供的低层访问归因能力。

因此建议新增 HVM Memory Watch / First-touch Attribution

第一阶段目标不是实现无限持续、逐条无损的硬件内存追踪器,而是先可靠解决一个非常明确的问题:

监视一个目标页的下一次 R/W/X 访问,在首次命中时记录访问者证据,自动解除该监视,让原指令正常继续,并保持 HVM resident 不退出。

也就是首先把“下一次是谁动了它”做可靠。


一、核心目标

实现一个用户可直接使用的 HVM Watch 功能。

用户可以对指定内核虚拟地址、物理地址或 Ksword 已知内核对象建立:

HVM Watch Read
HVM Watch Write
HVM Watch Execute

第一版统一采用:

Mode = ONESHOT / FIRST_TOUCH

语义必须明确:

  1. 安装 Watch;

  2. 对目标物理页设置对应 EPT tripwire;

  3. 第一次目标访问产生 EPT violation;

  4. HVM 捕获访问现场;

  5. Watch 原子地从 ARMED 进入 TRIGGERED

  6. 自动恢复该页正常 EPT 权限;

  7. 完成必要的 EPT invalidation;

  8. 原 RIP 不推进;

  9. VMRESUME;

  10. 原访问重新执行并正常完成;

  11. resident HVM 继续运行;

  12. Watch 进入 DISARMED,不会继续产生事件,除非用户主动重新 Arm。

最重要的验收语义是:

命中 Watch ≠ HVM 退场。

当前 strict EPT tripwire 命中后通过 devirtualize 来保证 guest 最终能够重新完成访问,这对于底层安全兜底是合理的,但不能作为用户层 Memory Watch 的正常行为。

Memory Watch 命中以后应该只解除这一条 Watch,不应该因为成功抓到一次访问就结束整个 resident monitor。


二、第一版明确要回答的问题

用户建立一次 Watch 后,最终至少应该能够回答:

监视的是什么?
发生了什么访问?
什么时候发生?
在哪一个 CPU 上?
访问的实际 GPA / GLA 是什么?
当时执行到哪个 RIP?
RIP 属于哪个模块?
能否解析到符号?
当时处于哪个地址空间?
HVM 在命中后是否仍然 resident?

一个理想的事件展示例如:

HVM WATCH HIT

Watch:
ID 17
Target \Driver\Foo
Field MajorFunction[IRP_MJ_DEVICE_CONTROL]
Requested VA FFFFF80112345678 Backing GPA 0000000183A45000
Access WRITE
Mode FIRST_TOUCH

Hit:
Time 10:35:21.334
CPU 6
GLA FFFFF80112345678 GPA 0000000183A45678
RIP FFFFF80652C01834 CR3 00000001xxxxxxxx
RSP FFFFxxxx`xxxxxxxx

Attribution:
Module suspicious.sys
Symbol suspicious!ModifyDispatch+0x34

HVM:
Watch State TRIGGERED -> DISARMED
Resident Before 8/8
Resident After 8/8

这里最重要的是:

RIP -> Module -> Symbol

它才真正回答“是谁动的”。


三、第一阶段不是 Continuous Watch

需要明确控制范围。

P0 不要求:

每一次访问都产生事件

第一版只保证:

下一次访问产生一次可靠事件

即:

FIRST_TOUCH
FIRST_WRITE
FIRST_READ
FIRST_EXECUTE

原因是当前 HVM 在 nested Hyper-V 环境已经实测没有 Monitor Trap Flag。

传统 persistent EPT watch 的典型实现是:

EPT violation

临时恢复权限

单步一条指令

再次撤销权限

如果依赖 MTF,这条路径在当前 nested Hyper-V 环境不能作为通用方案。

因此本 ISSUE 不应该为了“Continuous”三个字,强行把:

  • guest TF/#DB 虚拟化;

  • 完整 x86 memory instruction emulation;

  • 新的单步机制;

  • 大规模 decode assist;

一起塞进第一阶段。

第一版的完成条件就是:

可靠、非破坏性的 One-shot Watch。

Continuous Watch 后续单独评估。


四、监视粒度必须如实展示

EPT 权限是页粒度。

因此第一版 Watch 的真实硬件监视单位必须明确写成:

4 KB Guest Physical Page

即使用户从:

DriverObject->MajorFunction[14]

这样的 8-byte 字段创建 Watch,真正安装到 EPT 上的仍然是该字段所在的整个 4KB 页。

UI 必须同时保留两套信息:

Requested Target:
VA FFFFFxxx`xxxx5678
Length 8

Effective Watch:
GPA 00000001`83A45000
Length 4096

不能在 UI 上把 EPT page watch 描述成“8 字节硬件断点”。

命中后如果 CPU 提供有效 GLA,则可以进一步判断:

GLA ∈ RequestedRange

并显示:

Exact target range: MATCH

或:

Same watched page, outside requested range

但这个判断属于事件归因,不改变第一阶段 EPT 本身仍是 page-granularity 的事实。


五、READ Watch 的架构语义要单独说明

EPT 权限组合存在架构限制。

不能简单假设:

R=0 W=1

可以作为普通叶项使用。

因此用户请求:

Watch Read

以后,实际生效权限可能同时影响 Write。

协议和 UI 都应该区分:

Requested Access
Effective Access

例如:

Requested: READ
Effective: READ | WRITE

如果硬件不支持需要的 execute-only 组合,也应该显示实际归一化后的结果,而不是假装只监视了用户点选的一项。


六、地址模型

P0 支持

优先支持:

Kernel VA
Physical Address

Kernel VA 创建 Watch 时:

VA

当前页表解析

GPA

EPT 4K Page

Watch 内同时保存:

requestedVirtualAddress
requestedLength

physicalPage
pageOffset

addressResolutionTime
HVM generation

第一版不承诺自动跟踪 VA remap

如果 Watch 建立后:

VA -> GPA A

随后 guest 页表被修改成:

VA -> GPA B

第一版 Watch 仍然监视原来的:

GPA A

不会声称自己自动跟踪了新的映射。

UI 应该允许重新解析并提示:

Current VA mapping no longer matches armed physical page

这种情况不能静默继续显示成“正在监视该 VA”。


七、建议复用现有 EPT Rule,而不是重新建立一套平行机制

当前已经有:

IOCTL_KSWORD_ARK_HVM_EPT_RULE
IOCTL_KSWORD_ARK_HVM_EVENTS

以及:

EPT_RULE_FLAG_LOG
EPT_RULE_FLAG_ALLOW_ONCE
EPT Rule table
EPT event ring
ruleId
R/W/X mask

因此实现上优先考虑:

给现有 EPT Rule 增加明确的 WATCH_ONCE / FIRST_TOUCH 处置语义。

而不是再新增一个几乎重复的 EPT page-rule subsystem。

例如逻辑上可以增加:

EPT_RULE_ACTION_TRIPWIRE
EPT_RULE_ACTION_WATCH_ONCE

或等价 flag。

但必须保证:

旧 Rule 语义完全不变

不能把现有 strict tripwire 的行为悄悄改掉。

Watch 应该是一种新的、显式请求的 action。


八、Watch 状态机

建议不要仅依赖 ruleId exists / not exists 表达生命周期。

至少具有:

CREATED

ARMED

TRIGGERED

DISARMED

错误时:

FAULTED

删除后:

REMOVED

多核第一次命中必须通过原子状态转换保证:

ARMED -> TRIGGERED

只有一个 CPU 成为逻辑上的 first hit owner。

其它 CPU 如果在解除权限传播完成前也进入同一页的 EPT violation:

  • 不应该产生第二个独立的 First-touch 事件;

  • 不应该再次修改 Watch 生命周期;

  • 不应该触发全局 devirtualization;

  • 应按照已经处于 TRIGGERED 状态的规则安全等待/完成恢复路径。

最终用户看到的是:

一个 Watch
一个 first-hit
一个逻辑事件

而不是因为 SMP race 出现若干个看似不同的“第一次”。


九、Watch 命中事件需要补充的现场信息

现有 HVM event 已经有:

sequence
timestamp
GPA
GLA
RIP
qualification
CPU
access
ruleId

建议 Watch Hit 至少再保存:

watchId / ruleId
watchGeneration

guestRSP
guestCR3

requestedAddress
requestedLength

requestedAccess
effectiveAccess

GLA valid
rangeMatch

watchStateAfterHit

其中真正必须在 VM-exit 现场保存的是:

RIP
RSP
CR3
GPA
GLA
qualification
CPU
access
timestamp

不要在 VMX root 里做复杂 Windows 对象解析

VM-exit 热路径里不建议直接解析:

PEPROCESS
ETHREAD
模块路径
PDB symbol
调用栈

这些应该尽量留给 R0 普通上下文或 R3 做 enrichment。

流程建议:

VMX root

记录最小可信现场

event ring

ArkDriverClient

KSword Attribution

Module / Symbol / Process / UI

这样 HVM 负责提供“事实”,Windows-aware 层负责解释事实。


十、RIP 归属必须作为 P0 功能

如果最终只能显示:

RIP = FFFFF80652C01834

那么对普通用户价值仍然有限。

第一版必须至少做到:

RIP

Loaded kernel module range

module.sys + RVA

有符号时进一步显示:

module!Function+0x34

无符号时:

module.sys+0x1834

如果 RIP 不属于任何已知 loaded module,则明确显示:

Unknown executable region

并提供:

打开内存
打开反汇编
查看所在页

而不能简单显示 Unknown 后结束。


十一、PID / TID 归因采用 Best-effort,不允许伪造确定性

建议事件记录 guestCR3

用户态或正常 R0 上下文可以尝试:

CR3 -> Process

得到:

PID
Image

但必须考虑:

  • KVA Shadow;

  • System address space;

  • kernel worker thread;

  • CR3 reuse;

  • 进程退出;

  • 事件和解析之间存在时间差。

因此显示结果需要区分:

Observed
Resolved
Inferred
Unavailable

例如:

CR3      0x12345000       Observed
PID 4120 Resolved from current process snapshot
TID Unknown

不能因为无法可靠获取 TID 就猜一个出来。

TID / ETHREAD 精确归因可以放到后续阶段。


十二、写入值的语义不要写错

第一版不要承诺:

Old Value
New Value

都是精确的。

EPT violation 发生在导致访问的指令真正完成之前。

因此命中 WRITE 时:

事件发生

原写指令尚未退休

此时可以得到“写之前”的现场,但在没有可靠 post-instruction trap / instruction emulation 的情况下,无法在同一个 VM-exit 中知道该指令最终写进去的完整结果。

所以第一阶段:

可以做

Arm 时保存一个:

Baseline Snapshot

命中后允许用户重新读取:

Current Value

并在 UI 中明确标成:

Post-hit sample

不应该声称

Exact New Value

除非以后有可靠的单指令完成观测机制。

同样,Baseline Snapshot 也需要注明:

它是 Arm 时的内容,不是对 DMA 或其它非 CPU/EPT 可见修改的绝对保证。


十三、UI

HVM 页面新增:

Memory Watch

建议表格:

ID Target Requested Range GPA Page Access Effective Mode State Hits Last RIP Module

操作:

Add Watch
Remove
Re-arm
Clear
Show Event
Open Target Memory
Open Writer Disassembly
Copy Evidence

创建窗口:

Address type:
    Kernel VA
    Physical

Address:
Length:

Access:
[ ] Read
[ ] Write
[ ] Execute

Mode:
First touch (当前唯一支持)

[Arm]

下方必须显示:

EPT monitoring is page-granular.
Requested range: 8 bytes
Effective monitored page: 4096 bytes

十四、接入现有页面

功能不能永远只停留在 HVM 实验页。

完成底层和统一 UI API 后,建议增加:

openHvmWatch(target)

之类的统一入口,使已有页面不需要理解 EPT 细节。

首批建议接入:

Memory

右键地址
→ HVM Watch
→ Read
→ Write
→ Execute

Kernel Disassembly

选中指令
→ HVM Watch Execute

SSDT / SSSDT

选中 entry
→ HVM Watch Write

DriverObject / MajorFunction / FastIo

选中 dispatch entry
→ HVM Watch Write

后续再逐步接:

Callback
IDT
关键 PTE
Token
EPROCESS 字段
其它内核对象

目标是最终让用户不需要知道:

GPA
EPT leaf
ruleId

也能使用 HVM。


十五、事件详情页

Watch Hit 建议有独立详情,而不是只往日志里打一行文本。

例如:

Target

Label DriverObject MajorFunction
Requested VA ...
Requested len 8
Physical page ...
Page offset ...
Access WRITE
Effective WRITE

Event

Sequence
Timestamp
CPU
GPA
GLA
GLA valid
Range match
RIP
RSP
CR3
Qualification

Attribution

Module
Module RVA
Symbol
PID
Process image
Confidence

HVM

Watch generation
State
Resident before
Resident after
Event loss status

按钮:

[查看写入者反汇编]
[查看目标内存]
[查看模块]
[重新监视]
[复制证据]

十六、事件丢失必须可见

现有 HVM Event Ring 已经区分:

published
dropped
overwritten

Memory Watch 不应该隐藏这些状态。

如果某个 Watch 已经:

hitCount = 1
state = DISARMED

但对应事件因为 ring contention 没有成功发布,则 UI 应该明确显示:

Watch triggered, event evidence lost

而不是:

No event

否则用户会错误认为目标没有被访问。

因此 Watch 自身至少应保留:

hitCount
lastHitSequence
lastHitStatus

这样 event ring 丢失与“从未命中”能够区分。


十七、与现有 EPT View / Rule 的冲突

P0 不要尝试自动组合复杂规则。

如果目标页已经存在:

CLOAK
HOOK
其它 incompatible EPT Rule

则建立 Watch 时直接返回明确的:

LEAF_CONFLICT

UI 显示:

This physical page is already owned by HVM view/rule #xx.
Remove it before arming Memory Watch.

不要静默覆盖,也不要自动改变现有 view。

同一物理页上的多个 Watch 第一版也可以直接拒绝。

先保证:

one physical page

one clear owner

后续真的有需求再设计规则合并。


十八、HVM 生命周期

Watch 必须绑定:

HVM generation

发生:

STOP_RESIDENT
TEARDOWN
FAULT
RESET
power transition

后,旧 Watch 不允许在下一次 residency 中静默继续生效。

建议:

HVM stop

所有 ARMED Watch -> INVALIDATED

UI 仍可保留历史记录,但需要显示:

Stale / needs re-arm

重新启动 HVM 后必须显式 Re-arm。


十九、安全边界

该功能是:

观察 / 归因功能。

不是:

安全隔离或不可绕过保护机制。

第一版禁止把 Watch 宣传成:

Memory Protection
Kernel Protection
Anti-rootkit enforcement
Unbypassable guard

它的目标仅仅是:

发现一次访问
记录现场
让原访问继续

尤其需要说明:

  • EPT 是 page-granularity;

  • DMA 修改不经过 CPU EPT;

  • VA 映射可能变化;

  • event ring 可能发生证据丢失;

  • PID/TID/符号解析属于后处理;

  • First-touch 不是持续监视;

  • Watch 不阻止目标访问。


二十、本 ISSUE 明确不包含

以下内容不作为本 ISSUE 的完成条件:

  • eVMCS;

  • VPID;

  • Nested VMX;

  • VMFUNC 新功能;

  • HVM 性能调优;

  • 完整 x86 指令模拟器;

  • 无限制 Continuous EPT Watch;

  • 精确 post-instruction New Value

  • 全量 kernel stack unwind;

  • 强制阻止目标访问;

  • Anti-PatchGuard / PatchGuard bypass;

  • 把 HVM Watch 做成安全边界。

这些可以独立评估,不应该拖住 First-touch Attribution。


二十一、CLI / 自动化接口

建议同时提供最小 CLI,使测试不依赖 Qt。

例如:

hvm watch add --va FFFFF80112345678 --write --once
hvm watch add --pa 183A45000 --execute --once

hvm watch list
hvm watch events
hvm watch remove 17
hvm watch rearm 17
hvm watch clear

输出必须包含:

watchId
state
requested address
effective physical page
requested/effective access
hitCount
lastHitSequence

这样 VM 靶机自动化测试可以直接判断功能是否真的生效。


二十二、验收测试

1. WRITE First-touch

在测试驱动分配一个稳定的 nonpaged page。

Arm:

HVM Watch WRITE

随后由测试路径执行一次写入。

要求:

  • 产生一个 Watch Hit;

  • access 包含 WRITE;

  • GPA 落在目标物理页;

  • GLA 有效时指向实际访问地址;

  • RIP 对应实际测试写入指令;

  • CPU 编号正确;

  • Watch 从 ARMED -> TRIGGERED -> DISARMED

  • 原写入最终成功;

  • 写入后的目标值发生预期变化;

  • HVM resident processor count 不减少;

  • 不发生 unexpected devirtualization;

  • 第二次写入不会再次产生该 Watch 的事件。

2. READ First-touch

  • 第一次读取产生事件;

  • UI 同时展示 requested / effective access;

  • 原读取最终正常完成;

  • HVM 保持 resident。

3. EXECUTE First-touch

  • 对测试代码页建立 execute watch;

  • 第一次执行产生事件;

  • RIP 位于目标页;

  • 自动 disarm 后原函数正常执行;

  • HVM 保持 resident。

4. Nested Hyper-V

现有已验证的 2-vCPU nested Hyper-V 靶机:

  • 不依赖 Monitor Trap Flag;

  • WRITE First-touch 可正常 Arm;

  • 第一次写入正常命中;

  • residentBefore = 2;

  • residentAfter = 2;

  • guest 不挂死;

  • 不触发全局 fail-closed。

这是 P0 的关键验收项。

5. SMP 同时命中

两个 CPU 尽可能同时访问同一 watched page。

要求:

  • 无死锁;

  • 无 EPT corruption;

  • 无 unexpected devirtualization;

  • Watch 只有一个逻辑 first-hit owner;

  • hitCount / event 语义确定;

  • 最终权限恢复;

  • 两个 CPU 均继续运行。

6. View 冲突

目标页已有 CLOAK / HOOK:

  • Watch 安装失败;

  • 返回明确 conflict;

  • 原 view 不发生变化。

7. VA 映射变化

Arm 后修改测试 VA 的 backing page:

  • Watch 仍明确显示自己绑定原 GPA;

  • UI 能检测当前 VA->GPA 与 Arm 时不同;

  • 不错误声称仍然精确监视当前 VA。

8. HVM restart

  • STOP_RESIDENT 后 Watch 不继续生效;

  • 下一次 START 不会静默恢复旧 Watch;

  • UI 将其显示为 stale / invalidated;

  • 用户可以显式 Re-arm。

9. Event loss

人为制造 event ring 压力:

  • dropped / overwritten 仍可观察;

  • Watch 命中状态不会因为 event publish 失败而错误保持 ARMED;

  • 能区分“从未命中”和“已命中但详细事件丢失”。


二十三、阶段划分

P0:First-touch 可用

  • EPT WATCH_ONCE 语义;

  • R/W/X;

  • 命中后自动解除;

  • 原指令继续;

  • HVM 不退出;

  • GPA / GLA / RIP / RSP / CR3 / CPU;

  • RIP -> module;

  • HVM Memory Watch 页面;

  • CLI;

  • 2-vCPU nested Hyper-V 验证。

完成这一阶段,就已经可以正式把功能称为:

HVM First-touch Attribution

P1:ARK 页面接入

  • Memory;

  • Kernel Disassembly;

  • SSDT / SSSDT;

  • DriverObject MajorFunction / FastIo;

  • Callback;

  • Symbol resolve;

  • Process best-effort attribution;

  • Evidence detail / copy / export。

P2:Continuous Watch 可行性研究

单独评估:

MTF
guest TF + intercepted #DB
bounded instruction emulation
其它可靠 re-arm mechanism

只有找到:

不会破坏 guest debug semantics
支持 SMP
nested Hyper-V 可工作
不会制造不可控窗口

的方案后,再增加:

Mode = CONTINUOUS

不能为了 UI 上多一个 Continuous 选项而降低正确性要求。


二十四、最终完成定义

这个功能完成的标准不是:

“EPT violation 能打印日志。”

而是用户可以完成下面这个真实工作流:

Ksword 发现某个 DriverObject dispatch 当前正常

用户选择 MajorFunction[IRP_MJ_DEVICE_CONTROL]

右键:HVM Watch Write

继续正常使用系统

某驱动第一次修改所在页

Ksword 捕获 HVM Watch Hit

显示:
时间
CPU
精确 GLA/GPA
RIP
writer module
symbol(如果可用)
CR3 / process context

原写入正常继续

HVM 仍保持 resident

用户点击“查看写入者反汇编”

即:

把 Ksword 从“发现一个东西被改过”,推进到“观察下一次修改,并给出修改者证据”。

这是本 ISSUE 的核心目标。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions