2020-01-27 15:22:29 +00:00
# include "stdafx.h"
# include "LoadPe.h"
2020-03-05 15:30:50 +00:00
namespace FSecure : : Loader
2020-01-27 15:22:29 +00:00
{
2020-03-18 14:47:58 +00:00
static constexpr DWORD cleanupWaitMiliseconds =
# if defined CEBULOADER_CLEANUP_WAIT
CEBULOADER_CLEANUP_WAIT ;
# else
10000 ; // default 10 seconds
# endif
2020-01-27 15:22:29 +00:00
# pragma warning( push )
# pragma warning( disable : 4214 ) // nonstandard extension
typedef struct
{
WORD offset : 12 ;
WORD type : 4 ;
} IMAGE_RELOC , * PIMAGE_RELOC ;
# pragma warning(pop)
# ifdef _WIN64
constexpr auto HostMachine = IMAGE_FILE_MACHINE_AMD64 ;
# else
constexpr auto HostMachine = IMAGE_FILE_MACHINE_I386 ;
# endif
typedef BOOL ( WINAPI * DllEntryPoint ) ( HINSTANCE , DWORD , LPVOID ) ;
struct
{
UINT_PTR m_DllBaseAddress ;
DWORD m_SizeOfTheDll ;
} moduleData ;
2020-01-28 09:50:25 +00:00
# if defined _M_AMD64
2020-01-29 08:58:16 +00:00
void * RtlPcToFileHeaderDetour ( PVOID pc , PVOID * baseOfImage )
2020-01-27 15:22:29 +00:00
{
2020-01-27 16:07:29 +00:00
if ( pc > ( void * ) moduleData . m_DllBaseAddress and pc < ( void * ) ( moduleData . m_DllBaseAddress + moduleData . m_SizeOfTheDll ) )
2020-01-27 15:22:29 +00:00
{
2020-01-27 16:07:29 +00:00
* baseOfImage = 0 ;
return ( void * ) moduleData . m_DllBaseAddress ;
2020-01-27 15:22:29 +00:00
}
2020-01-27 16:07:29 +00:00
else
{
return RtlPcToFileHeader ( pc , baseOfImage ) ;
}
}
2020-01-29 08:58:16 +00:00
# elif defined _M_IX86
BOOL GetModuleHandleExWDetour ( DWORD dwFlags , LPCWSTR lpModuleName , HMODULE * phModule )
{
// try to filter out different call sites by checking if the flags match the ones set by `msvcp140.dll.Concurrency::details::'anonymous namespace'::_Task_scheduler_callback`
// and if the address is within our Dll
auto addr = reinterpret_cast < const void * > ( lpModuleName ) ;
if ( ( dwFlags = = ( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT ) ) and
addr > ( void * ) moduleData . m_DllBaseAddress and addr < ( void * ) ( moduleData . m_DllBaseAddress + moduleData . m_SizeOfTheDll ) )
return 0 ;
else
return GetModuleHandleExW ( dwFlags , lpModuleName , phModule ) ;
}
2020-01-28 09:50:25 +00:00
# endif
2020-01-27 15:22:29 +00:00
2020-01-29 08:58:16 +00:00
/// Get address of detour function
/// @param dllName - name of dll to import function from
/// @param funcName - name of a function to import
/// @returns If function should be redirected - address of detour function, else nullptr
void * GetDetourAddress ( const char * dllName , const char * funcName )
2020-01-27 16:07:29 +00:00
{
2020-01-28 09:50:25 +00:00
# if defined _M_AMD64
2020-01-29 08:58:16 +00:00
// detour RtlPcToFileHeader to return our Dll base address, std::eception throwing and exception_ptr creation use this address
2020-01-27 16:07:29 +00:00
if ( _stricmp ( dllName , " kernel32.dll " ) = = 0 & & strcmp ( funcName , " RtlPcToFileHeader " ) = = 0 )
2020-01-29 08:58:16 +00:00
return ( void * ) RtlPcToFileHeaderDetour ;
# elif defined _M_IX86
// detour GetModuleHandleExW because Windows thread pool tries to free our Dll on callback completion
// see msvcp140.dll.Concurrency::details::`anonymous namespace'::_Task_scheduler_callback
// see C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Tools\MSVC\14.16.27023\crt\src\stl\taskscheduler.cpp -> around line 147
if ( _stricmp ( dllName , " kernel32.dll " ) = = 0 & & strcmp ( funcName , " GetModuleHandleExW " ) = = 0 )
return ( void * ) GetModuleHandleExWDetour ;
2020-01-27 16:07:29 +00:00
2020-01-29 08:58:16 +00:00
# endif
2020-01-27 16:07:29 +00:00
return nullptr ;
2020-01-27 15:22:29 +00:00
}
int LoadPe ( void * dllData , std : : string_view callExport )
{
// Loader code based on Shellcode Reflective DLL Injection by Nick Landers https://github.com/monoxgas/sRDI
// which is derived from "Improved Reflective DLL Injection" from Dan Staples https://disman.tl/2015/01/30/an-improved-reflective-dll-injection-technique.html
// which itself is derived from the original project by Stephen Fewer. https://github.com/stephenfewer/ReflectiveDLLInjection
auto dosHeader = Rva2Va < PIMAGE_DOS_HEADER > ( dllData , 0 ) ;
auto ntHeaders = Rva2Va < PIMAGE_NT_HEADERS > ( dllData , dosHeader - > e_lfanew ) ;
auto sizeOfImage = ntHeaders - > OptionalHeader . SizeOfImage ;
// Perform sanity checks on the image (Stolen from https://github.com/fancycode/MemoryModule/blob/master/MemoryModule.c)
if ( ntHeaders - > Signature ! = IMAGE_NT_SIGNATURE )
return 1 ;
if ( ntHeaders - > FileHeader . Machine ! = HostMachine )
return 1 ;
if ( ntHeaders - > OptionalHeader . SectionAlignment & 1 )
return 1 ;
// Align the image to the page size (Stolen from https://github.com/fancycode/MemoryModule/blob/master/MemoryModule.c)
auto sectionHeader = IMAGE_FIRST_SECTION ( ntHeaders ) ;
DWORD lastSectionEnd = 0 ;
DWORD endOfSection ;
for ( size_t i = 0 ; i < ntHeaders - > FileHeader . NumberOfSections ; i + + , sectionHeader + + )
{
if ( sectionHeader - > SizeOfRawData = = 0 )
endOfSection = sectionHeader - > VirtualAddress + ntHeaders - > OptionalHeader . SectionAlignment ;
else
endOfSection = sectionHeader - > VirtualAddress + sectionHeader - > SizeOfRawData ;
if ( endOfSection > lastSectionEnd )
lastSectionEnd = endOfSection ;
}
SYSTEM_INFO sysInfo ;
GetNativeSystemInfo ( & sysInfo ) ;
auto alignedImageSize = AlignValueUp ( ntHeaders - > OptionalHeader . SizeOfImage , sysInfo . dwPageSize ) ;
if ( alignedImageSize ! = AlignValueUp ( lastSectionEnd , sysInfo . dwPageSize ) )
return 1 ;
UINT_PTR baseAddress = ( UINT_PTR ) VirtualAlloc ( NULL , alignedImageSize , MEM_RESERVE | MEM_COMMIT , PAGE_READWRITE ) ;
if ( ! baseAddress )
return 1 ;
2020-01-29 08:58:16 +00:00
// set global module data
moduleData . m_DllBaseAddress = baseAddress ;
moduleData . m_SizeOfTheDll = ntHeaders - > OptionalHeader . SizeOfImage ;
2020-01-27 15:22:29 +00:00
/// Copy headers
memcpy ( ( void * ) baseAddress , dllData , ntHeaders - > OptionalHeader . SizeOfHeaders ) ;
// STEP 3: Load in the sections
sectionHeader = IMAGE_FIRST_SECTION ( ntHeaders ) ;
for ( int i = 0 ; i < ntHeaders - > FileHeader . NumberOfSections ; i + + , sectionHeader + + )
{
auto sectionVa = Rva2Va < void * > ( baseAddress , sectionHeader - > VirtualAddress ) ;
auto sectionRawData = Rva2Va < void * > ( dllData , sectionHeader - > PointerToRawData ) ;
memcpy ( sectionVa , sectionRawData , sectionHeader - > SizeOfRawData ) ;
}
///
// STEP 4: process all of our images relocations (assuming we missed the preferred address)
///
auto baseOffset = ( UINT_PTR ) baseAddress - ntHeaders - > OptionalHeader . ImageBase ;
auto dataDir = & ntHeaders - > OptionalHeader . DataDirectory [ IMAGE_DIRECTORY_ENTRY_BASERELOC ] ;
if ( baseOffset & & dataDir - > Size )
{
auto relocation = Rva2Va < PIMAGE_BASE_RELOCATION > ( baseAddress , dataDir - > VirtualAddress ) ;
while ( relocation - > VirtualAddress )
{
auto relocList = ( PIMAGE_RELOC ) ( relocation + 1 ) ;
while ( ( PBYTE ) relocList ! = ( PBYTE ) relocation + relocation - > SizeOfBlock )
{
if ( relocList - > type = = IMAGE_REL_BASED_DIR64 )
* ( PULONG_PTR ) ( ( PBYTE ) baseAddress + relocation - > VirtualAddress + relocList - > offset ) + = baseOffset ;
else if ( relocList - > type = = IMAGE_REL_BASED_HIGHLOW )
* ( PULONG_PTR ) ( ( PBYTE ) baseAddress + relocation - > VirtualAddress + relocList - > offset ) + = ( DWORD ) baseOffset ;
else if ( relocList - > type = = IMAGE_REL_BASED_HIGH )
* ( PULONG_PTR ) ( ( PBYTE ) baseAddress + relocation - > VirtualAddress + relocList - > offset ) + = HIWORD ( baseOffset ) ;
else if ( relocList - > type = = IMAGE_REL_BASED_LOW )
* ( PULONG_PTR ) ( ( PBYTE ) baseAddress + relocation - > VirtualAddress + relocList - > offset ) + = LOWORD ( baseOffset ) ;
relocList + + ;
}
relocation = ( PIMAGE_BASE_RELOCATION ) relocList ;
}
}
///
// STEP 5: process our import table
///
dataDir = & ntHeaders - > OptionalHeader . DataDirectory [ IMAGE_DIRECTORY_ENTRY_IMPORT ] ;
if ( dataDir - > Size )
{
auto importDesc = Rva2Va < PIMAGE_IMPORT_DESCRIPTOR > ( baseAddress , dataDir - > VirtualAddress ) ;
for ( ; importDesc - > Name ; importDesc + + )
{
2020-01-27 16:07:29 +00:00
auto libName = ( LPCSTR ) ( baseAddress + importDesc - > Name ) ;
auto libraryAddress = ( PBYTE ) LoadLibraryA ( libName ) ;
2020-01-27 15:22:29 +00:00
auto firstThunk = Rva2Va < PIMAGE_THUNK_DATA > ( baseAddress , importDesc - > FirstThunk ) ;
auto origFirstThunk = Rva2Va < PIMAGE_THUNK_DATA > ( baseAddress , importDesc - > OriginalFirstThunk ) ;
// iterate through all imported functions, importing by ordinal if no name present
for ( ; origFirstThunk - > u1 . Function ; firstThunk + + , origFirstThunk + + )
{
if ( IMAGE_SNAP_BY_ORDINAL ( origFirstThunk - > u1 . Ordinal ) )
{
firstThunk - > u1 . Function = ( ULONG_PTR ) GetProcAddress ( ( HMODULE ) libraryAddress , ( LPCSTR ) IMAGE_ORDINAL ( origFirstThunk - > u1 . Ordinal ) ) ;
}
else
{
auto importByName = Rva2Va < PIMAGE_IMPORT_BY_NAME > ( baseAddress , origFirstThunk - > u1 . AddressOfData ) ;
2020-01-29 08:58:16 +00:00
// check if the function should be detoured by redirecting the imported function address
void * addr = GetDetourAddress ( libName , importByName - > Name ) ;
2020-01-27 16:07:29 +00:00
if ( ! addr )
addr = GetProcAddress ( ( HMODULE ) libraryAddress , importByName - > Name ) ;
firstThunk - > u1 . Function = ( ULONG_PTR ) addr ;
2020-01-27 15:22:29 +00:00
}
}
}
}
///
// STEP 6: process our delayed import table
///
dataDir = & ntHeaders - > OptionalHeader . DataDirectory [ IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT ] ;
if ( dataDir - > Size )
{
auto delayDesc = Rva2Va < PIMAGE_DELAYLOAD_DESCRIPTOR > ( baseAddress , dataDir - > VirtualAddress ) ;
for ( ; delayDesc - > DllNameRVA ; delayDesc + + )
{
2020-01-27 16:07:29 +00:00
auto libName = ( LPCSTR ) ( baseAddress + delayDesc - > DllNameRVA ) ;
auto libraryAddress = ( PBYTE ) LoadLibraryA ( libName ) ;
2020-01-27 15:22:29 +00:00
auto firstThunk = Rva2Va < PIMAGE_THUNK_DATA > ( baseAddress , delayDesc - > ImportAddressTableRVA ) ;
auto origFirstThunk = Rva2Va < PIMAGE_THUNK_DATA > ( baseAddress , delayDesc - > ImportNameTableRVA ) ;
// iterate through all imported functions, importing by ordinal if no name present
for ( ; firstThunk - > u1 . Function ; firstThunk + + , origFirstThunk + + )
{
if ( IMAGE_SNAP_BY_ORDINAL ( origFirstThunk - > u1 . Ordinal ) )
{
firstThunk - > u1 . Function = ( ULONG_PTR ) GetProcAddress ( ( HMODULE ) libraryAddress , ( LPCSTR ) IMAGE_ORDINAL ( origFirstThunk - > u1 . Ordinal ) ) ;
}
else
{
auto importByName = Rva2Va < PIMAGE_IMPORT_BY_NAME > ( baseAddress , origFirstThunk - > u1 . AddressOfData ) ;
2020-01-29 08:58:16 +00:00
void * addr = GetDetourAddress ( libName , importByName - > Name ) ;
2020-01-27 16:07:29 +00:00
if ( ! addr )
addr = GetProcAddress ( ( HMODULE ) libraryAddress , importByName - > Name ) ;
firstThunk - > u1 . Function = ( ULONG_PTR ) addr ;
2020-01-27 15:22:29 +00:00
}
}
}
}
///
// STEP 7: Finalize our sections. Set memory protections.
///
sectionHeader = IMAGE_FIRST_SECTION ( ntHeaders ) ;
for ( int i = 0 ; i < ntHeaders - > FileHeader . NumberOfSections ; i + + , sectionHeader + + )
{
if ( sectionHeader - > SizeOfRawData )
{
// determine protection flags based on characteristics
bool executable = ( sectionHeader - > Characteristics & IMAGE_SCN_MEM_EXECUTE ) ! = 0 ;
bool readable = ( sectionHeader - > Characteristics & IMAGE_SCN_MEM_READ ) ! = 0 ;
bool writeable = ( sectionHeader - > Characteristics & IMAGE_SCN_MEM_WRITE ) ! = 0 ;
DWORD protect = 0 ;
if ( ! executable & & ! readable & & ! writeable )
protect = PAGE_NOACCESS ;
else if ( ! executable & & ! readable & & writeable )
protect = PAGE_WRITECOPY ;
else if ( ! executable & & readable & & ! writeable )
protect = PAGE_READONLY ;
else if ( ! executable & & readable & & writeable )
protect = PAGE_READWRITE ;
else if ( executable & & ! readable & & ! writeable )
protect = PAGE_EXECUTE ;
else if ( executable & & ! readable & & writeable )
protect = PAGE_EXECUTE_WRITECOPY ;
else if ( executable & & readable & & ! writeable )
protect = PAGE_EXECUTE_READ ;
else if ( executable & & readable & & writeable )
protect = PAGE_EXECUTE_READWRITE ;
if ( sectionHeader - > Characteristics & IMAGE_SCN_MEM_NOT_CACHED )
protect | = PAGE_NOCACHE ;
// change memory access flags
VirtualProtect ( Rva2Va < LPVOID > ( baseAddress , sectionHeader - > VirtualAddress ) , sectionHeader - > SizeOfRawData , protect , & protect ) ;
}
}
// We must flush the instruction cache to avoid stale code being used
FlushInstructionCache ( ( HANDLE ) - 1 , nullptr , 0 ) ;
///
// STEP 7.1: Set static TLS values
///
2020-03-05 15:30:50 +00:00
FSecure : : Loader : : UnexportedWinApi : : LdrpHandleTlsData ( ( void * ) baseAddress ) ;
2020-01-27 15:22:29 +00:00
///
// STEP 8: execute TLS callbacks
///
dataDir = & ntHeaders - > OptionalHeader . DataDirectory [ IMAGE_DIRECTORY_ENTRY_TLS ] ;
if ( dataDir - > Size )
{
auto tlsDir = Rva2Va < PIMAGE_TLS_DIRECTORY > ( baseAddress , dataDir - > VirtualAddress ) ;
auto callback = ( PIMAGE_TLS_CALLBACK * ) ( tlsDir - > AddressOfCallBacks ) ;
for ( ; * callback ; callback + + )
( * callback ) ( ( LPVOID ) baseAddress , DLL_PROCESS_ATTACH , NULL ) ;
}
//
// STEP 8.1: Add Exception handling
//
# if defined _M_X64
auto pImageEntryException = & ntHeaders - > OptionalHeader . DataDirectory [ IMAGE_DIRECTORY_ENTRY_EXCEPTION ] ;
if ( pImageEntryException - > Size > 0 )
{
auto functionTable = Rva2Va < PRUNTIME_FUNCTION > ( baseAddress , pImageEntryException - > VirtualAddress ) ;
DWORD count = pImageEntryException - > Size / sizeof ( IMAGE_RUNTIME_FUNCTION_ENTRY ) ;
if ( ! RtlAddFunctionTable ( functionTable , count , ( DWORD64 ) baseAddress ) )
return 1 ;
}
# elif defined _M_IX86
2020-03-05 15:30:50 +00:00
FSecure : : Loader : : UnexportedWinApi : : RtlInsertInvertedFunctionTable ( ( void * ) baseAddress , ntHeaders - > OptionalHeader . SizeOfImage ) ;
2020-01-27 15:22:29 +00:00
# endif
2020-02-18 13:15:57 +00:00
// wipe the headers
SecureZeroMemory ( ( void * ) baseAddress , ntHeaders - > OptionalHeader . SizeOfHeaders ) ;
2020-01-27 15:22:29 +00:00
///
// STEP 9: call our images entry point
///
auto dllEntryPoint = Rva2Va < DllEntryPoint > ( baseAddress , ntHeaders - > OptionalHeader . AddressOfEntryPoint ) ;
dllEntryPoint ( ( HINSTANCE ) baseAddress , DLL_PROCESS_ATTACH , NULL ) ;
///
// STEP 10: call our exported function
///
if ( ! callExport . empty ( ) )
{
do
{
dataDir = & ntHeaders - > OptionalHeader . DataDirectory [ IMAGE_DIRECTORY_ENTRY_EXPORT ] ;
if ( ! dataDir - > Size )
break ;
auto exportDir = ( PIMAGE_EXPORT_DIRECTORY ) ( baseAddress + dataDir - > VirtualAddress ) ;
if ( ! exportDir - > NumberOfNames | | ! exportDir - > NumberOfFunctions )
break ;
auto expName = Rva2Va < PDWORD > ( baseAddress , exportDir - > AddressOfNames ) ;
auto expOrdinal = Rva2Va < PWORD > ( baseAddress , exportDir - > AddressOfNameOrdinals ) ;
for ( size_t i = 0 ; i < exportDir - > NumberOfNames ; i + + , expName + + , expOrdinal + + )
{
auto expNameStr = Rva2Va < LPCSTR > ( baseAddress , * expName ) ;
if ( ! expNameStr )
break ;
if ( expNameStr = = callExport & & expOrdinal )
{
auto exportFunc = Rva2Va < ExportFunc > ( baseAddress , * ( PDWORD ) ( baseAddress + exportDir - > AddressOfFunctions + ( * expOrdinal * 4 ) ) ) ;
exportFunc ( ) ;
break ;
}
}
} while ( 0 ) ;
}
2020-03-18 14:47:58 +00:00
// Wait for async tasks to finish. Tasks might invoke a callback that can fail after call to DllMain with DLL_PROCESS_DETACH
Sleep ( cleanupWaitMiliseconds ) ;
dllEntryPoint ( ( HINSTANCE ) baseAddress , DLL_PROCESS_DETACH , NULL ) ;
2020-01-27 15:22:29 +00:00
// STEP 11 Cleanup
# if defined _M_X64
if ( pImageEntryException - > Size > 0 )
{
auto functionTable = Rva2Va < PRUNTIME_FUNCTION > ( baseAddress , pImageEntryException - > VirtualAddress ) ;
RtlDeleteFunctionTable ( functionTable ) ;
}
# elif defined _M_IX86
// TODO cleanup after RtlInsertInvertedFunctionTable -> see ntdll!_RtlRemoveInvertedFunctionTable@4
# endif
2020-03-18 14:47:58 +00:00
// Freeing this memory will cause access violation if any async task invoke a callback to our dll
VirtualFree ( ( void * ) baseAddress , 0 , MEM_RELEASE ) ;
2020-01-27 15:22:29 +00:00
return 0 ;
}
}