diff --git a/driver/asm.asm b/driver/asm.asm
new file mode 100644
index 0000000..0e57177
--- /dev/null
+++ b/driver/asm.asm
@@ -0,0 +1,64 @@
+.code
+
+
+; Tests the emulation of the INVD instruction
+;
+; source and references:
+;
+; https://secret.club/2020/04/13/how-anti-cheats-detect-system-emulation.html#invdwbinvd
+; https://www.felixcloutier.com/x86/invd
+; https://www.felixcloutier.com/x86/wbinvd
+;
+; Returns int
+
+TestINVDEmulation PROC
+
+ pushfq
+ cli
+ push 1 ; push some dummy data onto the stack which will exist in writeback memory
+ wbinvd ; flush the internal cpu caches and write back all modified caches
+ ; lines to main memory
+ mov byte ptr [rsp], 0 ; set our dummy value to 0, this takes place inside writeback memory
+ invd ; flush the internal caches, however this instruction will not write
+ ; back to system memory as opposed to wbinvd, meaning our previous
+ ; instruction which only operated on cached writeback data and not
+ ; system memory has been invalidated.
+ pop rax ; on a real system as a result of our data update instruction being
+ ; invalidated, the result will be 1. On a system that does not
+ ; properly implement INVD, the result will be 0 as the instruction does
+ ; not properly flush the caches.
+ xor rax, 1 ; invert result so function returns same way as all verification methods
+ popfq
+ ret
+
+TestINVDEmulation ENDP
+
+
+;
+; Note: fild and fistp respectively are used for loading and storing integers in the FPU,
+; while fld and fstp are used for floating point numbers. No need to use xmm registers
+; as we dont need that level of precision and we need to be as efficient as possible
+;
+; compiler will take care of saving the SSE state for us and restoring it source:
+; https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/using-floating-point-or-mmx-in-a-wdm-driver
+;
+; arguments: INT64 in RCX
+; returns resulting number lol
+
+MySqrt PROC
+
+ push rbp
+ mov rbp, rsp
+ sub rsp, 16
+ mov [rsp + 8], rcx ; cannot directly move from a register into a fp register
+ fild qword ptr[rsp + 8] ; push our number onto the FPU stack
+ fsqrt ; perform the square root
+ fistp qword ptr[rsp] ; pop the value from the floating point stack into our general purpose stack
+ mov rax, qword ptr[rsp] ; store value in rax for return
+ add rsp, 16
+ pop rbp
+ ret
+
+MySqrt ENDP
+
+END
\ No newline at end of file
diff --git a/driver/driver.vcxproj b/driver/driver.vcxproj
index 4ccb1fc..560f6da 100644
--- a/driver/driver.vcxproj
+++ b/driver/driver.vcxproj
@@ -64,6 +64,7 @@
+
@@ -143,7 +144,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/driver/driver.vcxproj.filters b/driver/driver.vcxproj.filters
index 1cc244a..9c4a00b 100644
--- a/driver/driver.vcxproj.filters
+++ b/driver/driver.vcxproj.filters
@@ -72,4 +72,9 @@
Header Files
+
+
+ Source Files
+
+
\ No newline at end of file
diff --git a/driver/hv.c b/driver/hv.c
index 793662a..25920bf 100644
--- a/driver/hv.c
+++ b/driver/hv.c
@@ -9,35 +9,92 @@
#define IA32_APERF_MSR 0x000000E8
/*
-* 1. Bind thread to a single core
-* 2. Raise the IRQL to HIGH_LEVEL
-* 3. disable interrupts
+* TODO: Perform the test in a loop and average the delta out, then compare it
+* to an instruction such as FYL2XP1 (source: secret.club) which has an average
+* execution time slightly higher then the CPUID instruction then compare the two.
+* If the average time for the CPUID instruction is higher then the average time
+* then the FYL2XP1 instruction it is a dead giveaway we are running on a
+* virtualized system.
+*
+* source: https://secret.club/2020/01/12/battleye-hypervisor-detection.html
*/
-VOID APERFMsrTimingCheck()
+
+INT APERFMsrTimingCheck()
{
+ KAFFINITY new_affinity = { 0 };
+ KAFFINITY old_affinity = { 0 };
ULONG64 old_irql;
INT cpuid_result[ 4 ];
- old_irql = __readcr8();
+ /*
+ * First thing we do is we lock the current thread to the logical processor
+ * its executing on.
+ */
+ new_affinity = ( KAFFINITY )( 1 << KeGetCurrentProcessorNumber() );
+ old_affinity = KeSetSystemAffinityThreadEx( new_affinity );
+ /*
+ * Once we've locked our thread to the current core, we saved the old irql
+ * and raise to HIGH_LEVEL to ensure the chance our thread is preempted
+ * by a thread with a higher IRQL is extremely low.
+ */
+ old_irql = __readcr8();
__writecr8( HIGH_LEVEL );
+ /*
+ * Then we also disable interrupts, once again making sure our thread
+ * is not preempted.
+ */
_disable();
+ /*
+ * Once our thread is ready for the test, we read the APERF from the
+ * MSR register and store it. We then execute a CPUID instruction
+ * which we don't really care about and immediately after read the APERF
+ * counter once again and store it in a seperate variable.
+ */
UINT64 aperf_before = __readmsr( IA32_APERF_MSR ) << 32;
-
__cpuid( cpuid_result, 1 );
-
UINT64 aperf_after = __readmsr( IA32_APERF_MSR ) << 32;
+ /*
+ * Once we have performed our test, we want to make sure we are not
+ * hogging the cpu time from other threads, so we reverse the initial
+ * preparation process. i.e we first enable interrupts, lower our irql
+ * to the threads previous irql before it was raised and then restore the
+ * threads affinity back to its original affinity.
+ */
_enable();
-
__writecr8( old_irql );
+ KeRevertToUserAffinityThreadEx( old_affinity );
+ /*
+ * Now the only thing left to do is calculate the change. Now, on some VMs
+ * such as VMWARE the aperf value will be 0, meaning the change will be 0.
+ * This is a dead giveaway we are executing in a VM.
+ */
UINT64 aperf_delta = aperf_after - aperf_before;
- _enable();
+ return aperf_delta == 0 ? TRUE : FALSE;
+}
- DEBUG_LOG( "delta: %llx", aperf_delta );
+NTSTATUS PerformVirtualizationDetection(
+ _In_ PIRP Irp
+)
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ HYPERVISOR_DETECTION_REPORT report;
+ report.aperf_msr_timing_check = APERFMsrTimingCheck();
+ report.invd_emulation_check = TestINVDEmulation();
+
+ Irp->IoStatus.Information = sizeof( HYPERVISOR_DETECTION_REPORT );
+
+ RtlCopyMemory(
+ Irp->AssociatedIrp.SystemBuffer,
+ &report,
+ sizeof( HYPERVISOR_DETECTION_REPORT )
+ );
+
+ return status;
}
\ No newline at end of file
diff --git a/driver/hv.h b/driver/hv.h
index e30ae70..7dc4e91 100644
--- a/driver/hv.h
+++ b/driver/hv.h
@@ -3,6 +3,17 @@
#include
-VOID APERFMsrTimingCheck();
+typedef struct _HYPERVISOR_DETECTION_REPORT
+{
+ INT aperf_msr_timing_check;
+ INT invd_emulation_check;
+
+}HYPERVISOR_DETECTION_REPORT, *PHYPERVISOR_DETECTION_REPORT;
+
+NTSTATUS PerformVirtualizationDetection(
+ _In_ PIRP Irp
+);
+
+extern INT TestINVDEmulation();
#endif
\ No newline at end of file
diff --git a/driver/ioctl.c b/driver/ioctl.c
index 86bd574..0212c06 100644
--- a/driver/ioctl.c
+++ b/driver/ioctl.c
@@ -98,8 +98,6 @@ NTSTATUS DeviceControl(
case IOCTL_HANDLE_REPORTS_IN_CALLBACK_QUEUE:
- APERFMsrTimingCheck();
-
status = HandlePeriodicCallbackReportQueue(Irp);
if ( !NT_SUCCESS( status ) )
@@ -107,6 +105,15 @@ NTSTATUS DeviceControl(
break;
+ case IOCTL_PERFORM_VIRTUALIZATION_CHECK:
+
+ status = PerformVirtualizationDetection( Irp );
+
+ if ( !NT_SUCCESS( status ) )
+ DEBUG_ERROR( "PerformVirtualizationDetection failed with status %x", status );
+
+ break;
+
default:
DEBUG_ERROR( "Invalid IOCTL passed to driver" );
break;
diff --git a/driver/ioctl.h b/driver/ioctl.h
index e8e8fb0..a0ac205 100644
--- a/driver/ioctl.h
+++ b/driver/ioctl.h
@@ -11,6 +11,7 @@
#define IOCTL_VALIDATE_DRIVER_OBJECTS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x2002, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_NOTIFY_DRIVER_ON_PROCESS_LAUNCH CTL_CODE(FILE_DEVICE_UNKNOWN, 0x2004, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_HANDLE_REPORTS_IN_CALLBACK_QUEUE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x2005, METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define IOCTL_PERFORM_VIRTUALIZATION_CHECK CTL_CODE(FILE_DEVICE_UNKNOWN, 0x2006, METHOD_BUFFERED, FILE_ANY_ACCESS)
typedef struct _DRIVER_INITIATION_INFORMATION
{
diff --git a/driver/modules.h b/driver/modules.h
index 5af89bf..6d5a7c3 100644
--- a/driver/modules.h
+++ b/driver/modules.h
@@ -5,7 +5,7 @@
#include
#define REPORT_MODULE_VALIDATION_FAILURE 60
-#define MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT 5
+#define MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT 20
#define MODULE_REPORT_DRIVER_NAME_BUFFER_SIZE 128
diff --git a/user/km/driver.cpp b/user/km/driver.cpp
index 6f143d6..74851fe 100644
--- a/user/km/driver.cpp
+++ b/user/km/driver.cpp
@@ -192,8 +192,6 @@ void kernelmode::Driver::QueryReportQueue()
( UINT64 )buffer + sizeof( global::report_structures::OPEN_HANDLE_FAILURE_REPORT_HEADER ) +
i * sizeof( global::report_structures::OPEN_HANDLE_FAILURE_REPORT ) );
- std::cout << report->process_id << " " << report->process_name << std::endl;
-
this->report_interface->ReportViolation( report );
}
@@ -232,21 +230,33 @@ void kernelmode::Driver::NotifyDriverOnProcessLaunch()
LOG_ERROR( "DeviceIoControl failed with status code 0x%x", GetLastError() );
}
-void kernelmode::Driver::CompleteQueuedCallbackReports()
+void kernelmode::Driver::DetectSystemVirtualization()
{
+ BOOLEAN status;
+ HYPERVISOR_DETECTION_REPORT report;
+ DWORD bytes_returned;
-}
+ status = DeviceIoControl(
+ this->driver_handle,
+ IOCTL_PERFORM_VIRTUALIZATION_CHECK,
+ NULL,
+ NULL,
+ &report,
+ sizeof( HYPERVISOR_DETECTION_REPORT ),
+ &bytes_returned,
+ NULL
+ );
-void kernelmode::Driver::EnableProcessLoadNotifyCallbacks()
-{
- /*
- * note: no need for these since when the dll is loaded it will simply
- * notify the driver.
- */
-}
+ if ( status == NULL )
+ {
+ LOG_ERROR( "DeviceIoControl failed virtualization detect with status %x", GetLastError() );
+ return;
+ }
-void kernelmode::Driver::DisableProcessLoadNotifyCallbacks()
-{
+ if ( report.aperf_msr_timing_check == TRUE || report.invd_emulation_check == TRUE )
+ LOG_INFO( "HYPERVISOR DETECTED!!!" );
+
+ /* shutdown the application or smth lmao */
}
void kernelmode::Driver::ValidateKPRCBThreads()
diff --git a/user/km/driver.h b/user/km/driver.h
index 75b63d3..98efa07 100644
--- a/user/km/driver.h
+++ b/user/km/driver.h
@@ -11,6 +11,7 @@
#define IOCTL_MONITOR_CALLBACKS_FOR_REPORTS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x2003, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_NOTIFY_DRIVER_ON_PROCESS_LAUNCH CTL_CODE(FILE_DEVICE_UNKNOWN, 0x2004, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_HANDLE_REPORTS_IN_CALLBACK_QUEUE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x2005, METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define IOCTL_PERFORM_VIRTUALIZATION_CHECK CTL_CODE(FILE_DEVICE_UNKNOWN, 0x2006, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define MAX_HANDLE_REPORTS_PER_IRP 10
@@ -23,6 +24,7 @@ namespace kernelmode
std::shared_ptr report_interface;
void QueryReportQueue();
+
public:
Driver(LPCWSTR DriverName, std::shared_ptr ReportInterface );
@@ -31,9 +33,7 @@ namespace kernelmode
void VerifySystemModules();
void RunCallbackReportQueue();
void NotifyDriverOnProcessLaunch();
- void CompleteQueuedCallbackReports();
- void EnableProcessLoadNotifyCallbacks();
- void DisableProcessLoadNotifyCallbacks();
+ void DetectSystemVirtualization();
void ValidateKPRCBThreads();
void CheckDriverHeartbeat();
/* todo: driver integrity check */
@@ -43,6 +43,12 @@ namespace kernelmode
{
LONG protected_process_id;
};
+
+ struct HYPERVISOR_DETECTION_REPORT
+ {
+ INT aperf_msr_timing_check;
+ INT invd_emulation_check;
+ };
}
#endif
diff --git a/user/km/kmanager.cpp b/user/km/kmanager.cpp
index 5b8d908..f8057a9 100644
--- a/user/km/kmanager.cpp
+++ b/user/km/kmanager.cpp
@@ -20,3 +20,8 @@ void kernelmode::KManager::MonitorCallbackReports()
{
this->thread_pool->QueueJob( [ this ]() { this->driver_interface->RunCallbackReportQueue(); } );
}
+
+void kernelmode::KManager::DetectSystemVirtualization()
+{
+ this->thread_pool->QueueJob( [ this ]() { this->driver_interface->DetectSystemVirtualization(); } );
+}
diff --git a/user/km/kmanager.h b/user/km/kmanager.h
index 857ce55..e8aac89 100644
--- a/user/km/kmanager.h
+++ b/user/km/kmanager.h
@@ -20,6 +20,7 @@ namespace kernelmode
void RunNmiCallbacks();
void VerifySystemModules();
void MonitorCallbackReports();
+ void DetectSystemVirtualization();
};
}
diff --git a/user/main.cpp b/user/main.cpp
index 45d47f1..8a5aed2 100644
--- a/user/main.cpp
+++ b/user/main.cpp
@@ -28,9 +28,10 @@ DWORD WINAPI Init(HINSTANCE hinstDLL)
usermode::UManager umanager( thread_pool, report_interface );
kernelmode::KManager kmanager( driver_name, thread_pool, report_interface);
- kmanager.MonitorCallbackReports();
+ //kmanager.MonitorCallbackReports();
//kmanager.RunNmiCallbacks();
- kmanager.VerifySystemModules();
+ //kmanager.VerifySystemModules();
+ kmanager.DetectSystemVirtualization();
//umanager.ValidateProcessModules();
//umanager.ValidateProcessMemory();
diff --git a/user/report.h b/user/report.h
index 9be491b..f8d483c 100644
--- a/user/report.h
+++ b/user/report.h
@@ -9,7 +9,7 @@
#define REPORT_BUFFER_SIZE 1024
#define MAX_SIGNATURE_SIZE 256
-#define MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT 5
+#define MODULE_VALIDATION_FAILURE_MAX_REPORT_COUNT 20
#define REPORT_CODE_MODULE_VERIFICATION 10
#define REPORT_CODE_START_ADDRESS_VERIFICATION 20