/* * Process Hacker - * mapped library * * Copyright (C) 2010 wj32 * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . */ /* * This file contains functions to load and retrieve information for library/archive files (lib). * The file format for archive files is explained in the PE/COFF specification located at: * * http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx */ #include #include VOID PhpMappedArchiveProbe( _In_ PPH_MAPPED_ARCHIVE MappedArchive, _In_ PVOID Address, _In_ SIZE_T Length ); NTSTATUS PhpGetMappedArchiveMemberFromHeader( _In_ PPH_MAPPED_ARCHIVE MappedArchive, _In_ PIMAGE_ARCHIVE_MEMBER_HEADER Header, _Out_ PPH_MAPPED_ARCHIVE_MEMBER Member ); NTSTATUS PhInitializeMappedArchive( _Out_ PPH_MAPPED_ARCHIVE MappedArchive, _In_ PVOID ViewBase, _In_ SIZE_T Size ) { NTSTATUS status; PCHAR start; start = (PCHAR)ViewBase; memset(MappedArchive, 0, sizeof(PH_MAPPED_ARCHIVE)); MappedArchive->ViewBase = ViewBase; MappedArchive->Size = Size; __try { // Verify the file signature. PhpMappedArchiveProbe(MappedArchive, start, IMAGE_ARCHIVE_START_SIZE); if (memcmp(start, IMAGE_ARCHIVE_START, IMAGE_ARCHIVE_START_SIZE) != 0) PhRaiseStatus(STATUS_INVALID_IMAGE_FORMAT); } __except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); } // Get the members. // Note: the names are checked. // First linker member status = PhpGetMappedArchiveMemberFromHeader( MappedArchive, (PIMAGE_ARCHIVE_MEMBER_HEADER)(start + IMAGE_ARCHIVE_START_SIZE), &MappedArchive->FirstLinkerMember ); if (!NT_SUCCESS(status)) return status; if (MappedArchive->FirstLinkerMember.Type != LinkerArchiveMemberType) return STATUS_INVALID_PARAMETER; MappedArchive->FirstStandardMember = &MappedArchive->FirstLinkerMember; // Second linker member status = PhGetNextMappedArchiveMember( &MappedArchive->FirstLinkerMember, &MappedArchive->SecondLinkerMember ); if (!NT_SUCCESS(status)) return status; if (MappedArchive->SecondLinkerMember.Type != LinkerArchiveMemberType) return STATUS_INVALID_PARAMETER; // Longnames member // This member doesn't seem to be mandatory, contrary to the specification. // So we'll check if it's actually a longnames member, and if not, ignore it. status = PhGetNextMappedArchiveMember( &MappedArchive->SecondLinkerMember, &MappedArchive->LongnamesMember ); if ( NT_SUCCESS(status) && MappedArchive->LongnamesMember.Type == LongnamesArchiveMemberType ) { MappedArchive->HasLongnamesMember = TRUE; MappedArchive->LastStandardMember = &MappedArchive->LongnamesMember; } else { MappedArchive->LastStandardMember = &MappedArchive->SecondLinkerMember; } return STATUS_SUCCESS; } NTSTATUS PhLoadMappedArchive( _In_opt_ PWSTR FileName, _In_opt_ HANDLE FileHandle, _In_ BOOLEAN ReadOnly, _Out_ PPH_MAPPED_ARCHIVE MappedArchive ) { NTSTATUS status; status = PhMapViewOfEntireFile( FileName, FileHandle, ReadOnly, &MappedArchive->ViewBase, &MappedArchive->Size ); if (NT_SUCCESS(status)) { status = PhInitializeMappedArchive( MappedArchive, MappedArchive->ViewBase, MappedArchive->Size ); if (!NT_SUCCESS(status)) { NtUnmapViewOfSection(NtCurrentProcess(), MappedArchive->ViewBase); } } return status; } NTSTATUS PhUnloadMappedArchive( _Inout_ PPH_MAPPED_ARCHIVE MappedArchive ) { return NtUnmapViewOfSection( NtCurrentProcess(), MappedArchive->ViewBase ); } VOID PhpMappedArchiveProbe( _In_ PPH_MAPPED_ARCHIVE MappedArchive, _In_ PVOID Address, _In_ SIZE_T Length ) { PhProbeAddress(Address, Length, MappedArchive->ViewBase, MappedArchive->Size, 1); } /** * Gets the next archive member. * * \param Member An archive member structure. * \param NextMember A variable which receives a structure describing the next archive member. This * pointer may be the same as the pointer specified in \a Member. */ NTSTATUS PhGetNextMappedArchiveMember( _In_ PPH_MAPPED_ARCHIVE_MEMBER Member, _Out_ PPH_MAPPED_ARCHIVE_MEMBER NextMember ) { PIMAGE_ARCHIVE_MEMBER_HEADER nextHeader; nextHeader = (PIMAGE_ARCHIVE_MEMBER_HEADER)PTR_ADD_OFFSET( Member->Data, Member->Size ); // 2 byte alignment. if ((ULONG_PTR)nextHeader & 0x1) nextHeader = (PIMAGE_ARCHIVE_MEMBER_HEADER)PTR_ADD_OFFSET(nextHeader, 1); return PhpGetMappedArchiveMemberFromHeader( Member->MappedArchive, nextHeader, NextMember ); } NTSTATUS PhpGetMappedArchiveMemberFromHeader( _In_ PPH_MAPPED_ARCHIVE MappedArchive, _In_ PIMAGE_ARCHIVE_MEMBER_HEADER Header, _Out_ PPH_MAPPED_ARCHIVE_MEMBER Member ) { WCHAR integerString[11]; ULONG64 size; PH_STRINGREF string; PWSTR digit; PSTR slash; if ((ULONG_PTR)Header >= (ULONG_PTR)MappedArchive->ViewBase + MappedArchive->Size) return STATUS_NO_MORE_ENTRIES; __try { PhpMappedArchiveProbe(MappedArchive, Header, sizeof(IMAGE_ARCHIVE_MEMBER_HEADER)); } __except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); } Member->MappedArchive = MappedArchive; Member->Header = Header; Member->Data = PTR_ADD_OFFSET(Header, sizeof(IMAGE_ARCHIVE_MEMBER_HEADER)); Member->Type = NormalArchiveMemberType; // Read the size string, terminate it after the last digit and parse it. if (!PhCopyStringZFromBytes(Header->Size, 10, integerString, 11, NULL)) return STATUS_INVALID_PARAMETER; string.Buffer = integerString; string.Length = 0; digit = string.Buffer; while (iswdigit(*digit++)) string.Length += sizeof(WCHAR); if (!PhStringToInteger64(&string, 10, &size)) return STATUS_INVALID_PARAMETER; Member->Size = (ULONG)size; __try { PhpMappedArchiveProbe(MappedArchive, Member->Data, Member->Size); } __except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); } // Parse the name. if (!PhCopyBytesZ(Header->Name, 16, Member->NameBuffer, 20, NULL)) return STATUS_INVALID_PARAMETER; Member->Name = Member->NameBuffer; slash = strchr(Member->NameBuffer, '/'); if (!slash) return STATUS_INVALID_PARAMETER; // Special names: // * If the slash is the first character, then this is a linker member. // * If there is a slash after the slash which is a first character, then this is the longnames // member. // * If there are digits after the slash, then the real name is stored in the longnames member. if (slash == Member->NameBuffer) { if (Member->NameBuffer[1] == '/') { // Longnames member. Set the name to "/". Member->NameBuffer[0] = '/'; Member->NameBuffer[1] = 0; Member->Type = LongnamesArchiveMemberType; } else { // Linker member. Set the name to "". Member->NameBuffer[0] = 0; Member->Type = LinkerArchiveMemberType; } } else { if (isdigit(slash[1])) { PSTR digita; ULONG64 offset64; ULONG offset; // The name is stored in the longnames member. // Note: we make sure we have the longnames member first. if (!MappedArchive->LongnamesMember.Header) return STATUS_INVALID_PARAMETER; // Find the last digit and null terminate the string there. digita = slash + 2; while (isdigit(*digita)) digita++; *digita = 0; // Parse the offset and make sure it lies within the longnames member. if (!PhCopyStringZFromBytes(slash + 1, -1, integerString, 11, NULL)) return STATUS_INVALID_PARAMETER; PhInitializeStringRefLongHint(&string, integerString); if (!PhStringToInteger64(&string, 10, &offset64)) return STATUS_INVALID_PARAMETER; offset = (ULONG)offset64; if (offset >= MappedArchive->LongnamesMember.Size) return STATUS_INVALID_PARAMETER; // TODO: Probe the name. Member->Name = (PSTR)PTR_ADD_OFFSET(MappedArchive->LongnamesMember.Data, offset); } else { // Null terminate the string. slash[0] = 0; } } return STATUS_SUCCESS; } BOOLEAN PhIsMappedArchiveMemberShortFormat( _In_ PPH_MAPPED_ARCHIVE_MEMBER Member ) { PIMAGE_FILE_HEADER header; header = (PIMAGE_FILE_HEADER)Member->Data; return header->Machine != IMAGE_FILE_MACHINE_UNKNOWN; } NTSTATUS PhGetMappedArchiveImportEntry( _In_ PPH_MAPPED_ARCHIVE_MEMBER Member, _Out_ PPH_MAPPED_ARCHIVE_IMPORT_ENTRY Entry ) { IMPORT_OBJECT_HEADER *importHeader; importHeader = (IMPORT_OBJECT_HEADER *)Member->Data; if (Member->Type != NormalArchiveMemberType) return STATUS_INVALID_PARAMETER; if ( importHeader->Sig1 != IMAGE_FILE_MACHINE_UNKNOWN || importHeader->Sig2 != IMPORT_OBJECT_HDR_SIG2 ) return STATUS_INVALID_PARAMETER; Entry->Type = (BYTE)importHeader->Type; Entry->NameType = (BYTE)importHeader->NameType; Entry->Machine = importHeader->Machine; // TODO: Probe the name. Entry->Name = (PSTR)PTR_ADD_OFFSET(importHeader, sizeof(IMPORT_OBJECT_HEADER)); Entry->DllName = (PSTR)PTR_ADD_OFFSET(Entry->Name, strlen(Entry->Name) + 1); // Ordinal/NameHint are union'ed, so these statements are exactly the same. // It's there in case this changes in the future. if (Entry->NameType == IMPORT_OBJECT_ORDINAL) { Entry->Ordinal = importHeader->Ordinal; } else { Entry->NameHint = importHeader->Hint; } return STATUS_SUCCESS; }