// // Registers on image load callback then applies vmhook to EAC // // #include "scn.hpp" #include "shithook.hpp" #include "sha1.hpp" #include "md5.hpp" // // game cheat offset flash backs... // #define EAC_VM_HANDLE_OFFSET 0xE93D #define EAC_SHA1_OFFSET 0x4C00 #define EAC_MD5_OFFSET 0x37378 #define EAC_CRC32_OFFSET 0x27C8C #define EAC_IMAGE_BASE 0x140000000 #define DBG_PRINT(format, ...) \ DbgPrintEx( DPFLTR_SYSTEM_ID, DPFLTR_ERROR_LEVEL, \ "[vmhook-eac [core number = %d]]" format, KeGetCurrentProcessorNumber(), __VA_ARGS__) // // vm handler indexes for READQ... // u8 readq_idxs[] = { 247, 215, 169, 159, 71, 60, 55, 43, 23 }; // // vm handler indexes for READDW // u8 readdw_idxs[] = { 218, 180, 179, 178, 163, 137, 92, 22, 12 }; // // vm handler indexes for READB // u8 readb_idxs[] = { 249, 231, 184, 160, 88, 85, 48, 9, 2 }; vm::hook_t* g_vmhook = nullptr; vm::handler::table_t* g_vm_table = nullptr; u64 __image_base = 0u, __image_size = 0u, __image_clone = 0u; inline_hook_t __crc32_hook, __sha1_hook, __md5_hook; void* operator new( u64 size ) { // // Could have also used ExAllocatePoolZero... // return RtlZeroMemory(ExAllocatePool(NonPagedPool, size), size); } void operator delete ( void* ptr, u64 size ) { UNREFERENCED_PARAMETER(size); ExFreePool(ptr); } __declspec(noinline) void hook_sha1( void *data, unsigned int len, void* result ) { sha1_ctx ctx; sha1_init(&ctx); if (scn::read_only(__image_base, (u64)data)) { DBG_PRINT("sha1 hash data = 0x%p, len = 0x%x, result = 0x%p\n", data, len, result); sha1_update(&ctx, (unsigned char*)__image_clone + (((u64)data) - __image_base), len); sha1_final((unsigned char*)result, &ctx); return; } sha1_update(&ctx, (unsigned char*) data, len); sha1_final((unsigned char*)result, &ctx); } void image_loaded( PUNICODE_STRING image_name, HANDLE pid, PIMAGE_INFO image_info ) { // // PID is zero when the module being loaded is going into the kernel... // if (!pid && wcsstr(image_name->Buffer, L"EasyAntiCheat.sys")) { if (g_vmhook && g_vm_table && __image_clone) delete g_vmhook, delete g_vm_table, ExFreePool((void*)__image_clone); // // allocate memory for a g_vmhook, g_vm_table and then zero it... // // > 0x00007FF77A233736 mov rcx, [r12+rax*8] // > 0x00007FF77A23373D ror rcx, 0x30 <--- decrypt vm handler entry... // > 0x00007FF77A233747 add rcx, r13 // > 0x00007FF77A23374A jmp rcx vm::decrypt_handler_t _decrypt_handler = [](u64 val) -> u64 { return _rotr64(val, 0x30); }; // > 0x00007FF77A233736 mov rcx, [r12+rax*8] // > 0x00007FF77A23373D ror rcx, 0x30 <--- inverse to encrypt vm handler entry... // > 0x00007FF77A233747 add rcx, r13 // > 0x00007FF77A23374A jmp rcx vm::encrypt_handler_t _encrypt_handler = [](u64 val) -> u64 { return _rotl64(val, 0x30); }; vm::handler::edit_entry_t _edit_entry = [](u64* entry_ptr, u64 val) -> void { // // disable write protect bit in cr0... // { auto cr0 = __readcr0(); cr0 &= 0xfffffffffffeffff; __writecr0(cr0); _disable(); } *entry_ptr = val; // // enable write protect bit in cr0... // { auto cr0 = __readcr0(); cr0 |= 0x10000; _enable(); __writecr0(cr0); } }; auto image_base = reinterpret_cast(image_info->ImageBase); auto handler_table_ptr = reinterpret_cast(image_base + EAC_VM_HANDLE_OFFSET); __image_clone = reinterpret_cast(ExAllocatePool(NonPagedPool, image_info->ImageSize)); RtlCopyMemory((void*)__image_clone, (void*)image_base, image_info->ImageSize); g_vm_table = new vm::handler::table_t(handler_table_ptr, _edit_entry); g_vmhook = new vm::hook_t(image_base, EAC_IMAGE_BASE, _decrypt_handler, _encrypt_handler, g_vm_table); __image_base = image_base, __image_size = image_info->ImageSize; const auto callback = [](vm::registers* regs, u8 handler_idx) { const auto read_addr = reinterpret_cast(regs->rbp)[0]; // shoot the tires right off the virtualized integrity checks in about 2 lines of code... if (scn::read_only(__image_base, read_addr)) { DBG_PRINT(" READ(Q/DW/B) EasyAntiCheat.sys+0x%x\n", (read_addr - __image_base)); reinterpret_cast(regs->rbp)[0] = __image_clone + (read_addr - __image_base); } }; // install hooks on READQ virtual machine handlers... for (auto idx = 0u; idx < sizeof readq_idxs; ++idx) g_vm_table->set_callback(readq_idxs[idx], callback); // install hooks on READDW virtual machine handlers... for (auto idx = 0u; idx < sizeof readdw_idxs; ++idx) g_vm_table->set_callback(readdw_idxs[idx], callback); // install hooks on READB virtual machine handlers... for (auto idx = 0u; idx < sizeof readb_idxs; ++idx) g_vm_table->set_callback(readb_idxs[idx], callback); // // hooks all vm handlers and starts callbacks... // g_vmhook->start(); // hook on sha1... make_inline_hook(&__sha1_hook, reinterpret_cast( image_base + EAC_SHA1_OFFSET), &hook_sha1, true); } } /*++ Routine Description: This is the entry routine for the vmhook-eac driver. Arguments: drv_object - Pointer to driver object created by the system. reg_path - Receives the full registry path to the SERVICES node of the current control set. Return Value: An NTSTATUS code. --*/ extern "C" NTSTATUS DriverEntry( // entry called from ZwSwapCert... PDRIVER_OBJECT drv_object, PUNICODE_STRING reg_path ) { UNREFERENCED_PARAMETER(drv_object); UNREFERENCED_PARAMETER(reg_path); // // This kernel driver cannot be unloaded so there is no unload routine... // This is because ZwSwapCert will cause the system to crash... // DBG_PRINT("> Registering ImageLoad Callbacks...\n"); return PsSetLoadImageNotifyRoutine(&image_loaded); }