[2022]hypervisor一个麻烦: Time Stamp Counter huoji 虚拟机,虚拟机检测,hypervisor,虚拟化,rdtsc 2022-07-19 824 次浏览 1 次点赞 ### Time Stamp Counter 时间戳计数器( TSC ) 是一个 64 位寄存器,存在于自Pentium以来的所有x86处理器上.它计算自复位以来的 CPU周期数.该指令返回 EDX:EAX 中的 TSC.在x86-64模式下,还会清除RAX和RDX的高32位. ### 特性 对CPU执行的时间高度敏感 大部分GUI、网络设备依赖它用于计数从而实现一些功能 包括ETW也有可选项使用它计数 ### 问题 一个对CPU执行时间高度敏感、而且不容易篡改的东西,最容易拿来干啥? 检测CPU的周期,用于探测hypervisor是否存在 这是一个简单的例子: ```cpp __rdtsc(); cpuid(null,0x1); __rdtsc(); ``` 诺没有存在hypervisor cpuid不会导致vmexit,也不会产生额外的时间 诺存在hypervisor **cpuid会进入到vmexit handle里面.从而产生了额外的时间.** 通过比对退出时间,很容易轻而易举的检测到hypervisor的存在 部分木马在2016年就开始使用这种手段对hypervisor进行检测从而防止自己在沙箱之中 这是检测的poc: https://github.com/huoji120/DuckSandboxDetect ### 错误的对策 聪明的你已经想到,如果在rdtsc里面减少对应的值,是不是就行了? 当你实际操作后你会发现,系统GUI、网络设备都开始莫名其妙的闪烁、黑屏。2分钟后电脑彻底完蛋 这因为两件事: 1. CPU是多核的 2. 因为CPU是多核的,RDTSC也不是同步的 3. 因为(1)和(2)如果你在一个核心减少了tsc,另外一个核心很有可能比前一个核心的tsc少,导致程序计算中出现**负数**.从而崩溃 ### 解决 有非常多的方法可以解决,复杂的方法如XEN、kvm是通过计算CPU频率去"缩放"tsc,一个简单的方法是我们保持所有的核心同步一个tsc. 因此我们需要给每个线程分配一个ctx,记录他的上次退出事件、tsc时间.然后自己模拟tsc上的退出时间.如cpuid一般是+200、msr一般是+150左右(可以事先在进入虚拟化之前测量) ```cpp auto tsc_ctx = get_rdtsc_ctx(); ..... switch (tsc_ctx->last_vmexit) { case svm_intercepted_cpuid: { tsc_ctx->modify_tsc += svm->time->cpuid; break; } case svm_intercepted_rdtsc: { tsc_ctx->modify_tsc += svm->time->rdtsc; break; } case svm_intercepted_rdtscp: { tsc_ctx->modify_tsc += svm->time->rdtscp; break; } ..... } ``` 然后在rdtsc的handle设置我们的handle ```cpp void svm_exit::svm_rdtsc_handle(_svm_guest_status* guest_context) { ULARGE_INTEGER tsc{}; tsc.QuadPart = global::installed ? guest_context->vcpu->last_exit_start : __rdtsc(); guest_context->guest_register->Rax = tsc.LowPart; guest_context->guest_register->Rdx = tsc.HighPart; advance_rip(guest_context); } ``` 这就能绕过大多数tsc的检查了并且保证不会出现太大问题  本文由 huoji 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。 点赞 1
还不快抢沙发