1 """Implements scanners and plugins to find hypervisors in memory."""
2
3 from itertools import groupby
4 import struct
5
6 from rekall import plugin
7 from rekall import obj
8 from rekall import scan
9 from rekall import session as session_module
10 from rekall.plugins.addrspaces import amd64
11 from rekall.plugins.addrspaces import intel
12 from rekall.plugins.overlays import basic
13 from rekall_lib import utils
14
15
16 KNOWN_REVISION_IDS = {
17
18
19 0x01: "VMWARE_NESTED",
20
21 0x11e57ed0: "KVM_NESTED",
22
23 0xda0400: "XEN_NESTED",
24
25 0x0d: "PENRYN",
26 0x0e: "NEHALEM",
27 0x0f: "WESTMERE",
28 0x10: "SANDYBRIDGE",
29 0x12: "HASWELL",
30 }
31
32
33
34 KNOWN_ABORT_INDICATOR_CODES = {
35 '\x00\x00\x00\x00': "NO ABORT",
36 '\x05\x00\x00\x00': "MACHINE CHECK DURING VM EXIT",
37 '\x0d\x00\x00\x00': "TXT SHUTDOWN",
38 }
39
40
41 vmcs_overlay = {
42 'NEHALEM_VMCS' : [None, {
43 'IS_NESTED': lambda x: False,
44 }],
45 'SANDYBRIDGE_VMCS' : [None, {
46 'IS_NESTED': lambda x: False,
47 }],
48 'HASWELL_VMCS' : [None, {
49 'IS_NESTED': lambda x: False,
50 }],
51 'WESTMERE_VMCS' : [None, {
52 'IS_NESTED': lambda x: False,
53 }],
54 'PENRYN_VMCS' : [None, {
55 'IS_NESTED': lambda x: False,
56 }],
57 'VMWARE_NESTED_VMCS' : [None, {
58 'IS_NESTED': lambda x: True,
59 }],
60 'KVM_NESTED_VMCS' : [None, {
61 'IS_NESTED': lambda x: True,
62 }],
63 'XEN_NESTED_VMCS' : [None, {
64 'IS_NESTED': lambda x: True,
65 }],
66 }
67
68
69 -class Error(Exception):
71
75
78 """An attempt was done at comparing VMCS from different address spaces."""
79
82 """The provided VM is invalid."""
83
86 """Profile to parse hypervisor control structures.
87
88 We use the basic profile for 64 bit Linux systems to get the expected width
89 for each data type.
90 """
91
92 @classmethod
96
99 - def check(self, buffer_as, offset):
100
101
102
103
104
105
106
107 if buffer_as.read(offset+4, 4) not in KNOWN_ABORT_INDICATOR_CODES:
108 return False
109
110
111 (revision_id,) = struct.unpack_from("<I", buffer_as.read(offset, 4))
112 revision_id = revision_id & 0x7FFFFFFF
113
114
115 platform = KNOWN_REVISION_IDS.get(revision_id)
116 if platform is None:
117 return False
118
119 try:
120 vmcs_obj = self.profile.Object("%s_VMCS" % platform,
121 offset=offset,
122 vm=buffer_as)
123 except (AttributeError, TypeError):
124 return False
125
126
127 if not vmcs_obj.HOST_CR4 & 0x2000:
128 return False
129
130
131
132 if vmcs_obj.VMCS_LINK_PTR_FULL != 0xFFFFFFFFFFFFFFFF:
133 return False
134
135 return True
136
139 """Scans the memory attempting to find VMCS structures.
140
141 Uses the techniques discussed on "Hypervisor Memory Forensics"
142 (http://s3.eurecom.fr/docs/raid13_graziano.pdf) with slight changes
143 to identify VT-x hypervisors.
144 """
145
146 overlap = 0
147
148 checks = [["VMCSCheck", {}]]
149
153
154 - def scan(self, offset=0, end=None, **_):
165
166
167 - def skip(self, buffer_as, offset):
169
172 """Represents a virtual machine.
173
174 A virtual machine is made of VMCS. In Intel processors, each CPU that runs
175 a VM will have its own VMCS.
176 """
177
178 - def __init__(self, host_rip=None, ept=None, parent=None, name=None,
179 session=None):
180 self.ept = long(ept)
181 self.host_rip = long(host_rip)
182 self.parent = parent
183 self.name = name
184 self.base_session = session
185 self.vmcss = set()
186
187
188 self.vmcs_validation = dict()
189 self.virtual_machines = set()
190
191 @utils.safe_property
193 """A VM is valid if at least one of its VMCS is valid."""
194 if any([self.vmcs_validation.get(vmcs, False) for vmcs in self.vmcss]):
195 return True
196 return False
197
198 @utils.safe_property
200 """A VM is nested if it has a parent or all its VMCS are nested."""
201 return self.parent != None
202
203 @utils.safe_property
212
213 @utils.safe_property
215 """The number of virtual cores of this VM."""
216 valid_vmcss = filter(self.is_valid_vmcs, self.vmcss)
217
218 uniq_vpids = set([v.VPID for v in valid_vmcss])
219 if len(uniq_vpids) != 1:
220 return len(uniq_vpids)
221 else:
222 return len(valid_vmcss)
223
224 @utils.safe_property
226 """The architecture of the host that started this VM."""
227 all_host_as = set([self.get_vmcs_host_as_type(v) for v in self.vmcss
228 if self.is_valid_vmcs(v)])
229 if len(all_host_as) == 1:
230 return all_host_as.pop()
231 return "???"
232
233 @utils.safe_property
235 """The architecture of the guest OS of the VM."""
236 all_guest_as = set([self.get_vmcs_guest_as_type(v) for v in self.vmcss
237 if self.is_valid_vmcs(v)])
238 if len(all_guest_as) == 1:
239 return all_guest_as.pop()
240 return "???"
241
242 @utils.safe_property
255
256 @utils.safe_property
267
268
269 @classmethod
271 """Returns the address space type of the guest of a VMCS.
272
273 One of I386, I386+PAE, AMD64 or None.
274 """
275 if not vmcs.GUEST_CR4 & (1 << 5):
276
277 return "I386"
278 elif not vmcs.ENTRY_CONTROLS & (1 << 9):
279
280 return "I386+PAE"
281 elif vmcs.ENTRY_CONTROLS & (1 << 9):
282
283 return "AMD64"
284 else:
285
286 return None
287
288 @classmethod
290 """Returns the address space type of the host of a VMCS.
291
292 One of I386, I386+PAE, AMD64 or None.
293 """
294 if not vmcs.HOST_CR4 & (1 << 5):
295
296 return "I386"
297 elif not vmcs.EXIT_CONTROLS & (1 << 9):
298
299 return "I386+PAE"
300 elif vmcs.EXIT_CONTROLS & (1 << 9):
301
302 return "AMD64"
303 else:
304
305 return None
306
307 @classmethod
309 """Returns the address_space of the host of the VMCS."""
310 return cls.get_vmcs_address_space(vmcs, host=True, base_as=base_as)
311
312 @classmethod
314 """Returns the address_space of the guest of the VMCS."""
315 return cls.get_vmcs_address_space(vmcs, host=False, base_as=base_as)
316
317 @classmethod
344
345 - def add_vmcs(self, vmcs, validate=True):
346 """Add a VMCS to this virtual machine.
347
348 Raises:
349 UnrelatedVmcsError if the VMCS doesn't match the VM's HOST_RIP or EPT.
350 """
351 if self.host_rip == None:
352 self.host_rip = long(vmcs.HOST_RIP)
353
354 if self.ept == None:
355 self.ept = long(vmcs.m("EPT_POINTER_FULL"))
356
357 if self.host_rip != vmcs.HOST_RIP:
358 raise UnrelatedVmcsError("VMCS HOST_RIP differ from the VM's")
359
360 if vmcs.m("EPT_POINTER_FULL") != self.ept:
361 raise UnrelatedVmcsError("VMCS EPT differs from the VM's")
362
363 if validate:
364 self.validate_vmcs(vmcs)
365
366 self.vmcss.add(vmcs)
367
369 """Sets the parent of this VM and resets the validation cache."""
370 if self.parent != parent:
371 self.parent = parent
372 self.vmcs_validation.clear()
373
376
378 """Validates a VMCS and returns if it's valid in this VM's context.
379
380 A VMCS is valid if the page where it's mapped is found in the HOST_CR3
381 that it points to. The result of this validation is cached. Use
382 the _reset_validation_state method if you need to invalidate cache
383 entries.
384
385 A VMCS object will only validate properly if its defined in the context
386 of the address space of the physical AS of the parent of the VM.
387 """
388 if vmcs in self.vmcs_validation:
389 return self.vmcs_validation.get(vmcs)
390
391 validated = False
392
393
394
395
396
397
398
399
400 page_walk_length = (vmcs.EPT_POINTER_FULL & 0b111000) >> 3
401
402 if (vmcs.EPT_POINTER_FULL & 0b111111000000 or
403 page_walk_length != 3):
404 self.vmcs_validation[vmcs] = validated
405 return validated
406
407
408
409 try:
410 validation_as = self.get_vmcs_host_address_space(vmcs)
411 except TypeError:
412 return False
413
414 for run in validation_as.get_mappings():
415 if self.base_session:
416 self.base_session.report_progress(
417 "Validating VMCS %08X @ %08X" % (
418 vmcs.obj_offset, run.start))
419 if (vmcs.obj_offset >= run.file_offset and
420 vmcs.obj_offset < run.file_offset + run.length):
421 validated = True
422 break
423
424 self.vmcs_validation[vmcs] = validated
425 return validated
426
428 """Returns whether the vmcs is valid or None if it wasn't validated.
429
430 Doesn't force validation.
431 """
432 return self.vmcs_validation.get(vmcs)
433
435 """Returns a session valid for this VM."""
436
437 if not self.is_valid:
438 raise InvalidVM()
439
440 session_override = {
441 "ept": self.ept_list,
442 "profile": None,
443 "session_name": u"VM %s" % u','.join(
444 [u'0x%X' % s for s in self.ept_list]),
445 }
446
447 return self.base_session.clone(**session_override)
448
449 - def RunPlugin(self, plugin_name, *args, **kwargs):
453
455 """Tries to add the list of VMs as nested VMs of this one.
456
457 To validate nested VMs, we need to see if its identifying VMCS are
458 mapped in our physical AS and then try to validate them via HOST_CR3
459 in our context.
460 """
461 _ = validate_all
462 if not vm_list:
463 return
464
465
466
467 phys_as = self.physical_address_space
468 for run in phys_as.get_mappings():
469 for vm in vm_list:
470 if self.base_session:
471 self.base_session.report_progress(
472 u"Validating VM(%X) > VM(%X) @ %#X",
473 self.ept, vm.ept, run.file_offset)
474
475 for vmcs in vm.vmcss:
476
477 if vm.is_valid_vmcs(vmcs):
478 continue
479
480
481
482 if (run.file_offset <= vmcs.obj_offset and
483 vmcs.obj_offset < run.file_offset + run.length):
484
485
486 vm.set_parent(self)
487 vmcs_stored_vm = vmcs.obj_vm
488 vmcs_stored_offset = vmcs.obj_offset
489
490 vmcs.obj_vm = self.physical_address_space
491
492
493
494
495
496
497
498
499 vmcs.obj_offset = (run.start +
500 (run.file_offset - vmcs.obj_offset))
501 if vm.validate_vmcs(vmcs):
502
503
504 self.virtual_machines.update([vm])
505 else:
506
507 vmcs.obj_vm = vmcs_stored_vm
508 vmcs.obj_offset = vmcs_stored_offset
509
510
511 for vm in self.virtual_machines:
512 try:
513 vm_list.remove(vm)
514 except ValueError:
515 pass
516
518 """Invalidates the vmcs validation cache entry for vmcs."""
519 self.vmcs_validation.pop(vmcs, None)
520
522 return "VirtualMachine(Hypervisor=%#X, EPT=%#X)" % (
523 self.host_rip, self.ept)
524
525
526 -class VmScan(plugin.PhysicalASMixin,
527 plugin.TypedProfileCommand, plugin.Command):
528 """Scan the physical memory attempting to find hypervisors.
529
530 Once EPT values are found, you can use them to inspect virtual machines
531 with any of the rekall modules by using the --ept parameter and
532 specifying the guest virtual machine profile.
533
534 Supports the detection of the following virtualization techonlogies:
535 * Intel VT-X with EPT. Microarchitectures:
536 + Westmere
537 + Nehalem
538 + Sandybridge
539 + Ivy Bridge
540 + Haswell
541
542 * Intel VT-X without EPT (unsupported page translation in rekall).
543 + Penryn
544
545 For the specific processor models that support EPT, please check:
546 http://ark.intel.com/products/virtualizationtechnology.
547 """
548 __name = "vmscan"
549
550 __args = [
551 dict(name="quick", type="Boolean",
552 help="Perform quick VM detection."),
553
554 dict(name="no_nested", type="Boolean",
555 help="Don't do nested VM detection."),
556
557 dict(name="offset", type="IntParser", default=0,
558 help="Offset in the physical image to start the scan."),
559
560 dict(name="show_all", default=False, type="Boolean",
561 help="Also show VMs that failed validation."),
562
563 dict(name="image_is_guest", default=False, type="Boolean",
564 help="The image is for a guest VM, not the host."),
565
566 dict(name="no_validation", default=False, type="Boolean",
567 help="[DEBUG SETTING] Disable validation of VMs.")
568 ]
569
574
576 """Finds virtual machines in physical memory and returns a list of them.
577 """
578
579 all_vmcs = VMCSScanner(
580 address_space=self.physical_address_space,
581 session=self.session,
582 profile=obj.NoneObject).scan(
583 offset=self.plugin_args.offset,
584 end=self.physical_address_space.end())
585
586 host_vms = []
587 nested_vms = []
588
589
590
591
592
593
594
595
596 for host_rip, rip_vmcs_list in groupby(
597 sorted(all_vmcs, key=lambda x: long(x.HOST_RIP)),
598 lambda x: long(x.HOST_RIP)):
599
600 sorted_rip_vmcs_list = sorted(
601 rip_vmcs_list, key=lambda x: long(x.m("EPT_POINTER_FULL")))
602
603 for ept, rip_ept_vmcs_list in groupby(
604 sorted_rip_vmcs_list,
605 lambda x: long(x.m("EPT_POINTER_FULL"))):
606
607 vm = VirtualMachine(host_rip=host_rip, ept=ept,
608 session=self.session)
609 for vmcs in rip_ept_vmcs_list:
610 try:
611
612
613
614
615
616 if vmcs.IS_NESTED:
617 if (self.plugin_args.image_is_guest or
618 self.physical_address_space.metadata(
619 "ept")):
620 vm.add_vmcs(
621 vmcs,
622 validate=not self.plugin_args.no_validate)
623 else:
624 vm.add_vmcs(vmcs, validate=False)
625 else:
626 vm.add_vmcs(
627 vmcs,
628 validate=not self.plugin_args.no_validate)
629
630 if vm.is_valid_vmcs(vmcs):
631 if self.plugin_args.quick:
632 break
633 except UnrelatedVmcsError:
634
635
636
637
638
639 continue
640
641
642 if not vm.vmcss:
643 continue
644
645
646
647
648
649
650 may_be_nested = all([v.IS_NESTED for v in vm.vmcss])
651 if may_be_nested and not vm.is_valid:
652
653
654
655 nested_vms.append(vm)
656 else:
657 host_vms.append(vm)
658
659 if self.plugin_args.no_nested:
660 return host_vms
661
662
663
664
665
666
667
668
669 if not self.plugin_args.no_validate:
670 candidate_hosts = [vm for vm in host_vms if vm.is_valid]
671 else:
672 candidate_hosts = []
673
674
675
676 for candidate_host_vm in candidate_hosts:
677 candidate_host_vm.add_nested_vms(
678 nested_vms, validate_all=not self.plugin_args.quick)
679
680
681
682 host_vms.extend(nested_vms)
683 return host_vms
684
685
686 - def render(self, renderer=None):
687 renderer.table_header([
688 dict(name="Description", type="TreeNode", max_depth=5, child=dict(
689 type="VirtualizationNode", style="light",
690 quick=self.plugin_args.quick)),
691 ("Type", "type", ">20s"),
692 ("Valid", "valid", ">8s"),
693 ("EPT", "ept", "s")
694 ])
695
696 virtual_machines = self.get_vms()
697
698
699 for vm in virtual_machines:
700
701 if not self.plugin_args.show_all and not vm.is_valid:
702 continue
703 self.render_vm(renderer, vm, indent_level=0)
704
705 if self.plugin_args.verbosity > 2:
706 for vm in virtual_machines:
707 for vmcs in vm.vmcss:
708 if (not self.plugin_args.show_all and
709 not vm.is_valid_vmcs(vmcs)):
710 continue
711 renderer.section("VMCS @ %#x" % vmcs.obj_offset)
712 renderer.table_header([("Details", "details", "s")])
713 self.session.plugins.p(vmcs).render(renderer)
714
715 for nested_vm in vm.virtual_machines:
716 for vmcs in nested_vm.vmcss:
717 if (not self.plugin_args.show_all and
718 not vm.is_valid_vmcs(vmcs)):
719 continue
720 renderer.section("VMCS @ %#x" % vmcs.obj_offset)
721 renderer.table_header([("Details", "details", "s")])
722 self.session.plugins.p(vmcs).render(renderer)
723
724 - def render_vm(self, renderer, vm, indent_level=0):
725 vm_ept = ','.join(["0x%X" % e for e in vm.ept_list])
726 renderer.table_row(vm, 'VM', vm.is_valid, vm_ept, depth=indent_level)
727
728 if vm.is_valid and isinstance(
729 self.session, session_module.InteractiveSession):
730 self.session.session_list.append(vm.GetSession())
731
732 if self.plugin_args.verbosity > 1:
733 for vmcs in sorted(vm.vmcss,
734 key=lambda x: x.m("VPID")):
735 if not self.plugin_args.show_all and not vm.is_valid_vmcs(vmcs):
736 continue
737
738 valid = vm.is_valid_vmcs(vmcs)
739 renderer.table_row(
740 vmcs,
741 vmcs.obj_name, valid, '', depth=indent_level+1)
742
743 for nested_vm in vm.virtual_machines:
744 if not self.plugin_args.show_all and not nested_vm.is_valid:
745 continue
746 self.render_vm(renderer, nested_vm, indent_level=indent_level+1)
747