[NTOS:Mm] Rewrite MiWriteProtectSystemImage (#749)

* The previous version was overcomplicated and broken and therefore disabled.
* The new version also enforces NX protection on x64.
* Now that protecting works, also protect the boot loaded images.
This commit is contained in:
Timo Kreuzer 2019-01-18 22:11:43 +01:00 committed by GitHub
parent 4b924a8685
commit 65dbfc2868
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 178 additions and 218 deletions

View file

@ -3,7 +3,8 @@
*/
#pragma once
#define _MI_PAGING_LEVELS 4
#define _MI_PAGING_LEVELS 4
#define _MI_HAS_NO_EXECUTE 1
/* Memory layout base addresses (This is based on Vista!) */
#define MI_USER_PROBE_ADDRESS (PVOID)0x000007FFFFFF0000ULL

View file

@ -4,6 +4,7 @@
#pragma once
#define _MI_PAGING_LEVELS 2
#define _MI_HAS_NO_EXECUTE 1
/* Memory layout base addresses */
#define MI_USER_PROBE_ADDRESS (PVOID)0x7FFF0000

View file

@ -5,8 +5,10 @@
#ifdef _PAE_
#define _MI_PAGING_LEVELS 3
#define _MI_HAS_NO_EXECUTE 1
#else
#define _MI_PAGING_LEVELS 2
#define _MI_NO_EXECUTE 0
#endif
/* Memory layout base addresses */

View file

@ -94,6 +94,14 @@
#define ExRaiseStatus RtlRaiseStatus
/* Also defined in fltkernel.h, but we don't want the entire header */
#ifndef Add2Ptr
#define Add2Ptr(P,I) ((PVOID)((PUCHAR)(P) + (I)))
#endif
#ifndef PtrOffset
#define PtrOffset(B,O) ((ULONG)((ULONG_PTR)(O) - (ULONG_PTR)(B)))
#endif
//
// Switch for enabling global page support
//

View file

@ -83,7 +83,7 @@ C_ASSERT(SYSTEM_PD_SIZE == PAGE_SIZE);
// while on certain architectures such as ARM, it is enabling the cache which
// requires a flag.
//
#if defined(_M_IX86) || defined(_M_AMD64)
#if defined(_M_IX86)
//
// Access Flags
//
@ -109,6 +109,34 @@ C_ASSERT(SYSTEM_PD_SIZE == PAGE_SIZE);
#define PTE_ENABLE_CACHE 0
#define PTE_DISABLE_CACHE 0x10
#define PTE_WRITECOMBINED_CACHE 0x10
#define PTE_PROTECT_MASK 0x612
#elif defined(_M_AMD64)
//
// Access Flags
//
#define PTE_READONLY 0x8000000000000000ULL
#define PTE_EXECUTE 0x0000000000000000ULL
#define PTE_EXECUTE_READ PTE_EXECUTE /* EXECUTE implies READ on x64 */
#define PTE_READWRITE 0x8000000000000002ULL
#define PTE_WRITECOPY 0x8000000000000200ULL
#define PTE_EXECUTE_READWRITE 0x0000000000000002ULL
#define PTE_EXECUTE_WRITECOPY 0x0000000000000200ULL
#define PTE_PROTOTYPE 0x0000000000000400ULL
//
// State Flags
//
#define PTE_VALID 0x0000000000000001ULL
#define PTE_ACCESSED 0x0000000000000020ULL
#define PTE_DIRTY 0x0000000000000040ULL
//
// Cache flags
//
#define PTE_ENABLE_CACHE 0x0000000000000000ULL
#define PTE_DISABLE_CACHE 0x0000000000000010ULL
#define PTE_WRITECOMBINED_CACHE 0x0000000000000010ULL
#define PTE_PROTECT_MASK 0x8000000000000612ULL
#elif defined(_M_ARM)
#define PTE_READONLY 0x200
#define PTE_EXECUTE 0 // Not worrying about NX yet
@ -118,16 +146,23 @@ C_ASSERT(SYSTEM_PD_SIZE == PAGE_SIZE);
#define PTE_EXECUTE_READWRITE 0 // Not worrying about NX yet
#define PTE_EXECUTE_WRITECOPY 0 // Not worrying about NX yet
#define PTE_PROTOTYPE 0x400 // Using the Shared bit
//
// Cache flags
//
#define PTE_ENABLE_CACHE 0
#define PTE_DISABLE_CACHE 0x10
#define PTE_WRITECOMBINED_CACHE 0x10
#define PTE_PROTECT_MASK 0x610
#else
#error Define these please!
#endif
//
// Mask for image section page protection
//
#define IMAGE_SCN_PROTECTION_MASK (IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE)
extern const ULONG_PTR MmProtectToPteMask[32];
extern const ULONG MmProtectToValue[32];
@ -2263,6 +2298,11 @@ MiMakePdeExistAndMakeValid(
IN KIRQL OldIrql
);
VOID
NTAPI
MiWriteProtectSystemImage(
_In_ PVOID ImageBase);
//
// MiRemoveZeroPage will use inline code to zero out the page manually if only
// free pages are available. In some scenarios, we don't/can't run that piece of

View file

@ -2341,252 +2341,146 @@ MiUseLargeDriverPage(IN ULONG NumberOfPtes,
return FALSE;
}
ULONG
NTAPI
MiComputeDriverProtection(IN BOOLEAN SessionSpace,
IN ULONG SectionProtection)
{
ULONG Protection = MM_ZERO_ACCESS;
/* Check if the caller gave anything */
if (SectionProtection)
{
/* Always turn on execute access */
SectionProtection |= IMAGE_SCN_MEM_EXECUTE;
/* Check if the registry setting is on or not */
if (!MmEnforceWriteProtection)
{
/* Turn on write access too */
SectionProtection |= (IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_EXECUTE);
}
}
/* Convert to internal PTE flags */
if (SectionProtection & IMAGE_SCN_MEM_EXECUTE) Protection |= MM_EXECUTE;
if (SectionProtection & IMAGE_SCN_MEM_READ) Protection |= MM_READONLY;
/* Check for write access */
if (SectionProtection & IMAGE_SCN_MEM_WRITE)
{
/* Session space is not supported */
if (SessionSpace)
{
DPRINT1("Session drivers not supported\n");
ASSERT(SessionSpace == FALSE);
}
else
{
/* Convert to internal PTE flag */
Protection = (Protection & MM_EXECUTE) ? MM_EXECUTE_READWRITE : MM_READWRITE;
}
}
/* If there's no access at all by now, convert to internal no access flag */
if (Protection == MM_ZERO_ACCESS) Protection = MM_NOACCESS;
/* Return the computed PTE protection */
return Protection;
}
VOID
NTAPI
MiSetSystemCodeProtection(IN PMMPTE FirstPte,
IN PMMPTE LastPte,
IN ULONG ProtectionMask)
MiSetSystemCodeProtection(
_In_ PMMPTE FirstPte,
_In_ PMMPTE LastPte,
_In_ ULONG Protection)
{
/* I'm afraid to introduce regressions at the moment... */
PMMPTE PointerPte;
MMPTE TempPte;
/* Loop the PTEs */
for (PointerPte = FirstPte; PointerPte <= LastPte; PointerPte++)
{
/* Read the PTE */
TempPte = *PointerPte;
/* Make sure it's valid */
ASSERT(TempPte.u.Hard.Valid == 1);
/* Update the protection */
TempPte.u.Hard.Write = BooleanFlagOn(Protection, IMAGE_SCN_MEM_WRITE);
#if _MI_HAS_NO_EXECUTE
TempPte.u.Hard.NoExecute = !BooleanFlagOn(Protection, IMAGE_SCN_MEM_EXECUTE);
#endif
MI_UPDATE_VALID_PTE(PointerPte, TempPte);
}
/* Flush it all */
KeFlushEntireTb(TRUE, TRUE);
return;
}
VOID
NTAPI
MiWriteProtectSystemImage(IN PVOID ImageBase)
MiWriteProtectSystemImage(
_In_ PVOID ImageBase)
{
PIMAGE_NT_HEADERS NtHeaders;
PIMAGE_SECTION_HEADER Section;
PFN_NUMBER DriverPages;
ULONG CurrentProtection, SectionProtection, CombinedProtection = 0, ProtectionMask;
ULONG Sections, Size;
ULONG_PTR BaseAddress, CurrentAddress;
PMMPTE PointerPte, StartPte, LastPte, CurrentPte, ComboPte = NULL;
ULONG CurrentMask, CombinedMask = 0;
PAGED_CODE();
PIMAGE_SECTION_HEADER SectionHeaders, Section;
ULONG i;
PVOID SectionBase, SectionEnd;
ULONG SectionSize;
ULONG Protection;
PMMPTE FirstPte, LastPte;
/* No need to write protect physical memory-backed drivers (large pages) */
if (MI_IS_PHYSICAL_ADDRESS(ImageBase)) return;
/* Check if the registry setting is on or not */
if (!MmEnforceWriteProtection)
{
/* Ignore section protection */
return;
}
/* Get the image headers */
/* Large page mapped images are not supported */
NT_ASSERT(!MI_IS_PHYSICAL_ADDRESS(ImageBase));
/* Session images are not yet supported */
NT_ASSERT(!MI_IS_SESSION_ADDRESS(ImageBase));
/* Get the NT headers */
NtHeaders = RtlImageNtHeader(ImageBase);
if (!NtHeaders) return;
/* Check if this is a session driver or not */
if (!MI_IS_SESSION_ADDRESS(ImageBase))
if (NtHeaders == NULL)
{
/* Don't touch NT4 drivers */
if (NtHeaders->OptionalHeader.MajorOperatingSystemVersion < 5) return;
if (NtHeaders->OptionalHeader.MajorImageVersion < 5) return;
}
else
{
/* Not supported */
UNIMPLEMENTED_DBGBREAK("Session drivers not supported\n");
DPRINT1("Failed to get NT headers for image @ %p\n", ImageBase);
return;
}
/* These are the only protection masks we care about */
ProtectionMask = IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE;
/* Calculate the number of pages this driver is occupying */
DriverPages = BYTES_TO_PAGES(NtHeaders->OptionalHeader.SizeOfImage);
/* Get the number of sections and the first section header */
Sections = NtHeaders->FileHeader.NumberOfSections;
ASSERT(Sections != 0);
Section = IMAGE_FIRST_SECTION(NtHeaders);
/* Loop all the sections */
CurrentAddress = (ULONG_PTR)ImageBase;
while (Sections)
/* Don't touch NT4 drivers */
if ((NtHeaders->OptionalHeader.MajorOperatingSystemVersion < 5) ||
(NtHeaders->OptionalHeader.MajorSubsystemVersion < 5))
{
/* Get the section size */
Size = max(Section->SizeOfRawData, Section->Misc.VirtualSize);
DPRINT1("Skipping NT 4 driver @ %p\n", ImageBase);
return;
}
/* Get its virtual address */
BaseAddress = (ULONG_PTR)ImageBase + Section->VirtualAddress;
if (BaseAddress < CurrentAddress)
/* Get the section headers */
SectionHeaders = IMAGE_FIRST_SECTION(NtHeaders);
/* Get the base address of the first section */
SectionBase = Add2Ptr(ImageBase, SectionHeaders[0].VirtualAddress);
/* Start protecting the image header as R/O */
FirstPte = MiAddressToPte(ImageBase);
LastPte = MiAddressToPte(SectionBase) - 1;
Protection = IMAGE_SCN_MEM_READ;
if (LastPte >= FirstPte)
{
MiSetSystemCodeProtection(FirstPte, LastPte, IMAGE_SCN_MEM_READ);
}
/* Loop the sections */
for (i = 0; i < NtHeaders->FileHeader.NumberOfSections; i++)
{
/* Get the section base address and size */
Section = &SectionHeaders[i];
SectionBase = Add2Ptr(ImageBase, Section->VirtualAddress);
SectionSize = max(Section->SizeOfRawData, Section->Misc.VirtualSize);
/* Get the first PTE of this section */
FirstPte = MiAddressToPte(SectionBase);
/* Check for overlap with the previous range */
if (FirstPte == LastPte)
{
/* Windows doesn't like these */
DPRINT1("Badly linked image!\n");
return;
/* Combine the old and new protection by ORing them */
Protection |= (Section->Characteristics & IMAGE_SCN_PROTECTION_MASK);
/* Update the protection for this PTE */
MiSetSystemCodeProtection(FirstPte, FirstPte, Protection);
/* Skip this PTE */
FirstPte++;
}
/* Remember the current address */
CurrentAddress = BaseAddress + Size - 1;
/* There can not be gaps! */
NT_ASSERT(FirstPte == (LastPte + 1));
/* Next */
Sections--;
Section++;
}
/* Get the end of the section and the last PTE */
SectionEnd = Add2Ptr(SectionBase, SectionSize - 1);
NT_ASSERT(SectionEnd < Add2Ptr(ImageBase, NtHeaders->OptionalHeader.SizeOfImage));
LastPte = MiAddressToPte(SectionEnd);
/* Get the number of sections and the first section header */
Sections = NtHeaders->FileHeader.NumberOfSections;
ASSERT(Sections != 0);
Section = IMAGE_FIRST_SECTION(NtHeaders);
/* Set the address at the end to initialize the loop */
CurrentAddress = (ULONG_PTR)Section + Sections - 1;
CurrentProtection = IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ;
/* Set the PTE points for the image, and loop its sections */
StartPte = MiAddressToPte(ImageBase);
LastPte = StartPte + DriverPages;
while (Sections)
{
/* Get the section size */
Size = max(Section->SizeOfRawData, Section->Misc.VirtualSize);
/* Get its virtual address and PTE */
BaseAddress = (ULONG_PTR)ImageBase + Section->VirtualAddress;
PointerPte = MiAddressToPte(BaseAddress);
/* Check if we were already protecting a run, and found a new run */
if ((ComboPte) && (PointerPte > ComboPte))
/* If there are no more pages (after an overlap), skip this section */
if (LastPte < FirstPte)
{
/* Compute protection */
CombinedMask = MiComputeDriverProtection(FALSE, CombinedProtection);
/* Set it */
MiSetSystemCodeProtection(ComboPte, ComboPte, CombinedMask);
/* Check for overlap */
if (ComboPte == StartPte) StartPte++;
/* One done, reset variables */
ComboPte = NULL;
CombinedProtection = 0;
}
/* Break out when needed */
if (PointerPte >= LastPte) break;
/* Get the requested protection from the image header */
SectionProtection = Section->Characteristics & ProtectionMask;
if (SectionProtection == CurrentProtection)
{
/* Same protection, so merge the request */
CurrentAddress = BaseAddress + Size - 1;
/* Next */
Sections--;
Section++;
NT_ASSERT(FirstPte == (LastPte + 1));
continue;
}
/* This is now a new section, so close up the old one */
CurrentPte = MiAddressToPte(CurrentAddress);
/* Get the section protection */
Protection = (Section->Characteristics & IMAGE_SCN_PROTECTION_MASK);
/* Check for overlap */
if (CurrentPte == PointerPte)
{
/* Skip the last PTE, since it overlaps with us */
CurrentPte--;
/* And set the PTE we will merge with */
ASSERT((ComboPte == NULL) || (ComboPte == PointerPte));
ComboPte = PointerPte;
/* Get the most flexible protection by merging both */
CombinedMask |= (SectionProtection | CurrentProtection);
}
/* Loop any PTEs left */
if (CurrentPte >= StartPte)
{
/* Sanity check */
ASSERT(StartPte < LastPte);
/* Make sure we don't overflow past the last PTE in the driver */
if (CurrentPte >= LastPte) CurrentPte = LastPte - 1;
ASSERT(CurrentPte >= StartPte);
/* Compute the protection and set it */
CurrentMask = MiComputeDriverProtection(FALSE, CurrentProtection);
MiSetSystemCodeProtection(StartPte, CurrentPte, CurrentMask);
}
/* Set new state */
StartPte = PointerPte;
CurrentAddress = BaseAddress + Size - 1;
CurrentProtection = SectionProtection;
/* Next */
Sections--;
Section++;
/* Update the protection for this section */
MiSetSystemCodeProtection(FirstPte, LastPte, Protection);
}
/* Is there a leftover section to merge? */
if (ComboPte)
{
/* Compute and set the protection */
CombinedMask = MiComputeDriverProtection(FALSE, CombinedProtection);
MiSetSystemCodeProtection(ComboPte, ComboPte, CombinedMask);
/* Handle overlap */
if (ComboPte == StartPte) StartPte++;
}
/* Finally, handle the last section */
CurrentPte = MiAddressToPte(CurrentAddress);
if ((StartPte < LastPte) && (CurrentPte >= StartPte))
{
/* Handle overlap */
if (CurrentPte >= LastPte) CurrentPte = LastPte - 1;
ASSERT(CurrentPte >= StartPte);
/* Compute and set the protection */
CurrentMask = MiComputeDriverProtection(FALSE, CurrentProtection);
MiSetSystemCodeProtection(StartPte, CurrentPte, CurrentMask);
}
/* Image should end with the last section */
NT_ASSERT(ALIGN_UP_POINTER_BY(SectionEnd, PAGE_SIZE) ==
Add2Ptr(ImageBase, NtHeaders->OptionalHeader.SizeOfImage));
}
VOID

View file

@ -203,6 +203,8 @@ MmInitSystem(IN ULONG Phase,
PMMPTE PointerPte;
MMPTE TempPte = ValidKernelPte;
PFN_NUMBER PageFrameNumber;
PLIST_ENTRY ListEntry;
PLDR_DATA_TABLE_ENTRY DataTableEntry;
/* Initialize the kernel address space */
ASSERT(Phase == 1);
@ -271,6 +273,18 @@ MmInitSystem(IN ULONG Phase,
/* Initialize the balance set manager */
MmInitBsmThread();
/* Loop the boot loaded images */
for (ListEntry = PsLoadedModuleList.Flink;
ListEntry != &PsLoadedModuleList;
ListEntry = ListEntry->Flink)
{
/* Get the data table entry */
DataTableEntry = CONTAINING_RECORD(ListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
/* Set up the image protection */
MiWriteProtectSystemImage(DataTableEntry->DllBase);
}
return TRUE;
}