freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

[2022] hypervisor: 检测与预防 (上和中)
2022-06-19 15:37:37
所属地 北京

rdtsc

大部分虚拟机包括沙箱用的都存在一个问题,cpuid等一些会造成vmexit的东西, 被硬件加速了 不开虚拟机的时候 只花很少的时间,大于200个CPU周期左右
一旦进了虚拟机
会到大约20000个周期不等
木马 外挂 可以通过判断CPU周期确定自己是不是在虚拟机里 导致沙箱不存活之类的

https://github.com/huoji120/DuckSandboxDetect/blob/main/ConsoleApplication1/ConsoleApplication1.cpp#L25
因此我们要在进入虚拟化后,算一下退出所消耗的周期,然后在tsc_offset减去对应的值即可

不可信参数

host不能接受不可信参数,比如xsetbv如果给0会造成PG.
可以通过try catch预防 但是很多manual map的虚拟机是没办法用try catch的,因此要么做idt shadow 要么老老实实按照xen的来检查惨

idt/gdt shadow

host机不能跟guest机用同一个idt/gdt
需要自己自定义异常处理
异常处理可以支持嵌套异常处理
这是简单的异常处理代码:

uintptr_t idt::search_seh_jmp_rip(uintptr_t rip) { 
 const auto exception_directory = &nt_headers->_optional_header._data_directory[IMAGE_DIRECTORY_ENTRY_EXCEPTION];
 ....
}
void idt::seh_handler(_idt_stack_register_error_code* stack_reg) 
{
    if (global::cpu_type == _cpu_type::amd)
    {
        svm::svm_context->vcpu[function::get_current_cpu_num()]->last_error_code = stack_reg->error_code;
    }
    else {
        //intel..
    }
    uintptr_t search_rip = stack_reg->rip;
    uintptr_t search_rsp = stack_reg->rsp;
    uintptr_t search_rbp = stack_reg->rbp;
    size_t frame = 0;
    DebugPrint("[hyperduck] search exception rip %p \n", search_rip);
    for (;;frame++)
    {
        if (search_rip < reinterpret_cast<uintptr_t>(MmSystemRangeStart) || search_rsp < reinterpret_cast<uintptr_t>(MmSystemRangeStart))
            break;

        if (!search_rip || !MmIsAddressValid((PVOID)search_rip) || !MmIsAddressValid((PVOID)search_rip)) {
            break;
        }
        search_rip = search_seh_jmp_rip(search_rip);
        /*
            fix me :
            Because the add value of RSP is not calculated through assembly, generally speaking, one or two layers of simple functions can be used, and complex functions are not very good. If you want to be accurate, you need to traverse the assembly like Microsoft, and encounter the increase of add RSP
            Refer to rtlvirtualunwind for details
            In addition, the recovery of registers and stacks is not completely restored
        */
        if (search_rip == 0) {
            search_rip = (uintptr_t)(*(uintptr_t*)search_rsp);
            search_rsp += 8;
        }
        else {
            break;
        }
    }
    if (search_rip == 0) {
        DebugPrint("[hyperduck] unhandle exception! %p \n", search_rip);
        function::bug_check();
    }
    DebugPrint("[hyperduck] handle exception jmp to %p frame: %d \n", search_rip, frame);
    stack_reg->rip = search_rip;
    stack_reg->rsp = search_rsp;
    stack_reg->rbp = search_rbp;
}

这样你就可以把之前所说的不可信参数加一个检查

__try
    {
        asm_xsetbv(xcr, xcr_value.QuadPart);
        //_xsetbv(xcr, xcr_value.QuadPart);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        inject_exception_gp_by_code(guest_context, guest_context->vcpu->last_error_code);
        return;
    }

另外也能预防类似于NMI异常导致的vmexit暴露的问题

msr

对于AMD来说(intel也一样)
有些MSR是不能改的
比如amd64_efer 如果在虚拟机运行的时候改了会造成DF异常
因此需要shadow这些MSR

void svm_exit::svm_shadow_msr(ULARGE_INTEGER* fake_msr_value, bool is_read, uintptr_t origin_msr, _svm_guest_status* guest_context) {
    if (is_read) {
        if (fake_msr_value->QuadPart == NULL) {
            fake_msr_value->QuadPart = origin_msr;
        }
        guest_context->guest_register->Rax = fake_msr_value->LowPart;
        guest_context->guest_register->Rdx = fake_msr_value->HighPart;
    }
    else {
        fake_msr_value->LowPart = guest_context->guest_register->Rax & MAXUINT32;
        fake_msr_value->HighPart = guest_context->guest_register->Rdx & MAXUINT32;
    }
}

这些MSR在svm中需要注意:
amd64_vmcr、amd64_tsx、amd64_efer

tsx

TSX指令会造成一个vmexit
但是这个vmexit是没有具体的vmexit code的,导致虚拟机崩溃
最好的办法是在CPUID部分给guest返回不支持RTM

switch (guest_context->guest_register->Rax)
    {
    case cpuid_get_extended_feature:
        _bittestandreset((long*)&cpuid_reg.rcx, amd64_cpuid_tsx_support);
        break;

并且在TSX MSR上注入PG异常

CR3

很多开源虚拟机,CR3是guest和host共享的

导致可以通过写垃圾CR3后提交vmexit让host崩溃
POC代码:

dpc level:
_writecr3(0x133337);
cpuid; //<-此时你的垃圾玩具机就崩溃了

修复:
host和guest要独立一份CR3

TSS

TSS在windows上负责异常的时候拷贝栈.
当异常发生并且执行被重定向到中断处理程序时,RSP中的地址不能被信任.因为有可能不正确.或者在权限转换的时候发生提权操作
修复:
拷贝TSS,修改host的gdt的tss为自己的.然后把几个比如NMI、de这些带stack的东西设置自己的堆栈就行

xsetbv

xsetbv 光靠try catch还是不太行.可能会发生”主机设置了后导致了异常,再try的时候来不及”的情况.所以要跟XEN/KVM一样在try前面再次检查字段有没有问题:

大概代码:

bool valid_xcr0(uintptr_t xcr0)
{
    /* FP must be unconditionally set. */
    if (!(xcr0 & X86_XCR0_FP))
        return false;

    /* YMM depends on SSE. */
    if ((xcr0 & X86_XCR0_YMM) && !(xcr0 & X86_XCR0_SSE))
        return false;

    if (xcr0 & (X86_XCR0_OPMASK | X86_XCR0_ZMM | X86_XCR0_HI_ZMM))
    {
        /* OPMASK, ZMM, and HI_ZMM require YMM. */
        if (!(xcr0 & X86_XCR0_YMM))
            return false;

        /* OPMASK, ZMM, and HI_ZMM must be the same. */
        if (~xcr0 & (X86_XCR0_OPMASK | X86_XCR0_ZMM | X86_XCR0_HI_ZMM))
            return false;
    }

    /* BNDREGS and BNDCSR must be the same. */
    if (!(xcr0 & X86_XCR0_BNDREGS) != !(xcr0 & X86_XCR0_BNDCSR))
        return false;
    return true;
}
....

    UINT32 xcr = (UINT32)guest_status->guest_context->Rcx;
    ULARGE_INTEGER xcr_value;
    if (xcr != 0) {
        //不为0 注入GP异常
        inject_event(guest_status, ia32_invalid_opcode, ia32_hardware_exception, false, NULL);
        return;
    }
    xcr_value.u.LowPart = (UINT32)guest_status->guest_context->Rax;
    xcr_value.u.HighPart = (UINT32)guest_status->guest_context->Rdx;

    __cpuid_params_t cpuid_reg;
    _huoji_cpuidex((int*)&cpuid_reg, 7, 0);

    if ((xcr_value.LowPart & cpuid_reg.rax) != cpuid_reg.rax || (xcr_value.HighPart & cpuid_reg.rdx) != cpuid_reg.rdx) {
        //cpu不支持这个指令
        inject_event(guest_status, ia32_invalid_opcode, ia32_hardware_exception, false, NULL);
        return;
    }

    if (!valid_xcr0(xcr_value.QuadPart)) {
        //给了个无效值
        inject_event(guest_status, ia32_invalid_opcode, ia32_hardware_exception, false, NULL);
        return;
    }
.....
# 系统安全 # Hypervisor
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录