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 vol.py --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 00005: 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 00012: 00013: except BaseException: 00014: return False 00015: 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 (http://www.codemachine.com/article_kernelstruct.html#KPCR) we find that this is the list head of threads waiting to run on this CPU:
nt!_KTHREAD 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.
I wrote a quick plugin to implement this: https://code.google.com/p/volatility/source/browse/branches/scudette/volatility/plugins/windows/kpcr.py
00001: for kthread in task.Pcb.ThreadListHead.list_of_type( 00002: "_KTHREAD", "ThreadListEntry"): 00003: 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"): 00010: 00011: # Assume the kwaiter is actually the KPRCB.WaitListHead. 00012: possible_kpcr = self.profile._KPCR( 00013: kwaiter.WaitListEntry.obj_offset - offset) 00014: 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 vol.py -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:
Post a Comment