Monday, October 29, 2012

How to Defeat Windows 8 ASLR in Getting the Address of KPCR


How to Defeat Windows 8 ASLR in Getting the Address of KPCR

by cawan (cawan[at]ieee.org)

on 29/10/2012

In Windows XP or earlier version, KPCR is always located at 0xffdff000.
It is very important for kernel shellcode in getting system process token
in order to duplicate it into another eprocess object which has lower
privilege. This action is normally known as token stealing, and it is
very useful for privilege escalation. However, starting from Windows 7,
it has been ASLRed, and so in Windows 8. This has increased the difficulty
in creating kernel shellcode. However, it is not impossible to defeat it.
First of all, let see how it is ASLRed in Windows 8.

kd> !pcr
KPCR for Processor 0 at 81214000:
    Major 1 Minor 1
NtTib.ExceptionList: 8089612c
   NtTib.StackBase: 00000000
  NtTib.StackLimit: 00000000
NtTib.SubSystemTib: 80879000
     NtTib.Version: 005ab660
 NtTib.UserPointer: 00000001
     NtTib.SelfTib: 00000000

           SelfPcr: 81214000
              Prcb: 81214120
              Irql: 0000001f
               IRR: 00000000
               IDR: 00000000
     InterruptMode: 00000000
               IDT: 8087c400
               GDT: 8087c000
               TSS: 80879000

     CurrentThread: 812230c0
        NextThread: 00000000
        IdleThread: 812230c0

         DpcQueue:

Well, the KPCR is at 0x81214000. This address is valid until next reboot.
Let see which address range of this KPCR located to.

kd> ln 81214000
(81214000)   nt!KiInitialPCR   |  (81218280)   nt!BcpCursor
Exact matches:
    nt!KiInitialPCR =

It is within nt kernel with internal symbol KiInitialPCR. Of course the
KiInitialPCR will not be existed in the list of Export Table of nt PE
header. So, how to get the address? Method 1, by using the fixed offset
of KPCR to a well-known symbol which is included in the Export Table of
nt PE header, let's say HalDispatchTable.

kd> dd nt!HalDispatchTable l1
811def28  00000004

So, HalDispatchTable is at 0x811def28. Let's check the offset to KPCR.

kd> ? 81214000-811def28
Evaluate expression: 217304 = 000350d8

Well, it is 0x00350d8. So, when we need to exploit a device driver, we
can calculate the address of KPCR in user mode first before creating the
kernel shellcode to be run for privelege escalation. To calculate the
KPCR, use the following steps,

1. Use NtQuerySystemInformation() with SystemInformationClass of
   SystemModuleInformation to get nt kernel base
2. Use LoadLibrary() to load nt kernel image file into user space and
   get its base (nt kernel base in user space)
3. Use GetProcAddress() to get the address of HalDispatchTable in user
   space (HalDispatchTable in user space)
4. (HalDispatchTable in kernel base) = (nt kernel base) + (HalDispatchTable
   in user space) - (nt kernel base in user space)
5. KPCR = (HalDispatchTable in kernel base) + 0x350d8

Well, the 0x350d8 is valid for Windows 8 system. Let's verify by rebooting
the machine.

kd> dd nt!HalDispatchTable l1
81039f28  00000004

The HalDispatchTable is at 0x81039f28. So by adding 0x350d8 to 0x81039f28
it suppose to be KPCR.

kd> ? 81039f28+350d8
Evaluate expression: -2130251776 = 8106f000

The KPCR should be located at 0x8106f000 now. Let's check.

kd> !pcr
KPCR for Processor 0 at 8106f000:
    Major 1 Minor 1
NtTib.ExceptionList: 8051612c
   NtTib.StackBase: 00000000
  NtTib.StackLimit: 00000000
NtTib.SubSystemTib: 804f9000
     NtTib.Version: 0001604a
 NtTib.UserPointer: 00000001
     NtTib.SelfTib: 00000000

           SelfPcr: 8106f000
              Prcb: 8106f120
              Irql: 0000001f
               IRR: 00000000
               IDR: 00000000
     InterruptMode: 00000000
               IDT: 804fc400
               GDT: 804fc000
               TSS: 804f9000

     CurrentThread: 8107e0c0
        NextThread: 00000000
        IdleThread: 8107e0c0

         DpcQueue:

Bingo! We win. For your info, in Windows 7, it is 0x92c. I believe it
is service pack dependent, so, if you have chance to verify it in
different Windows version, please let me know.

Now, let's go to method 2, to defeat ASLR with FS register. In user
mode, FS:[0] is always pointing to the current thread object; but in
kernel mode, it is always pointing to KPCR. Let's check.

kd> r fs
fs=00000030

In all current windows system FS always be 0x30. It is easy to get the
base address of this FS value in windbg.

kd> dg 30
                                  P Si Gr Pr Lo
Sel    Base     Limit     Type    l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
0030 8106f000 00004280 Data RW Ac 0 Bg By P  Nl 00000493

Since we don't reboot the mechine yet, the KPCR is still at 0x8106f000.
The question now is can we do what dg command did manually? The answer
is yes. Let's go for it. First, lets deconstruct the FS value.

15...3|2   |1 0
index |TI  |RPL
0..110|0  |0 0

RPL=0 means kernel mode
TI=0 means using GDT
index=110 means selector index 6 of descriptors

Now, get the GDT address by using windbg. Of course in creating kernel
shellcode, sgdt instruction should be used.

kd> r gdtr
gdtr=804fc000

The GDT is at 0x804fc000. Let's dump its memory.

kd> dd 804fc000
804fc000  00000000 00000000 0000ffff 00cf9b00
804fc010  0000ffff 00cf9300 0000ffff 00cffb00
804fc020  0000ffff 00cff300 900020ab 80008b4f
804fc030  f0004280 81409306 00000fff 0040f300
804fc040  0400ffff 0000f200 00000000 00000000
804fc050  90000068 81008903 90680068 81008903
804fc060  00000000 00000000 00000000 00000000
804fc070  c00003ff 8000924f 00000000 00000000

Each descriptor is 8 bytes, so, descriptor at index 6 is at 0x804fc030

kd> db 804fc030 l8
804fc030  80 42 00 f0 06 93 40 81

Let's refer the format of descriptor

63...56        |55|54|53|52|51...48        |47|46   |45|44...40  |39...16                   |15...0
Base 31-24 |G |D  |R |U |Limit 19-16 |P  |DPL|S  |TYPE   |Segment Base 23-0|Segment Limit 15-0

R reserved (0)
DPL 0 means kernel, 3 means user
G 1 means 4K granularity (Always set in Linux)
D 1 means default operand size 32bits
U programmer definable
P 1 means present in physical memory
S 0 means system segment, 1 means normal code or data segment.
Type There are many possibilities. Interpreted differently for system and normal descriptors.

So, the base address can be reconstructed as shown below.

LSB  | 80 42 00 f0 06 93 40 81 | MSB
MSB | 81 40 93 06 f0 00 42 80 | LSB

63...56 = 81
39...16 = 06 f0 00

Well, the base address is at 0x8106f000 and it is exactly the KPCR address
that we obtained by using "dg 30" command in windbg. In next paper, I will
discuss about how to build a platform in creating and testing kernel shellcode
in windbg.

pdf version:
http://www.scribd.com/doc/112328778/How-to-Defeat-Windows-8-ASLR-in-Getting-the-Address-of-KPCR


1 comment: