Skip to content

Commit 96b5ff8

Browse files
author
Requiem
committed
feat: interrupt shadow technique v2
1 parent 8d409a8 commit 96b5ff8

3 files changed

Lines changed: 62 additions & 38 deletions

File tree

docs/documentation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ VMAware provides a convenient way to not only check for VMs, but also have the f
585585
| `VM::ACPI_SIGNATURE` | Check for VM-specific ACPI device signatures | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9601) |
586586
| `VM::TRAP` | Check if after raising two traps at the same RIP, a hypervisor interferes with the instruction pointer delivery | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9746) |
587587
| `VM::UD` | Check if no waveform-audio output devices are present in the system | 🪟 | 25% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L9493) |
588-
| `VM::BLOCKSTEP` | Check if a hypervisor does not properly restore the interruptibility state after a VM-exit in compatibility mode | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10023) |
588+
| `VM::INTERRUPT_SHADOW` | Check if a hypervisor does not properly restore the interruptibility state after a VM-exit in compatibility mode | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10023) |
589589
| `VM::DBVM_HYPERCALL` | Check if Dark Byte's VM is present | 🪟 | 150% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10070) |
590590
| `VM::BOOT_LOGO` | Check boot logo for known VM images | 🪟 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L10191) |
591591
| `VM::MAC_SYS` | Check for VM-strings in system profiler commands for MacOS | 🍏 | 100% | | | | [link](https://github.com/kernelwernel/VMAware/tree/main/src/vmaware.hpp#L8091) |

src/cli.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -988,7 +988,7 @@ static void general(
988988
checker(VM::ACPI_SIGNATURE, "ACPI device signatures");
989989
checker(VM::TRAP, "hypervisor interception");
990990
checker(VM::UD, "undefined exceptions");
991-
checker(VM::BLOCKSTEP, "single step with trap flag");
991+
checker(VM::INTERRUPT_SHADOW, "interrupt shadows");
992992
checker(VM::DBVM, "DBVM hypervisor");
993993
checker(VM::BOOT_LOGO, "boot logo");
994994
checker(VM::MAC_SYS, "system profiler");

src/vmaware.hpp

Lines changed: 60 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ struct VM {
586586
CUCKOO_PIPE,
587587
TRAP,
588588
UD,
589-
BLOCKSTEP,
589+
INTERRUPT_SHADOW,
590590
DBVM,
591591
KERNEL_OBJECTS,
592592
NVRAM,
@@ -10472,31 +10472,44 @@ struct VM {
1047210472
/**
1047310473
* @brief Check if a hypervisor does not properly restore the interruptibility state after a VM-exit
1047410474
* @category Windows
10475-
* @implements VM::BLOCKSTEP
10475+
* @implements VM::INTERRUPT_SHADOW
1047610476
*/
10477-
[[nodiscard]] static bool blockstep() {
10478-
volatile int saw_single_step = 0;
10477+
[[nodiscard]] static bool interrupt_shadow() {
10478+
volatile ULONG_PTR trap_ip = 0;
1047910479

1048010480
#if (x86_32) && !(CLANG || GCC)
10481+
ULONG_PTR baremetal_target_ip = 0;
10482+
1048110483
__try {
1048210484
__asm {
10485+
mov dword ptr[baremetal_target_ip], offset baremetal_target // get exact baremetal trap target address dynamically
10486+
push ebx
10487+
xor eax, eax
10488+
mov ax, ss
1048310489
pushfd
1048410490
or dword ptr[esp], 0x100 // set TF
1048510491
popfd
10486-
xor eax, eax
10487-
mov ax, ss
1048810492
mov ss, ax // this blocks any debug exception for exactly one instruction
1048910493
cpuid
10490-
nop // TF's single-step should fire here on baremetal except on a few buggy processors
10491-
pushfd
10492-
and dword ptr[esp], 0xFFFFFEFF
10493-
popfd
10494+
baremetal_target :
10495+
pop ebx // baremetal delays the #DB until here due to shadow suppression
10496+
nop
10497+
pushfd
10498+
and dword ptr[esp], 0xFFFFFEFF
10499+
popfd
1049410500
}
1049510501
}
10496-
__except (GetExceptionCode() == EXCEPTION_SINGLE_STEP ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
10497-
saw_single_step = 1;
10498-
}
10499-
return (saw_single_step == 0);
10502+
__except (GetExceptionCode() == EXCEPTION_SINGLE_STEP ?
10503+
(
10504+
trap_ip = GetExceptionInformation()->ContextRecord->Eip,
10505+
GetExceptionInformation()->ContextRecord->EFlags &= ~0x100, // clear TF so execution resumes cleanly and stack restores
10506+
EXCEPTION_CONTINUE_EXECUTION
10507+
) : EXCEPTION_CONTINUE_SEARCH) {}
10508+
10509+
// hypervisor is detected if the trap fired at any IP differing from the expected baremetal target
10510+
// OR if the single step exception never fired at all (trap_ip == 0)
10511+
return (trap_ip == 0 || trap_ip != baremetal_target_ip);
10512+
1050010513
#elif (x86_64) || ((x86_32) && (CLANG || GCC))
1050110514
const HMODULE ntdll = util::get_ntdll();
1050210515
if (!ntdll) return false;
@@ -10516,22 +10529,22 @@ struct VM {
1051610529
if (!nt_alloc || !nt_protect || !nt_flush || !nt_free) return false;
1051710530

1051810531
// these opcodes are byte-for-byte identical for both x86_32 and x86_64 architectures
10519-
// like, 0x53 maps to push ebx in 32-bit and push rbx in 64-bit
10532+
// 0x53 maps to push ebx in 32-bit and push rbx in 64-bit
1052010533
static constexpr u8 blockstep_opcodes[] = {
10521-
0x53, // push rbx/ebx (preserve non-volatile register against cpuid)
10522-
0x9C, // pushfq/pushfd
10523-
0x81, 0x0C, 0x24, 0x00, 0x01, 0x00, 0x00, // or dword ptr [rsp/esp], 0x100
10524-
0x9D, // popfq/popfd
10525-
0x31, 0xC0, // xor eax, eax
10526-
0x8C, 0xD0, // mov ax, ss
10527-
0x8E, 0xD0, // mov ss, ax
10528-
0x0F, 0xA2, // cpuid
10529-
0x90, // nop
10530-
0x9C, // pushfq/pushfd
10531-
0x81, 0x24, 0x24, 0xFF, 0xFE, 0xFF, 0xFF, // and dword ptr [rsp/esp], 0xFFFFFEFF
10532-
0x9D, // popfq/popfd
10533-
0x5B, // pop rbx/ebx
10534-
0xC3 // ret
10534+
0x53, // 0: push rbx/ebx (preserve non-volatile register)
10535+
0x31, 0xC0, // 1: xor eax, eax
10536+
0x8C, 0xD0, // 3: mov ax, ss
10537+
0x9C, // 5: pushfq/pushfd
10538+
0x81, 0x0C, 0x24, 0x00, 0x01, 0x00, 0x00, // 6: or dword ptr [rsp/esp], 0x100
10539+
0x9D, // 13: popfq/popfd
10540+
0x8E, 0xD0, // 14: mov ss, ax <- shadow starts here
10541+
0x0F, 0xA2, // 16: cpuid <- buggy hypervisor traps here
10542+
0x5B, // 18: pop rbx/ebx <- baremetal traps here
10543+
0x90, // 19: nop
10544+
0x9C, // 20: pushfq/pushfd
10545+
0x81, 0x24, 0x24, 0xFF, 0xFE, 0xFF, 0xFF, // 21: and dword ptr [rsp/esp], 0xFFFFFEFF
10546+
0x9D, // 28: popfq/popfd
10547+
0xC3 // 29: ret
1053510548
};
1053610549

1053710550
const HANDLE current_process = reinterpret_cast<HANDLE>(-1LL);
@@ -10547,26 +10560,37 @@ struct VM {
1054710560
ULONG old_protection = 0;
1054810561
NTSTATUS st = nt_protect(current_process, &base, &region_size, PAGE_EXECUTE_READ, &old_protection);
1054910562

10563+
// expect the trap explicitly at offset +18 (pop rbx) because of shadow suppression on real hardware
10564+
const ULONG_PTR baremetal_target_ip = reinterpret_cast<ULONG_PTR>(base) + 18;
10565+
1055010566
if (NT_SUCCESS(st)) {
1055110567
nt_flush(current_process, base, region_size);
1055210568
__try {
1055310569
reinterpret_cast<void(*)()>(base)();
1055410570
}
10555-
__except (GetExceptionCode() == EXCEPTION_SINGLE_STEP ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
10556-
saw_single_step = 1;
10557-
}
10571+
__except (GetExceptionCode() == EXCEPTION_SINGLE_STEP ?
10572+
(
10573+
#if (x86_64)
10574+
trap_ip = GetExceptionInformation()->ContextRecord->Rip,
10575+
#else
10576+
trap_ip = GetExceptionInformation()->ContextRecord->Eip,
10577+
#endif
10578+
GetExceptionInformation()->ContextRecord->EFlags &= ~0x100, // to avoid infinite looping
10579+
EXCEPTION_CONTINUE_EXECUTION
10580+
) : EXCEPTION_CONTINUE_SEARCH) {}
1055810581
}
1055910582

1056010583
region_size = 0;
1056110584
nt_free(current_process, &base, &region_size, MEM_RELEASE);
1056210585

10563-
return NT_SUCCESS(st) && (saw_single_step == 0);
10586+
// hypervisor is detected if execution trapped at any offset other than expected baremetal
10587+
// OR if the single step exception never fired at all (trap_ip == 0)
10588+
return NT_SUCCESS(st) && (trap_ip == 0 || trap_ip != baremetal_target_ip);
1056410589
#else
1056510590
return false;
1056610591
#endif
1056710592
}
1056810593

10569-
1057010594
/**
1057110595
* @brief Check if Dark Byte's VM is present
1057210596
* @category Windows
@@ -13618,7 +13642,7 @@ struct VM {
1361813642
case ACPI_SIGNATURE: return "ACPI_SIGNATURE";
1361913643
case TRAP: return "TRAP";
1362013644
case UD: return "UNDEFINED_INSTRUCTION";
13621-
case BLOCKSTEP: return "BLOCKSTEP";
13645+
case INTERRUPT_SHADOW: return "INTERRUPT_SHADOW";
1362213646
case DBVM: return "DBVM_HYPERCALL";
1362313647
case BOOT_LOGO: return "BOOT_LOGO";
1362413648
case MAC_SYS: return "MAC_SYS";
@@ -14176,7 +14200,7 @@ std::array<VM::core::technique, VM::enum_size + 1> VM::core::technique_table = [
1417614200
{VM::EIP_OVERFLOW, {100, VM::eip_overflow}},
1417714201
{VM::HYPERVISOR_HOOK, {100, VM::hypervisor_hook}},
1417814202
{VM::POPF, {100, VM::popf}},
14179-
{VM::BLOCKSTEP, {100, VM::blockstep}},
14203+
{VM::INTERRUPT_SHADOW, {100, VM::interrupt_shadow}},
1418014204
{VM::MSR, {100, VM::msr}},
1418114205
{VM::EDID, {100, VM::edid}},
1418214206
{VM::VIRTUAL_PROCESSORS, {100, VM::virtual_processors}},

0 commit comments

Comments
 (0)