反作弊如何检测系统仿真(3)

2021-01-05 10:31:15 浏览数 (1)

IET发散

作为对标准定时攻击的反对,我们提供了一种更新颖的方法,该方法不依赖时间戳计数器,并且需要进行更多的欺骗工作。在这里的帖子中已经简要提到了这一点,我们决定进一步详细介绍。

IET差异是使用两个不同的指令对指令执行时间(IET)的度量和比较。与rdtsc;cpuid;rdtsc组合类似,IET散度测试使用计数器来分析两条指令,计算平均执行时间,然后比较两条指令的结果。此检测方法使用通过IA32_APERF MSR访问的实际性能计数器,而不是时间戳计数器。如前所述,TSC可以相对轻松地进行仿真,并且对标准检测方法构成威胁。欺骗APERF计数器要困难得多,而且不如在APERF MSR上强制VM退出并执行与TSC仿真类似的操作那样简单。

测试工作如下:

  • 禁用中断。
  • 设置寄存器进行性能分析循环。
  • 捕获指令的开始时钟计数(cpuid)。
  • 每次都用相同的叶子执行,并捕获结束时钟计数。
  • 计算该循环的时钟计数之差。
  • 将结果存储在CPUID_IET数组中。
  • 重复第二条候选指令。
  • 重复循环N次,以获得更精确的时序配置。

选择指令是此检查中最困难的部分,因为您将需要使用无条件退出的指令以确保如果存在VMM,则处理器会陷入其中。理想的候选者是cpuid,但必须谨慎选择第二条指令。平均IET必须比cpuid实际硬件上的IET长,否则检查毫无意义。关键是将一条指令的执行时间与在真实系统上花费更长的一条指令进行比较,因为在虚拟环境cpuid中,完成前会消耗很多周期。候选指令应该比配置目标花费更长的时间,而不是可能被管理程序捕获的指令,并且可以相对容易地设置。

IET发散测试被一些反作弊方法用来抵消作弊所使用的更高级的虚拟机管理程序。下面显示了示例实现。

代码语言:javascript复制
cli
xor     r8d, r8d
mov     ecx, IA32_APERF_MSR
rdmsr
shl     rdx, 20h
or      rax, rdx
mov     r9, rax
lea     rsi, [rsp 20h]
xor     eax, eax
cpuid
mov     [rsi], eax
mov     [rsi 4], ebx
mov     [rsi 8], ecx
mov     [rsi 0Ch], edx
mov     ecx, IA32_APERF_MSR
rdmsr
shl     rdx, 20h
or      rax, rdx
mov     rdx, [rsp 30h]
sub     rax, r9
mov     [rdx r8*8], rax

;
; TODO:
; 	Capture comparable instruction IET.
; 	Store result.
; 	Loop. Break at end.
; 	Enable interrupts.
; 	End profile.
;

这是不完整的实现,对于执行时间长于的第二条指令,需要重复执行这些步骤cpuid。选择第二条指令对于获得清晰的结果很重要。话虽如此,这种检测方法的有效性非常出色,因为它甚至可以捕获最坚固的管理程序。如果您有平台,并且对研究使用该平台的产品感兴趣,我们鼓励您退出APERF访问,并看看一些私人的防作弊手段。

WoW64描述符表

大多数公开的微型管理程序都忽略了这个细节,尽管其中之一hvpp可以正确处理这种常见的监督。众所周知,WoW64在兼容模式下运行代码以支持32位x86(i686)代码的执行。尽管WoW64本身以兼容模式运行代码,但是管理程序仍将以x64代码本机执行。当系统管理程序配置为捕获GDT / IDT访问时,这可能会造成混淆,因为在兼容模式下运行时,真正的处理器只会向描述符表寄存器写入6个字节,而在长模式下运行时,则不会写入10个字节。不能正确处理这是一个易于检测的常见错误。

让我们思考如何检测到这一点。我们需要做的第一件事就是在CPL 0的兼容模式下运行。这需要在CPL 0的环境下完成,因为Windows使用了用户模式指令阻止(UMIP) x86安全功能。我们可以非常简单地从Ring-0暂时暂时进入保护模式。这是很多工作,但是这是实现此检测的方法。

或者,从理论上讲,在内核线程暂时禁用CR4.UMIP之后,可以同步WoW64用户模式代码以执行SIDT / SGDT指令。

如果您想出了如何进入兼容/保护模式的方法,请参见以下内容:

代码语言:javascript复制
UINT8 Descriptor[10];
*(UINT32 *)&Descriptor[6] = 0x13371337;
__sgdt(Descriptor);
if (*(UINT32 *)&Descriptor[6] != 0x13371337) {
    // Gotcha!
}

它通过检查管理程序是否意外将4个额外的字节写入我们的描述符存储器来工作。真的就是这么简单!缓解措施比较简单,需要VMM检查当前段是否为长模式段,并写入适当的长度。

这就是缓解的SIDT样子。我们将缓解措施留给SGDT读者。

代码语言:javascript复制
VmcsReadGuestGdtEntry(X86_REG_CS, &SegmentDesc);

switch (InstructionInfo.Bits.InstructionIdentity) {
	case VMX_GDT_IDT_IDENTITY_SIDT:
		Descriptor->Limit = (u16)VmcsRead32(VMCS_GUEST_IDTR_LIMIT);
		if (SegmentDesc.Bits.L) {
			__writeqword((u64*)((UINTN)Descriptor   FIELD_OFFSET(X86_DESCRIPTOR, Base.UInt64)), (u64)VmcsRead(VMCS_GUEST_IDTR_BASE));
		} else {
			__writedword((u32*)((UINTN)Descriptor   FIELD_OFFSET(X86_DESCRIPTOR, Base.UInt32)),(u32)VmcsRead(VMCS_GUEST_IDTR_BASE));
		}
		break;

防作弊高压检测

下面记录的是BattlEyeEAC用于检测虚拟系统的方法。我们的研究始于捕获所有各种MSR:VMX MSR,EFER,功能控制,APERF,MPERF,DEBUGCTL和LSTAR。我们还限制了VMX / SVM指令的执行,并追溯了RDTSC和CPUID的所有执行。结果有些令人失望。

BattlEye程序

RDTSC / CPUID / RDTSC

BattlEye采用的方法是使用rdtsc;cpuid;rdtsc组合的标准定时攻击。自从文章发表在会员博客上以来,他们已经对代码进行了适度的改进,并将支票移到了内核上。尽管如此,它仍然存在。目前没有进行其他检查。我们预计随着新技术的推出,也将引起更多有关虚拟机管理程序的注意。

他们已经开始映射到内核的这个新组件仍在调查中。

EasyAntiCheat程序

虚拟机读取

当前,EACvmread在驱动程序初始化时执行一次。

他们将其适当地包装在异常处理程序中,这是对以前记录的某些方法的改进。

不幸的是,解决这个问题的方法很简单,但是如果对他们有用,那么似乎某个平台并不会拒绝对vmreadCPL 0的访问。解决方案是#UD在VMM捕获到时向来宾注入vmread。除了LSTAR之外,我们没有发现对CR4或我们检查的MSR的任何访问,这与VMM的LSTAR钩子无关。

提供商似乎已禁用了PatchGuard,并修改了LSTAR,他们添加了LSTAR检查来捕获它。

x86

0 人点赞