Package rekall :: Package plugins :: Package windows :: Module pfn
[frames] | no frames]

Source Code for Module rekall.plugins.windows.pfn

  1  # Rekall Memory Forensics 
  2  # 
  3  # Copyright 2013 Google Inc. All Rights Reserved. 
  4  # 
  5  # Authors: 
  6  # Michael Cohen <scudette@gmail.com> 
  7  # 
  8  # This program is free software; you can redistribute it and/or modify 
  9  # it under the terms of the GNU General Public License as published by 
 10  # the Free Software Foundation; either version 2 of the License, or (at 
 11  # your option) any later version. 
 12  # 
 13  # This program is distributed in the hope that it will be useful, but 
 14  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 16  # General Public License for more details. 
 17  # 
 18  # You should have received a copy of the GNU General Public License 
 19  # along with this program; if not, write to the Free Software 
 20  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 21  # 
 22   
 23  # References: 
 24  # http://www.codemachine.com/article_kernelstruct.html#MMPFN 
 25  # http://www.reactos.org/wiki/Techwiki:Memory_management_in_the_Windows_XP_kernel#MmPfnDatabase 
 26   
 27  # pylint: disable=protected-access 
 28  import re 
 29  import StringIO 
 30   
 31  from rekall import kb 
 32  from rekall import testlib 
 33  from rekall import plugin 
 34  from rekall.ui import text 
 35  from rekall.plugins import core 
 36  from rekall.plugins.addrspaces import intel 
 37  from rekall.plugins.windows import common 
 38  from rekall.plugins.windows import pagefile 
 39  from rekall_lib import utils 
 40   
 41   
42 -class VtoP(core.VtoPMixin, common.WinProcessFilter):
43 """Prints information about the virtual to physical translation."""
44 45
46 -class PFNInfo(common.WindowsCommandPlugin):
47 """Prints information about an address from the PFN database.""" 48 49 __name = "pfn" 50 51 # Size of page. 52 PAGE_SIZE = 0x1000 53 PAGE_BITS = 12 54 55 __args = [ 56 dict(name="pfn", type="IntParser", positional=True, required=True, 57 help="The PFN to examine.") 58 ] 59 60 table_header = [ 61 dict(name="fact", width=25), 62 dict(name="Address", style="address"), 63 dict(name="Value"), 64 ] 65 66
67 - def collect(self):
68 pfn_obj = self.profile.get_constant_object("MmPfnDatabase")[ 69 self.plugin_args.pfn] 70 71 yield "PFN", self.plugin_args.pfn 72 yield "PFN Record VA", pfn_obj.obj_offset 73 74 yield "Type", None, pfn_obj.Type 75 76 # In these states the other fields are meaningless. 77 if pfn_obj.Type in ("Zeroed", "Freed", "Bad"): 78 yield "Flink", pfn_obj.u1.Flink 79 yield "Blink", pfn_obj.u2.Blink 80 81 return 82 83 # The flags we are going to print. 84 flags = ["Modified", 85 "ParityError", 86 "ReadInProgress", 87 "WriteInProgress"] 88 89 long_flags_string = " ".join( 90 [v for v in flags if pfn_obj.u3.e1.m(v) == 0]) 91 92 yield "Flags", None, long_flags_string 93 94 containing_page = int(pfn_obj.u4.PteFrame) 95 pte_physical_address = ((containing_page << self.PAGE_BITS) | 96 (int(pfn_obj.PteAddress) & 0xFFF)) 97 98 yield "Reference", None, pfn_obj.u3.e2.ReferenceCount 99 yield "ShareCount", None, pfn_obj.u2.ShareCount 100 yield "Color", None, pfn_obj.multi_m("u3.e1.PageColor", "u4.PageColor") 101 102 yield "Controlling PTE (VA)", pfn_obj.PteAddress 103 yield "Controlling PTE (PA)", pte_physical_address 104 yield ("Controlling PTE Type", None, 105 "Prototype" if pfn_obj.IsPrototype else "Hardware") 106 107 # PFN is actually a DTB. 108 if containing_page == self.plugin_args.pfn: 109 owning_process = pfn_obj.u1.Flink.cast( 110 "Pointer", target="_EPROCESS") 111 112 yield "Owning process", owning_process 113 114 # Now describe the PTE and Prototype PTE pointed to by this PFN entry. 115 collection = intel.DescriptorCollection(self.session) 116 self.session.kernel_address_space.describe_pte( 117 collection, pfn_obj.PteAddress, 118 pfn_obj.PteAddress.Long, 0) 119 120 yield "Controlling PTE", None, collection 121 122 if pfn_obj.OriginalPte: 123 collection = intel.DescriptorCollection(self.session) 124 self.session.kernel_address_space.describe_proto_pte( 125 collection, pfn_obj.OriginalPte.v(), 126 pfn_obj.OriginalPte.Long, 0) 127 128 yield "Original PTE", None, collection
129 130
131 -class PtoV(common.WinProcessFilter):
132 """Converts a physical address to a virtual address.""" 133 134 __name = "ptov" 135 136 PAGE_SIZE = 0x1000 137 PAGE_BITS = 12 138 139 __args = [ 140 dict(name="physical_address", type="IntParser", positional=True, 141 help="The Virtual Address to examine.") 142 ] 143
144 - def __init__(self, *args, **kwargs):
145 super(PtoV, self).__init__(*args, **kwargs) 146 147 if self.profile.metadata("arch") == "I386": 148 if self.profile.metadata("pae"): 149 self.table_names = ["Phys", "PTE", "PDE", "DTB"] 150 self.bit_divisions = [12, 9, 9, 2] 151 152 else: 153 self.table_names = ["Phys", "PTE", "PDE", "DTB"] 154 self.bit_divisions = [12, 10, 10] 155 156 elif self.profile.metadata("arch") == "AMD64": 157 self.table_names = ["Phys", "PTE", "PDE", "PDPTE", "PML4E", "DTB"] 158 self.bit_divisions = [12, 9, 9, 9, 9, 4] 159 160 else: 161 raise plugin.PluginError("Memory model not supported.")
162
163 - def ptov(self, collection, physical_address):
164 pfn_obj = self.profile.get_constant_object("MmPfnDatabase")[ 165 physical_address >> self.PAGE_BITS] 166 167 # The PFN points at a prototype PTE. 168 if pfn_obj.IsPrototype: 169 collection.add(pagefile.WindowsFileMappingDescriptor, 170 pte_address=pfn_obj.PteAddress.v(), 171 page_offset=physical_address & 0xFFF, 172 original_pte=pfn_obj.OriginalPte) 173 174 else: 175 # PTE is a system PTE, we can directly resolve the virtual address. 176 self._ptov_x64_hardware_PTE(collection, physical_address)
177
178 - def _ptov_x64_hardware_PTE(self, collection, physical_address):
179 """An implementation of ptov for x64.""" 180 pfn_database = self.session.profile.get_constant_object("MmPfnDatabase") 181 182 # A list of PTEs and their physical addresses. 183 physical_addresses = dict(Phys=physical_address) 184 185 # The physical and virtual address of the pte that controls the named 186 # member. 187 phys_addresses_of_pte = {} 188 ptes = {} 189 p_addr = physical_address 190 pfns = {} 191 192 # Starting with the physical address climb the PFN database in reverse 193 # to reach the DTB. At each page table entry we store the its physical 194 # offset. Then below we traverse the page tables in the forward order 195 # and add the bits into the virtual address. 196 for i, name in enumerate(self.table_names): 197 pfn = p_addr >> self.PAGE_BITS 198 pfns[name] = pfn_obj = pfn_database[pfn] 199 200 # The PTE which controls this pfn. 201 pte = pfn_obj.PteAddress 202 203 # PTE is not valid - this may be a large page. We dont currently 204 # know how to handle large pages. 205 #if not pte.u.Hard.Valid: 206 # return 207 208 if i > 0: 209 physical_addresses[name] = ptes[ 210 self.table_names[i-1]].obj_offset 211 212 # The physical address of the PTE. 213 p_addr = ((pfn_obj.u4.PteFrame << self.PAGE_BITS) | 214 (pte.v() & 0xFFF)) 215 216 phys_addresses_of_pte[name] = p_addr 217 218 # Hold on to the PTE in the physical AS. This is important as it 219 # ensures we can always access the correct PTE no matter the process 220 # context. 221 ptes[name] = self.session.profile._MMPTE( 222 p_addr, vm=self.session.physical_address_space) 223 224 self.session.logging.getChild("PageTranslation").debug( 225 "%s %#x is controlled by pte %#x (PFN %#x)", 226 name, physical_addresses[name], ptes[name], pfns[name]) 227 228 # The DTB must be page aligned. 229 dtb = p_addr & ~0xFFF 230 231 # Now we construct the virtual address by locating the offset in each 232 # page table where the PTE is and deducing the bits covered within that 233 # range. 234 virtual_address = 0 235 start_of_page_table = dtb 236 size_of_pte = self.session.profile._MMPTE().obj_size 237 238 for name, bit_division in reversed(zip( 239 self.table_names, self.bit_divisions)): 240 pte = ptes[name] 241 virtual_address += ( 242 ptes[name].obj_offset - start_of_page_table) / size_of_pte 243 244 virtual_address <<= bit_division 245 246 # The physical address where the page table begins. The next 247 # iteration will find the offset of the next higher up page table 248 # level in this table. 249 start_of_page_table = pte.u.Hard.PageFrameNumber << self.PAGE_BITS 250 251 if name == "Phys": 252 collection.add(intel.PhysicalAddressDescriptor, 253 address=physical_address) 254 255 elif name == "DTB": 256 # The DTB must be page aligned. 257 collection.add(pagefile.WindowsDTBDescriptor, 258 dtb=physical_addresses["DTB"] & ~0xFFF) 259 260 else: 261 collection.add(pagefile.WindowsPTEDescriptor, 262 object_name=name, pte_value=pte.Long, 263 pte_addr=pte.obj_offset, session=self.session) 264 265 virtual_address = self.session.profile.integer_to_address( 266 virtual_address) 267 virtual_address += physical_address & 0xFFF 268 269 collection.add(intel.VirtualAddressDescriptor, dtb=dtb, 270 address=virtual_address)
271
272 - def render(self, renderer):
273 if self.plugin_args.physical_address is None: 274 return 275 276 descriptors = intel.DescriptorCollection(self.session) 277 self.ptov(descriptors, self.plugin_args.physical_address) 278 279 for descriptor in descriptors: 280 descriptor.render(renderer)
281 282
283 -class WinRammap(common.WindowsCommandPlugin):
284 """Scan all physical memory and report page owners.""" 285 286 name = "rammap" 287 288 __args = [ 289 dict(name="start", type="IntParser", default=0, positional=True, 290 help="Physical memory address to start displaying."), 291 dict(name="end", type="IntParser", 292 help="Physical memory address to end displaying."), 293 ] 294 295 table_header = [ 296 dict(name="phys_offset", max_depth=1, 297 type="TreeNode", child=dict(style="address", align="l"), 298 width=16), 299 dict(name="List", width=10), 300 dict(name="Use", width=15), 301 dict(name="Pr", width=2), 302 dict(name="Process", type="_EPROCESS"), 303 dict(name="VA", style="address"), 304 dict(name="Offset", style="address"), 305 dict(name="Filename"), 306 ] 307
308 - def __init__(self, *args, **kwargs):
309 super(WinRammap, self).__init__(*args, **kwargs) 310 self.plugin_args.start &= ~0xFFF 311 self.ptov_plugin = self.session.plugins.ptov() 312 self.pfn_database = self.session.profile.get_constant_object( 313 "MmPfnDatabase") 314 self.pools = self.session.plugins.pools()
315
316 - def describe_phys_addr(self, phys_off):
317 pfn_obj = self.pfn_database[phys_off >> 12] 318 319 collection = intel.DescriptorCollection(self.session) 320 self.ptov_plugin.ptov(collection, phys_off) 321 result = dict(phys_offset=phys_off, 322 List=pfn_obj.Type, 323 Pr=pfn_obj.Priority) 324 325 # Go through different kinds of use and display them in the table. 326 descriptor = collection["VirtualAddressDescriptor"] 327 if descriptor: 328 dtb_descriptor = collection["WindowsDTBDescriptor"] 329 # Address is in kernel space. 330 if descriptor.address > self.session.GetParameter( 331 "highest_usermode_address"): 332 _, _, pool = self.pools.is_address_in_pool(descriptor.address) 333 if pool: 334 yield dict(Use=pool.PoolType, 335 VA=descriptor.address, **result) 336 else: 337 yield dict(Use="Kernel", 338 VA=descriptor.address, **result) 339 else: 340 yield dict(Use="Private", 341 Process=dtb_descriptor.owner(), 342 VA=descriptor.address, **result) 343 344 return 345 346 descriptor = collection["WindowsFileMappingDescriptor"] 347 if descriptor: 348 subsection = descriptor.get_subsection() 349 filename, file_offset = descriptor.filename_and_offset( 350 subsection=subsection) 351 352 # First show the owner that mapped the file. 353 virtual_address = None 354 355 depth = 0 356 # A real mapped file. 357 for process, virtual_address in descriptor.get_owners( 358 subsection=subsection): 359 360 yield dict(Use="Mapped File", 361 Filename=filename, 362 Offset=file_offset, 363 depth=depth, 364 Process=process, 365 VA=virtual_address, **result) 366 367 if self.plugin_args.verbosity <= 1: 368 return 369 370 # If the user wants more, also show the other processes which 371 # map this file. 372 depth = 1 373 374 # We could not find a process owner so we just omit it. 375 if depth == 0: 376 yield dict(Use="Mapped File", 377 Filename=filename, 378 Offset=file_offset, 379 **result) 380 return 381 382 if pfn_obj.u3.e2.ReferenceCount == 0: 383 result["Use"] = "Unused" 384 yield result 385 return 386 387 yield result
388
389 - def collect(self):
390 phys_off = self.plugin_args.start 391 392 end = self.plugin_args.end 393 if end is None or end < phys_off: 394 end = phys_off + 10 * 0x1000 395 396 for phys_off in utils.xrange(self.plugin_args.start, end, 0x1000): 397 for result in self.describe_phys_addr(phys_off): 398 yield result 399 400 # Re-run from here next invocation. 401 self.plugin_args.start = phys_off
402
403 - def summary(self):
404 """Return a multistring summary of the result.""" 405 # We just use the WideTextRenderer to render the records. 406 fd = StringIO.StringIO() 407 with text.WideTextRenderer(session=self.session, fd=fd) as renderer: 408 self.render(renderer) 409 410 return filter(None, 411 re.split(r"(^|\n)\*+\n", fd.getvalue(), re.S | re.M))
412 413
414 -class TestWinRammap(testlib.SimpleTestCase):
415 PARAMETERS = dict( 416 commandline="rammap %(start)s", 417 start=0x4d7000, 418 )
419 420
421 -class DTBScan(common.WinProcessFilter):
422 """Scans the physical memory for DTB values. 423 424 This plugin can compare the DTBs found against the list of known processes 425 to find hidden processes. 426 """ 427 428 __name = "dtbscan" 429 430 __args = [ 431 dict(name="limit", type="IntParser", default=2**64, 432 help="Stop scanning after this many mb.") 433 ] 434 435 table_header = [ 436 dict(name="DTB", style="address"), 437 dict(name="VA", style="address"), 438 dict(name="Owner", type="_EPROCESS"), 439 dict(name="Known", type="Bool"), 440 ] 441
442 - def collect(self):
443 ptov = self.session.plugins.ptov(session=self.session) 444 pslist = self.session.plugins.pslist(session=self.session) 445 pfn_database = self.session.profile.get_constant_object("MmPfnDatabase") 446 447 # Known tasks: 448 known_tasks = set() 449 for task in pslist.list_eprocess(): 450 known_tasks.add(task.obj_offset) 451 452 seen_dtbs = set() 453 454 # Now scan all the physical address space for DTBs. 455 for run in self.physical_address_space.get_mappings(): 456 for page in range(run.start, run.end, 0x1000): 457 self.session.report_progress("Scanning 0x%08X (%smb)" % ( 458 page, page/1024/1024)) 459 460 # Quit early if requested to. 461 if page > self.plugin_args.limit: 462 return 463 464 collection = intel.DescriptorCollection(self.session) 465 ptov.ptov(collection, page) 466 dtb_descriptor = collection["WindowsDTBDescriptor"] 467 468 if dtb_descriptor: 469 dtb = dtb_descriptor.dtb 470 if dtb not in seen_dtbs: 471 seen_dtbs.add(dtb) 472 473 pfn_obj = pfn_database[dtb >> 12] 474 475 # Report the VA of the DTB (Since DTBs contains 476 # themselves this will equal to the VA of the DTB. 477 va = pfn_obj.PteAddress.v() 478 task = dtb_descriptor.owner() 479 480 yield (dtb, va, task, 481 task.obj_offset in known_tasks)
482 483
484 -class TestDTBScan(testlib.SimpleTestCase):
485 PARAMETERS = dict( 486 commandline="dtbscan --limit 10mb", 487 )
488 489
490 -class WinSubsectionProducer(kb.ParameterHook):
491 """Produce all the subsection objects we know about. 492 493 Returns a dict keyed with subsection offsets with values being a details 494 dict. The details include the vad and the _EPROCESS address for this 495 process. 496 """ 497 name = "subsections" 498
499 - def calculate(self):
500 result = {} 501 for task in self.session.plugins.pslist().filter_processes(): 502 self.session.report_progress("Inspecting VAD for %s", task.name) 503 for vad in task.RealVadRoot.traverse(): 504 subsection_list = vad.multi_m( 505 "Subsection", "ControlArea.FirstSubsection") 506 for subsection in subsection_list.walk_list( 507 "NextSubsection", include_current=True): 508 record = result.setdefault(subsection.obj_offset, []) 509 record.append(dict(task=task.obj_offset, 510 vad=vad.obj_offset, 511 type=vad.obj_type)) 512 return result
513 514
515 -class WinPrototypePTEArray(kb.ParameterHook):
516 """A ranged collection for Prototype PTE arrays.""" 517 518 name = "prototype_pte_array_subsection_lookup" 519
520 - def calculate(self):
521 result = utils.RangedCollection() 522 for subsection_offset in self.session.GetParameter("subsections"): 523 subsection = self.session.profile._SUBSECTION(subsection_offset) 524 start = subsection.SubsectionBase.v() 525 526 # Pte Arrays are always allocated from kernel pools. 527 if start < self.session.GetParameter("highest_usermode_address"): 528 continue 529 530 end = start + (subsection.PtesInSubsection * 531 subsection.SubsectionBase[0].obj_size) 532 result.insert(start, end, subsection_offset) 533 534 return result
535