反作弊如何检测VT(2)

[复制链接]

该用户从未签到

2380

主题

2433

帖子

9139

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
9139
QQ
跳转到指定楼层
楼主
发表于 2022-4-7 16:09:29 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

想要查看内容赶紧注册登陆吧!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

LBR虚拟化
跟踪中断,消息,分支等是通过IA32_DEBUGCTLMSR在Intel硬件上实现的非常有用的功能。特别是,一些管理程序利用最后分支记录(LBR)和分支跟踪来跟踪混淆产品(例如反欺诈)中的分支。由于某些反作弊产品的欺骗返回地址可以利用LBR / BTS来跟踪发生分支的确切位置。

私人CS:GO联赛在许多反作弊中注意到了这一点。下面是此检测结果的简要示例:

    mov rcx, 01D9h
    xor rdx, rdx
    wrmsr
    rdmsr
    shl rdx, 20h        ; EDX:EAX for wrmsr
    or rax, rdx
    jmp check_msr

check_msr:
    test al, 1
    jnz no_detect
    mov al, 1
    ret
   
no_detect:
    xor rax, rax
    xor rdx, rdx
    mov rdx, 01D9h
    wrmsr
    ret
这将大致翻译为:

__writemsr(IA32_DEBUGCTL, DEBUGCTL_LBR | DEBUGCTL_BTS);
res = __readmsr(IA32_DEBUGCTL);

if(!(res & DEBUGCTL_LBR))
    return 1;

return 0;
更重要的是,将检查可能引发的潜在异常。在CS:GO的各种联盟反作弊中已经看到了这一点,并且在确定是否使用LBR / BTS虚拟化方面非常有效。

那么,有人将如何减轻这种检查?答案仅仅是通过将a#GP注入来宾中,这是不支持LBR / BTS时真正的硬件所要做的。

LBR堆栈检查
除了上述检查之外,还可以根据VMX转换之间已保存/加载的LBR信息的使用情况,进行防作弊检测。由于许多开源项目无法正确处理LBR信息的存储/加载,因此反欺诈可能会通过使用无条件退出指令(如)来强制VM退出CPUID。执行之后,他们将需要在恢复来宾操作之后检查从LBR堆栈中获取的最后一个分支。如果目标地址与期望值不匹配,则意味着存在一些自省引擎。

// Save current LBR top of stack
auto last_branch_taken_pre = __read_lbr_tos();

// Force VM-exit with CPUID
__cpuid(0, &regs);

// Save post VM-exit LBR top of stack
auto last_branch_taken_post = __read_lbr_tos();

// Compare last branch taken
if(last_branch_taken_pre != last_branch_taken_post)
    return TRUE;
这对于捕获利用LBR的虚拟机管理程序非常有效,但不适当地处理LBR信息的存储。LBR堆栈由存储最后一个分支源和目标地址的MSR对组成。下面列出了8个与此相关的MSR。

// Last Branch Source Addresses
MSR_LASTBRANCH_0_FROM_IP
MSR_LASTBRANCH_N-1_FROM_IP
   
// Last Branch Target Addresses
MSR_LASTBRANCH_0_TO_IP
MSR_LASTBRANCH_N-1_TO_IP
而MSR_LASTBRANCH_TOSMSR是包含LBR堆栈顶部指针的MSR。有了此知识并了解了英特尔SDM中记录的VM退出/ VM进入MSR存储区域,我们可以在遇到VM退出时保存LBR堆栈和堆栈顶部,然后将它们在VM进入时还原到来宾。LBR堆栈大小可以从下表中确定:

系统管理程序将需要分配一个区域,然后在其中存储LBR堆栈信息的值,然后将加载/存储计数和地址写入相应的VMCS字段:VMCS_VM_EXIT_MSR_(LOAD/STORE)_COUNT和VMCS_VM_EXIT_MSR_(LOAD/STORE)_ADDRESS。这将成功阻止LBR堆栈检查捕获VMM。

合成MSR
虚拟机管理程序平台通常使用合成MSR向访客报告有关主机的信息。Hyper-V,VMware和VirtualBox是实现Synthetic MSR的商业管理程序的示例。您通常会看到它们在range中实现40000000h - 400000FFh,因为该范围被标记为保留范围。据记录,将来的处理器将永远不会将此范围用于任何功能。

市场上当前的处理器中还没有实现某些MSR,但是它们具有有效的MSR地址。使用rdmsr平台查询时,通常会生成一般保护例外(#GP)。但是,在虚拟化环境中,读取未实现的地址可能会产生不确定的结果。例如,在VMware上从MSR地址2到5进行读取将提供随机数据,并且不会产生异常。

对上述保留范围的探测以及任何未实现的MSR地址都可以用于确定当前系统是否已虚拟化。为了解决这个问题,管理程序应强制对保留的或未实现的MSR进行任何访问,以将a#GP注入来宾。一个简单的检测如下所示:

BOOLEAN KiSyntheticMsrCheck(VOID)
{

#define HV_SYNTHETIC_MSR_RANGE_START 0x40000000
   
    __try
    {
        __readmsr( HV_SYNTHETIC_MSR_RANGE_START );
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        return FALSE;
    }
   
    return TRUE;
}
该检查对于确定是否正在使用商业管理程序非常有用,因为大多数调查对象都在此范围内实施了MSR。

CRn访问退出
像我们的成员在VMware中发现的错误一样,检测到不正确处理控制寄存器访问出口的管理程序的风险也很大。确定VMware虚拟机管理程序未正确模拟处理器行为。他已经为此撰写了荣誉,并且在他的博客中提出了检测用例和缓解方法。

CRn访问正在退出| VMware错误

CRn Mask和VMX可用性
对于与CRx来宾/主机掩码中的一位清除位相对应的每个位置,目标操作数将加载CRx中相应位的值。对于与CRx来宾/主机掩码中设置的位相对应的每个位置,目标操作数将加载CRx读取阴影中的相应位的值。因此,如果在CRx来宾/主机掩码中清除了每个位,则来自CRx的MOV会正常读取CRx;如果在CRx来宾/主机掩码中设置了每个位,则来自CRx的MOV返回CRx读阴影的值。

TL; DR:在来宾/主机掩码中设置了一位的地方,出现了阴影位。来宾/主机掩码中没有位时,将显示实际位。当来宾试图修改来宾/主机掩码中设置的任何位时,会发生VM退出。

基于此,系统管理程序可以CR4.VMXE在CR4读取阴影中将该位设置为0,同时将来宾/主机掩码的CR4该位设置为1。这意味着,如果来宾要读取该位的值,则它们将变为0。 ,在某些平台上可以利用的错误是它们表示VMXE已被禁用,但VMX指令仍然执行。这是不可能的,这是使用虚拟化的明确指示。

解决方案是,#UD如果您打算屏蔽该VMXE位,则在执行VMX指令时将未定义的操作码异常()注入来宾。某些公共平台#GP(0)向来宾注入以执行VMX指令,而#UD这是适当的例外。这也表明存在。

CPUID Leaf比较
与保留的MSR地址范围类似的一种快速方法是CPUID对照其通常的值检查保留的响应。例如,叶子40000000h是由体系结构标记为保留的CPUID叶子,最常用于报告VMM的功能。有两个选项可以检查无效的叶子或返回相同数据的叶子。下面是两个示例。

第一个示例显示使用无效的CPUID叶来确定系统是否已虚拟化。

UINT64 UmpIsSystemVirtualized(void)
{
    unsigned int invalid_leaf = 0x13371337;
    unsigned int valid_leaf = 0x40000000;
   
    struct _HV_DETAILS
    {
        unsigned int Data[4];
    };
   
    _HV_DETAILS InvalidLeafResponse = { 0 };
    _HV_DETAILS ValidLeafResponse = { 0 };
  
    __cpuid( &InvalidLeafResponse, invalid_leaf );
    __cpuid( &ValidLeafResponse, valid_leaf );
  
    if( ( InvalidLeafResponse.Data[ 0 ] != ValidLeafResponse.Data[ 0 ] ) ||
        ( InvalidLeafResponse.Data[ 1 ] != ValidLeafResponse.Data[ 1 ] ) ||
        ( InvalidLeafResponse.Data[ 2 ] != ValidLeafResponse.Data[ 2 ] ) ||
        ( InvalidLeafResponse.Data[ 3 ] != ValidLeafResponse.Data[ 3 ] ) )
        return STATUS_HV_DETECTED;
   
    return STATUS_HV_NOT_PRESENT;
}
第二个示例使用最高的低功能叶将数据与实际系统上提供的数据进行比较。

UINT64 UmpIsSystemVirtualized2(void)
{
    cpuid_buffer_t regs;
    __cpuid((int32_t*)&regs, 0x40000000);

    cpuid_buffer_t reserved_regs;
    __cpuid((int32_t*)&reserved_regs, 0);
    __cpuid((int32_t*)&reserved_regs, reserved_regs.eax);

    if (reserved_regs.eax != regs.eax ||
        reserved_regs.ebx != regs.ebx ||
        reserved_regs.ecx != regs.ecx ||
        reserved_regs.edx != regs.edx)
            return STATUS_HV_DETECTED;
            
    return STATUS_HV_NOT_PRESENT;
}
先进的检测方法
本节将介绍高级检测方法,这些方法虽然可靠,但可能难以实施,有些可能需要在执行后进一步检查结果。缓存侧信道攻击有很大不同。可以使用的缓存侧通道之一非常简单,但是非常有效。在解决发现的反作弊方法之前,我们还将介绍标准的定时攻击及其缺陷。

INVD / WBINVD
此方法用于确定系统管理程序是否正确模拟了INVD指令。不出所料,许多公共平台没有适当地模拟指令,从而使检测向量大开。此方法由我们的提请其成员提供,他使用了该方法并证实了其有效性。

pushfq
cli
push 1                 ; Set cache data
wbinvd                 ; Flush writeback data set from previous instruction to system memory.
mov byte ptr [rsp], 0  ; Set memory to 0. This is in WB memory so it will not be in system memory.
invd                   ; Flush the caches but do not write back to system memory. Real hardware will result in loss of previous operation.
pop rax                ; Proper system behaviour will have AL = 1; Hypervisor/emulator that uses WBINVD or does nothing will have AL = 0.
popfq
ret
应该正确模拟真实系统的细微行为,以避免这种类型的检测。它存在于少数几个开源管理程序平台上。作为读者的练习,请尝试确定如何缓解此副渠道。

还有许多其他缓存侧通道;例如,最常见的是收集有关缓存未命中的统计信息并查找冲突的缓存集。根据实现的不同,这些测试可能会命中或遗漏,并且在实现之前需要进行大量测试,以确保很少(如果有的话)误报。这些类型的解决方案需要确保预取器无法通过随机化高速缓存集访问来确定高速缓存使用情况,并确保从高速缓存集读取的新内容在读取多少行,是否正确探测有效的缓存行方面是正确的,等等。这是一个非常复杂的过程,因此我们没有计划在本文中包括这种检查的实现。

上面的方法足以验证示例,我们建议您也进行验证!请注意,#GP(0)如果使用SGX ,这可能会导致这种情况。

RDTSC / CPUID / RDTSC
如果您已经进行了性能分析,或者为防恶意软件而进行了沙箱检测(或更可疑的目的),则可能已经使用或遇到了这种计时检查。有大量文献报道了这种攻击的细节,并且在大多数情况下它是相对有效的。但是,系统管理程序开发人员变得越来越聪明,并且已经设计出将时间差异降低到非常低的幅度的方法。

用于确定系统是否已虚拟化的这种定时攻击在反作弊中很常见,作为基线检测向量。恶意软件也使用它来确定是否已沙箱化。在有效性方面,我们会说它非常有效。该解决方案虽然有些混乱,但通过了pafish检查和反作弊检查。我们不会泄露任何代码,但让我们简要介绍一下逻辑。

我们知道,定时攻击通过直接使用IA32_TIMESTAMP_COUNTERMSR或内部函数两次查询时间戳计数器__rdtsc。通常情况下,两者之间会有说明。这些指令通常会导致VM退出,因此,其想法是自己模拟周期计数-添加到模拟周期计数器。您可以使用反汇编程序,添加平均值或设计更为准确的方法。从第一个追踪rdtsc第二条指令,将平均周期计数添加到仿真计数器。尽管可以使用MTF,但是没有使用TSC偏移或其他功能-尽管您可以利用MTF。您还需要确定VM转换所需的平均周期数,然后从模拟计数器中减去该平均周期数。在现代处理器上,典型的平均周期为1.2k-2k。

成功实施该解决方案虽然不完美,但其结果要比文献中提供的大多数经过测试的解决方案更好,并且可以通过设计的虚拟化检查。在尝试实施该解决方案时,不要被不必要的细节(如SMI)或同步所困扰,这一点很重要(您必须拥有一个在逻辑处理器之间不变的计数器)。保持简单,愚蠢。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。
分享到:  QQ好友和群QQ好友和群
收藏收藏
回复

使用道具 举报

快速回复高级模式
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表