CyberC00kie / volatility

Automatically exported from code.google.com/p/volatility
GNU General Public License v2.0
0 stars 0 forks source link

kpcrscan may be unnecessary #269

Closed GoogleCodeExporter closed 8 years ago

GoogleCodeExporter commented 8 years ago
Hey folks, 

The kpcrscan plugin has a few weaknesses which I'd like to discuss. First of 
all, the self-referencing scan takes waaaaaay too long. On a 256 MB memory 
dump, this can take 15-20 minutes. On a larger memory dump, there's not much 
chance of kpcrscan finishing before I get impatient and just stop it from 
running. Also, when we scan, the first kpcr result may be for the 8th CPU, not 
necessarily the 0th CPU. Thus any code using VolMagic(addr_space).KPCR.v() will 
get any kpcr, but no way to specify or determine which CPU the kpcr is for. 

It turns out we can find kpcr via kdbg, in addition to the ordering of 
associated CPUs. Given that we'd need kdbg anyway before doing anything useful 
like listing processes etc, I don't currently see a disadvantage to replacing 
the current kpcrscan method with one that scans for kdbg (like kdbgscan) and 
then just prints the kpcrs it finds off kdbg. 

Discussion welcome, patches forthcoming. 

Original issue reported on code.google.com by michael.hale@gmail.com on 1 Jun 2012 at 1:57

GoogleCodeExporter commented 8 years ago
We use the KPCR as a fallback for finding the KDBG structure, so we'll want to 
decide which one we're intending to rely on and then make sure we stick to 
that. Going around in circles trying to find one our the other would be very 
bad...

Original comment by mike.auty@gmail.com on 1 Jun 2012 at 5:57

GoogleCodeExporter commented 8 years ago
Ah you're right, I forgot about the backup. So the backup is good because 
someone could modify the "KDBG" signature that our kdbgscan relies on, without 
causing instability to the system. I have a gut feeling if someone changed the 
self-referencing members of KPCR (which is what kpcrscan relies on) then the 
system would quickly become instable. It would be good to keep the backup 
around for those cases, although the backup method was broken from Jan 2011 to 
April 2012 (see r1612) and no one noticed (meaning it was probably never 
used/needed in over a year). 

I still think we can improve in this area. We definitely want to maintain 
robustness, but I'm not sure the frequency that its useful (how often do 
attackers modify the "KDBG" header) is worth the time it takes up for normal 
analysis. Consider BDG's robust process scanner for instance, its an awesome 
option to have, but we still just use psscan (pool tag scanner) for everyday 
usage and only the robust scanner if there's reason to believe pool tags were 
modified. That reminds me, anyone want to convert the robust scanner to 2.1? I 
thought AW had tried to port it to 2.0 but found it was crazy slow. 

So anyway, it seems like if KPCR can be found from KDBG, then we should 
leverage that in the imageinfo plugin. Currently imageinfo scans for KDBG, then 
scans again for KPCR, and only reports the first KPCR found, whether its for 
CPU #0 or CPU #8. It can be misleading when the output of imageinfo says there 
are multiple processors but only lists one KPCR. Hmm more thoughts welcome...

Original comment by michael.hale@gmail.com on 1 Jun 2012 at 7:10

GoogleCodeExporter commented 8 years ago
Oh another thing about the backup method...it won't always work on x64. 
Although we do have a working kpcrscan for x64 (see Issue #184), the backup 
method of finding KDBG via KPCR is the part that may not always work. This is 
because the _KPCR.KdVersionBlock pointer is often NULL in x64 systems (its 
always like this, not just the result of a bad acquisition or something). For 
example, you see when attaching with a kernel debugger:

kd> !pcr 0 
KPCR for Processor 0 at fffff800019c3500:

kd> dt _KPCR fffff800019c3500 KdVersionBlock
nt!_KPCR
   +0x108 KdVersionBlock : (null) 

So if KdVersionBlock is NULL, we cannot find KDBG from KPCR. However, if 
KdVersionBlock is NULL, we can fallback to a different way of finding 
KdVersionBlock, which is what I'm coding up right now. 

Original comment by michael.hale@gmail.com on 1 Jun 2012 at 8:55

GoogleCodeExporter commented 8 years ago
We're not going to hold 2.1.x back on this, given it's how it's always worked 
(so people aren't getting *worse* performance).

Original comment by mike.auty@gmail.com on 6 Jun 2012 at 8:31

GoogleCodeExporter commented 8 years ago
OK, I wanted to update with some findings and conclusions (I think we can close 
the issue after this, and the next small patch which I'm about to apply). Here 
are the conclusions up front if you don't want to read the details: 

* kpcrscan is not unnecessary, but the cases in which it can serve as a backup 
for finding KDBG are limited 
* if the primary way of finding KDBG fails because someone overwrites the 
"KDBG<header>", then the backup way will fail also, because even when found via 
KPCR, the KDBG header string "KDBG" must be present to be considered valid 
* only the KPCR for the first CPU has a valid KdVersionBlock, and sometimes, 
especially on x64, even the KPCR for the first (and only) CPU has a NULL 
KdVersionBlock (which prevents us from finding KDBG). the VolMagic returns the 
first KPCR found, but not necessarily the KPCR for the first CPU. therefore, to 
make the backup method as robust as possible, we should iterate though 
VolMagic.generate_suggestions() until we find a KPCR with a valid 
KdVersionBlock (if any exist). 

So for the details, here's the output of kpcrscan (modified to show different 
fields) on a Win7 x86 system with 2 CPUs. 

$ python vol.py -f b0f427db.vmem --profile=Win7SP0x86 kpcrscan
Volatile Systems Volatility Framework 2.1_alpha
Offset(V) KdVersionBlock IDT GDT CurrentThread Number VendorString Cr3
---------- -------------- ---------- ---------- ------------- ---------- 
--------------------
0x807c4000 0x000000000000 0x807cd020 0x807ccc20 0x00086898290 1 GenuineIntel 
0x3e553780
0x82969c00 0x000082968c00 0x80b95400 0x80b95000 0x00085719d48 0 GenuineIntel 
0x3e553780

As you can see, the first KPCR found at offset 0x807c4000 is for CPU #1 and its 
KdVersionBlock is NULL. In this case, even our current backup method would 
fail. It needs to go on and find the second KPCR at offset 0x82969c00 which is 
for CPU #0. 

The next output is from a 2008 x86 with 4 CPUs. 

$ python vol.py -f 045712.raw --profile=Win2008SP2x86 kpcrscan
Volatile Systems Volatility Framework 2.1_alpha
Offset(V) KdVersionBlock IDT GDT CurrentThread Number VendorString Cr3
---------- -------------- ---------- ---------- ------------- ---------- 
-------------------- 
0x806d1000 0x000000000000 0x806d8950 0x806d8550 0x000806d51e0 1 GenuineIntel 
0x122000
0x80705000 0x000000000000 0x8070c950 0x8070c550 0x000807091e0 2 GenuineIntel 
0x122000
0x80739000 0x000000000000 0x80740950 0x80740550 0x0008073d1e0 3 GenuineIntel 
0x122000
0x81925800 0x000081924c70 0x814fe400 0x814fe000 0x0008402ad78 0 GenuineIntel 
0x122000 

In this case, the KPCR for CPU #0 (the only one with a valid KdVersionBlock) is 
the last one found by the scanner...so taking the first/best suggestion from 
the VolMagic would not work. 

Next output is from 2003 x64 with a single CPU: 

$ python vol.py -f 6f1bedec.vmem kpcrscan --profile=Win2003SP2x64
Volatile Systems Volatility Framework 2.1_alpha
Offset(V) KdVersionBlock IDT GDT CurrentThread Number VendorString Cr3
------------------ ------------------ ------------------ ------------------ 
------------------ ---------- -------------------- 
0x0000f80001177000 0xfffff80001175cb0 0xfffff800004f7070 0xfffff800004f7000 
0xfffff8000117b300 0 GenuineIntel 0x1a293000

Last but not least, here's the output from a Vista x64 with a single CPU:

$ python vol.py -f 9fe7abd5.vmem --profile=VistaSP2x64 kpcrscan
Volatile Systems Volatility Framework 2.1_alpha
Offset(V)          KdVersionBlock     IDT                GDT                
CurrentThread      Number     VendorString                        Cr3
------------------ ------------------ ------------------ ------------------ 
------------------ ---------- -------------------- ------------------
0x0000f800019c3500 0x0000000000000000 0xfffff80002b67070 0xfffff80002b67000 
0xfffffa80049926c0          0 GenuineIntel                 0x299a1000

So of the two systems tested with 1 CPU, the (one and only) KPCR does not have 
a valid KdVersionBlock on one, but does have a valid value on the other. 

Original comment by michael.hale@gmail.com on 12 Jun 2012 at 3:45

GoogleCodeExporter commented 8 years ago
This issue was closed by revision r1865.

Original comment by michael.hale@gmail.com on 12 Jun 2012 at 3:47