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