diff --git a/dll/ntdll/ldr/ldrutils.c b/dll/ntdll/ldr/ldrutils.c index 2542a9a324..cb9a322483 100644 --- a/dll/ntdll/ldr/ldrutils.c +++ b/dll/ntdll/ldr/ldrutils.c @@ -1013,6 +1013,247 @@ LdrpSetProtection(PVOID ViewBase, return STATUS_SUCCESS; } +#define DLL_VERIFY(cond) ((cond) || (__debugbreak(), 0)) + +ULONG +LdrpGetSimilarity( + _In_ const VOID *Mem1, + _In_ const VOID *Mem2, + _In_ ULONG Length) +{ + const UCHAR *p1 = Mem1; + const UCHAR *p2 = Mem2; + ULONG i; + ULONG Same = 0; + for (i = 0; i < Length; i++) + { + Same += p1[i] == p2[i]; + } + return Same; +} + +PVOID LdrpBreakAddress;// = (PVOID)0x005f7000; +ULONG LdrpBreakIndex;// = 0xd6; + +NTSTATUS +LdrpVerifySection( + _In_ const UCHAR *LoadedSection, + _In_ HANDLE FileHandle, + _Inout_ PUCHAR PageBuffer, + _In_ const IMAGE_SECTION_HEADER *Section, + _Out_ PULONG BytesDifferent) +{ + NTSTATUS Status; + IO_STATUS_BLOCK IoStatus; + LARGE_INTEGER FileOffset; + ULONG NumberOfPages = Section->SizeOfRawData / PAGE_SIZE; + ULONG i; + ULONG OffsetInPage; + ULONG Same; + + *BytesDifferent = 0; + OffsetInPage = Section->PointerToRawData % PAGE_SIZE; + FileOffset.QuadPart = Section->PointerToRawData / PAGE_SIZE * PAGE_SIZE; + for (i = 0; i < NumberOfPages; i++) + { + if (LdrpBreakIndex && i == LdrpBreakIndex) __debugbreak(); + if (LdrpBreakAddress && &LoadedSection[i * PAGE_SIZE] == LdrpBreakAddress) __debugbreak(); + Status = NtReadFile(FileHandle, + NULL, + NULL, + NULL, + &IoStatus, + PageBuffer, + 2 * PAGE_SIZE, + &FileOffset, + NULL); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ReadFile for section failed: 0x%lx\n", Status); + goto Exit; + } + Same = LdrpGetSimilarity(&LoadedSection[i * PAGE_SIZE], + &PageBuffer[OffsetInPage], + PAGE_SIZE); + if (!DLL_VERIFY(Same >= PAGE_SIZE / 2)) + { + DPRINT1("Page at %p is broken, only %lu bytes match\n", &LoadedSection[i * PAGE_SIZE], Same); + } + *BytesDifferent += PAGE_SIZE - Same; + FileOffset.QuadPart += PAGE_SIZE; + } + Status = STATUS_SUCCESS; + +Exit: + return Status; +} + +NTSTATUS +LdrpVerifyDll( + _In_ HANDLE LoadedDll, + _In_ PCUNICODE_STRING DllName) +{ + NTSTATUS Status; + HANDLE FileHandle = NULL; + PUCHAR PageBuffer = NULL; + OBJECT_ATTRIBUTES ObjectAttributes; + LARGE_INTEGER FileOffset; + IO_STATUS_BLOCK IoStatus; + ULONG BytesRead; + ULONG BytesDifferent; + + PageBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, 2 * PAGE_SIZE); + if (PageBuffer == NULL) + { + Status = STATUS_INSUFFICIENT_RESOURCES; + DPRINT1("Failed to allocate memory\n"); + goto Exit; + } + + InitializeObjectAttributes(&ObjectAttributes, + (PUNICODE_STRING)DllName, + OBJ_CASE_INSENSITIVE, + NULL, + NULL); + Status = NtOpenFile(&FileHandle, + FILE_READ_DATA | SYNCHRONIZE, + &ObjectAttributes, + &IoStatus, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + | FILE_NO_INTERMEDIATE_BUFFERING); + if (!NT_SUCCESS(Status)) + { + DPRINT1("Failed to open file: 0x%lx\n", Status); + goto Exit; + } + + FileOffset.QuadPart = 0; + Status = NtReadFile(FileHandle, + NULL, + NULL, + NULL, + &IoStatus, + PageBuffer, + PAGE_SIZE, + &FileOffset, + NULL); + if (!NT_SUCCESS(Status)) + { + DPRINT1("ReadFile failed: 0x%lx\n", Status); + goto Exit; + } + BytesRead = IoStatus.Information; + + /* First page */ + { + PIMAGE_DOS_HEADER DosHeader = LoadedDll; + if (!DLL_VERIFY(DosHeader->e_magic == 'ZM')) + { + DPRINT1("In-memory DOS magic invalid: 0x%x\n", DosHeader->e_magic); + } + } + /* Find and verify code section */ + { + PIMAGE_DOS_HEADER DosHeader; + PIMAGE_NT_HEADERS NtHeaders; + PIMAGE_SECTION_HEADER Section; + ULONG i; + + if (!DLL_VERIFY(BytesRead >= sizeof(*DosHeader))) + { + Status = STATUS_INVALID_IMAGE_FORMAT; + DPRINT1("File too small for DOS header: %lu\n", BytesRead); + goto Exit; + } + + DosHeader = (PVOID)PageBuffer; + if (!DLL_VERIFY(DosHeader->e_magic == 'ZM')) + { + Status = STATUS_INVALID_IMAGE_FORMAT; + DPRINT1("DOS magic invalid: 0x%x\n", DosHeader->e_magic); + goto Exit; + } + + if (!DLL_VERIFY(DosHeader->e_lfanew < BytesRead)) + { + Status = STATUS_INVALID_IMAGE_FORMAT; + DPRINT1("PE header pointer beyond first page: 0x%lx\n", DosHeader->e_lfanew); + goto Exit; + } + if (!DLL_VERIFY(BytesRead >= DosHeader->e_lfanew + FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader))) + { + Status = STATUS_INVALID_IMAGE_FORMAT; + DPRINT1("PE does not fit in first page: 0x%lx/0x%lx\n", DosHeader->e_lfanew, BytesRead); + goto Exit; + } + + NtHeaders = (PVOID)(PageBuffer + DosHeader->e_lfanew); + if (!DLL_VERIFY(NtHeaders->Signature == 'EP')) + { + Status = STATUS_INVALID_IMAGE_FORMAT; + DPRINT1("PE magic invalid: 0x%x\n", NtHeaders->Signature); + goto Exit; + } + if (!DLL_VERIFY(NtHeaders->FileHeader.SizeOfOptionalHeader + >= FIELD_OFFSET(IMAGE_OPTIONAL_HEADER, DataDirectory))) + { + Status = STATUS_INVALID_IMAGE_FORMAT; + DPRINT1("Optional Header too small: 0x%lx\n", NtHeaders->FileHeader.SizeOfOptionalHeader); + goto Exit; + } + if (!DLL_VERIFY(NtHeaders->FileHeader.SizeOfOptionalHeader + < BytesRead - DosHeader->e_lfanew - FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader))) + { + Status = STATUS_INVALID_IMAGE_FORMAT; + DPRINT1("Optional Header too big: 0x%lx\n", NtHeaders->FileHeader.SizeOfOptionalHeader); + goto Exit; + } + + for (i = 0; i < NtHeaders->FileHeader.NumberOfSections; i++) + { + PUCHAR LoadedSection; + IMAGE_SECTION_HEADER SectionHeader; + + Section = IMAGE_FIRST_SECTION(NtHeaders); + if ((PUCHAR)(Section + 1) - PageBuffer > BytesRead) + { + Status = STATUS_INVALID_IMAGE_FORMAT; + DPRINT1("Section %lu header not on first page: 0x%lx\n", i, (PUCHAR)(Section + 1) - PageBuffer); + goto Exit; + } + + SectionHeader = *Section; + if ((NtHeaders->OptionalHeader.AddressOfEntryPoint == 0 && + Section->Name[0] == '.' && + Section->Name[1] == 't') || + (NtHeaders->OptionalHeader.AddressOfEntryPoint >= Section->VirtualAddress && + NtHeaders->OptionalHeader.AddressOfEntryPoint < Section->VirtualAddress + Section->Misc.VirtualSize)) + { + LoadedSection = (PUCHAR)LoadedDll + Section->VirtualAddress; + Status = LdrpVerifySection(LoadedSection, FileHandle, PageBuffer, Section, &BytesDifferent); + DPRINT1("Section %.8s of %wZ is %ls, %lu bytes differ\n", + SectionHeader.Name, DllName, NT_SUCCESS(Status) ? L"good" : L"bad", BytesDifferent); + goto Exit; + } + } + } + + DPRINT1("Section with entry point not found\n"); + Status = STATUS_INVALID_IMAGE_FORMAT; + +Exit: + if (FileHandle != NULL) + { + NtClose(FileHandle); + } + if (PageBuffer != NULL) + { + RtlFreeHeap(RtlGetProcessHeap(), 0, PageBuffer); + } + return Status; +} + /* NOTE: Not yet reviewed */ NTSTATUS NTAPI @@ -1438,6 +1679,15 @@ SkipCheck: /* Make sure we changed the protection */ if (NT_SUCCESS(Status)) { + if (RtlDosPathNameToNtPathName_U(FullDllName.Buffer, + &NtPathDllName, + NULL, + NULL)) + { + LdrpVerifyDll(ViewBase, &NtPathDllName); + RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathDllName.Buffer); + } + /* Do the relocation */ Status = LdrRelocateImageWithBias(ViewBase, 0LL, NULL, STATUS_SUCCESS, STATUS_CONFLICTING_ADDRESSES, STATUS_INVALID_IMAGE_FORMAT); @@ -1521,6 +1771,15 @@ NoRelocNeeded: } } + if (RtlDosPathNameToNtPathName_U(FullDllName.Buffer, + &NtPathDllName, + NULL, + NULL)) + { + LdrpVerifyDll(ViewBase, &NtPathDllName); + RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathDllName.Buffer); + } + // FIXME: LdrpCheckCorImage() is missing /* Check if this is an SMP Machine and a DLL */