Files
iMHLv2 8e56052f1d refs #74 enhance kdbgscan performance for win8/2012 x64
traditionally, kdbgscan has been used for two reasons: to determine a
suggested profile or to gather information used to speed up other
plugins. this patch optimizes kdbgscan when its used for the later
purpose. specifically, when you call kdbgscan with —profile (thus you
must already know its win8/2012 x64), it will skip the typical
signature-based KDBG scan. this reduced wyatt’s execution time from 30
minutes to 6 minutes on a 140 GB dump.
2014-08-18 19:23:49 -05:00

245 lines
11 KiB
Python

# Volatility
#
# Authors:
# Mike Auty <mike.auty@gmail.com>
#
# This file is part of Volatility.
#
# Volatility is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Volatility is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Volatility. If not, see <http://www.gnu.org/licenses/>.
#
import volatility.obj as obj
import volatility.scan as scan
import volatility.cache as cache
import volatility.plugins.common as common
import volatility.addrspace as addrspace
import volatility.registry as registry
import volatility.utils as utils
import volatility.exceptions as exceptions
class MultiStringFinderCheck(scan.ScannerCheck):
""" Checks for multiple strings per page """
def __init__(self, address_space, needles = None):
scan.ScannerCheck.__init__(self, address_space)
if not needles:
needles = []
self.needles = needles
self.maxlen = 0
for needle in needles:
self.maxlen = max(self.maxlen, len(needle))
if not self.maxlen:
raise RuntimeError("No needles of any length were found for the " + self.__class__.__name__)
def check(self, offset):
verify = self.address_space.read(offset, self.maxlen)
for match in self.needles:
if verify[:len(match)] == match:
return True
return False
def skip(self, data, offset):
nextval = len(data)
for needle in self.needles:
dindex = data.find(needle, offset + 1)
if dindex > -1:
nextval = min(nextval, dindex)
return nextval - offset
class MultiPrefixFinderCheck(MultiStringFinderCheck):
""" Checks for multiple strings per page, finishing at the offset """
def check(self, offset):
verify = self.address_space.read(offset - self.maxlen, self.maxlen)
for match in self.needles:
if verify.endswith(match):
return True
return False
class KDBGScanner(scan.BaseScanner):
checks = [ ]
def __init__(self, window_size = 8, needles = None):
oses = set()
arches = set()
for needle in needles:
header = str(needle).split('KDBG')
arches.add(header[0])
oses.add('KDBG' + header[1])
self.checks = [ ("PoolTagCheck", {'tag': "KDBG"}),
("MultiPrefixFinderCheck", {'needles':arches}),
("MultiStringFinderCheck", {'needles':oses})]
scan.BaseScanner.__init__(self, window_size)
def scan(self, address_space, offset = 0, maxlen = None):
for offset in scan.BaseScanner.scan(self, address_space, offset, maxlen):
# Compensate for KDBG appearing within the searched for structure
# (0x10 should really be the offset of OwnerTag from with the structure,
# however we don't know which profile to read it from, so it's hardwired)
# NOTE: this will not work correctly for _KDDEBUGGER_DATA32 structures
# however they're only necessary for NT or older
offset = offset - 0x10
yield offset
class KDBGScan(common.AbstractWindowsCommand):
"""Search for and dump potential KDBG values"""
@staticmethod
def register_options(config):
config.add_option('KDBG', short_option = 'g', default = None, type = 'int',
help = "Specify a specific KDBG virtual address")
@cache.CacheDecorator(lambda self: "tests/kdbgscan/kdbg={0}".format(self._config.KDBG))
def calculate(self):
"""Determines the address space"""
profilelist = [ p.__name__ for p in registry.get_plugin_classes(obj.Profile).values() ]
encrypted_kdbg_profiles = []
proflens = {}
maxlen = 0
origprofile = self._config.PROFILE
for p in profilelist:
self._config.update('PROFILE', p)
buf = addrspace.BufferAddressSpace(self._config)
if buf.profile.metadata.get('os', 'unknown') == 'windows':
proflens[p] = str(obj.VolMagic(buf).KDBGHeader)
maxlen = max(maxlen, len(proflens[p]))
if (buf.profile.metadata.get('memory_model', '64bit') == '64bit' and
(buf.profile.metadata.get('major', 0),
buf.profile.metadata.get('minor', 0)) >= (6, 2)):
encrypted_kdbg_profiles.append(p)
self._config.update('PROFILE', origprofile)
# keep track of the number of potential KDBGs we find
count = 0
if origprofile not in encrypted_kdbg_profiles:
scanner = KDBGScanner(needles = proflens.values())
aspace = utils.load_as(self._config, astype = 'any')
for offset in scanner.scan(aspace):
val = aspace.read(offset, maxlen + 0x10)
for l in proflens:
if val.find(proflens[l]) >= 0:
kdbg = obj.Object("_KDDEBUGGER_DATA64", offset = offset, vm = aspace)
yield l, kdbg
count += 1
# only perform the special win8/2012 scan if we didn't find
# any others and if a virtual x64 address space is available
if count == 0:
if origprofile in encrypted_kdbg_profiles:
encrypted_kdbg_profiles = [origprofile]
for profile in encrypted_kdbg_profiles:
self._config.update('PROFILE', profile)
aspace = utils.load_as(self._config, astype = 'any')
if hasattr(aspace, 'vtop'):
for kdbg in obj.VolMagic(aspace).KDBG.generate_suggestions():
yield profile, kdbg
def render_text(self, outfd, data):
"""Renders the KPCR values as text"""
for profile, kdbg in data:
outfd.write("*" * 50 + "\n")
outfd.write("Instantiating KDBG using: {0} {1} ({2}.{3}.{4} {5})\n".format(
kdbg.obj_vm.name, kdbg.obj_vm.profile.__class__.__name__,
kdbg.obj_vm.profile.metadata.get('major', 0),
kdbg.obj_vm.profile.metadata.get('minor', 0),
kdbg.obj_vm.profile.metadata.get('build', 0),
kdbg.obj_vm.profile.metadata.get('memory_model', '32bit'),
))
# Will spaces with vtop always have a dtb also?
has_vtop = hasattr(kdbg.obj_native_vm, 'vtop')
# Always start out with the virtual and physical offsets
if has_vtop:
outfd.write("{0:<30}: {1:#x}\n".format("Offset (V)", kdbg.obj_offset))
outfd.write("{0:<30}: {1:#x}\n".format("Offset (P)", kdbg.obj_native_vm.vtop(kdbg.obj_offset)))
else:
outfd.write("{0:<30}: {1:#x}\n".format("Offset (P)", kdbg.obj_offset))
if hasattr(kdbg, 'KdCopyDataBlock'):
outfd.write("{0:<30}: {1:#x}\n".format("KdCopyDataBlock (V)", kdbg.KdCopyDataBlock))
if hasattr(kdbg, 'block_encoded'):
outfd.write("{0:<30}: {1}\n".format("Block encoded", "Yes" if kdbg.block_encoded == 1 else "No"))
if hasattr(kdbg, 'wait_never'):
outfd.write("{0:<30}: {1:#x}\n".format("Wait never", kdbg.wait_never))
if hasattr(kdbg, 'wait_always'):
outfd.write("{0:<30}: {1:#x}\n".format("Wait always", kdbg.wait_always))
# These fields can be gathered without dereferencing
# any pointers, thus they're available always
outfd.write("{0:<30}: {1}\n".format("KDBG owner tag check", str(kdbg.is_valid())))
outfd.write("{0:<30}: {1}\n".format("Profile suggestion (KDBGHeader)", profile))
verinfo = kdbg.dbgkd_version64()
if verinfo:
outfd.write("{0:<30}: {1:#x} (Major: {2}, Minor: {3})\n".format(
"Version64", verinfo.obj_offset, verinfo.MajorVersion,
verinfo.MinorVersion))
# Print details only available when a DTB can be found
# and we have an AS with vtop.
if has_vtop:
outfd.write("{0:<30}: {1}\n".format("Service Pack (CmNtCSDVersion)", kdbg.ServicePack))
outfd.write("{0:<30}: {1}\n".format("Build string (NtBuildLab)", kdbg.NtBuildLab.dereference()))
try:
num_tasks = len(list(kdbg.processes()))
except AttributeError:
num_tasks = 0
try:
num_modules = len(list(kdbg.modules()))
except AttributeError:
num_modules = 0
cpu_blocks = list(kdbg.kpcrs())
outfd.write("{0:<30}: {1:#x} ({2} processes)\n".format(
"PsActiveProcessHead", kdbg.PsActiveProcessHead, num_tasks))
outfd.write("{0:<30}: {1:#x} ({2} modules)\n".format(
"PsLoadedModuleList", kdbg.PsLoadedModuleList, num_modules))
outfd.write("{0:<30}: {1:#x} (Matches MZ: {2})\n".format(
"KernelBase", kdbg.KernBase, str(kdbg.obj_native_vm.read(kdbg.KernBase, 2) == "MZ")))
try:
dos_header = obj.Object("_IMAGE_DOS_HEADER",
offset = kdbg.KernBase,
vm = kdbg.obj_native_vm)
nt_header = dos_header.get_nt_header()
except (ValueError, exceptions.SanityCheckException):
pass
else:
outfd.write("{0:<30}: {1}\n".format(
"Major (OptionalHeader)",
nt_header.OptionalHeader.MajorOperatingSystemVersion))
outfd.write("{0:<30}: {1}\n".format(
"Minor (OptionalHeader)",
nt_header.OptionalHeader.MinorOperatingSystemVersion))
for kpcr in cpu_blocks:
outfd.write("{0:<30}: {1:#x} (CPU {2})\n".format(
"KPCR", kpcr.obj_offset, kpcr.ProcessorBlock.Number))
else:
outfd.write("{0:<30}: {1:#x}\n".format("PsActiveProcessHead", kdbg.PsActiveProcessHead))
outfd.write("{0:<30}: {1:#x}\n".format("PsLoadedModuleList", kdbg.PsLoadedModuleList))
outfd.write("{0:<30}: {1:#x}\n".format("KernelBase", kdbg.KernBase))
outfd.write("\n")