Recently, this post (Windows 8 Kernel Memory Protections Bypass) presents a generic technique for exploiting kernel vulnerabilities with bypassing SMEP and DEP. It just requires only a single vulnerability that provides an attacker with a write-what-where primitive, then exploits it with modifying the page tables (U/S and XD bit flags) intentionally to bypass SMEP and DEP protections.
As we all know that SMEP (Supervisor Mode Execution Prevention) is a mitigation that aims to prevent the CPU from running code from user-mode while in kernel-mode. Internally the processor check the U/S bit flag in corresponding page structure tables when fetching instruction for execution in kernel mode. Hence if we can corrupt the paging structures to modify the U/S flag, then we can cause a user memory to be interpreted as kernel memory without any other additional changes.
Similarly, DEP (Data Execution Prevention) depends on the NX bit flag (set) to prohibit a data page being executed. If we can clear such a flag by corrupting the paging structures, we can cause a data page to be marked as executable.
Now, I am proposing another solution to solve this issue by write-protecting page table structures with CR0.WP capability.
The basic idea is to set data page of page structures/tables themselves with Read-Only permission. And because CR0.WP bit is set by default, so any write access to page table structures will generate #PF exception by processor. But for legitimate modification to page table structures, use the code sequence below:
disable_wp(); // clear CR0.WP bit.
On Windows 8 system, both SMEP and DEP are enabled by default, and the KASLR (Kernel Address Space Layout Randomization) is also enabled. But unfortunately, the virtual address of a corresponding PTE entry address for a particular virtual address (for example, a user mode address) is fixed and easy to calculated. So how to retrieve page table addresses?
For example, on 32bit PAE Windows system, the code below can get the virtual address of PTE (not the PTE contents) for a particular virtual address as input.
#define PT_VIRTUAL_BASE_ADDRESS 0xC0000000
#define PAGE_TABLE_SHIFT 12
#define PAGE_DIR_SHIFT 21
#define PAGE_DIR_POINTER_SHIFT 30
__inline
UINT32 PAEGetPteAtVirtualAddress(UINT32 Vaddr)
{
return (UINT32)
( PT_VIRTUAL_BASE_ADDRESS +
((Vaddr & 0xC0000000) >> PAGE_DIR_POINTER_SHIFT) * 0x200000 +
((Vaddr & 0x3FE00000) >> PAGE_DIR_SHIFT) * 0x1000 +
((Vaddr & 0x001FF000) >> PAGE_TABLE_SHIFT) * 8
);
}
On 64-bit Windows system, similarly.
/* you can see this definition in Win DDK/SDK ntddk.h file */
#define PTE_BASE 0xFFFFF68000000000UI64
#define PTE_SHIFT 3
#define PTI_SHIFT 12
#define PDI_SHIFT 21
#define PPI_SHIFT 30
#define PXI_SHIFT 39
#define VIRTUAL_ADDRESS_BITS 48
#define VIRTUAL_ADDRESS_MASK ((((UINT64)1) << VIRTUAL_ADDRESS_BITS) - 1)
#define X64GetPteAddress(va) \
(((((UINT64)(va) & VIRTUAL_ADDRESS_MASK) >> PTI_SHIFT) << PTE_SHIFT) + PTE_BASE)
Then if there is write-what-where kernel vulnerability, an attacker can corrupt the corresponding PTE based upon the calculations above for a particular virtual address of user mode code that is controlled by attacker.
So now, how to mitigate this kind of SMEP/DEP bypassing?
As the author of that post said, randomization for page table address itself is not possible because it is recognised that many of the core functions of the kernel memory management may rely on this mapping to locate and update paging structures.
The author also proposed two solutions to mitigate it:
- One is to use a separate data segment for holding page structures.
This requires an extra dedicated segment register. Maybe GS is unused in 32bit Windows, and FS is unused in 64bit Windows, then we can use this solution.
- The other one is to set hardware debug breakpoints on the access to the paging structures (or key fields of the structures).
Hardware breakpoint is a very limited resource (only max 4 H/W breakpoints supported), and it may also cause other compatibility issues.
Now, I am proposing another solution to solve this issue by write-protecting page table structures with CR0.WP capability.
The basic idea is to set data page of page structures/tables themselves with Read-Only permission. And because CR0.WP bit is set by default, so any write access to page table structures will generate #PF exception by processor. But for legitimate modification to page table structures, use the code sequence below:
disable_wp(); // clear CR0.WP bit.
write access to RO page structures.
enable_wp(); // set CR0.WP bit again.
I have talked about this idea before in my previous posts, please check details below:
Update:
Some references from M$FT slides about Windows self-mapping page tables:
Update:
Some references from M$FT slides about Windows self-mapping page tables:
No comments:
Post a Comment