Package rekall :: Package plugins :: Package darwin :: Module common
[frames] | no frames]

Source Code for Module rekall.plugins.darwin.common

  1  # Rekall Memory Forensics 
  2  # 
  3  # Copyright 2013 Google Inc. All Rights Reserved. 
  4  # 
  5  # This program is free software; you can redistribute it and/or modify 
  6  # it under the terms of the GNU General Public License as published by 
  7  # the Free Software Foundation; either version 2 of the License, or (at 
  8  # your option) any later version. 
  9  # 
 10  # This program is distributed in the hope that it will be useful, but 
 11  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 13  # General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU General Public License 
 16  # along with this program; if not, write to the Free Software 
 17  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 18   
 19  # The code in this directory is based on original code and algorithms by Andrew 
 20  # Case (atcuno@gmail.com). 
 21  __author__ = "Michael Cohen <scudette@google.com>" 
 22   
 23  # Disabled because pylint is wrong about it pretty much all the time. 
 24  # pylint: disable=abstract-method 
 25   
 26   
 27  from rekall import kb 
 28  from rekall import plugin 
 29  from rekall import scan 
 30   
 31  from rekall.plugins import core 
 32  from rekall_lib import utils 
 33   
 34  # A few notes on XNU's (64bit) memory layout: 
 35  # 
 36  # Because of the way the Darwin kernel (XNU) is bootstrapped, a section of its 
 37  # virtual address space maps linearly to the base of the physical address space. 
 38  # This relationship is basically: 
 39  # KERNEL_MIN_ADDRESS + the_physical_address = the_virtual_address 
 40  # 
 41  # The kernel ensures this when allocating certain data structures, most notably 
 42  # the page tables [1]. However, the kernel doesn't actually "know" the value of 
 43  # KERNEL_MIN_ADDRESS, which is defined the Makefile [2]. Instead, allocations 
 44  # are done by keeping a cursor at the lowest available physical address [3]. 
 45  # 
 46  # Because of this, when the kernel needs to convert an address from the virtual 
 47  # address space to the physical address space without relying on the page 
 48  # tables, it uses a "safer" variation on the above rule and masks out the first 
 49  # 32 bits of the address using a macro called ID_MAP_VTOP [4,5], which is a 
 50  # simple bitmask (LOW_4GB_MASK [6]). 
 51  # 
 52  # We copy/adapt all three #defines below. When we need to bootstrap the virtual 
 53  # address space, relying on ID_MAP_VTOP is preferrable, because it's less 
 54  # fragile. However, KERNEL_MIN_ADDRESS can be a good heuristic for deciding 
 55  # whether a particular value is a valid pointer in the kernel virtual address 
 56  # space, so I decided to keep it around. 
 57  # 
 58  # [1] 
 59  # github.com/opensource-apple/xnu/blob/10.9/osfmk/i386/i386_init.c#L134 
 60  # 
 61  # [2] 
 62  # github.com/opensource-apple/xnu/blob/10.9/makedefs/MakeInc.def#L258 
 63  # 
 64  # [3] This is where physfree is defined as the next free page, after a blank 
 65  # page, after the last page of the kernel image as determined by the bootloader. 
 66  # github.com/opensource-apple/xnu/blob/10.9/osfmk/i386/i386_init.c#L330 
 67  # 
 68  # [4] 
 69  # github.com/opensource-apple/xnu/blob/10.9/osfmk/i386/pmap.h#L353 
 70  # 
 71  # [5] Example use, to set the physical address of the DTB when switching address 
 72  # spaces, knowing the virtual address of the first page table: 
 73  # github.com/opensource-apple/xnu/blob/10.9/osfmk/i386/pal_routines.c#L254 
 74  # 
 75  # [6] 
 76  # github.com/opensource-apple/xnu/blob/10.9/osfmk/i386/pmap.h#L119 
 77  LOW_4GB_MASK = 0x00000000ffffffff 
 78  KERNEL_MIN_ADDRESS = 0xffffff8000000000 
79 80 81 -def ID_MAP_VTOP(x):
82 return x & LOW_4GB_MASK
83 84 # On x64, only 48 bits of the pointer are addressable. 85 X64_POINTER_MASK = 0x0000ffffffffffff
86 87 88 -class DarwinOnlyMixin(object):
89 """Every Darwin-only plugin or hook will have this mixin in their MRO.""" 90 91 mode = "mode_darwin_memory"
92
93 94 -class AbstractDarwinParameterHook(DarwinOnlyMixin, kb.ParameterHook):
95 """Base class for session parameter hooks on Darwin.""" 96 __abstract = True
97
98 99 -class AbstractDarwinCommand(DarwinOnlyMixin, 100 plugin.KernelASMixin, 101 plugin.PhysicalASMixin, 102 plugin.TypedProfileCommand, 103 plugin.ProfileCommand):
104 """Base class for Darwin profile commands.""" 105 __abstract = True
106
107 108 -class AbstractDarwinProducer(plugin.Producer, 109 AbstractDarwinCommand):
110 """Base class for Darwin producers using the physical AS.""" 111 __abstract = True
112
113 114 -class AbstractDarwinCachedProducer(plugin.CachedProducer, 115 AbstractDarwinCommand):
116 """Base class for Darwin producers backed by a session param hook.""" 117 __abstract = True
118
119 120 -class KernelSlideHook(AbstractDarwinParameterHook):
121 """Find the kernel slide if needed.""" 122 123 name = "vm_kernel_slide" 124
125 - def calculate(self):
126 if self.session.GetParameter("mode_darwin_mountain_lion_plus"): 127 return DarwinFindKASLR(session=self.session).vm_kernel_slide() 128 129 # Kernel slide should be treated as 0 if not relevant. 130 return 0
131
132 133 -class CatfishScanner(scan.BaseScanner):
134 checks = [ 135 ("StringCheck", dict(needle="Catfish \x00\x00")) 136 ]
137
138 139 -class CatfishOffsetHook(AbstractDarwinParameterHook):
140 """Find the actual offset of the _lowGlo struct.""" 141 142 name = "catfish_offset" 143
144 - def calculate(self):
145 limit = self.session.GetParameter( 146 "autodetect_scan_length", 10*1024*1024*1024) 147 148 for hit in CatfishScanner( 149 address_space=self.session.physical_address_space, 150 session=self.session).scan(0, maxlen=limit): 151 return hit
152
153 154 -class DarwinKASLRMixin(object):
155 """Ensures that KASLR slide is computed and stored in the session.""" 156 157 @classmethod
158 - def args(cls, parser):
159 super(DarwinKASLRMixin, cls).args(parser) 160 161 parser.add_argument("--vm_kernel_slide", type="IntParser", 162 help="OS X 10.8 and later: kernel ASLR slide.")
163
164 - def __init__(self, vm_kernel_slide=None, **kwargs):
165 """A mixin for Darwin plugins that require a valid KASLR slide. 166 167 Args: 168 vm_kernel_slide: The integer KASLR slide used in this image. If not 169 given it will be computed. 170 """ 171 super(DarwinKASLRMixin, self).__init__(**kwargs) 172 173 if not self.session.GetParameter("mode_darwin_mountain_lion_plus"): 174 return 175 176 if vm_kernel_slide is not None: 177 self.session.SetCache("vm_kernel_slide", vm_kernel_slide)
178
179 180 -class DarwinFindKASLR(plugin.PhysicalASMixin, DarwinOnlyMixin, 181 plugin.ProfileCommand):
182 """A scanner for KASLR slide values in the Darwin kernel. 183 184 The scanner works by looking up a known data structure and comparing 185 its actual location to its expected location. Verification is a similar 186 process, using a second constant. This takes advantage of the fact that both 187 data structures are in a region of kernel memory that maps to the physical 188 memory in a predictable way (see ID_MAP_VTOP). 189 190 Human-readable output includes values of the kernel version string (which is 191 used for validation) for manual review, in case there are false positives. 192 """ 193 194 name = "find_kaslr" 195 mode = "mode_darwin_mountain_lion_plus" 196
197 - def all_catfish_hits(self):
198 """Yields possible lowGlo offsets, starting with session-cached one. 199 200 Because the first hit on the catfish string isn't necessarily the right 201 one, this function will yield subsequent ones by scanning the physical 202 address space, starting with the offset of the cached first hit. 203 204 The caller is responsible for updating the session cache with the 205 correct offset. 206 """ 207 first_hit = self.session.GetParameter("catfish_offset") 208 yield first_hit 209 210 maxlen = self.session.GetParameter("autodetect_scan_length") 211 for hit in CatfishScanner( 212 address_space=self.session.physical_address_space, 213 session=self.session).scan(offset=first_hit + 1, 214 maxlen=maxlen): 215 yield hit
216
217 - def vm_kernel_slide_hits(self):
218 """Tries to compute the KASLR slide. 219 220 In an ideal scenario, this should return exactly one valid result. 221 222 Yields: 223 (int) semi-validated KASLR value 224 """ 225 226 expected_offset = self.profile.get_constant( 227 "_lowGlo", is_address=False) 228 expected_offset = ID_MAP_VTOP(expected_offset) 229 230 for hit in self.all_catfish_hits(): 231 vm_kernel_slide = int(hit - expected_offset) 232 233 if self._validate_vm_kernel_slide(vm_kernel_slide): 234 self.session.SetCache("catfish_offset", hit) 235 yield vm_kernel_slide
236
237 - def vm_kernel_slide(self):
238 """Returns the first result of vm_kernel_slide hits and stops the scan. 239 240 This is the idiomatic way of using this plugin if all you need is the 241 likely KASLR slide value. 242 243 Returns: 244 A value for the KASLR slide that appears sane. 245 """ 246 self.session.logging.debug("Searching for KASLR hits.") 247 for vm_kernel_slide in self.vm_kernel_slide_hits(): 248 return vm_kernel_slide
249
250 - def _lookup_version_string(self, vm_kernel_slide):
251 """Uses vm_kernel_slide to look up kernel version string. 252 253 This is used for validation only. Physical address space is 254 asumed to map to kernel virtual address space as expressed by 255 ID_MAP_VTOP. 256 257 Args: 258 vm_kernel_slide: KASLR slide to be used for lookup. Overrides whatever 259 may already be set in session. 260 261 Returns: 262 Kernel version string (should start with "Darwin Kernel" 263 """ 264 version_offset = self.profile.get_constant( 265 "_version", is_address=False) 266 version_offset += vm_kernel_slide 267 version_offset = ID_MAP_VTOP(version_offset) 268 269 return self.profile.String(vm=self.physical_address_space, 270 offset=version_offset)
271
272 - def _validate_vm_kernel_slide(self, vm_kernel_slide):
273 """Checks sanity of vm_kernel_slide by looking up kernel version. 274 If the result a string that looks like the kernel version string the 275 slide value is assumed to be valid. Note that this can theoretically 276 give false positives. 277 278 Args: 279 vm_kernel_slide: KASLR slide to be used for validation. Overrides 280 whatever may already be set in session. 281 282 Returns: 283 True if vm_kernel_slide value appears sane. False otherwise. 284 """ 285 version_string = self._lookup_version_string(vm_kernel_slide) 286 return version_string[0:13] == "Darwin Kernel"
287
288 - def render(self, renderer):
289 renderer.table_header([ 290 ("KASLR Slide", "vm_kernel_slide", "[addrpad]"), 291 ("Kernel Version", "_version", "30"), 292 ]) 293 294 for vm_kernel_slide in self.vm_kernel_slide_hits(): 295 renderer.table_row(vm_kernel_slide, 296 self._lookup_version_string(vm_kernel_slide))
297
298 299 -class DarwinFindDTB(DarwinKASLRMixin, DarwinOnlyMixin, core.FindDTB):
300 """Tries to find the DTB address for the Darwin/XNU kernel. 301 302 As the XNU kernel developed over the years, the best way of deriving this 303 information changed. This class now offers multiple methods of finding the 304 DTB. Calling find_dtb should automatically select the best method for the 305 job, based on the profile. It will also attempt to fall back on less ideal 306 ways of getting the DTB if the best way fails. 307 """ 308 309 __name = "find_dtb" 310
311 - def _dtb_hits_idlepml4(self):
312 """On 10.8 and later, x64, tries to determine the DTB using IdlePML4. 313 314 IdlePML4 is the address (in Kernel AS) of the kernel DTB [1]. The DTB 315 itself happens to be located in a section of kernel memory that sits at 316 the base of the physical address space [2], and its virtual address can 317 be converted to its physical address using the ID_MAP_VTOP macro 318 which kernel defines for this express purpose [3]. 319 320 Should work on: 10.8 and later. 321 Best for: 10.9 and later. 322 323 Yields: 324 The physical address of the DTB, not verified. 325 326 1: 327 github.com/opensource-apple/xnu/blob/10.9/osfmk/i386/i386_init.c#L281 328 329 Here the kernel initializes the page register at the address IdlePML4 330 points to (masked using the bitmask macro). The same function switches 331 to the newly initialized address space right before returning. 332 333 // IdlePML4 single entry for kernel space. 334 fillkpt(IdlePML4 + KERNEL_PML4_INDEX, 335 INTEL_PTE_WRITE, (uintptr_t)ID_MAP_VTOP(IdlePDPT), 0, 1); 336 337 2: 338 The first page of IdlePML4 is allocated by the ALLOCPAGES function 339 located here: 340 github.com/opensource-apple/xnu/blob/10.9/osfmk/i386/i386_init.c#L134 341 342 3: 343 ID_MAP_VTOP is defined here, as simple bitmask: 344 github.com/opensource-apple/xnu/blob/10.9/osfmk/i386/pmap.h#L353 345 """ 346 idlepml4 = ID_MAP_VTOP(self.profile.get_constant( 347 "_IdlePML4", True)) 348 dtb = self.profile.Object("unsigned int", offset=idlepml4, 349 vm=self.physical_address_space) 350 yield int(dtb)
351
352 - def _dtb_hits_legacy(self):
353 """The original way of getting the DTB, adapted from Volatility. 354 355 I have no idea how or why this is intended to work, but it seems to for 356 old images. 357 358 Should work on: 10.7 and earlier. 359 360 Yields: 361 The physical address of the DTB, not verified. 362 """ 363 if self.profile.metadata("arch") == "I386": 364 result = self.profile.get_constant("_IdlePDPT") 365 366 # Since the DTB must be page aligned, if this is not, it is probably 367 # a pointer to the real DTB. 368 if result % 0x1000: 369 result = self.profile.get_constant_object( 370 "_IdlePDPT", "unsigned int") 371 372 yield result 373 else: 374 result = self.profile.get_constant("_IdlePML4", is_address=True) 375 if result > 0xffffff8000000000: 376 result -= 0xffffff8000000000 377 378 yield result
379
380 - def _dtb_hits_kernel_pmap(self):
381 """On 64-bit systems, finds the DTB from the kernel pmap struct. 382 383 This is a very easy way of getting the DTB on systems where the kernel 384 pmap is a static symbol (which seems to be most of them.) 385 386 Yields: 387 The physical address of the DTB, not verified. 388 """ 389 kernel_pmap_addr = self.profile.get_constant( 390 "_kernel_pmap_store", is_address=True) 391 kernel_pmap = self.profile.pmap(offset=ID_MAP_VTOP(kernel_pmap_addr), 392 vm=self.physical_address_space) 393 yield int(kernel_pmap.pm_cr3)
394
395 - def _dtb_methods(self):
396 """Determines viable methods of getting the DTB based on profile. 397 398 Yields: 399 Callable object that will yield DTB values. 400 """ 401 if self.session.GetParameter("mode_darwin_mountain_lion_plus"): 402 yield self._dtb_hits_idlepml4 403 else: 404 yield self._dtb_hits_legacy 405 406 if self.profile.metadata("arch") == "AMD64": 407 yield self._dtb_hits_kernel_pmap
408
409 - def dtb_hits(self):
410 for method in self._dtb_methods(): 411 for dtb_hit in method(): 412 yield dtb_hit
413
414 - def VerifyHit(self, hit):
415 address_space = self.CreateAS(hit) 416 417 if address_space: 418 address = self.profile.get_constant( 419 "_version", is_address=True) 420 if not address_space.is_valid_address(address): 421 return 422 423 if address_space.read(address, 13) != "Darwin Kernel": 424 return 425 426 return address_space
427
428 - def render(self, renderer):
429 renderer.table_header([("DTB", "dtb", "[addrpad]"), 430 ("Verified", "verified", "8"), 431 ("Source", "method", "15")]) 432 for method in self._dtb_methods(): 433 for dtb_hit in method(): 434 renderer.table_row( 435 dtb_hit, 436 self.VerifyHit(dtb_hit) is not None, 437 method.__name__)
438
439 440 -class ProcessFilterMixin(object):
441 """Adds methods and arguments that enable easy fitlering by process.""" 442 443 __args = [ 444 dict(name="pids", type="ArrayIntParser", positional=True, 445 help="One or more pids of processes to select."), 446 447 dict(name="proc_regex", type="RegEx", 448 help="A regex to select a process by name."), 449 450 dict(name="proc", type="ArrayIntParser", 451 help="Kernel addresses of proc structs."), 452 ] 453 454 @classmethod
455 - def methods(cls):
456 """Return the names of available proc enumeration methods.""" 457 # Find all the producers that collect procs and inherit from 458 # AbstractDarwinCachedProducer. 459 methods = [] 460 for subclass in AbstractDarwinCachedProducer.classes.itervalues(): 461 if (issubclass(subclass, AbstractDarwinCachedProducer) 462 and subclass.type_name == "proc"): 463 methods.append(subclass.name) 464 methods.sort() 465 466 return methods
467 468 @utils.safe_property
469 - def filtering_requested(self):
470 return (self.plugin_args.pids or self.plugin_args.proc_regex or 471 self.plugin_args.eprocess)
472
473 - def filter_processes(self):
474 """Filters proc list using phys_proc and pids lists.""" 475 # No filtering required: 476 procs = sorted(self.session.plugins.collect("proc"), 477 key=lambda proc: proc.pid) 478 479 if not self.filtering_requested: 480 for proc in procs: 481 yield proc 482 else: 483 for offset in self.plugin_args.proc: 484 yield self.profile.proc(vm=self.kernel_address_space, 485 offset=int(offset)) 486 487 # We need to filter by pids 488 for proc in procs: 489 if int(proc.p_pid) in self.plugin_args.pids: 490 yield proc 491 492 elif (self.plugin_args.proc_regex and 493 self.plugin_args.proc_regex.match( 494 utils.SmartUnicode(proc.p_comm))): 495 yield proc
496
497 - def virtual_process_from_physical_offset(self, physical_offset):
498 """Tries to return an proc in virtual space from a physical offset. 499 500 We do this by reflecting off the list elements. 501 502 Args: 503 physical_offset: The physcial offset of the process. 504 505 Returns: 506 A proc object or a NoneObject on failure. 507 """ 508 physical_proc = self.profile.proc(offset=int(physical_offset), 509 vm=self.kernel_address_space.base) 510 511 # We cast our list entry in the kernel AS by following Flink into the 512 # kernel AS and then the Blink. Note the address space switch upon 513 # dereferencing the pointer. 514 our_list_entry = physical_proc.procs.next.dereference( 515 vm=self.kernel_address_space).prev.dereference() 516 517 # Now we get the proc_struct object from the list entry. 518 return our_list_entry.dereference_as("proc_struct", "procs")
519
520 521 -class KernelAddressCheckerMixIn(object):
522 """A plugin mixin which does kernel address checks.""" 523
524 - def __init__(self, **kwargs):
525 super(KernelAddressCheckerMixIn, self).__init__(**kwargs) 526 527 # We use the module plugin to help us local addresses inside kernel 528 # modules. 529 self.module_plugin = self.session.plugins.lsmod(session=self.session)
530