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:
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.