#include "../PE/PEImage.h" #include "../Include/Macro.h" #include "../Misc/Utils.h" #include "../Misc/DynImport.h" #include #define TLS32(ptr) ((const IMAGE_TLS_DIRECTORY32*)ptr) // TLS directory #define TLS64(ptr) ((const IMAGE_TLS_DIRECTORY64*)ptr) // TLS directory #define THK32(ptr) ((const IMAGE_THUNK_DATA32*)ptr) // Import thunk data #define THK64(ptr) ((const IMAGE_THUNK_DATA64*)ptr) // Import thunk data namespace blackbone { namespace pe { PEImage::PEImage( void ) { } PEImage::~PEImage( void ) { Release(); } /// /// Load image from file /// /// File path /// If true - do not initialize activation context /// Status code NTSTATUS PEImage::Load( const std::wstring& path, bool skipActx /*= false*/ ) { Release( true ); _imagePath = path; _noFile = false; _hFile = CreateFileW( path.c_str(), FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL ); if (_hFile) { // Try mapping as image _hMapping = CreateFileMappingW( _hFile, NULL, SEC_IMAGE | PAGE_READONLY, 0, 0, NULL ); if (_hMapping) { _isPlainData = false; _pFileBase = MapViewOfFile( _hMapping, FILE_MAP_READ, 0, 0, 0 ); } // Map as simple datafile else { _isPlainData = true; _hMapping = CreateFileMappingW( _hFile, NULL, PAGE_READONLY, 0, 0, NULL ); if (_hMapping) _pFileBase = MapViewOfFile( _hMapping, FILE_MAP_READ, 0, 0, 0 ); } // Mapping failed if (!_pFileBase) return LastNtStatus(); } else return LastNtStatus(); auto status = Parse(); if (!NT_SUCCESS( status )) return status; return skipActx ? status : PrepareACTX( _imagePath.c_str() ); } /// /// Load image from memory location /// /// Image data /// Data size. /// If false - data has image layout /// Status code NTSTATUS PEImage::Load( void* pData, size_t size, bool plainData /*= true */ ) { Release( true ); _noFile = true; _pFileBase = pData; _isPlainData = plainData; auto status = Parse(); if (!NT_SUCCESS( status )) return status; return PrepareACTX(); } /// /// Reload closed image /// /// Status code NTSTATUS PEImage::Reload() { return Load( _imagePath ); } /// /// Release mapping, if any /// /// Preserve file paths for file reopening void PEImage::Release( bool temporary /*= false*/ ) { if (_pFileBase) { UnmapViewOfFile( _pFileBase ); _pFileBase = nullptr; } _hMapping.reset(); _hFile.reset(); _hctx.reset(); // Reset pointers to data _pImageHdr32 = nullptr; _pImageHdr64 = nullptr; if(!temporary) { _imagePath.clear(); // Ensure temporary file is deleted if (_noFile) DeleteFileW( _manifestPath.c_str() ); _manifestPath.clear(); } } /// /// Parses PE image /// /// Status code NTSTATUS PEImage::Parse( void* pImageBase /*= nullptr*/ ) { const IMAGE_DOS_HEADER *pDosHdr = nullptr; const IMAGE_SECTION_HEADER *pSection = nullptr; if (pImageBase != nullptr) _pFileBase = pImageBase; // Something went wrong if (!_pFileBase) return STATUS_INVALID_ADDRESS; // Get DOS header pDosHdr = reinterpret_cast(_pFileBase); // File not a valid PE file if (pDosHdr->e_magic != IMAGE_DOS_SIGNATURE) return STATUS_INVALID_IMAGE_FORMAT; // Get image header _pImageHdr32 = reinterpret_cast(reinterpret_cast(pDosHdr) + pDosHdr->e_lfanew); _pImageHdr64 = reinterpret_cast(_pImageHdr32); // File not a valid PE file if (_pImageHdr32->Signature != IMAGE_NT_SIGNATURE) return STATUS_INVALID_IMAGE_FORMAT; auto GetHeaderData = [this, &pSection]( auto pImageHeader ) { _imgBase = pImageHeader->OptionalHeader.ImageBase; _imgSize = pImageHeader->OptionalHeader.SizeOfImage; _hdrSize = pImageHeader->OptionalHeader.SizeOfHeaders; _epRVA = pImageHeader->OptionalHeader.AddressOfEntryPoint; _subsystem = pImageHeader->OptionalHeader.Subsystem; _DllCharacteristics = pImageHeader->OptionalHeader.DllCharacteristics; pSection = reinterpret_cast(pImageHeader + 1); }; // Detect x64 image if (_pImageHdr32->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { _is64 = true; GetHeaderData( _pImageHdr64 ); } else { _is64 = false; GetHeaderData( _pImageHdr32 ); } // Exe file _isExe = !(_pImageHdr32->FileHeader.Characteristics & IMAGE_FILE_DLL); // Pure IL image auto pCorHdr = reinterpret_cast(DirectoryAddress( IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR )); _isPureIL = (pCorHdr && (pCorHdr->Flags & COMIMAGE_FLAGS_ILONLY)) ? true : false; if (_isPureIL) { _ILFlagOffset = static_cast( reinterpret_cast(pCorHdr) - reinterpret_cast(_pFileBase) + static_cast(offsetof( IMAGE_COR20_HEADER, Flags ))); #ifdef COMPILER_MSVC if (_netImage.Init( _imagePath )) _netImage.Parse(); #endif } // Sections for (int i = 0; i < _pImageHdr32->FileHeader.NumberOfSections; ++i, ++pSection) _sections.emplace_back( *pSection ); return STATUS_SUCCESS; } /// /// Processes image imports /// /// Process delayed import instead /// Import data mapImports& PEImage::GetImports( bool useDelayed /*= false*/ ) { if(useDelayed) { auto pImportTbl = reinterpret_cast(DirectoryAddress( IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT )); if (!pImportTbl) return _delayImports; // Delayed Imports for (; pImportTbl->DllNameRVA; ++pImportTbl) { uint8_t* pRVA = nullptr; DWORD IAT_Index = 0; char *pDllName = reinterpret_cast(ResolveRVAToVA( pImportTbl->DllNameRVA )); auto dllStr = Utils::AnsiToWstring( pDllName ); pRVA = reinterpret_cast(ResolveRVAToVA( pImportTbl->ImportNameTableRVA )); while (_is64 ? THK64( pRVA )->u1.AddressOfData : THK32( pRVA )->u1.AddressOfData) { uint64_t AddressOfData = _is64 ? THK64( pRVA )->u1.AddressOfData : THK32( pRVA )->u1.AddressOfData; auto pAddressTable = reinterpret_cast(ResolveRVAToVA( static_cast(AddressOfData) )); ImportData data; // import by name if (AddressOfData < (_is64 ? IMAGE_ORDINAL_FLAG64 : IMAGE_ORDINAL_FLAG32) && pAddressTable->Name[0]) { data.importByOrd = false; data.importName = reinterpret_cast(pAddressTable->Name); data.importOrdinal = 0; } // import by ordinal else { data.importByOrd = true; data.importOrdinal = static_cast(AddressOfData & 0xFFFF); } data.ptrRVA = pImportTbl->ImportAddressTableRVA + IAT_Index; _delayImports[dllStr].emplace_back( data ); // Go to next entry pRVA += _is64 ? sizeof(IMAGE_THUNK_DATA64) : sizeof(IMAGE_THUNK_DATA32); IAT_Index += _is64 ? sizeof(uint64_t) : sizeof(uint32_t); } } return _delayImports; } else { auto *pImportTbl = reinterpret_cast(DirectoryAddress( IMAGE_DIRECTORY_ENTRY_IMPORT )); if (!pImportTbl) return _imports; // Imports for (; pImportTbl->Name; ++pImportTbl) { uint8_t* pRVA = nullptr; DWORD IAT_Index = 0; char *pDllName = reinterpret_cast(ResolveRVAToVA( pImportTbl->Name )); auto dllStr = Utils::AnsiToWstring( pDllName ); pRVA = reinterpret_cast(ResolveRVAToVA( pImportTbl->OriginalFirstThunk ? pImportTbl->OriginalFirstThunk : pImportTbl->FirstThunk )); while (_is64 ? THK64( pRVA )->u1.AddressOfData : THK32( pRVA )->u1.AddressOfData) { uint64_t AddressOfData = _is64 ? THK64( pRVA )->u1.AddressOfData : THK32( pRVA )->u1.AddressOfData; auto* pAddressTable = reinterpret_cast(ResolveRVAToVA( static_cast(AddressOfData) )); ImportData data; // import by name if (AddressOfData < (_is64 ? IMAGE_ORDINAL_FLAG64 : IMAGE_ORDINAL_FLAG32) && pAddressTable->Name[0]) { data.importByOrd = false; data.importName = reinterpret_cast(pAddressTable->Name); data.importOrdinal = 0; } // import by ordinal else { data.importByOrd = true; data.importOrdinal = static_cast(AddressOfData & 0xFFFF); } // Save address to IAT if (pImportTbl->FirstThunk) data.ptrRVA = pImportTbl->FirstThunk + IAT_Index; // Save address to OrigianlFirstThunk else data.ptrRVA = static_cast(AddressOfData) - reinterpret_cast(_pFileBase); _imports[dllStr].emplace_back( data ); // Go to next entry pRVA += _is64 ? sizeof(IMAGE_THUNK_DATA64) : sizeof(IMAGE_THUNK_DATA32); IAT_Index += _is64 ? sizeof(uint64_t) : sizeof(uint32_t); } } return _imports; } } /// /// Retrieve all exported functions with names /// /// Found exports void PEImage::GetExports( vecExports& exports ) { exports.clear(); Reload(); auto pExport = reinterpret_cast(DirectoryAddress( IMAGE_DIRECTORY_ENTRY_EXPORT )); if (pExport == 0) return; DWORD *pAddressOfNames = reinterpret_cast(pExport->AddressOfNames + reinterpret_cast(_pFileBase)); DWORD *pAddressOfFuncs = reinterpret_cast(pExport->AddressOfFunctions + reinterpret_cast(_pFileBase)); WORD *pAddressOfOrds = reinterpret_cast (pExport->AddressOfNameOrdinals + reinterpret_cast(_pFileBase)); for (DWORD i = 0; i < pExport->NumberOfNames; ++i) exports.push_back( ExportData( reinterpret_cast(_pFileBase)+pAddressOfNames[i], pAddressOfFuncs[pAddressOfOrds[i]] ) ); std::sort( exports.begin(), exports.end() ); return Release( true ); } /// /// Retrieve data directory address /// /// Directory index /// Keep address relative to image base /// Directory address uintptr_t PEImage::DirectoryAddress( int index, AddressType type /*= VA*/ ) const { // Sanity check if (index < 0 || index >= IMAGE_NUMBEROF_DIRECTORY_ENTRIES) return 0; const auto idd = _is64 ? _pImageHdr64->OptionalHeader.DataDirectory : _pImageHdr32->OptionalHeader.DataDirectory; return idd[index].VirtualAddress == 0 ? 0 : ResolveRVAToVA( idd[index].VirtualAddress, type ); } /// /// Get data directory size /// /// Data directory index /// Data directory size size_t PEImage::DirectorySize( int index ) const { // Sanity check if (index < 0 || index >= IMAGE_NUMBEROF_DIRECTORY_ENTRIES) return 0; const IMAGE_DATA_DIRECTORY* idd = _is64 ? _pImageHdr64->OptionalHeader.DataDirectory : _pImageHdr32->OptionalHeader.DataDirectory; return idd[index].VirtualAddress != 0 ? static_cast(idd[index].Size) : 0; } /// /// Resolve virtual memory address to physical file offset /// /// Memory address /// Keep address relative to file start /// Resolved address uintptr_t PEImage::ResolveRVAToVA( uintptr_t Rva, AddressType type /*= VA*/ ) const { switch (type) { case blackbone::pe::RVA: return Rva; case blackbone::pe::VA: case blackbone::pe::RPA: if (_isPlainData) { for (auto& sec : _sections) { if (Rva >= sec.VirtualAddress && Rva < sec.VirtualAddress + sec.Misc.VirtualSize) if (type == VA) return reinterpret_cast(_pFileBase) + Rva - sec.VirtualAddress + sec.PointerToRawData; else return Rva - sec.VirtualAddress + sec.PointerToRawData; } return 0; } else return (type == VA) ? (reinterpret_cast(_pFileBase) + Rva) : Rva; default: return 0; } } /// /// Retrieve image TLS callbacks /// Callbacks are rebased for target image /// /// Target image base /// Found callbacks /// Number of TLS callbacks in image int PEImage::GetTLSCallbacks( module_t targetBase, std::vector& result ) const { uint8_t *pTls = reinterpret_cast(DirectoryAddress( IMAGE_DIRECTORY_ENTRY_TLS )); uint64_t* pCallback = 0; if (!pTls) return 0; uint64_t offset = _is64 ? TLS64( pTls )->AddressOfCallBacks : TLS32( pTls )->AddressOfCallBacks; if (offset == 0) return 0; // Not at base if (imageBase() != reinterpret_cast(_pFileBase)) pCallback = reinterpret_cast(ResolveRVAToVA( static_cast(offset - imageBase()) )); else pCallback = reinterpret_cast(offset); if(_is64) { for (; *pCallback; pCallback++) result.push_back( REBASE( *pCallback, imageBase(), targetBase ) ); } else { for (uint32_t *pCallback2 = reinterpret_cast(pCallback); *pCallback2; pCallback2++) result.push_back( REBASE( *pCallback2, imageBase(), targetBase ) ); } return (int)result.size(); } /// /// Prepare activation context /// /// Path to PE file. If nullptr - manifest is extracted from memory to disk /// Status code NTSTATUS PEImage::PrepareACTX( const wchar_t* filepath /*= nullptr*/ ) { wchar_t tempPath[256] = { 0 }; uint32_t manifestSize = 0; ACTCTXW act = { 0 }; act.cbSize = sizeof( act ); // No manifest found, skip auto pManifest = GetManifest( manifestSize, _manifestIdx ); if (!pManifest) return STATUS_SUCCESS; // // Dump manifest to TMP folder // if (filepath == nullptr) { wchar_t tempDir[256] = { 0 }; GetTempPathW( ARRAYSIZE( tempDir ), tempDir ); if (GetTempFileNameW( tempDir, L"ImageManifest", 0, tempPath ) == 0) return STATUS_SXS_CANT_GEN_ACTCTX; auto hTmpFile = FileHandle( CreateFileW( tempPath, FILE_GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 0, NULL ) ); if (hTmpFile != INVALID_HANDLE_VALUE) { DWORD bytes = 0; WriteFile( hTmpFile, pManifest, manifestSize, &bytes, NULL ); hTmpFile.reset(); act.lpSource = tempPath; _manifestPath = tempPath; } else return LastNtStatus(); } else { act.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID; act.lpResourceName = MAKEINTRESOURCEW( _manifestIdx ); act.lpSource = filepath; _manifestPath = _imagePath; } // Create ACTX _hctx = CreateActCtxW( &act ); if (_hctx != INVALID_HANDLE_VALUE) return STATUS_SUCCESS; // Return success if current process is protected if (LastNtStatus() == STATUS_ACCESS_DENIED) { _manifestIdx = 0; return STATUS_SUCCESS; } return LastNtStatus(); } /// /// Get manifest from image data /// /// Manifest size /// Mmanifest ID /// Manifest data void* PEImage::GetManifest( uint32_t& size, int32_t& manifestID ) { // 3 levels of pointers to nodes const IMAGE_RESOURCE_DIRECTORY_ENTRY *pDirNode1 = nullptr; const IMAGE_RESOURCE_DIRECTORY_ENTRY *pDirNode2 = nullptr; const IMAGE_RESOURCE_DIRECTORY_ENTRY *pDirNode3 = nullptr; // 3 levels of nodes const IMAGE_RESOURCE_DIRECTORY *pDirNodePtr1 = nullptr; const IMAGE_RESOURCE_DIRECTORY *pDirNodePtr2 = nullptr; const IMAGE_RESOURCE_DIRECTORY *pDirNodePtr3 = nullptr; // resource entry data const IMAGE_RESOURCE_DATA_ENTRY *pDataNode = nullptr; size_t ofst_1 = 0; // first level nodes offset size_t ofst_2 = 0; // second level nodes offset size_t ofst_3 = 0; // third level nodes offset // Get section base auto secBase = DirectoryAddress( IMAGE_DIRECTORY_ENTRY_RESOURCE ); if (secBase == 0) { size = 0; manifestID = 0; return nullptr; } pDirNodePtr1 = reinterpret_cast(secBase); ofst_1 += sizeof( IMAGE_RESOURCE_DIRECTORY ); // first-level nodes for (int i = 0; i < pDirNodePtr1->NumberOfIdEntries + pDirNodePtr1->NumberOfNamedEntries; ++i) { pDirNode1 = reinterpret_cast(secBase + ofst_1); // Not a manifest directory if (!pDirNode1->DataIsDirectory || pDirNode1->Id != 0x18) { ofst_1 += sizeof( IMAGE_RESOURCE_DIRECTORY_ENTRY ); continue; } pDirNodePtr2 = reinterpret_cast(secBase + pDirNode1->OffsetToDirectory); ofst_2 = pDirNode1->OffsetToDirectory + sizeof( IMAGE_RESOURCE_DIRECTORY ); // second-level nodes for (int j = 0; j < pDirNodePtr2->NumberOfIdEntries + pDirNodePtr2->NumberOfNamedEntries; ++j) { pDirNode2 = reinterpret_cast(secBase + ofst_2); if (!pDirNode2->DataIsDirectory) { ofst_2 += sizeof( IMAGE_RESOURCE_DIRECTORY_ENTRY ); continue; } // Check if this is a valid manifest resource if (pDirNode2->Id == 1 || pDirNode2->Id == 2 || pDirNode2->Id == 3) { pDirNodePtr3 = reinterpret_cast(secBase + pDirNode2->OffsetToDirectory); ofst_3 = pDirNode2->OffsetToDirectory + sizeof( IMAGE_RESOURCE_DIRECTORY ); pDirNode3 = reinterpret_cast(secBase + ofst_3); pDataNode = reinterpret_cast(secBase + pDirNode3->OffsetToData); manifestID = pDirNode2->Id; size = pDataNode->Size; return reinterpret_cast(ResolveRVAToVA( pDataNode->OffsetToData )); } ofst_2 += sizeof( IMAGE_RESOURCE_DIRECTORY_ENTRY ); } ofst_1 += sizeof( IMAGE_RESOURCE_DIRECTORY_ENTRY ); } return nullptr; } } }