Rust Hypervisor Attack Defense Part A
一半人生 发表于 浙江 WEB安全 1525浏览 · 2024-02-19 07:45

前言

  笔者大多数代码基于C/C++实践,部分使用Go/Py实践,对Rust的态度保持中立,但Rust肯定值得探索和使用。基于Rust尝试重构C/C++代码,Rust Hypervisor系列文章也是过程性知识分享和探讨。
  文章不局限于Rust Driver,EPT,MSR,SPI等内容,注重实践过程,对端点安全和虚拟化攻防,具有实践意义和研究价值。
  这里虚拟化泛指Intel VT-x技术实践,对系统的攻防有什么研究价值和意义呢?

虚拟化漏洞挖掘和研究

  虚拟化漏洞较为火热的话题便是虚拟机逃逸,Kvm Xen Hyper-v等本质上是VT-x和AMD-V代码缺陷,真实危害程度和利用难度都非常高。

游戏反作弊对抗

  功和防都很激烈,VT反调试和VT调试器,EPT读写保护和EPT读写绕过,CR/DR寄存器控制权争夺战。

沙箱/EDR/端点引擎对抗

  安全类产品应偏于HOOK探针,VT在X64过PG,基于MSR/EPT的SSDT HOOK较为常见。微软已经有了Hyper-PG,后续VT HOOK高版本X64是否可以安全使用,还是未知数。

Rootkit/Bootkit攻防对抗

  Rootkit泛指Kernel下恶意驱动,Windows X64 PG已经对Rootkit做了最大的限制,仍然有很多利用方式,如PCI,SPI,APIC(IPI),是把双刃剑。

Rust Windows

  基于Rust编码Linux/Windows语法一致,涉及到不同平台API调用差异比较大,基于Windows System代码做基础铺垫,过程性代码。

Rust Project

工程创建
cargo new rs-hypervisor
cargo build 
cargo run

cargo new --lib rdriver
cargo new --lib rhypervisor
单元测试
- .rs文件内直接声明cfg(test)
#[cfg(test)]
mod test{
}
集成测试
- 创建tests目录
- tests目录下,创建集成测试.rs文件,use引用集成.rs
- use netCom::DrivenManageImpl;

Win API

Cargo.toml
[dependencies.windows]
version = "0.52"
features = [
    "Win32_Foundation",
    "Win32_Security",
    "Win32_Storage_FileSystem",
    "Win32_System_Threading",
    "Win32_System_IO",
]
main.rs
use windows::{
    core::*, Win32::Foundation::*, Win32::Storage::FileSystem::*, Win32::System::Threading::*,
    Win32::System::IO::*,
};
unsafe {
    let dwAttribute:u32 = 2147483648u32 | 1073741824u32;
    let hResult: std::prelude::v1::Result<HANDLE, Error> = CreateFileA(
        PCSTR(strDrivenName.as_ptr()),
        dwAttribute,
        FILE_SHARE_MODE(0),
        None,
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED,
        None,
    );
    if hResult.is_ok() {
        let hDriver: HANDLE = hResult.unwrap();
        self.m_hDriver = hDriver;
        return true;
    }
    else {
        let hResultError: std::prelude::v1::Result<(), Error> = GetLastError();
        println!("{}",hResultError.unwrap_err().code()); 
        return false;
    }
}

Win Driven

Cargo.toml
[lib]
# staticlib
crate-type = ["cdylib"]

[dependencies]
wdk = "0.1.0"
wdk-alloc = "0.1.0"
wdk-panic = "0.1.0"
wdk-sys = "0.1.0"
wdk-macros = "0.1.0"

[build-dependencies]
wdk-build = "0.1.0"

[profile.dev]
panic = "abort"
lto = true # optional setting to enable Link Time Optimizations

[profile.release]
panic = "abort"
lto = true # optional setting to enable Link Time Optimizations
lib.rs
use wdk_alloc::WDKAllocator;
use wdk_macros::call_unsafe_wdf_function_binding;
use wdk_sys::{
    ntddk::DbgPrint,
    DRIVER_OBJECT,
    NTSTATUS,
    PCUNICODE_STRING,
    ULONG,
    WDFDEVICE,
    WDFDEVICE_INIT,
    WDFDRIVER,
    WDF_DRIVER_CONFIG,
    WDF_NO_HANDLE,
    WDF_NO_OBJECT_ATTRIBUTES,
};

/// Function is unsafe since it dereferences raw pointers passed to it from WDF
#[export_name = "DriverEntry"] // WDF expects a symbol with the name DriverEntry
pub unsafe extern "system" fn driver_entry(
    driver: &mut DRIVER_OBJECT,
    registry_path: PCUNICODE_STRING,
) -> NTSTATUS {
    0
}
build.rs
fn main() -> Result<(), wdk_build::ConfigError> {
    wdk_build::Config::from_env_auto()?.configure_binary_build();
    Ok(())
}
Build Driver
  • Install cargo make
    cargo install --locked cargo-make --no-default-features --features tls-native
    // 可选
    cargo install cargo-expand cargo-edit cargo-workspaces
    // 文档
    cargo doc --document-private-items --open
  • 编译指令
    cargo make
  • 官网文案
    See: https://github.com/microsoft/windows-drivers-rs
  • Build驱动工程出现错误如下,表示EWDK环境出现了问题:
    WDKContentRoot should be able to be detected. Ensure that the WDK is installed, or that the environment setup scripts in the eWDK have been run.
    按照Rust Windows去配置,EWDK完整ISO下载下来有15GB大小,要注意磁盘空间:
See: https://learn.microsoft.com/en-us/windows-hardware/drivers/develop/using-the-enterprise-wdk

安装完成以后有两种编译方案:

1. LaunchBuildEnv.cmd里面编译Rust Driven
2. 设置系统环境变量WDKContentRoot=F:\Program Files\Windows Kits\10\

LIBCLANG_PATH依赖相关问题,LLVM变量设置问题:

thread 'main' panicked at D:\Rust\.cargo\registry\src\index.crates.io-6f17d22bba15001f\bindgen-0.68.1\lib.rs:611:31:
Unable to find libclang: "couldn't find any valid shared libraries matching: ['clang.dll', 'libclang.dll'], set the `LIBCLANG_PATH` environment variable to a path where one of these files can be found (invalid: [])"

解决方案根据提示设置环境变量,设置完成后重启下VSCode:

set LIBCLANG_PATH=D:\LLVM\bin\ 或者系统变量设置
如果仍有问题,可以尝试:
https://github.com/KyleMayes/clang-sys

最终效果编译出来文件包含.sys,具备基本的Windows Rust Driver实践能力.

VMCS Table Padding

默认读者有虚拟化基础,对Intel VT-X技术有了解,具备虚拟化实践编码能力,反之请先移步到基础读物阅读:

1. KVM循序渐进耳之基础篇:https://xz.aliyun.com/t/9587
2. KVM循序渐进耳之原理篇:https://xz.aliyun.com/t/13491
3. Intel Vt虚拟化笔记:  https://blog.51cto.com/u_13352079/2545416
4. Intel Virtualization Technology: 19~31章节

CR/DR

  • Control Registers

CR0 - 处理器标志控制位,如PE,PG,WP等
CR1 - 预留
CR2 - 页面故障线性地址(导致页面故障的线性地址)
CR3 - CR3寄存器页目录指针表的基址
CR4 - 包含一组标志架构扩展,并指示操作系统或对特定处理器功能的执行支持。
CR8 - 提供对任务优先级寄存器(TPR)的读写访问。指定优先级阈值,操作系统用来控制允许中断的外部中断的优先级类处理器。(64位模式可用)
See: 2.5 CONTROL REGISTERS

  游戏攻防关注基本是CR3,它的意义在于对进程物理页保护,让恶意程序或者没有办法拿到地址,不能做内存读写。常见做法就是构造假的CR3地址或者直接将CR3清零,某厂保护这样做过。

  • DR

DR0-DR3 硬件断点的地址,
DR4 - 保留
DR5 - 保留
DR6 - 调试异常信息
DR7 - 属性,断点启用、类型、长度等信息。
See: v3 17Part Debug Registers

  反调试方式有很多种,PEB,TLS,三方调试器检测,API返回等,DR0~DR3保存了硬中断地址,硬中断最多有4个只提供了四个寄存器,这里就可以做反硬中断了,清0寄存器可以实现效果.
  R3也可以调试器,int3软中断0xcc访问触发异常,异常分发中处理,设置eflage.TF标志位进行单步. 同样硬中断也可以设置寄存器,但是不管哪一种,硬中断也需要处理DR7,这里不做过多扩展:

if (0 == pDr7->L1){
        m_conText->Dr1 = uAddress;
        pDr7->RW1 = 0;
        pDr7->LEN1 = 0;
        pDr7->L1 = 1;
    }
typedef struct _DBG_REG7
{
    /*
    // 局部断点(L0~3)与全局断点(G0~3)的标记位
    */
    unsigned L0 : 1;  // 对Dr0保存的地址启用 局部断点
    unsigned G0 : 1;  // 对Dr0保存的地址启用 全局断点
    unsigned L1 : 1;  // 对Dr1保存的地址启用 局部断点
    unsigned G1 : 1;  // 对Dr1保存的地址启用 全局断点
    unsigned L2 : 1;  // 对Dr2保存的地址启用 局部断点
    unsigned G2 : 1;  // 对Dr2保存的地址启用 全局断点
    unsigned L3 : 1;  // 对Dr3保存的地址启用 局部断点
    unsigned G3 : 1;  // 对Dr3保存的地址启用 全局断点
                      /*
                      // 【以弃用】用于降低CPU频率,以方便准确检测断点异常
                      */
    unsigned LE : 1;
    unsigned GE : 1;
    /*
    // 保留字段
    */
    unsigned Reserve1 : 3;
    /*
    // 保护调试寄存器标志位,如果此位为1,则有指令修改条是寄存器时会触发异常
    */
    unsigned GD : 1;
    /*
    // 保留字段
    */
    unsigned Reserve2 : 2;
    /*
    保存Dr0~Dr3地址所指向位置的断点类型(RW0~3)与断点长度(LEN0~3),状态描述如下:
    00:执行 01:写入  11:读写
    00:1字节 01:2字节  11:4字节
    */
    unsigned RW0 : 2;  // 设定Dr0指向地址的断点类型 
    unsigned LEN0 : 2;  // 设定Dr0指向地址的断点长度
    unsigned RW1 : 2;  // 设定Dr1指向地址的断点类型
    unsigned LEN1 : 2;  // 设定Dr1指向地址的断点长度
    unsigned RW2 : 2;  // 设定Dr2指向地址的断点类型
    unsigned LEN2 : 2;  // 设定Dr2指向地址的断点长度
    unsigned RW3 : 2;  // 设定Dr3指向地址的断点类型
    unsigned LEN3 : 2;  // 设定Dr3指向地址的断点长度
}DBG_REG7, *PDBG_REG7;

EPT

说EPT之前先了解点基础东西,CR0寄存器里面有很多标志蛮重要的,比如想知道是不是开了保护模式和分页机制检测PE、PG标志位即可。

CR0.PE=1
CR0.PG=1

分页模式有非物理地址扩展模式(非PAE模式) 和 物理地址扩展模式(PAE模式),CR3可以拿到页目录地址,三级页目录结构:

名称 描述
页目录索引表(PDT) 一级索引
页表索引表(PTT) 二级索引
页表项(PTE) 页表项 PDT(1024项PDE),PTT(1024项PTE)

EPT(Extended Page Table)好像和这些无关?上述转换来说确实无关,EPT是为了实现内存虚拟化添加的,Guest不允许直接访问HPA,需要耗费很大性能才能访问到HPA:

GVA(Guest Virtual Address) - GPA(Guest Physical Address) -> HVA(Host Virtual Address) -> HPA(Host Physical Address)

  EPT为了减少上述换搞出来,也就是GVA->HPA的过程,使用了硬件来维护了GPA->HPA。另外还有影子页表也是类似的功效,如KVM也会对CR3退出监控,其实触发VMEXIT Hash记录映射一张表Table,但是CR3每次都会触发VMExit,影子页表维护映射Table和CR3 VMX切换开销还是很大的,但是EPT性能的话只有缺页才会引发VMX切换。
  EPT的概念太多,安全切面我们经常可以听到一些词,EPT无痕HOOK,EPT隐藏,这其实都是对EPT结构和VMX切换(VMExit)做处理,EPT Hook通常用于SSDT Hook,做安全沙箱和API防护,EPT隐藏指的物理内存隐藏,这些东西甚至还有论文,文雅点叫EptViolation的欺骗。

EPT:Extended Page Table,扩展页表
EPTP:Extended-Page-Table Pointer,扩展页表指针
EPT PML4T:EPT Page Map Level 4 Table , EPT PML4E 是其表项  L4
PDPT:Page-Directory-Pointer Table , PDPTE 是其表项  L3
PDT:Page-Directory Table , PDE 是其表项 L2
PT:Page Table , PTE 是其表项 L1
GPA:Guest-Physical Address,虚拟机物理地址
HPA:Host-Physical Address,真实机物理地址
EPT MMU:一块专门的用作硬件虚拟化的硬件

MSRs

Model Specific Register寄存器,我们关注的是Instruction-specific support

• Performance-monitoring counters (see Chapter 20, “Performance Monitoring”).
• Debug extensions (see Chapter 18, “Debug, Branch Profile, TSC, and Intel® Resource Director Technology
(Intel® RDT) Features”).
• Machine-check exception capability and its accompanying machine-check architecture (see Chapter 16,
“Machine-Check Architecture”).
• MTRRs (see Section 12.11, “Memory Type Range Registers (MTRRs)”).
• Thermal and power management.
• Instruction-specific support (for example: SYSENTER, SYSEXIT, SWAPGS, etc.).
• Processor feature/mode support (for example: IA32_EFER, IA32_FEATURE_CONTROL).

  简单说通过指令操作系统可以从应用层切换到内核层,过个程就涉及到SYSENTER, SYSEXIT指令执行。MSR HOOK指的什么?R3/R0 都可以实现,泛指HOOK的方式不同,应用层r3主流就是对KiFastSystemCall进行Inline HOOK,R0是对KiSystemServiceCopyEnd、KiSystemCall64进行HOOK.

SyscallEntryPoint PROC
    ;cli                                    ; Disable interrupts
    swapgs                                  ; swap GS base to kernel PCR
    mov         gs:[USERMD_STACK_GS], rsp   ; save user stack pointer

    cmp         rax, MAX_SYSCALL_INDEX      ; Is the index larger than the array size?
    jge         KiSystemCall64              ;

    lea         rsp, offset HookEnabled     ; RSP = &SyscallHookEnabled
    cmp         byte ptr [rsp + rax], 0     ; Is hooking enabled for this index?
    jne         KiSystemCall64_Emulate      ; NE = index is hooked
SyscallEntryPoint ENDP

  VT MSR Hook并不是修改了这种HOOK方式,只不过对PG进行欺骗,PG会读取MSR的值,这时候触发VMX切换VMexit进行MSR.0xC0000082处理,从而绕过PG使MSR Hook相对稳定。

PCI/SPI

Industry Standard Architecture(ISA)
Peripheral Component Interconnect(PCI)
Peripheral Component Interconnect Express(PCIe)
Low Pin Count(LPC)
Serial Peripheral Interface(SPI)
Enhanced Serial Peripheral Interface(eSPI)

  很多老式电脑圆孔绿色插鼠标,紫色插键盘,就是PS(Personal System)/2标准,这个是IBM发明的。Windows下PS/2外设和USB处理是两套驱动,就拿键盘来说,PS/2键盘端口驱动是i8042prt,基于USB是Kbdhid,这块有兴趣可以扩展阅读下。

I8042_STATUS_REG
IN al, 0x60h
OUT 0x64, al

  现代的Inetl应该只有南桥芯片了,南桥芯片是一个大集合,外设输入输出不与CPU直接打交道,低速总线处理都要过南桥芯片,上述说的PS/2 USB SAST都在南桥芯片上,还有电源管理啥的都属于南桥控制范围,HAL层这类驱动应该也是和南桥芯片接口打交道。
  基于VT-x和Ept可以做点什么?可以监控SPI Flash ROM还有xHCI(USB3)攻击和异常,这里有一个相对成熟的项目可以参考

https://github.com/KelvinMsft/DeviceMon?tab=readme-ov-file

Part A偏技术科普和铺垫,续篇会对VMCS/VMExit处理和Rust实践源码分析,对攻防的技术原理细节分享。

0 条评论
某人
表情
可输入 255