#include "NtLoader.h" #include "../../Process/Process.h" #include "../../Include/Macro.h" #include "../../Misc/Utils.h" #include "../../Misc/DynImport.h" #include "../../Misc/trace.hpp" #include "../../Symbols/SymbolData.h" #include <3rd_party/VersionApi.h> namespace blackbone { NtLdr::NtLdr( Process& proc ) : _process( proc ) { } NtLdr::~NtLdr(void) { } /// /// Initialize some loader stuff /// /// Target module type /// true on success(); FindLdrpHashTable(); if (IsWindows8OrGreater()) FindLdrpModuleIndexBase(); } else { FindLdrHeap(); FindLdrpHashTable(); if (IsWindows8OrGreater()) FindLdrpModuleIndexBase(); } _nodeMap.clear(); // Report errors #ifndef BLACKBONE_NO_TRACE if (_LdrHeapBase == 0) BLACKBONE_TRACE( "NativeLdr: LdrHeapBase not found" ); if (_LdrpHashTable == 0) BLACKBONE_TRACE( "NativeLdr: LdrpHashTable not found" ); if (IsWindows8OrGreater() && _LdrpModuleIndexBase == 0) BLACKBONE_TRACE( "NativeLdr: LdrpModuleIndexBase not found" ); #endif return true; } /// /// Add module to some loader structures /// (LdrpHashTable, LdrpModuleIndex( win8+ only ), InMemoryOrderModuleList( win7 only )) /// /// Module data /// true on success bool NtLdr::CreateNTReference( NtLdrEntry& mod ) { // Skip if (mod.flags == Ldr_None) return true; // Check if reinitialization is required if (_initializedFor != mod.type) Init( mod.type ); bool x64Image = (mod.type == mt_mod64); bool w8 = IsWindows8OrGreater(); // Win 8 and higher if (w8) mod.ldrPtr = CALL_64_86( x64Image, InitW8Node, mod ); // Windows 7 and earlier else mod.ldrPtr = CALL_64_86( x64Image, InitW7Node, mod ); if (mod.ldrPtr == 0) return false; _nodeMap.emplace( mod.baseAddress, mod.ldrPtr ); // Insert into module graph if (mod.flags & Ldr_ModList && w8) CALL_64_86( x64Image, InsertTreeNode, mod.ldrPtr, mod ); // Insert into LdrpHashTable if (mod.flags & Ldr_HashTable) { auto ptr = FIELD_PTR_64_86( x64Image, mod.ldrPtr, _LDR_DATA_TABLE_ENTRY_BASE_T, HashLinks ); CALL_64_86( x64Image, InsertHashNode, ptr, mod.hash ); } // Insert into ldr lists if (mod.flags & Ldr_ThdCall || (!w8 && mod.flags & Ldr_ModList)) { _process.memory().Write( FIELD_PTR_64_86( x64Image, mod.ldrPtr, _LDR_DATA_TABLE_ENTRY_BASE_T, Flags ), 0x80004 ); ptr_t loadPtr = 0, initptr = 0; loadPtr = FIELD_PTR_64_86( x64Image, mod.ldrPtr, _LDR_DATA_TABLE_ENTRY_BASE_T, InLoadOrderLinks ); if(w8) initptr = FIELD_PTR_64_86( x64Image, mod.ldrPtr, _LDR_DATA_TABLE_ENTRY_BASE_T, InInitializationOrderLinks ); CALL_64_86( x64Image, InsertMemModuleNode, 0, loadPtr, initptr ); } return true; } /// /// Get module native node ptr or create new /// /// node pointer (if nullptr - new dummy node is allocated) /// Module base address /// Node address template ptr_t NtLdr::SetNode( ptr_t ptr, Module pModule ) { if(ptr == 0) { auto mem = _process.memory().Allocate( sizeof( T ), PAGE_READWRITE, 0, false ); if (!mem) return 0; ptr = mem->ptr(); mem->Write( offsetOf( &T::DllBase ), pModule ); } return ptr; } /// /// Create thread static TLS array /// /// Module data /// TLS directory of target image /// Status code NTSTATUS NtLdr::AddStaticTLSEntry( NtLdrEntry& mod, ptr_t tlsPtr ) { bool wxp = IsWindowsXPOrGreater() && !IsWindowsVistaOrGreater(); ptr_t pNode = _nodeMap.count( mod.baseAddress ) ? _nodeMap[mod.baseAddress] : 0; // Allocate appropriate structure ptr_t LdrpHandleTlsData = 0; if (mod.type == mt_mod64) { LdrpHandleTlsData = g_symbols.LdrpHandleTlsData64; pNode = SetNode<_LDR_DATA_TABLE_ENTRY_BASE64>( pNode, mod.baseAddress ); } else { LdrpHandleTlsData = g_symbols.LdrpHandleTlsData32; pNode = SetNode<_LDR_DATA_TABLE_ENTRY_BASE32>( pNode, mod.baseAddress ); } if (pNode == 0) return STATUS_NO_MEMORY; // Update ptr if (mod.ldrPtr == 0) mod.ldrPtr = pNode; // Manually add TLS table if (wxp && tlsPtr != 0) { ptr_t pTeb = 0; pTeb = _process.remote().getExecThread()->teb( static_cast<_TEB32*>(nullptr) ); auto mem = _process.memory().Allocate( 0x1000, PAGE_READWRITE, 0, false ); if (!mem) return mem.status; auto tlsStore = std::move( mem.result() ); IMAGE_TLS_DIRECTORY remoteTls = { 0 }; _process.memory().Read( tlsPtr, sizeof( remoteTls ), &remoteTls ); auto size = remoteTls.EndAddressOfRawData - remoteTls.StartAddressOfRawData; std::unique_ptr buf( new uint8_t[size]() ); _process.memory().Read( remoteTls.StartAddressOfRawData, size, buf.get() ); tlsStore.Write( 0, tlsStore.ptr() + 0x800 ); tlsStore.Write( 0x800, size, buf.get() ); return _process.memory().Write( fieldPtr( pTeb, &_TEB32::ThreadLocalStoragePointer ), tlsStore.ptr() ); } // Use native method if (LdrpHandleTlsData) { auto a = AsmFactory::GetAssembler( mod.type ); uint64_t result = 0; a->GenPrologue(); a->GenCall( LdrpHandleTlsData, { pNode }, IsWindows8Point1OrGreater() ? cc_thiscall : cc_stdcall ); _process.remote().AddReturnWithEvent( *a ); a->GenEpilogue(); auto status = _process.remote().ExecInWorkerThread( (*a)->make(), (*a)->getCodeSize(), result ); if (!NT_SUCCESS( status )) return status; return static_cast(result); } else return STATUS_ORDINAL_NOT_FOUND; } /// /// Create module record in LdrpInvertedFunctionTable /// Used to create fake SAFESEH entries /// /// Module data /// true on success bool NtLdr::InsertInvertedFunctionTable( NtLdrEntry& mod ) { ptr_t RtlInsertInvertedFunctionTable = g_symbols.RtlInsertInvertedFunctionTable64; ptr_t LdrpInvertedFunctionTable = g_symbols.LdrpInvertedFunctionTable64; if (mod.type == mt_mod32) { RtlInsertInvertedFunctionTable = g_symbols.RtlInsertInvertedFunctionTable32; LdrpInvertedFunctionTable = g_symbols.LdrpInvertedFunctionTable32; } // Invalid addresses. Probably pattern scan has failed if (RtlInsertInvertedFunctionTable == 0 || LdrpInvertedFunctionTable == 0) return false; auto InsertP = [&]( auto table ) { uint64_t result = 0; auto a = AsmFactory::GetAssembler( mod.type ); auto& memory = _process.memory(); memory.Read( LdrpInvertedFunctionTable, sizeof( table ), &table ); for (ULONG i = 0; i < table.Count; i++) if (table.Entries[i].ImageBase == mod.baseAddress) return true; a->GenPrologue(); if (IsWindows8Point1OrGreater()) a->GenCall( RtlInsertInvertedFunctionTable, { mod.baseAddress, mod.size }, cc_fastcall ); else if (IsWindows8OrGreater()) a->GenCall( RtlInsertInvertedFunctionTable, { mod.baseAddress, mod.size } ); else a->GenCall( RtlInsertInvertedFunctionTable, { LdrpInvertedFunctionTable, mod.baseAddress, mod.size } ); _process.remote().AddReturnWithEvent( *a ); a->GenEpilogue(); _process.remote().ExecInWorkerThread( (*a)->make(), (*a)->getCodeSize(), result ); memory.Read( LdrpInvertedFunctionTable, sizeof( table ), &table ); for (DWORD i = 0; i < table.Count; i++) { if (table.Entries[i].ImageBase != mod.baseAddress) continue; // If Image has SAFESEH, RtlInsertInvertedFunctionTable is enough if (table.Entries[i].SizeOfTable != 0) return mod.safeSEH = true; // // Create fake Exception directory // Directory will be filled later, during exception handling // // Allocate memory for 2048 possible handlers auto mem = memory.Allocate( sizeof( DWORD ) * 0x800, PAGE_READWRITE, 0, false ); if (!mem) return false; // EncodeSystemPointer( mem->ptr() ) uint32_t size = 0; ptr_t pEncoded = 0; auto cookie = *reinterpret_cast(0x7FFE0330); if (mod.type == mt_mod64) { size = sizeof( uint64_t ); pEncoded = _rotr64( cookie ^ mem->ptr(), cookie & 0x3F ); } else { size = sizeof( uint32_t ); pEncoded = _rotr( cookie ^ mem->ptr(), cookie & 0x1F ); } // m_LdrpInvertedFunctionTable->Entries[i].ExceptionDirectory uintptr_t field_ofst = reinterpret_cast(&table.Entries[i].ExceptionDirectory) - reinterpret_cast(&table); // In Win10 LdrpInvertedFunctionTable is located in mrdata section // mrdata is read-only by default // LdrProtectMrdata is used to make it writable when needed DWORD flOld = 0; memory.Protect( LdrpInvertedFunctionTable + field_ofst, sizeof( ptr_t ), PAGE_EXECUTE_READWRITE, &flOld ); auto status = memory.Write( LdrpInvertedFunctionTable + field_ofst, size, &pEncoded ); memory.Protect( LdrpInvertedFunctionTable + field_ofst, sizeof( ptr_t ), flOld, &flOld ); return NT_SUCCESS( status ); } return false; }; if (IsWindows8OrGreater()) { if(mod.type == mt_mod64) return InsertP( _RTL_INVERTED_FUNCTION_TABLE8() ); else return InsertP( _RTL_INVERTED_FUNCTION_TABLE8() ); } else { if (mod.type == mt_mod64) return InsertP( _RTL_INVERTED_FUNCTION_TABLE7() ); else return InsertP( _RTL_INVERTED_FUNCTION_TABLE7() ); } } /// /// Free static TLS /// /// Target module /// Don't create new threads during remote call /// Status code NTSTATUS NtLdr::UnloadTLS( const NtLdrEntry& mod, bool noThread /*= false*/ ) { // No loader entry to free if (mod.ldrPtr == 0) return STATUS_INVALID_ADDRESS; ptr_t LdrpReleaseTlsEntry = g_symbols.LdrpReleaseTlsEntry64; if (mod.type == mt_mod32) LdrpReleaseTlsEntry = g_symbols.LdrpReleaseTlsEntry32; // Not available if (LdrpReleaseTlsEntry == 0) return STATUS_ORDINAL_NOT_FOUND; auto a = AsmFactory::GetAssembler( mod.type ); uint64_t result = 0; a->GenPrologue(); a->GenCall( LdrpReleaseTlsEntry, { mod.ldrPtr, 0 }, IsWindows8Point1OrGreater() ? cc_fastcall : cc_stdcall ); _process.remote().AddReturnWithEvent( *a ); a->GenEpilogue(); _process.remote().CreateRPCEnvironment( noThread ? Worker_UseExisting : Worker_CreateNew, true ); _process.remote().ExecInWorkerThread( (*a)->make(), (*a)->getCodeSize(), result ); return STATUS_SUCCESS; } /// /// Initialize OS-specific module entry /// /// Module data /// Pointer to created entry template ptr_t NtLdr::InitBaseNode( NtLdrEntry& mod ) { using EntryType = _LDR_DATA_TABLE_ENTRY_BASE_T; ptr_t entryPtr = AllocateInHeap( mod.type, sizeof( _LDR_DATA_TABLE_ENTRY_W8 ) ).result( 0 ); if (entryPtr == 0) return 0; // Allocate space for Unicode string _UNICODE_STRING_T strLocal = { 0 }; auto mem = _process.memory().Allocate( 0x1000, PAGE_READWRITE, 0, false ); if (!mem) return 0; auto StringBuf = std::move( mem.result() ); // entryPtr->DllBase = ModuleBase; _process.memory().Write( fieldPtr( entryPtr, &EntryType::DllBase ), static_cast(mod.baseAddress) ); // entryPtr->SizeOfImage = ImageSize; _process.memory().Write( fieldPtr( entryPtr, &EntryType::SizeOfImage ), mod.size ); // entryPtr->EntryPoint = entryPoint; _process.memory().Write( fieldPtr( entryPtr, &EntryType::EntryPoint ), static_cast(mod.entryPoint) ); // Dll name hash mod.hash = HashString( mod.name ); // Dll name strLocal.Length = static_cast(mod.name.length() * sizeof( wchar_t )); strLocal.MaximumLength = 0x600; strLocal.Buffer = StringBuf.ptr(); StringBuf.Write( 0, strLocal.Length + 2, mod.name.c_str() ); // entryPtr->BaseDllName = strLocal; _process.memory().Write( fieldPtr( entryPtr, &EntryType::BaseDllName ), strLocal ); // Dll full path strLocal.Length = static_cast(mod.fullPath.length() * sizeof( wchar_t )); strLocal.Buffer = StringBuf.ptr() + 0x800; StringBuf.Write( 0x800, strLocal.Length + 2, mod.fullPath.c_str() ); // entryPtr->FullDllName = strLocal; _process.memory().Write( fieldPtr( entryPtr, &EntryType::FullDllName ), strLocal ); // entryPtr->LoadCount = -1; _process.memory().Write( fieldPtr( entryPtr, &EntryType::LoadCount ), strLocal ); return entryPtr; } /// /// Initialize OS-specific module entry /// /// Module data /// Pointer to created entry template ptr_t NtLdr::InitW8Node( NtLdrEntry& mod ) { using EntryType = _LDR_DATA_TABLE_ENTRY_W8; using DdagType = _LDR_DDAG_NODE; ptr_t entryPtr = InitBaseNode( mod ); ptr_t DdagNodePtr = AllocateInHeap( mod.type, sizeof( DdagType ) ).result( 0 ); if (!entryPtr || !DdagNodePtr) return 0; // entryPtr->BaseNameHashValue = hash; _process.memory().Write( fieldPtr( entryPtr, &EntryType::BaseNameHashValue ), mod.hash ); // // Ddag node // // entryPtr->DdagNode = pDdagNode; _process.memory().Write( fieldPtr( entryPtr, &EntryType::DdagNode ), static_cast(DdagNodePtr) ); // DdagNodePtr->State = LdrModulesReadyToRun; _process.memory().Write( fieldPtr( DdagNodePtr, &DdagType::State ), LdrModulesReadyToRun ); // DdagNodePtr->ReferenceCount = 1; _process.memory().Write( fieldPtr( DdagNodePtr, &DdagType::ReferenceCount ), 1 ); // DdagNodePtr->LoadCount = -1; _process.memory().Write( fieldPtr( DdagNodePtr, &DdagType::LoadCount ), -1 ); return entryPtr; } /// /// Initialize OS-specific module entry /// /// Module data /// Pointer to created entry template ptr_t NtLdr::InitW7Node( NtLdrEntry& mod ) { using EntryType = _LDR_DATA_TABLE_ENTRY_W7; ptr_t entryPtr = InitBaseNode( mod ); if (!entryPtr) return 0; // Forward Links _process.memory().Write( fieldPtr( entryPtr, &EntryType::ForwarderLinks ), fieldPtr( entryPtr, &EntryType::ForwarderLinks ) ); _process.memory().Write( fieldPtr( entryPtr, &EntryType::ForwarderLinks ) + sizeof( T ), fieldPtr( entryPtr, &EntryType::ForwarderLinks ) ); // Static links _process.memory().Write( fieldPtr( entryPtr, &EntryType::StaticLinks ), fieldPtr( entryPtr, &EntryType::StaticLinks ) ); _process.memory().Write( fieldPtr( entryPtr, &EntryType::StaticLinks ) + sizeof( T ), fieldPtr( entryPtr, &EntryType::StaticLinks ) ); return entryPtr; } /// /// Insert entry into win8 module graph /// /// Node to insert /// Module data template void NtLdr::InsertTreeNode( ptr_t nodePtr, const NtLdrEntry& mod ) { // // Win8 module tree // auto root = _process.memory().Read( _LdrpModuleIndexBase ); if (!root) return; auto LdrNodePtr = structBase( root.result(), &_LDR_DATA_TABLE_ENTRY_W8::BaseAddressIndexNode ); auto LdrNode = _process.memory().Read<_LDR_DATA_TABLE_ENTRY_W8>( LdrNodePtr ).result( _LDR_DATA_TABLE_ENTRY_W8() ); bool bRight = false; // Walk tree for (;;) { if (static_cast(mod.baseAddress) < LdrNode.DllBase) { if (LdrNode.BaseAddressIndexNode.Left) { LdrNodePtr = structBase( LdrNode.BaseAddressIndexNode.Left, &_LDR_DATA_TABLE_ENTRY_W8::BaseAddressIndexNode ); _process.memory().Read( LdrNodePtr, sizeof(LdrNode), &LdrNode ); } else break; } else if (static_cast(mod.baseAddress) > LdrNode.DllBase) { if (LdrNode.BaseAddressIndexNode.Right) { LdrNodePtr = structBase( LdrNode.BaseAddressIndexNode.Right, &_LDR_DATA_TABLE_ENTRY_W8::BaseAddressIndexNode ); _process.memory().Read( LdrNodePtr, sizeof(LdrNode), &LdrNode ); } else { bRight = true; break; } } // Already in tree (increase ref counter) else { // pLdrNode->DdagNode->ReferenceCount++; auto Ddag = _process.memory().Read<_LDR_DDAG_NODE>( LdrNode.DdagNode ); if (!Ddag) return; Ddag->ReferenceCount++; _process.memory().Write( LdrNode.DdagNode, Ddag.result() ); return; } } // Insert using RtlRbInsertNodeEx auto a = AsmFactory::GetAssembler( mod.type ); uint64_t result = 0; auto RtlRbInsertNodeEx = _process.modules().GetNtdllExport( "RtlRbInsertNodeEx", mod.type ); if (!RtlRbInsertNodeEx) return; a->GenPrologue(); a->GenCall( RtlRbInsertNodeEx->procAddress, { _LdrpModuleIndexBase, fieldPtr( LdrNodePtr, &_LDR_DATA_TABLE_ENTRY_W8::BaseAddressIndexNode ), static_cast(bRight), fieldPtr( nodePtr, &_LDR_DATA_TABLE_ENTRY_W8::BaseAddressIndexNode ) } ); _process.remote().AddReturnWithEvent( *a, mod.type ); a->GenEpilogue(); _process.remote().ExecInWorkerThread( (*a)->make(), (*a)->getCodeSize(), result ); } /// /// Insert entry into InLoadOrderModuleList and InMemoryOrderModuleList /// /// InMemoryOrderModuleList link of entry to be inserted /// InLoadOrderModuleList link of entry to be inserted template void NtLdr::InsertMemModuleNode( ptr_t pNodeMemoryOrderLink, ptr_t pNodeLoadOrderLink, ptr_t pNodeInitOrderLink ) { ptr_t pPeb = _process.core().peb(); ptr_t pLdr = 0; if (pPeb) pLdr = _process.memory().Read( fieldPtr( pPeb, &PEB_T::Ldr ) ).result( 0 ); if (pLdr) { // pLdr->InMemoryOrderModuleList if (pNodeMemoryOrderLink) InsertTailList( fieldPtr( pLdr, &_PEB_LDR_DATA2_T::InMemoryOrderModuleList ), pNodeMemoryOrderLink ); // pLdr->InLoadOrderModuleList if (pNodeLoadOrderLink) InsertTailList( fieldPtr( pLdr, &_PEB_LDR_DATA2_T::InLoadOrderModuleList ), pNodeLoadOrderLink ); // pLdr->InInitializationOrderModuleList if (pNodeInitOrderLink) InsertTailList( fieldPtr( pLdr, &_PEB_LDR_DATA2_T::InInitializationOrderModuleList ), pNodeInitOrderLink ); } } /// /// Insert entry into LdrpHashTable[] /// /// Link of entry to be inserted /// Module hash template void NtLdr::InsertHashNode( ptr_t pNodeLink, ULONG hash ) { if(pNodeLink) { // LrpHashTable record auto pHashList = _process.memory().Read( _LdrpHashTable + sizeof( _LIST_ENTRY_T )*(hash & 0x1F) ); if(pHashList) InsertTailList( pHashList.result(), pNodeLink ); } } /// /// Insert entry into standard double linked list /// /// List head pointer /// Entry list link to be inserted template void NtLdr::InsertTailList( ptr_t ListHead, ptr_t Entry ) { // PrevEntry = ListHead->Blink; auto PrevEntry = _process.memory().Read( fieldPtr( ListHead, &_LIST_ENTRY_T::Blink ) ).result( 0 ); // Entry->Flink = ListHead; // Entry->Blink = PrevEntry; _process.memory().Write( fieldPtr( Entry, &_LIST_ENTRY_T::Flink ), sizeof(T), &ListHead ); _process.memory().Write( fieldPtr( Entry, &_LIST_ENTRY_T::Blink ), sizeof( T ), &PrevEntry ); // PrevEntry->Flink = Entry; // ListHead->Blink = Entry; _process.memory().Write( fieldPtr( PrevEntry, &_LIST_ENTRY_T::Flink ), sizeof( T ), &Entry ); _process.memory().Write( fieldPtr( ListHead, &_LIST_ENTRY_T::Blink ), sizeof( T ), &Entry ); } /// /// Hash image name /// /// Iamge name /// Hash ULONG NtLdr::HashString( const std::wstring& str ) { ULONG hash = 0; if (IsWindows8OrGreater()) { UNICODE_STRING ustr; SAFE_CALL( RtlInitUnicodeString, &ustr, str.c_str() ); SAFE_NATIVE_CALL( RtlHashUnicodeString, &ustr, (BOOLEAN)TRUE, 0, &hash ); } else { for (auto& ch : str) hash += 0x1003F * static_cast(SAFE_CALL( RtlUpcaseUnicodeChar, ch )); } return hash; } /// /// Allocate memory from heap if possible /// /// Module type /// Size to allocate /// Allocated address call_result_t NtLdr::AllocateInHeap( eModType mt, size_t size ) { NTSTATUS status = STATUS_SUCCESS; auto RtlAllocateHeap = _process.modules().GetNtdllExport( "RtlAllocateHeap", mt ); if (_LdrHeapBase && RtlAllocateHeap) { auto a = AsmFactory::GetAssembler( mt ); // // HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size); // a->GenPrologue(); a->GenCall( RtlAllocateHeap->procAddress, { _LdrHeapBase, HEAP_ZERO_MEMORY, size } ); _process.remote().AddReturnWithEvent( (*a), mt ); a->GenEpilogue(); uint64_t result = 0; status = _process.remote().ExecInWorkerThread( (*a)->make(), (*a)->getCodeSize(), result ); if (NT_SUCCESS( status )) return result; } else status = STATUS_ORDINAL_NOT_FOUND; if (!NT_SUCCESS( status )) { auto mem = _process.memory().Allocate( size, PAGE_READWRITE, 0, false ); if (!mem) return mem.status; return mem->ptr(); } return STATUS_ILLEGAL_FUNCTION; } /// /// Find LdrpHashTable[] variable /// /// true on success template bool NtLdr::FindLdrpHashTable() { _PEB_T Peb = { 0 }; _process.core().peb( &Peb ); if (Peb.ImageBaseAddress == 0) return false; auto Ldr = _process.memory().Read<_PEB_LDR_DATA2_T>( Peb.Ldr ); if (!Ldr) return false; // Get loader entry _LDR_DATA_TABLE_ENTRY_BASE_T entry = { 0 }; auto entryPtr = structBase( Ldr->InInitializationOrderModuleList.Flink, &_LDR_DATA_TABLE_ENTRY_BASE_T::InInitializationOrderLinks ); if (!NT_SUCCESS( _process.memory().Read( entryPtr, entry ) )) return false; wchar_t nameBuf[260] = { 0 }; if (!NT_SUCCESS( _process.memory().Read( entry.BaseDllName.Buffer, entry.BaseDllName.Length + 2, nameBuf ) )) return false; ULONG NtdllHashIndex = HashString( nameBuf ) & 0x1F; T NtdllBase = static_cast(entry.DllBase); T NtdllEndAddress = NtdllBase + entry.SizeOfImage - 1; // scan hash list to the head (head is located within ntdll) T NtdllHashHeadPtr = 0; _LIST_ENTRY_T hashNode = entry.HashLinks; for (auto e = hashNode.Flink; e != fieldPtr( entryPtr, &_LDR_DATA_TABLE_ENTRY_BASE_T::HashLinks ); e = hashNode.Flink) { if (e >= NtdllBase && e < NtdllEndAddress) { NtdllHashHeadPtr = e; break; } if (!NT_SUCCESS ( _process.memory().Read( hashNode.Flink, hashNode ) )) return false; } if (NtdllHashHeadPtr != 0) _LdrpHashTable = NtdllHashHeadPtr - NtdllHashIndex * sizeof( _LIST_ENTRY_T ); return _LdrpHashTable != 0; } /// /// Find LdrpModuleIndex variable under win8 /// /// true on success template bool NtLdr::FindLdrpModuleIndexBase() { _PEB_T Peb = { 0 }; _process.core().peb( &Peb ); if (Peb.ImageBaseAddress != 0) { T lastNode = 0; auto Ldr = _process.memory().Read<_PEB_LDR_DATA2_T>( Peb.Ldr ); if (!Ldr) return false; auto entryPtr = structBase( Ldr->InInitializationOrderModuleList.Flink, &_LDR_DATA_TABLE_ENTRY_W8::InInitializationOrderLinks ); _RTL_BALANCED_NODE node = { 0 }; if (!NT_SUCCESS( _process.memory().Read( fieldPtr( entryPtr, &_LDR_DATA_TABLE_ENTRY_W8::BaseAddressIndexNode ), node ) )) return false; // Get root node for(; node.ParentValue; ) { // Ignore last few bits lastNode = node.ParentValue & T( -8 ); if (!NT_SUCCESS( _process.memory().Read( lastNode, node ) )) return false; } // Get pointer to root pe::PEImage ntdllPE; T* pStart = nullptr; T* pEnd = nullptr; auto pNtdll = _process.modules().GetModule( L"ntdll.dll" ); std::unique_ptr localBuf( new uint8_t[pNtdll->size] ); _process.memory().Read( pNtdll->baseAddress, pNtdll->size, localBuf.get() ); ntdllPE.Parse( localBuf.get() ); for (auto& section : ntdllPE.sections()) if (_stricmp( reinterpret_cast(section.Name), ".data") == 0 ) { pStart = reinterpret_cast( localBuf.get() + section.VirtualAddress); pEnd = reinterpret_cast( localBuf.get() + section.VirtualAddress + section.Misc.VirtualSize); break; } auto iter = std::find( pStart, pEnd, lastNode ); if (iter != pEnd) { _LdrpModuleIndexBase = REBASE( iter, localBuf.get(), pNtdll->baseAddress ); return true; } } return false; } /// /// Find Loader heap base /// /// true on success template bool NtLdr::FindLdrHeap() { int32_t retries = 50; _PEB_T Peb = { 0 }; _process.core().peb( &Peb ); for (; Peb.Ldr == 0 && retries > 0; retries--, Sleep( 10 )) _process.core().peb( &Peb ); if (Peb.Ldr) { auto Ldr = _process.memory().Read<_PEB_LDR_DATA2_T>( Peb.Ldr ); if (!Ldr) return false; for (; Ldr->InMemoryOrderModuleList.Flink == Ldr->InMemoryOrderModuleList.Blink && retries > 0; retries--, Sleep( 10 )) Ldr = _process.memory().Read<_PEB_LDR_DATA2_T>( Peb.Ldr ); MEMORY_BASIC_INFORMATION64 mbi = { 0 }; auto NtdllEntry = Ldr->InMemoryOrderModuleList.Flink; if (NT_SUCCESS( _process.core().native()->VirtualQueryExT( NtdllEntry, &mbi ) )) { _LdrHeapBase = static_cast(mbi.AllocationBase); assert( _LdrHeapBase != _process.modules().GetModule( L"ntdll.dll" )->baseAddress ); return true; } } return false; } /// /// Unlink module from Ntdll loader /// /// Module data /// Don't create new threads during unlink /// true on success bool NtLdr::Unlink( const ModuleData& mod, bool noThread /*= false*/ ) { ptr_t ldrEntry = 0; auto x64Image = mod.type == mt_mod64; // Reinitialize if (_initializedFor != mod.type) Init( mod.type ); // Unlink from linked lists ldrEntry = CALL_64_86( x64Image, UnlinkFromLdr, mod ); // Unlink from graph // TODO: Unlink from _LdrpMappingInfoIndex. Still can't decide if it is required. if (IsWindows8OrGreater()) ldrEntry = CALL_64_86( x64Image, UnlinkTreeNode, mod, ldrEntry, noThread ); return ldrEntry != 0; } /// /// Unlink module from PEB_LDR_DATA /// /// Module data /// Address of removed record template ptr_t NtLdr::UnlinkFromLdr( const ModuleData& mod ) { auto ldrEntry = mod.ldrPtr; if (ldrEntry == 0) ldrEntry = FindLdrEntry( mod.baseAddress ); // Unlink from module lists if (ldrEntry != 0) { UnlinkListEntry( fieldPtr( ldrEntry, &_LDR_DATA_TABLE_ENTRY_BASE_T::InLoadOrderLinks ) ); UnlinkListEntry( fieldPtr( ldrEntry, &_LDR_DATA_TABLE_ENTRY_BASE_T::InMemoryOrderLinks ) ); UnlinkListEntry( fieldPtr( ldrEntry, &_LDR_DATA_TABLE_ENTRY_BASE_T::InInitializationOrderLinks ) ); UnlinkListEntry( fieldPtr( ldrEntry, &_LDR_DATA_TABLE_ENTRY_BASE_T::HashLinks ) ); } return ldrEntry; } /// /// Finds LDR entry for module /// /// Target module base /// Found entry /// Found LDR entry address template ptr_t NtLdr::FindLdrEntry( module_t moduleBase, _LDR_DATA_TABLE_ENTRY_BASE_T* found /*= nullptr*/ ) { auto native = _process.core().native(); _PEB_T peb = { }; _PEB_LDR_DATA2_T ldr = { }; _LDR_DATA_TABLE_ENTRY_BASE_T localEntry = { }; if (found == nullptr) found = &localEntry; if (native->getPEB( &peb ) != 0 && NT_SUCCESS( native->ReadProcessMemoryT( peb.Ldr, &ldr, sizeof( ldr ) ) )) { const auto ofst = offsetOf( &_LDR_DATA_TABLE_ENTRY_BASE_T::InLoadOrderLinks ); const auto head = fieldPtr( peb.Ldr, &_PEB_LDR_DATA2_T::InLoadOrderModuleList ); for (T entry = ldr.InLoadOrderModuleList.Flink; entry != 0 && entry != head; native->ReadProcessMemoryT( entry, &entry, sizeof( entry ) ) ) { native->ReadProcessMemoryT( entry - ofst, found, sizeof( *found ) ); if (found->DllBase == static_cast(moduleBase)) return entry - ofst; } } return 0; } /// /// Remove record from LIST_ENTRY structure /// /// Entry link template void NtLdr::UnlinkListEntry( ptr_t pListLink ) { T OldFlink = _process.memory().Read( fieldPtr( pListLink, &_LIST_ENTRY_T::Flink ) ).result( 0 ); T OldBlink = _process.memory().Read( fieldPtr( pListLink, &_LIST_ENTRY_T::Blink ) ).result( 0 ); // List is empty if (OldBlink == 0 || OldFlink == 0 || OldBlink == OldFlink) return; // OldFlink->Blink = OldBlink; _process.memory().Write( fieldPtr( OldFlink, &_LIST_ENTRY_T::Blink ), OldBlink ); // OldBlink->Flink = OldFlink; _process.memory().Write( fieldPtr( OldBlink, &_LIST_ENTRY_T::Flink ), OldFlink ); } /// /// Unlink from module graph /// /// Module data /// Module LDR entry /// Don't create new threads during unlink /// Address of removed record template ptr_t NtLdr::UnlinkTreeNode( const ModuleData& mod, ptr_t ldrEntry, bool noThread /*= false*/ ) { if (ldrEntry == 0) return ldrEntry; auto a = AsmFactory::GetAssembler( mod.type ); uint64_t result = 0; auto RtlRbRemoveNode = _process.modules().GetNtdllExport( "RtlRbRemoveNode" ); if (!RtlRbRemoveNode) return 0; a->GenPrologue(); a->GenCall( RtlRbRemoveNode->procAddress, { _LdrpModuleIndexBase, ldrEntry + offsetOf( &_LDR_DATA_TABLE_ENTRY_W8::BaseAddressIndexNode ) } ); _process.remote().AddReturnWithEvent( *a ); a->GenEpilogue(); _process.remote().CreateRPCEnvironment( noThread ? Worker_UseExisting : Worker_CreateNew, true ); _process.remote().ExecInWorkerThread( (*a)->make(), (*a)->getCodeSize(), result ); return ldrEntry; } }