jonomango / hv

Lightweight Intel VT-x Hypervisor.
MIT License
387 stars 78 forks source link

Hide from timing detections #19

Open jonomango opened 1 year ago

jonomango commented 1 year ago

List of timing detections to hide from:

  1. rdtsc+cpuid+rdtsc
    • The classic timing test.
  2. rdtsc+cpuid+rdtsc+sleep()
    • The sleep is particularly annoying because it gives time for the CPU to flush its caches which can lead to large variances in timing (so I believe).
  3. rdtsc+cpuid+rdtsc on VCPU1 while VCPU2 spams cpuid
    • This detection tries to target implementations that share the same timer across multiple VCPUs.
    • Not sharing a timer across VCPUs will lead to issues due to invariant TSC, but this can be avoided if we periodically resynchronize the virtual TSC to the real TSC.
jonomango commented 1 year ago

Related solution by Daax: https://www.unknowncheats.me/forum/3247434-post5.html. The only downside to his solution is that it requires a vm-exit on every rdtsc instruction, which results in a heavy performance hit. The general idea is what I'll be aiming for, however.

jonomango commented 1 year ago

One idea is to enable/disable rdtsc vm-exiting only when it is required: i.e. only when cpuid or a similar instruction has been executed.

jonomango commented 1 year ago

Wow this is annoying... one issue I'm encountering is the Windows scheduler kicking us off the CPU right after we execute cpuid, but before we execute rdtsc. This is much more common than I initially thought due to how much time we spend handling the cpuid vm-exit. This isn't a huge issue by itself because we'll eventually get execution back if we wait long enough, but the problem is that we might get swapped to another CPU which won't have rdtsc-exiting enabled (since cpuid wasn't executed on that CPU). I guess the solution is to send an IPI out to every CPU to enable rdtsc-exiting whenever cpuid is executed on any CPU.

jonomango commented 1 year ago

Optimization: we can ignore cpu->hide_vm_exit_overhead if we can confirm that the vm-exit was triggered from a "safe" module (i.e. a module that is guaranteed to not be timing us). Essentially, vm-exits from ntoskrnl.exe and hal can be safely ignored.

jonomango commented 1 year ago

You may also run into multi-threaded timing checks which are not so easily defeated. I'm not sure there is a way to actually hide time in this scenario aside suspending all other cores during an exit - especially if they use different timers such as a pure software one running without interrupts

Daax mentioned this: "This doesn't defeat MP timing attacks, such as the one EAC utilizes where one thread counts and the other executes the unconditionally exiting instruction."

Agreed. I don't think it is ever possible to fully hide from timing attacks in a generic manner. Vm-exits will always cause overhead that can be observed. However, the scenario that Daax illustrates isn't too hard to evade as long as we don't share a timer between threads (although the detection can be easily amplified by counting in BOTH threads, but that is a different matter entirely).

jonomango commented 1 year ago

The latest commit evades attacks 1 and 3 but will still fail for method 2.