Tuesday, November 18, 2014

Page Table Structure Corruption Attacks - How to Mitigate it?

On x86 and many other processor architectures (with MMU), page tables are critical data structures for address translations. And many hardware-based page level protection technologies in my previous post, like SMEP, XD/DEP, highly depend on correct page table settings. so what if page tables are controlled by an attacker? ...At the end of this post, I will propose an extra solution to mitigate page table structure attacks.

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

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:

  1. 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.
     
  2. 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. 


No comments:

Post a Comment