Thursday, October 11, 2012

Finding KPCR in memory images.

I was looking into finding the KPCR structure in memory images. The KPCR is used to store a lot of information about the running CPU and in windows version prior to Windows 7 also contains a link to the kernel debugger block.
Volatility already contains a scanner for KPCR (in this example the image is 821676355 bytes big):
~/projects/volatility$ time python --profile Win7SP1x64 --file ~/images/win7_trial_64bit.dmp kpcrscan
Volatile Systems Volatility Framework 2.2
Offset (V)                    : 0xf80002842d00
Offset (P)                    : 0x2842d00
KdVersionBlock                : 0x0
IDT                           : 0xf80000b95080
GDT                           : 0xf80000b95000
CurrentThread                 : 0xf80002850c40 TID 0 (Idle:0)
IdleThread                    : 0xf80002850c40 TID 0 (Idle:0)
Details                       : CPU 0 (GenuineIntel @ 2394 MHz)
CR3/DTB                       : 0x187000

real 48m33.825s
user 48m13.717s
sys 0m3.888s
As you can see this is extremely slow. Why is it so slow? We can look at the source code for the kpcr scanner check:
00001: def check(self, offset):
00002:         """ We check that _KCPR.pSelfPCR points to the start of the _KCPR struct """
00003:         paKCPR = offset
00004:         paPRCBDATA = offset + self.PrcbData_offset
00006:         try:
00007:             pSelfPCR = obj.Object('Pointer', offset = (offset + self.SelfPcr_offset), vm = self.address_space)
00008:             pPrcb = obj.Object('Pointer', offset = (offset + self.Prcb_offset), vm = self.address_space)
00009:             if pSelfPCR == paKCPR and pPrcb == paPRCBDATA:
00010:                 self.KPCR = pSelfPCR
00011:                 return True
00013:         except BaseException:
00014:             return False
00016:         return False
So the scanner compares each 4 byte position in the image by overlaying the _KPCR struct over it and checking that _KPCR.Self points at the address of _KPCR.
This is extremely slow since it will perform millions of comparisons in python as well as many very small read operations from the raw image (IO operations are very expensive on windows systems making this even slower).
There must be an easier way to find KPCR!
The solution is to examine the _KPCR object and see what we can leverage so we do not need to resort to an exhaustive search:
Win7SP1x64:win7_trial_64bit.raw 12:54:13> dt "_KPCR"
[_KPCR _KPCR] @ 0x00000000
  0x00 GdtBase
  0x00 NtTib                         [_NT_TIB NtTib] @ 0x00000000
  0x00 _GDT
  0x08 TssBase
  0x10 UserRsp                        [unsigned long long:UserRsp]: 0x00000000
  0x18 Self
  0x20 CurrentPrcb
  0x180 Prcb                          [_KPRCB Prcb] @ 0x00000180
And we can examine the contents of the Prcb:
Win7SP1x64:win7_trial_64bit.raw 12:54:19> dt "_KPRCB"
[_KPRCB _KPRCB] @ 0x00000000
  0x5F0 CpuType                                                [unsigned char:CpuType]: 0x00000000
  0x5F1 CpuID                                                  [unsigned char:CpuID]: 0x00000000
  0x5F2 CpuStep                                                [unsigned short:CpuStep]: 0x00000000
  0x5F2 CpuStepping                                            [unsigned char:CpuStepping]: 0x00000000
  0x5F3 CpuModel                                               [unsigned char:CpuModel]: 0x00000000
  0x5F4 MHz                                                    [unsigned long:MHz]: 0x00000000
  0x5F8 HalReserved
  0x638 MinorVersion                                           [unsigned short:MinorVersion]: 0x00000000
  0x63A MajorVersion                                           [unsigned short:MajorVersion]: 0x00000000
  0x63C BuildType                                              [unsigned char:BuildType]: 0x00000000
  0x63D CpuVendor                                              [unsigned char:CpuVendor]: 0x00000000
  0x4480 WaitListHead                                          [_LIST_ENTRY WaitListHead] @ 0x00004480
We notice the WaitListHead member. After Googling for this ( we find that this is the list head of threads waiting to run on this CPU:

The WaitListEntry field is used to add the KTHREAD structure to the list of
threads that have entered into a wait state on a particular CPU. The
WaitListHead field of the Kernel Processor Control Region (KPRCB) structure for
every CPU links such threads together via the KTHREAD.WaitListEntry
field. Threads are added to this list by the function KiCommitThreadWait() and
removed from this list by KiSignalThread().
So the trick is to enumerate all threads and see which ones are waiting to run on this CPU. In order to find the kernel DTB we already are searching for the System process so we usually already have one _EPROCESS object we know about.
00001:         for kthread in task.Pcb.ThreadListHead.list_of_type(
00002:             "_KTHREAD", "ThreadListEntry"):
00004:             # Look for threads in the Wait state. If this thread is in the Wait
00005:             # state, the WaitListEntry will belong to the list of all waiting
00006:             # threads. By following this list we should get to the list head
00007:             # which lives inside the _KPCR object.
00008:             for kwaiter in kthread.WaitListEntry.list_of_type(
00009:                 "_KTHREAD", "WaitListEntry"):
00011:                 # Assume the kwaiter is actually the KPRCB.WaitListHead.
00012:                 possible_kpcr = self.profile._KPCR(
00013:                     kwaiter.WaitListEntry.obj_offset - offset)
00015:                 # Check for validity using the usual condition.
00016:                 if possible_kpcr.Self == possible_kpcr.obj_offset:
00017:                     if possible_kpcr.obj_offset not in seen:
00018:                         seen[possible_kpcr.obj_offset] = possible_kpcr
This runs a lot faster:
~/volatility/svn$ time -f ~/images/win7_trial_64bit.dmp kpcr
Property                       Value
------------------------------ -----
Offset (V)                     0xf80002842d00
Offset (P)                     0x2842d00L
KdVersionBlock                 0
IDT                            0xf80000b95080L
GDT                            0xf80000b95000L
CurrentThread                 : 0xf80002850c40 TID 0 (System:0)
IdleThread                    : 0xf80002850c40 TID 0 (System:0)
Details                       : CPU 0 (GenuineIntel @ 2394 MHz)
CR3/DTB                       : 0x187000

real 0m6.457s
user 0m5.948s
sys 0m0.480s
Yes thats 6.5 seconds vs 48 minutes!
Unfortunately this only works on later versions of windows than XP right now since the WaitListHead was introduced with Windows 2003.
PS - I originally wanted to examine the KPCR as a way of quickly retrieving the KDBG but this does not work as recent windows versions set KdVersionBlock to 0. We can not use the KPCR as a substitute for the DTB scan either since all this work is done in the kernel virtual address space (so we need a DTB already). In practice DTB scanning is very quick and KDBG scans are also not too bad so its not a huge problem.

No comments: