反作弊如何检测VT(1)

[复制链接]

该用户从未签到

2380

主题

2433

帖子

9139

积分

管理员

Rank: 9Rank: 9Rank: 9

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

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

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

x
作为我们的第一篇文章介绍了检测VMM是否存在的各种方法,无论是商业的还是定制的,我们都希望做到透彻并将其与我们对流行的反作弊厂商的研究联系起来。首先,对于游戏黑客领域以外的人员来说,了解管理程序在作弊中的用途以及在使用作弊程序的作弊提供商中保持反欺诈的重要性非常重要。这篇文章将涵盖可用于Intel / AMD的几种标准检测方法;提供说明,缓解措施和一般效果评估。然后,我们将探讨一种高效的旁通道攻击-平台无关的。然后,我们将研究一些特定于OS的方法,这些方法会滥用WoW64中描述符表信息的某些误处理以及阻止自定义的syscall挂钩方法的方法,如Reverse Engineering博客上记录的。

更有趣的部分涵盖BattlEye和EasyAntiCheat使用的实际方法。我们将介绍可以进行的改进以及其方法的一般有效性。这绝不是对反作弊的抨击,因为要跟上技术发展的步伐,并且很难掌握滥用技术的最新方法。他们的工作表现非常出色,就像其他软件一样都有漏洞。这旨在帮助有兴趣了解双方运作方式以及攻击者/防御者击败/采用虚拟化检测的各种方式的人员。我们将以NtDeviceIoControlFile/IofCallDriver两个反作弊调用中的一些杂项数据转储来结束本文。这与虚拟机管理程序无关,但是我们在调查的同时发现了-为什么不呢?

让我们深入。

虚拟机监控程序盛行
虚拟机管理程序(VMM)的出现在安全研究界引起了很多炒作。这种炒作还激起了一些不以研究为基础的圈子,例如作弊/恶意软件社区,其最终目标是使用管理程序来模拟系统行为/隐藏存在。诸如EPT的各种新技术使攻击者能够滥用某些功能,例如EPTP切换或页面挂钩隐藏安全软件探针中的信息。随着这项技术的普及,许多开放源代码项目被发布,以帮助安全社区了解如何开发利用该技术的工具。这也意味着希望隐瞒自己的可疑社区开始使用这些开源项目,其中之一就是游戏黑客社区。

反作弊的主要好处是,许多付费作弊提供商开始使用经过稍微修改的开源版本的工具。这些开源工具仅用于教育目的,并未针对可能暴露其存在的众多攻击进行强化。反作弊利用这种缺乏灵活性来检测使用自定义/开放源代码项目的黑客,并试图扑灭群居。但是,近来,随着对技术的研究不断完善和广泛普及,原始的检测载体的有效性越来越低。

知道了这一点,我们将研究检测虚拟机管理程序当前是否正在计算机上运行的方法。检测向量很多,我们想详细介绍从无效到超级有效的几种技术。猫鼠游戏将继续,但它需要在相对方面进行更多创新。我们认为,反作弊在这一领域已停滞不前,我们希望提供更多有关我们为何会有这种感觉的信息。

标准检测方法
本节将介绍一些检测虚拟机管理程序的特定于平台的方法。大多数开放源代码管理程序都是为在Intel处理器上使用而构建的,因此将涉及更多的向量。尽管反作弊和作弊提供者较少支持,但许多方法也可以应用于AMD。

垃圾写入未实现的MSR
Intel和AMD均支持使用分别称为MSR位图/ MSR权限位图的位图。该位图使VMM可以控制rdmsr特定MSR是否导致VM退出。此位图只覆盖了从MSR值的特定范围0000-1FFF和C0000000-C0001FFF。这意味着在启用VMX / SVM时,对该范围以外的MSR进行的任何读/写操作都可能具有未定义的行为。

安全软件(无论是反作弊还是反恶意软件)可以利用此信息的一种方式是检查对范围之外的MSR的MSR访问是否导致生成异常。如果在真实硬件上执行未实现/保留的MSR地址的写操作,则处理器将生成一般保护异常。但是,某些开源虚拟机管理程序不会丢弃对无效/未实现的MSR的写入,而是会直写,从而导致系统不稳定。为了减轻这种情况,rdmsr应将未执行/保留的MSR地址上执行的操作注入#GP到来宾。

无论如何,这都不是一种非常有效的检测方法。

带TF的调试异常(#DB)
确定是否使用特定的开源系统管理程序的常用方法是,#DB在执行带有该EFLAGS.TF集合的退出指令时,检查异常是否在正确的指令边界上传递。如果未正确处理单步调试异常,则会发生这种情况。一种简单的检测方法是创建类似于以下内容的检测例程:

pushfq
or dword ptr [rsp], 0100h
mov eax, 0FFFFFFFFh
popfq
<exiting_inst>
nop
ret
要检测此特定平台,需要使用VEH来检查RIP并确定是否设置了单步陷阱标志。在执行将触发异常的检测过程之前,我们需要修改调试寄存器,然后设置线程上下文。重要的是要记住,您需要保留对调试寄存器的修改,ContextFlags并启用适当的位,以便为所有任务设置断点条件。

一旦检测到断点并将其与适当的DR7标志关联,便会生成调试异常。处理器不会清除任务切换上的这些标志,从而允许将断点应用于所有任务。

交付异常后,我们注册的异常处理程序将检查RIP并确定是否#DB按照正确的指令进行交付。在实际硬件上,例外将在上交付,<exiting_inst>而在虚拟环境中,在这种监督下,例外将在上交付nop。

此方法可有效检测特定的开源平台,缓解措施已在此处的成员博客中进行了说明。但是,尽管已出版,但仍在许多作弊提供者中使用。

XSETBV
该XSETBV指令有趣的部分是,它是导致VM无条件退出的少数指令之一。我们可以利用XSETBV指令的此属性来检测虚拟机监控程序的存在。

这种检测虚拟机监控程序存在的方法依赖于在主机XSETBVVM退出处理程序中引起异常。由于大多数已知的私有和公共小型虚拟机管理程序实现都盲目执行XSETBV主机XSETBV处理程序中的指令,因此,如果我们XSETBV以访客状态应引起故障的方式执行,则可以在这些简单的虚拟机管理程序实现下使主机出错。

首先,我们必须确定在哪些情况下XSETBV会导致故障。

由此可见,有几种方法可以强制执行一般保护故障(#GP)。本文档告诉我们必须始终设置位0,并且使用ECX = 0和执行指令EAX[0] = 0将导致#GP。这是一个如何导致主机故障的示例:

UINT64 XCR0 = _xgetbv(0);

__try {

    //
    // Clear the bit 0 of XCR0 to cause a #GP(0)!
    //
    _xsetbv(0, XCR0 & ~1);

} __except(EXCEPTION_EXECUTE_HANDLER) {

    //
    // If we get here, the host has properly handled XSETBV and injected a
    // #GP(0) into the guest.
    //
    LOG_DEBUG("1337!");
}
在这样处理的天真的虚拟机管理程序实现下运行会XSETBV导致主机发生故障,从而导致错误检查。这就是我们想要的!用于处理此指令的简单虚拟机管理程序实现如下所示:

VMM_EVENT_STATUS HVAPI VmmHandleXsetbv(PVIRTUAL_CPU VirtualCpu)
{
    UINT32 Xcr;
    ULARGE_INTEGER XcrValue;

    Xcr = (UINT32)VirtualCpu->Context->Rcx;
    XcrValue.u.LowPart = (UINT32)VirtualCpu->Context->Rax;
    XcrValue.u.HighPart = (UINT32)VirtualCpu->Context->Rdx;

    //
    // Blindly execute XSETBV with whatever the guest gives us, because we
    // trust our guest
    //
    _xsetbv(Xcr, XcrValue.QuadPart);

    return VMM_HANDLED_ADVANCE_RIP;
}
您可能想知道-引起这样的错误检查是否安全?好吧,不。正确编写的虚拟机管理程序实现在使用SEH时不会引起错误检查-但是,大多数用于作弊目的的虚拟机管理程序通过利用某些第三方驱动程序将其驱动程序映射到内核时,都无法使用SEH。有多种方法可以在未签名的驱动程序中实现SEH,但这超出了本文的范围。在裸机上运行或在具有适当XSETBV仿真的虚拟机监控程序下运行,只需输出1337!。

除了使您的用户烦恼之外,如何将其用作可靠的检测媒介?注册一个错误检查回调!这是在进行错误检查后执行代码并处理写入故障转储的数据的便捷方法。逻辑如下:

注册错误检查回调。
将幻数和GUID保存为转储的一部分。
在下次启动时解析转储。
这是很多工作,因此这是需要完成的一小部分工作:

KBUGCHECK_REASON_CALLBACK_RECORD BugCheckCallbackRecord = {0};
BOOLEAN BugCheckCallbackRegistered = FALSE;
static const UINT64 MagicNumber = 0x1337133713371337;
// b4911b81-7b73-4f2b-afcc-3b7ce3e1480c
static const GUID MagicDriverGuid = {0xb4911b81, 0x7b73, 0x4f2b, {0xaf, 0xcc, 0x3b, 0x7c, 0xe3, 0xe1, 0x48, 0x0c} };

VOID BugCheckCallbackRoutine(KBUGCHECK_CALLBACK_REASON Reason, struct _KBUGCHECK_REASON_CALLBACK_RECORD* Record, PVOID ReasonSpecificData, UINT32 ReasonSpecificDataLength)
{
    PKBUGCHECK_SECONDARY_DUMP_DATA SecondaryDumpData;
    SecondaryDumpData = (PKBUGCHECK_SECONDARY_DUMP_DATA)ReasonSpecificData;
    SecondaryDumpData->Guid = MagicDriverGuid;
    SecondaryDumpData->OutBuffer = (PVOID)&MagicNumber;
    SecondaryDumpData->OutBufferLength = sizeof(MagicNumber);
}

//
// ...
//

KeInitializeCallbackRecord(&BugCheckCallbackRecord);
BugCheckCallbackRegistered = KeRegisterBugCheckReasonCallback(&BugCheckCallbackRecord, BugCheckCallbackRoutine, KbCallbackSecondaryDumpData, (PUINT8)"secret.club");

if (!BugCheckCallbackRegistered) {
    return STATUS_UNSUCCESSFUL;
}
减轻这种类型的检测很耗时,但很重要。遵循的最佳规则:阅读该体系结构的SDM / APM。这是正确实施的XSETBVVM退出处理程序的示例:

static BOOLEAN VmmpIsValidXcr0(UINT64 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;
    }

    // BNDREGS and BNDCSR must be the same.
    if ((!(Xcr0 & X86_XCR0_BNDREGS)) != (!(Xcr0 & X86_XCR0_BNDCSR))) {
        return FALSE;
    }

    // Validate AVX512 xsave feature bits.
    if (Xcr0 & X86_XSTATE_MASK_AVX512) {

        // 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;
        }
    }

    // XCR0 feature bits are valid!
    return TRUE;
}

VMM_EVENT_STATUS HVAPI VmmHandleXsetbv(PVIRTUAL_CPU VirtualCpu)
{
    UINT32 Xcr;
    ULARGE_INTEGER XcrValue;

    Xcr = (UINT32)VirtualCpu->Context->Rcx;

    // Make sure the guest is not trying to write to a bogus XCR.
    //
    switch(Xcr) {
    case X86_XCR_XFEATURE_ENABLED_MASK:
        break;

    default:
        HV_DBG_BREAK();
    GPFault:
        VmxInjectGP(VirtualCpu, 0);
        return VMM_NOT_HANDLED;
    }

    XcrValue.u.LowPart = (UINT32)VirtualCpu->Context->Rax;
    XcrValue.u.HighPart = (UINT32)VirtualCpu->Context->Rdx;

    // Make sure the guest is not trying to set any unsupported bits.
    //
    if (XcrValue.QuadPart & ~GetSupportedXcr0Bits()) {
        HV_DBG_BREAK();
        goto GPFault;
    }

    // Make sure bits being set are architecturally valid.
    //
    if (!VmmpIsValidXcr0(XcrValue.QuadPart)) {
        HV_DBG_BREAK();
        goto GPFault;
    }

    // By this point, the XCR value should be accepted by hardware.
    //
    _xsetbv(Xcr, XcrValue.QuadPart);

    return VMM_HANDLED_ADVANCE_RIP;
}
分享到:  QQ好友和群QQ好友和群
收藏收藏
回复

使用道具 举报

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

本版积分规则

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