/usr/lib/python2.7/dist-packages/rekall/plugins/windows/kdbgscan.py is in python-rekall-core 1.6.0+dfsg-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | # Rekall Memory Forensics
#
# Copyright 2013 Google Inc. All Rights Reserved.
#
# Authors:
# Michael Cohen <scudette@gmail.com>
#
# This program 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.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# pylint: disable=protected-access
from rekall import obj
from rekall import scan
from rekall import plugin
from rekall import utils
from rekall.plugins.windows import common
class KDBGScanner(scan.BaseScanner):
"""Scans for _KDDEBUGGER_DATA64 structures.
Note that this does not rely on signatures, as validity of hits is
calculated through list reflection.
"""
checks = [("StringCheck", dict(needle="KDBG"))]
def scan(self, offset=0, maxlen=None):
maxlen = maxlen or self.profile.get_constant("MaxPointer")
# How far into the struct the OwnerTag is.
owner_tag_offset = self.profile.get_obj_offset(
"_DBGKD_DEBUG_DATA_HEADER64", "OwnerTag")
# Depending on the memory model this behaves slightly differently.
architecture = self.profile.metadata("arch", "I386")
# This basically iterates over all hits on the string "KDBG".
for offset in super(KDBGScanner, self).scan(offset, maxlen):
# For each hit we overlay a _DBGKD_DEBUG_DATA_HEADER64 on it and
# reflect through the "List" member.
result = self.profile.Object("_KDDEBUGGER_DATA64",
offset=offset - owner_tag_offset,
vm=self.address_space)
# We verify this hit by reflecting through its header list.
list_entry = result.Header.List
# On 32 bit systems the Header.List member seems to actually be a
# LIST_ENTRY32 instead of a LIST_ENTRY64, but it is still padded to
# take the same space:
if architecture == "I386":
list_entry = list_entry.cast("LIST_ENTRY32")
if list_entry.reflect():
yield result
elif (list_entry.Flink == list_entry.Blink and
not list_entry.Flink.dereference()):
self.session.logging.debug(
"KDBG list_head is not mapped, assuming its valid.")
yield result
class KDBGScan(plugin.KernelASMixin, common.AbstractWindowsCommandPlugin):
"""Scan for possible _KDDEBUGGER_DATA64 structures.
The scanner is detailed here:
http://moyix.blogspot.com/2008/04/finding-kernel-global-variables-in.html
The relevant structures are detailed here:
http://doxygen.reactos.org/d3/ddf/include_2psdk_2wdbgexts_8h_source.html
We can see that _KDDEBUGGER_DATA64.Header is:
typedef struct _DBGKD_DEBUG_DATA_HEADER64 {
LIST_ENTRY64 List;
ULONG OwnerTag;
ULONG Size;
}
We essentially search for an owner tag of "KDBG", then overlay the
_KDDEBUGGER_DATA64 struct on it. We test for validity by reflecting
through the Header.List member.
"""
__name = "kdbgscan"
__args = [
dict(name="full_scan", type="Boolean",
help="Scan the full address space.")
]
def hits(self):
if self.plugin_args.full_scan:
start, end = 0, 2**64
else:
# The kernel image is always loaded in the same range called the
# "Initial Loader Mappings". Narrowing the possible range makes
# scanning much faster. (See
# http://www.codemachine.com/article_x64kvas.html)
if self.session.profile.metadata("arch") == "AMD64":
start, end = 0xFFFFF80000000000, 0xFFFFF87FFFFFFFFF
else:
start, end = 0x80000000, 0xFFFFFFFF
scanner = KDBGScanner(
session=self.session, profile=self.profile,
address_space=self.kernel_address_space)
# Yield actual objects here
for kdbg in scanner.scan(
obj.Pointer.integer_to_address(start),
end - start):
yield kdbg
table_header = [
dict(name="Key", width=50),
dict(name="Value")
]
table_options = dict(
suppress_headers=True
)
def collect(self):
"""Renders the KPCR values as text"""
for kdbg in self.hits():
yield "Offset (V)", utils.HexInteger(kdbg.obj_offset)
yield "Offset (P)", utils.HexInteger(kdbg.obj_vm.vtop(
kdbg.obj_offset))
# These fields can be gathered without dereferencing
# any pointers, thus they're available always
yield "KDBG owner tag check", kdbg.is_valid()
verinfo = kdbg.dbgkd_version64()
if verinfo:
yield "Version64", "{0:#x} (Major: {1}, Minor: {2})\n".format(
verinfo.obj_offset, verinfo.MajorVersion,
verinfo.MinorVersion)
yield "Service Pack (CmNtCSDVersion)", kdbg.ServicePack
yield "Build string (NtBuildLab)", kdbg.NtBuildLab.dereference()
# Count the total number of tasks from PsActiveProcessHead.
try:
pslist = kdbg.PsActiveProcessHead.list_of_type(
"_EPROCESS", "ActiveProcessLinks")
num_tasks = len([x for x in pslist if x.pid > 0])
except AttributeError:
num_tasks = 0
try:
modules = self.session.plugins.modules(session=self.session)
num_modules = len(list(modules.lsmod()))
except AttributeError:
num_modules = 0
yield "PsActiveProcessHead", "{0:#x} ({1} processes)".format(
kdbg.PsActiveProcessHead, num_tasks)
yield "PsLoadedModuleList", "{0:#x} ({1} modules)".format(
kdbg.PsLoadedModuleList, num_modules)
yield "KernelBase", "{0:#x} (Matches MZ: {1})".format(
kdbg.KernBase, kdbg.obj_vm.read(kdbg.KernBase, 2) == "MZ")
# Parse the PE header of the kernel.
pe_profile = self.session.LoadProfile("pe")
dos_header = pe_profile._IMAGE_DOS_HEADER(
offset=kdbg.KernBase, vm=kdbg.obj_vm)
nt_header = dos_header.NTHeader
if nt_header:
yield ("Major (OptionalHeader)",
nt_header.OptionalHeader.MajorOperatingSystemVersion)
yield("Minor (OptionalHeader)",
nt_header.OptionalHeader.MinorOperatingSystemVersion)
# The CPU block.
for kpcr in kdbg.kpcrs():
yield "KPCR", "{0:#x} (CPU {1})".format(
kpcr.obj_offset, kpcr.ProcessorBlock.Number)
|