1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """This file adds pagefile support.
19
20 Although much of the address translation machinery occurs in hardware, when a
21 page fault occurs the operating system's pager is called. The pager is
22 responsible for faulting in invalid pages, and hence we need operating system
23 specific support.
24
25 Rekall's base paging address spaces emulate the hardware's MMU page translation,
26 but when the page is invalid Rekall emulates the operating system's page fault
27 handling code. The correct (OS dependent) address space is selected in
28 rekall.plugins.core.FindDTB.GetAddressSpaceImplementation() based on the profile
29 metadata.
30
31 This file implements the algorithms described in the paper:
32
33 Forensic Analysis of Windows User space Applications through Heap allocations.
34 Michael Cohen, 3rd IEEE International Workshop on Security and Forensics in
35 Communication Systems 2015 [1]
36
37 http://www.rekall-forensic.com/docs/References/Papers/p1138-cohen.pdf
38 """
39
40 __author__ = "Michael Cohen <scudette@google.com>"
41 import struct
42
43 from rekall import addrspace
44 from rekall import obj
45 from rekall.plugins.addrspaces import amd64
46 from rekall.plugins.addrspaces import intel
47 from rekall.plugins.windows import address_resolver
48 from rekall.plugins.windows import common
49 from rekall_lib import utils
55 def Wrapper(self, *args, **kwargs):
56 lock = "_lock" + func.__name__
57 if not getattr(self, lock, False):
58 try:
59 setattr(self, lock, True)
60 return func(self, *args, **kwargs)
61 except RuntimeError:
62 pass
63 finally:
64 setattr(self, lock, False)
65
66 return Wrapper
67
72 """Print the PTE in exploded view."""
73
74 default_pte_type = None
75 object_name = "pte"
76
77 - def __init__(self, pte_type=None, pte_value=None, pte_addr=None,
78 object_name=None, session=None):
90
101
105
109
113
117 """Describe a Demand Zero page."""
118
121
125 """A descriptor for Valid or in Transition PTEs."""
126
133
134
135 -class WindowsPagefileDescriptor(intel.AddressTranslationDescriptor):
136 """A descriptor to mark the final physical address resolution."""
137
138 - def __init__(self, address=0, pagefile_number=0, protection=0,
139 session=None):
140 super(WindowsPagefileDescriptor, self).__init__(session=session)
141 self.address = address
142 self.pagefile_number = pagefile_number
143 self.protection = protection
144
145 - def render(self, renderer):
146 renderer.format("Pagefile ({0}) @ {1:addr}\n",
147 self.pagefile_number, self.address)
148
151 """Describe a file mapping."""
152
153 - def __init__(self, pte_address=None, page_offset=0,
154 original_pte=None, **kwargs):
155 super(WindowsFileMappingDescriptor, self).__init__(
156 object_address=pte_address, **kwargs)
157 self.pte_address = pte_address
158 self.page_offset = page_offset
159 self.original_pte = original_pte
160
162 """Find the right subsection object for this pte."""
163
164
165 subsection_lookup = self.session.GetParameter(
166 "prototype_pte_array_subsection_lookup")
167
168 start, _, subsection_offset = subsection_lookup.get_containing_range(
169 self.pte_address)
170
171 if start:
172 return self.session.profile._SUBSECTION(subsection_offset)
173
174 if self.original_pte is not None:
175 return self.original_pte.u.Subsect.Subsection
176
178 """Return the filename of the file mapped (if it is a file mapping)."""
179 if subsection is None:
180 subsection = self.get_subsection()
181
182 if subsection:
183
184 ca = subsection.ControlArea
185
186
187 start = subsection.SubsectionBase.v()
188
189 if ca.u.Flags.File:
190 size_of_pte = self.session.profile.get_obj_size("_MMPTE")
191 mapped_offset_in_file = 0x1000 * (
192 self.pte_address - start) / size_of_pte + (
193 subsection.StartingSector * 512)
194
195 return (ca.FilePointer.file_name_with_drive(),
196 mapped_offset_in_file + self.page_offset)
197
198 return None, None
199
201 """Returns a list of _EPROCESS, virtual offsets for owners."""
202 result = []
203 if subsection is None:
204 subsection = self.get_subsection()
205
206 if subsection:
207 for details in self.session.GetParameter("subsections").get(
208 subsection.obj_offset, []):
209 task = self.session.profile._EPROCESS(details["task"])
210 vad = self.session.profile.Object(offset=details["vad"],
211 type_name=details["type"])
212
213
214 size_of_pte = self.session.profile.get_obj_size("_MMPTE")
215 relative_offset = (
216 self.pte_address - vad.FirstPrototypePte.v()) / size_of_pte
217
218 virtual_address = (
219 relative_offset * 0x1000 + vad.Start + self.page_offset)
220
221 result.append((task, virtual_address))
222
223 return result
224
240
243 """A descriptor for a subsection PTE."""
244
259
271
274 """A descriptor which applies specifically for Prototype PTEs from the VAD.
275
276 Windows uses placeholder values in the PTE to trigger a further resolution
277 of the PTE from the VAD. For example a PTE of 0xffffffff00000420 would
278 signal to consult the VAD for the real status of this PTE.
279 """
280
281 - def __init__(self, virtual_address=None, **kwargs):
282 """Define a windows PTE object.
283
284 Valid PTE types are all the members inside the _MMPTE
285 union. e.g. "Hard", "Transition", "Soft", etc).
286 """
287 super(VadPteDescriptor, self).__init__(pte_type="Soft", **kwargs)
288 self.virtual_address = virtual_address
289
320
323 """A descriptor for DTB values.
324
325 On windows the DTB holds a reference to the _EPROCESS that owns it. This
326 descriptor prints this information too.
327 """
328 object_name = "DTB"
329
333
338
347
350
351 """A mixin to implement windows specific paged memory address spaces.
352
353 This mixin allows us to share code between 32 and 64 bit implementations.
354 """
355
356 - def __init__(self, **kwargs):
357 super(WindowsPagedMemoryMixin, self).__init__(**kwargs)
358
359
360
361 self._resolve_vads = True
362 self._vad = None
363
364
365
366
367 pte = self.session.profile._MMPTE()
368 self.prototype_mask = pte.u.Proto.Prototype.mask
369 self.transition_mask = pte.u.Trans.Transition.mask
370 self.valid_mask = pte.u.Hard.Valid.mask
371 self.proto_protoaddress_mask = pte.u.Proto.ProtoAddress.mask
372 self.proto_protoaddress_start = pte.u.Proto.ProtoAddress.start_bit
373 self.soft_pagefilehigh_mask = pte.u.Soft.PageFileHigh.mask
374
375
376 self.proto_transition_mask = self.prototype_mask | self.transition_mask
377 self.proto_transition_valid_mask = (self.proto_transition_mask |
378 self.valid_mask)
379 self.transition_valid_mask = self.transition_mask | self.valid_mask
380 self.task = None
381
382 self.base_as_can_map_files = self.base.metadata("can_map_files")
383
384
385
386 self._resolving_pagefiles = False
387
388 @utils.safe_property
390 """Returns a cached RangedCollection() of vad ranges."""
391
392
393 if self.dtb == self.session.GetParameter("dtb"):
394 return
395
396
397 if self._vad is not None:
398 return self._vad
399
400
401 if not self._resolve_vads:
402 return obj.NoneObject("vads not available right now")
403
404 try:
405
406
407
408
409
410 self._resolve_vads = False
411
412
413 if self.task == None:
414
415
416 self.task = self.session.GetParameter("dtb2task").get(self.dtb)
417
418 self._vad = utils.RangedCollection()
419 task = self.session.profile._EPROCESS(self.task)
420 for vad in task.RealVadRoot.traverse():
421 self._vad.insert(vad.Start, vad.End, vad)
422
423 return self._vad
424 finally:
425 self._resolve_vads = True
426
427 - def _get_available_PTEs(self, pte_table, vaddr, start=0, end=2**64):
428 """Scan the PTE table and yield address ranges which are valid."""
429 tmp = vaddr
430 for i in xrange(0, len(pte_table)):
431 pfn = i << 12
432 pte_value = pte_table[i]
433
434 vaddr = tmp | pfn
435 if vaddr > end:
436 return
437
438 next_vaddr = tmp | ((i + 1) << 12)
439 if start >= next_vaddr:
440 continue
441
442
443
444
445 if self.vad:
446 start, _, _ = self.vad.get_containing_range(vaddr)
447 if pte_value == 0 and start is None:
448 continue
449
450 elif pte_value == 0:
451 continue
452
453 phys_addr = self._get_phys_addr_from_pte(vaddr, pte_value)
454
455
456
457 if phys_addr is not None:
458 yield addrspace.Run(start=vaddr,
459 end=vaddr + 0x1000,
460 file_offset=phys_addr,
461 address_space=self.base)
462
463 - def _get_phys_addr_from_pte(self, vaddr, pte_value):
464 """Gets the final physical address from the PTE value."""
465 collection = intel.PhysicalAddressDescriptorCollector(self.session)
466 self.describe_pte(collection, None, pte_value, vaddr)
467 return collection.physical_address
468
469 - def _describe_pde(self, collection, pde_addr, vaddr):
470 """Describe processing of the PDE.
471
472 The PDE is sometimes not present in main memory, we then implement most
473 of the algorithm described in Figure 2 of the paper (except for the
474 prototype state since the PDE can not use a prototype).
475 """
476 pde_value = self.read_pte(pde_addr)
477
478
479 if pde_value & self.transition_valid_mask:
480 collection.add(WindowsPDEDescriptor, pte_value=pde_value,
481 pte_addr=pde_addr)
482
483
484 if pde_value & self.valid_mask and pde_value & self.page_size_mask:
485 physical_address = ((pde_value & 0xfffffffe00000) |
486 (vaddr & 0x1fffff))
487 collection.add(intel.CommentDescriptor, "Large page mapped\n")
488
489 collection.add(
490 intel.PhysicalAddressDescriptor, address=physical_address)
491
492
493 else:
494 pte_addr = ((pde_value & 0xffffffffff000) |
495 ((vaddr & 0x1ff000) >> 9))
496 pte_value = self.read_pte(pte_addr)
497 self.describe_pte(collection, pte_addr, pte_value, vaddr)
498
499
500 elif pde_value & self.soft_pagefilehigh_mask:
501 collection.add(WindowsPDEDescriptor, pte_value=pde_value,
502 pte_addr=pde_addr, pte_type="Soft")
503
504 pde = self.session.profile._MMPTE()
505 pde.u.Long = pde_value
506
507
508 soft_pte = pde.u.Soft
509 pagefile_address = (soft_pte.PageFileHigh * 0x1000 +
510 ((vaddr & 0x1ff000) >> 9))
511
512 protection = soft_pte.Protection.v()
513 if protection == 0:
514 collection.add(intel.InvalidAddress, "Invalid Soft PTE")
515 else:
516 collection.add(WindowsPagefileDescriptor,
517 address=pagefile_address,
518 protection=protection,
519 pagefile_number=pde.u.Soft.PageFileLow.v())
520
521
522 pte_addr = self._get_pagefile_mapped_address(
523 soft_pte.PageFileLow.v(), pagefile_address)
524
525 if pte_addr is not None:
526 pte_value = self.read_pte(pte_addr)
527 self.describe_pte(collection, pte_addr, pte_value, vaddr)
528
529 else:
530 collection.add(DemandZeroDescriptor)
531
532 @Reentrant
533 - def _get_subsection_mapped_address(self, subsection_pte_address):
534 """Map the subsection into the physical address space.
535
536 Returns:
537 The offset in the physical AS where this subsection PTE is mapped to.
538 """
539 if self.base_as_can_map_files:
540 pte = self.session.profile._MMPTE(subsection_pte_address)
541 subsection = pte.u.Subsect.Subsection
542 subsection_base = subsection.SubsectionBase.v()
543
544 filename = subsection.ControlArea.FilePointer.file_name_with_drive()
545 if filename:
546
547
548
549
550
551
552
553 file_offset = (
554 (subsection_pte_address -
555 subsection_base) * 0x1000 / pte.obj_size +
556 subsection.StartingSector * 512)
557
558 return self.base.get_mapped_offset(filename, file_offset)
559
560 - def _get_pagefile_mapped_address(self, pagefile_number, pagefile_offset):
561 """Map the required pagefile into the physical AS.
562
563 Returns:
564 the mapped address of the required offset in the physical AS.
565 """
566 if self.base_as_can_map_files:
567
568
569 if (self._resolving_pagefiles and
570 not self.session.HasParameter("pagefiles")):
571 return
572
573
574 try:
575 self._resolving_pagefiles = True
576 pagefile_name, _ = self.session.GetParameter("pagefiles")[
577 pagefile_number]
578 except (KeyError, ValueError):
579 return
580
581 except RuntimeError:
582
583
584 pagefile_name = ur"c:\pagefile.sys"
585
586 finally:
587 self._resolving_pagefiles = False
588
589
590 return self.base.get_mapped_offset(pagefile_name, pagefile_offset)
591
592 - def _describe_vad_pte(self, collection, pte_addr, pte_value, vaddr):
593 if self.vad:
594 collection.add(intel.CommentDescriptor, "Consulting Vad: ")
595 start, _, mmvad = self.vad.get_containing_range(vaddr)
596 if start is not None:
597
598 if "FirstPrototypePte" in mmvad.members:
599 pte = mmvad.FirstPrototypePte[(vaddr - start) >> 12]
600 collection.add(VadPteDescriptor,
601 pte_value=pte_value, pte_addr=pte_addr,
602 virtual_address=vaddr)
603
604 self.describe_proto_pte(
605 collection, pte.obj_offset, pte.u.Long.v(), vaddr)
606
607 return
608
609 else:
610 collection.add(intel.CommentDescriptor,
611 "Vad type {0}\n", mmvad.Tag)
612
613
614 collection.add(DemandZeroDescriptor)
615
616 - def ResolveProtoPTE(self, pte_value, vaddr):
617 collection = intel.PhysicalAddressDescriptorCollector(self.session)
618 self.describe_proto_pte(collection, 0, pte_value, vaddr)
619
620 return collection.physical_address
621
622 - def describe_proto_pte(self, collection, pte_addr, pte_value, vaddr):
623 """Describe the analysis of the prototype PTE.
624
625 This essentially explains how we utilize the flow chart presented in [1]
626 Figure 3.
627
628 NOTE: pte_addr is given here in the kernel's Virtual Address Space since
629 prototype PTEs are always allocated from pool.
630 """
631 if pte_value & self.transition_valid_mask:
632 physical_address = (pte_value & 0xffffffffff000) | (vaddr & 0xfff)
633 collection.add(WindowsValidPTEDescriptor,
634 pte_value=pte_value, pte_addr=self.vtop(pte_addr))
635
636 collection.add(intel.PhysicalAddressDescriptor,
637 address=physical_address)
638
639
640 elif pte_value & self.prototype_mask:
641 collection.add(WindowsSubsectionPTEDescriptor,
642 pte_value=pte_value, pte_addr=pte_addr)
643
644
645 file_mapping = self._get_subsection_mapped_address(pte_addr)
646 if file_mapping is not None:
647
648 file_mapping += vaddr & 0xFFF
649 collection.add(intel.PhysicalAddressDescriptor,
650 address=file_mapping)
651
652
653 elif pte_value & self.soft_pagefilehigh_mask:
654 pte = self.session.profile._MMPTE()
655 pte.u.Long = pte_value
656
657
658 soft_pte = pte.u.Soft
659 pagefile_address = soft_pte.PageFileHigh * 0x1000 + (vaddr & 0xFFF)
660 protection = soft_pte.Protection.v()
661
662 if protection == 0:
663 collection.add(intel.InvalidAddress, "Invalid Soft PTE")
664 return
665
666 collection.add(WindowsPTEDescriptor,
667 pte_type="Soft", pte_value=pte_value,
668 pte_addr=pte_addr)
669
670 collection.add(WindowsPagefileDescriptor,
671 address=pagefile_address,
672 protection=protection,
673 pagefile_number=pte.u.Soft.PageFileLow.v())
674
675
676 physical_address = self._get_pagefile_mapped_address(
677 soft_pte.PageFileLow.v(), pagefile_address)
678
679 if physical_address is not None:
680 collection.add(
681 intel.PhysicalAddressDescriptor, address=physical_address)
682
683 else:
684 collection.add(DemandZeroDescriptor)
685
686 - def describe_pte(self, collection, pte_addr, pte_value, vaddr):
687 """Describe the initial analysis of the PTE.
688
689 This essentially explains how we utilize the flow chart presented in [1]
690 Figure 2.
691 """
692 if pte_value & self.transition_valid_mask:
693 physical_address = (pte_value & 0xffffffffff000) | (vaddr & 0xfff)
694 collection.add(WindowsValidPTEDescriptor,
695 pte_value=pte_value, pte_addr=pte_addr)
696
697 collection.add(intel.PhysicalAddressDescriptor,
698 address=physical_address)
699
700
701
702
703 elif pte_value & self.prototype_mask:
704 if ((self.proto_protoaddress_mask & pte_value) >>
705 self.proto_protoaddress_start in (0xffffffff0000,
706 0xffffffff)):
707
708 collection.add(WindowsSoftwarePTEDescriptor,
709 pte_value=pte_value, pte_addr=pte_addr)
710
711 self._describe_vad_pte(collection, pte_addr, pte_value, vaddr)
712
713 else:
714 collection.add(WindowsProtoTypePTEDescriptor,
715 pte_value=pte_value, pte_addr=pte_addr)
716
717
718
719
720 pte_addr = pte_value >> self.proto_protoaddress_start
721 pte_value = struct.unpack("<Q", self.read(pte_addr, 8))[0]
722
723 self.describe_proto_pte(collection, pte_addr, pte_value, vaddr)
724
725
726 elif pte_value & self.soft_pagefilehigh_mask == 0:
727 collection.add(WindowsSoftwarePTEDescriptor,
728 pte_value=pte_value, pte_addr=pte_addr)
729
730 self._describe_vad_pte(collection, pte_addr, pte_value, vaddr)
731
732
733 elif (pte_value >> 12) == 0:
734 collection.add(DemandZeroDescriptor)
735
736
737 elif pte_value & self.soft_pagefilehigh_mask:
738 pte = self.session.profile._MMPTE()
739 pte.u.Long = pte_value
740
741
742 soft_pte = pte.u.Soft
743 pagefile_address = soft_pte.PageFileHigh * 0x1000 + (vaddr & 0xFFF)
744 protection = soft_pte.Protection.v()
745 if protection == 0:
746 collection.add(intel.InvalidAddress, "Invalid Soft PTE")
747 return
748
749 collection.add(WindowsPTEDescriptor,
750 pte_type="Soft", pte_value=pte_value,
751 pte_addr=pte_addr)
752
753 collection.add(WindowsPagefileDescriptor,
754 address=pagefile_address,
755 protection=soft_pte.Protection,
756 pagefile_number=pte.u.Soft.PageFileLow.v())
757
758 physical_address = self._get_pagefile_mapped_address(
759 soft_pte.PageFileLow.v(), pagefile_address)
760
761
762 if physical_address is not None:
763 collection.add(
764 intel.PhysicalAddressDescriptor, address=physical_address)
765
766 else:
767
768 collection.add(intel.AddressTranslationDescriptor,
769 object_name="pte", object_value=pte_value,
770 object_address=pte_addr)
771
772 collection.add(intel.CommentDescriptor, "Error! Unknown PTE\n")
773
774 - def _get_pte_addr(self, vaddr, pde_value):
775 if pde_value & self.transition_valid_mask:
776 return (pde_value & 0xffffffffff000) | ((vaddr & 0x1ff000) >> 9)
777
778 if pde_value & self.soft_pagefilehigh_mask:
779 pde = self.session.profile._MMPTE()
780 pde.u.Long = pde_value
781 soft_pte = pde.u.Soft
782
783
784 pagefile_address = (soft_pte.PageFileHigh * 0x1000 +
785 ((vaddr & 0x1ff000) >> 9))
786
787 physical_address = self._get_pagefile_mapped_address(
788 soft_pte.PageFileLow.v(), pagefile_address)
789
790 return physical_address
791
792
793 -class WindowsIA32PagedMemoryPae(WindowsPagedMemoryMixin,
794 intel.IA32PagedMemoryPae):
795 """A Windows specific IA32PagedMemoryPae."""
796
797 __pae = True
798
799
800 -class WindowsAMD64PagedMemory(WindowsPagedMemoryMixin,
801 amd64.AMD64PagedMemory):
802 """A windows specific AMD64PagedMemory.
803
804 Implements support for reading the pagefile if the base address space
805 contains a pagefile.
806 """
807
808
809 -class Pagefiles(common.WindowsCommandPlugin):
810 """Report all the active pagefiles."""
811
812 name = "pagefiles"
813
814 table_header = [
815 dict(name='_MMPAGING_FILE', style="address"),
816 dict(name='number', align="r", width=3),
817 dict(name='size', align="r", width=10),
818 dict(name='filename', width=20),
819 ]
820
822 for pf_num, (pf_name, pf) in self.session.GetParameter(
823 "pagefiles").items():
824 pf = self.profile._MMPAGING_FILE(pf)
825 yield (pf, pf_num, pf.Size * 0x1000, pf_name)
826
827
828 -class PagefileHook(common.AbstractWindowsParameterHook):
829 """Map pagefile number to the filename."""
830
831 name = "pagefiles"
832
833 - def calculate(self):
834 result = {}
835 pagingfiles = self.session.profile.get_constant_object(
836 'MmPagingFile',
837 target='Array', target_args=dict(
838 target='Pointer',
839 count=16,
840 target_args=dict(
841 target='_MMPAGING_FILE'
842 )
843 )
844 )
845
846
847 if pagingfiles == None:
848 mistate = self.session.address_resolver.get_constant_object(
849 "nt!MiState", "_MI_SYSTEM_INFORMATION")
850
851 root = mistate.PagingIo.PageFileHead.Root
852 pagingfiles = root.traverse_as_type(
853 "_MMPAGING_FILE", "FileObjectNode")
854
855 for pf in pagingfiles:
856 if pf:
857 result[pf.PageFileNumber.v()] = (
858 pf.File.file_name_with_drive(), pf.v())
859
860 return result
861