Package rekall :: Module session
[frames] | no frames]

Source Code for Module rekall.session

   1  # Rekall Memory Forensics 
   2  # Copyright (C) 2012 Michael Cohen 
   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   
  20  """This module implements the rekall session. 
  21   
  22  The session stores information about the a specific user interactive 
  23  session. Sessions can be saved and loaded between runs and provide a convenient 
  24  way for people to save their own results. 
  25  """ 
  26   
  27  __author__ = "Michael Cohen <scudette@gmail.com>" 
  28   
  29  import logging 
  30  import os 
  31  import pdb 
  32  import sys 
  33  import time 
  34  import traceback 
  35  import weakref 
  36   
  37  from rekall import cache 
  38  from rekall import config 
  39  from rekall import constants 
  40  from rekall import io_manager 
  41  from rekall import kb 
  42  from rekall import obj 
  43  from rekall import plugin 
  44   
  45  from rekall.ui import renderer 
  46  from rekall_lib import registry 
  47  from rekall_lib import utils 
  48   
  49   
  50  config.DeclareOption( 
  51      "--repository_path", default=[], type="ArrayStringParser", 
  52      help="Path to search for profiles. This can take " 
  53      "any form supported by the IO Manager (e.g. zip files, " 
  54      "directories, URLs etc)") 
  55   
  56  config.DeclareOption("-f", "--filename", 
  57                       help="The raw image to load.") 
  58   
  59  config.DeclareOption( 
  60      "--buffer_size", default=20 * 1024 * 1024, 
  61      type="IntParser", 
  62      help="The maximum size of buffers we are allowed to read. " 
  63      "This is used to control Rekall memory usage.") 
  64   
  65  config.DeclareOption( 
  66      "--output", default=None, 
  67      help="If specified we write output to this file.") 
  68   
  69  config.DeclareOption( 
  70      "--max_collector_cost", default=4, type="IntParser", 
  71      help="If specified, collectors with higher cost will not be used.") 
  72   
  73  config.DeclareOption( 
  74      "--home", default=None, 
  75      help="An alternative home directory path. If not set we use $HOME.") 
  76   
  77  config.DeclareOption( 
  78      "--logging_format", 
  79      default="%(asctime)s:%(levelname)s:%(name)s:%(message)s", 
  80      help="The format string to pass to the logging module.") 
  81   
  82  config.DeclareOption( 
  83      "--performance", default="normal", type="Choices", 
  84      choices=["normal", "fast", "thorough"], 
  85      help="Tune Rekall's choice of algorithms, depending on performance " 
  86      "priority.") 
  87   
  88  LIVE_MODES = ["API", "Memory"] 
  89   
  90  config.DeclareOption( 
  91      "--live", default=None, type="Choice", required=False, 
  92      choices=LIVE_MODES, help="Enable live memory analysis.") 
93 94 95 -class RecursiveHookException(RuntimeError):
96 """Raised when a hook is invoked recursively."""
97
98 99 -class PluginRunner(obj.Curry):
100 """A runner for a specific plugin.""" 101
102 - def __init__(self, session, plugin_name):
106
107 - def Metadata(self):
108 """Return metadata about this plugin.""" 109 plugin_class = getattr( # pylint: disable=protected-access 110 self.session.plugins, self.plugin_name)._target 111 return config.CommandMetadata(plugin_class).Metadata()
112
113 114 -class PluginContainer(object):
115 """A container for plugins. 116 117 Dynamically figures out which plugins are active given the current session 118 state (profile, image file etc). 119 """ 120
121 - def __init__(self, session):
122 self.session = session 123 self.plugin_db = plugin.PluginMetadataDatabase(session)
124
125 - def GetPluginClass(self, name):
126 """Return the active plugin class that implements plugin name. 127 128 Plugins may not be active depending on the current configuration. 129 """ 130 # Try to see if the requested plugin is active right now. 131 metadata = self.plugin_db.GetActivePlugin(name) 132 if metadata == None: 133 return metadata 134 135 return metadata.plugin_cls
136
137 - def Metadata(self, name):
138 return self.plugin_db.GetActivePlugin(name)
139
140 - def __getattr__(self, name):
141 """Gets a wrapped active plugin class. 142 143 A convenience function that returns a curry wrapping the plugin class 144 with the session parameter so users do not need to explicitly pass the 145 session. 146 147 This makes it easy to use in the interactive console: 148 149 pslist_plugin = plugins.pslist() 150 """ 151 plugin_cls = self.GetPluginClass(name) 152 if plugin_cls == None: 153 return plugin_cls 154 155 return obj.Curry(plugin_cls, session=self.session)
156
157 - def __dir__(self):
158 """Enumerate all active plugins in the current configuration.""" 159 return [ 160 cls.name for cls in plugin.Command.GetActiveClasses(self.session) 161 if cls.name]
162
163 164 -class PluginRunnerContainer(PluginContainer):
165 """Like a PluginContainer but returns plugin runners.""" 166
167 - def __getattr__(self, name):
168 plugin_cls = self.GetPluginClass(name) 169 if plugin_cls == None: 170 return plugin_cls 171 172 return PluginRunner(self.session, name)
173
174 175 -class Configuration(utils.AttributeDict):
176 """The session's configuration is managed through this object. 177 178 The session can be configured using the SetParameter() method. However, 179 sometimes when a certain parameter is modified, some code needs to run in 180 response. For example, if the filename is modified, the profile must be 181 recalculated. 182 183 It is not sufficient to attach setter methods to every such parameter 184 though, because there is no guarantee which order these parameters are 185 configured. For example, suppose we want to set both the filename and the 186 profile: 187 188 session.SetParameter("filename", filename) 189 session.SetParameter("profile", "nt/...") 190 191 Since the profile is explicitly set we should not guess it, but if a simple 192 set hook is used, there is no way for the _set_filename() hook to determine 193 that the profile is explicitly given. So what will happen now is that the 194 filename will be changed, then a profile will be autodetected, then it will 195 be immediately overwritten with the user set profile. 196 197 To avoid this issue we use a context manager to essentially group 198 SetParameter() calls into an indivisible unit. The hooks are all run _after_ 199 all the parameters are set: 200 201 with session: 202 session.SetParameter("filename", filename) 203 session.SetParameter("profile", "nt/...") 204 205 Now the _set_filename() hook can see that the profile is explicitly set so 206 it should not be auto-detected. 207 208 Upon entering the context manager, we create a new temporary place to store 209 configuration parameters. Then, when exiting the context manager we ensure 210 that those parameters with hooks are called. The hooks are passed the newly 211 set parameters. Each hook returns the value that will actually be set in the 212 session (so the hook may actually modify the value). 213 """ 214 # The session which owns this configuration object. 215 session = None 216 217 # This holds a write lock on the configuration object. 218 _lock = False 219 _pending_parameters = None 220 _pending_hooks = None 221 222 _loaded_filename = None 223
224 - def __init__(self, session=None, **kwargs):
225 super(Configuration, self).__init__(**kwargs) 226 self.session = session 227 228 # These will be processed on exit from the context manager. 229 self._pending_parameters = {} 230 self._pending_hooks = [] 231 232 # Can not update the configuration object any more. 233 self._lock = 1
234
235 - def __repr__(self):
236 return "<Configuration Object>"
237
238 - def _set_live(self, live, _):
239 if live is not None and not self.live: 240 if isinstance(live, basestring): 241 live = [live] 242 243 # Default is to use Memory analysis. 244 if len(live) == 0: 245 mode = "Memory" 246 elif len(live) == 1: 247 mode = live[0] 248 else: 249 raise RuntimeError( 250 "--live parameter should specify only one mode.") 251 252 live_plugin = self.session.plugins.live(mode=mode) 253 live_plugin.live() 254 255 # When the session is destroyed, close the live plugin. 256 self.session.register_flush_hook(self, live_plugin.close) 257 258 return live
259
260 - def _set_home(self, home, _):
261 """Ensure the home directory is valid.""" 262 if home: 263 home = os.path.abspath(home) 264 if not os.path.isdir(home): 265 raise ValueError("Home directory must be a directory.") 266 else: 267 home = config.GetHomeDir(self.session) 268 269 # We must update the environment so things like os.path.expandvars 270 # work. 271 os.environ["HOME"] = home 272 return home
273
274 - def _set_filename(self, filename, parameters):
275 """Callback for when a filename is set in the session. 276 277 When the user changes the filename parameter we must reboot the session: 278 279 - Reset the cache. 280 - Update the filename 281 - Reload the profile and possibly autodetect it. 282 """ 283 if filename: 284 # This is used by the ipython prompt. 285 self.Set('base_filename', os.path.basename(filename)) 286 287 # Reset any caches. 288 if self.session: 289 self.session.Reset() 290 291 # If a profile is not configured at this time, we need to auto-detect 292 # it. 293 if 'profile' not in parameters: 294 # Clear the existing profile which will trigger profile 295 # autodetection on the new image. 296 del self['profile'] 297 self['filename'] = filename 298 299 return filename
300
301 - def _set_autodetect_build_local_tracked(self, tracked, _):
302 """Update the tracked modules. 303 304 When someone updates the build local tracked parameter we need to remove 305 them from all the address resolver caches. 306 """ 307 # Clear all profile caches in address resolver contexts. 308 for context in self.session.context_cache.values(): 309 context.reset() 310 311 return set(tracked)
312
313 - def _set_repository_path(self, profile_path, _):
314 # Flush the profile cache if we change the profile path. 315 self.session.profile_cache = {} 316 317 return profile_path
318
319 - def _set_profile(self, profile, _):
320 """This is triggered when someone explicitly sets the profile. 321 322 We force load the profile and avoid autodetection. 323 """ 324 profile_obj = self.session.LoadProfile(profile) 325 if profile_obj: 326 self.session.SetCache("profile_obj", profile_obj, 327 volatile=False) 328 329 return profile
330
331 - def _set_logging_level(self, level, _):
332 if isinstance(level, basestring): 333 level = getattr(logging, level.upper(), logging.INFO) 334 335 if level == None: 336 return 337 338 self.session.logging.debug("Logging level set to %s", level) 339 self.session.logging.setLevel(int(level)) 340 if isinstance(self.session, InteractiveSession): 341 # Also set the root logging level, to reflect it in the console. 342 logging.getLogger().setLevel(int(level)) 343 344 # Create subloggers and suppress their logging level. 345 for log_domain in constants.LOG_DOMAINS: 346 logger = self.session.logging.getChild(log_domain) 347 logger.setLevel(logging.WARNING)
348
349 - def _set_log_domain(self, domains, _):
350 for domain in domains: 351 logger = self.session.logging.getChild(domain) 352 logger.setLevel(logging.DEBUG)
353
354 - def _set_logging_format(self, logging_format, _):
355 formatter = logging.Formatter(fmt=logging_format) 356 357 # Set the logging format on the console 358 root_logger = logging.getLogger() 359 if not root_logger.handlers: 360 logging.basicConfig(format=logging_format) 361 else: 362 for handler in root_logger.handlers: 363 handler.setFormatter(formatter) 364 365 # Now set the format of our custom handler(s). 366 for handler in self.session.logging.handlers: 367 handler.setFormatter(formatter)
368
369 - def _set_ept(self, ept, _):
370 self.session.Reset() 371 self["ept"] = ept 372 return ept
373
374 - def _set_session_name(self, name, _):
375 self.session.session_name = name 376 return name
377
378 - def _set_session_id(self, session_id, __):
379 if self.Get("session_id") == None: 380 return session_id 381 382 # We are allowed to set a session id which is not already set. 383 for session in self.session.session_list: 384 if session_id == session.session_id: 385 raise RuntimeError("Session_id clashes with existing session.") 386 387 return session_id
388
389 - def Set(self, attr, value):
390 hook = getattr(self, "_set_%s" % attr, None) 391 if hook: 392 # If there is a set hook we must use the context manager. 393 if self._lock > 0: 394 raise ValueError( 395 "Can only update attribute %s using the context manager." % 396 attr) 397 398 if attr not in self._pending_hooks: 399 self._pending_hooks.append(attr) 400 401 self._pending_parameters[attr] = value 402 else: 403 super(Configuration, self).Set(attr, value)
404
405 - def __delitem__(self, item):
406 try: 407 super(Configuration, self).__delitem__(item) 408 except KeyError: 409 pass
410
411 - def __enter__(self):
412 self._lock -= 1 413 414 return self
415
416 - def __exit__(self, exc_type, exc_value, trace):
417 self._lock += 1 418 419 # Run all the hooks _after_ all the parameters have been set. 420 if self._lock == 1: 421 while self._pending_hooks: 422 hooks = list(reversed(self._pending_hooks)) 423 self._pending_hooks = [] 424 425 # Allow the hooks to call Set() by temporarily entering the 426 # context manager. 427 with self: 428 # Hooks can call Set() which might add more hooks. 429 for attr in hooks: 430 hook = getattr(self, "_set_%s" % attr) 431 value = self._pending_parameters[attr] 432 433 res = hook(value, self._pending_parameters) 434 if res is None: 435 res = value 436 437 self._pending_parameters[attr] = res 438 439 self.update(**self._pending_parameters) 440 self._pending_parameters = {}
441
442 - def __str__(self):
443 """Print the contents somewhat concisely.""" 444 result = [] 445 for k, v in self.iteritems(): 446 if isinstance(v, obj.BaseObject): 447 v = repr(v) 448 449 value = "\n ".join(str(v).splitlines()) 450 if len(value) > 100: 451 value = "%s ..." % value[:100] 452 453 result.append(" %s = %s" % (k, value)) 454 455 return "{\n" + "\n".join(sorted(result)) + "\n}"
456
457 458 -class ProgressDispatcher(object):
459 """An object to manage progress calls. 460 461 Since Rekall must be usable as a library it can not block for too 462 long. Rekall makes continuous reports of its progress to the 463 ProgressDispatcher, which then further dispatches them to other 464 callbacks. This allows users of the Rekall library to be aware of how 465 analysis is progressing. (e.g. to report it in a GUI). 466 """ 467
468 - def __init__(self):
469 self.callbacks = {}
470
471 - def Register(self, key, callback):
472 self.callbacks[key] = callback
473
474 - def UnRegister(self, key):
475 self.callbacks.pop(key, 0)
476
477 - def Broadcast(self, message, *args, **kwargs):
478 for handler in self.callbacks.values(): 479 handler(message, *args, **kwargs)
480
481 482 -class HoardingLogHandler(logging.Handler):
483 """A logging LogHandler that stores messages as long as a renderer hasn't 484 been assigned to it. Used to keep all messages that happen in Rekall before 485 a plugin has been initialized or run at all, to later send them to a 486 renderer. 487 """ 488
489 - def __init__(self, *args, **kwargs):
490 self.logrecord_buffer = [] 491 self.renderer = None 492 super(HoardingLogHandler, self).__init__(*args, **kwargs)
493
494 - def emit(self, record):
495 """Deliver a message if a renderer is defined or store it, otherwise.""" 496 if not self.renderer: 497 self.logrecord_buffer.append(record) 498 else: 499 self.renderer.Log(record)
500
501 - def SetRenderer(self, renderer_obj):
502 """Sets the renderer so messages can be delivered.""" 503 self.renderer = renderer_obj 504 self.Flush()
505
506 - def Flush(self):
507 """Sends all stored messages to the renderer.""" 508 if self.renderer: 509 for log_record in self.logrecord_buffer: 510 self.renderer.Log(log_record) 511 512 self.logrecord_buffer = []
513
514 515 -class Session(object):
516 """Base session. 517 518 This session contains the bare minimum to use rekall. 519 """ 520 521 # We only serialize the following session variables since they make this 522 # session unique. When we unserialize we merge the other state variables 523 # from this current session. 524 # 525 # TODO: This is, for the moment, necessary to support the web UI. Come up 526 # with a better way to represent or generate this list. 527 SERIALIZABLE_STATE_PARAMETERS = [ 528 ("ept", u"IntParser"), 529 ("profile", u"FileName"), 530 ("filename", u"FileName"), 531 ("pagefile", u"FileName"), 532 ("session_name", u"String"), 533 ("timezone", u"TimeZone"), 534 ] 535 536 __metaclass__ = registry.MetaclassRegistry 537 538 # The currently active address resolver. 539 _address_resolver = None 540 541 # Each session has a unique session id (within this process). The ID is only 542 # unique among the sessions currently active. 543 session_id = 0 544 545 # Privileged sessions are allowed to run dangerous plugins. 546 privileged = False 547
548 - def __init__(self, **kwargs):
549 self.progress = ProgressDispatcher() 550 551 # Cache the profiles we get from LoadProfile() below. 552 self.profile_cache = {} 553 554 # A container for active plugins. This is done so that the interactive 555 # console can see which plugins are active by simply command completing 556 # on this object. 557 self.plugins = PluginContainer(self) 558 559 # When the session switches process context we store various things in 560 # this cache, so we can restore the context quickly. The cache is 561 # indexed by the current process_context which can be found from 562 # session.GetParameter("process_context"). 563 self.context_cache = {} 564 self._repository_managers = [] 565 566 # Store user configurable attributes here. These will be read/written to 567 # the configuration file. 568 self.state = Configuration(session=self) 569 self.cache = cache.Factory(self, "memory") 570 with self.state: 571 for k, v in kwargs.items(): 572 self.state.Set(k, v) 573 574 # We use this logger if provided. 575 self.logger = kwargs.pop("logger", None) 576 self._logger = None 577 578 # Make this session id unique. 579 Session.session_id += 1 580 581 # At the start we haven't run any plugin. 582 self.last = None 583 584 # Locks for running hooks. 585 self._hook_locks = set() 586 587 # Hooks that will be called when we get flushed. 588 self._flush_hooks = [] 589 590 self.renderers = []
591 592 @utils.safe_property
593 - def logging(self):
594 if self.logger is not None: 595 return self.logger 596 597 logger_name = u"rekall.%s" % self.session_id 598 if self._logger is None or self._logger.name != logger_name: 599 # Set up a logging object. All rekall logging must be done 600 # through the session's logger. 601 self._logger = logging.getLogger(logger_name) 602 603 # A special log handler that hoards all messages until there's a 604 # renderer that can transport them. 605 self._log_handler = HoardingLogHandler() 606 607 # Since the logger is a global it must not hold a permanent 608 # reference to the HoardingLogHandler, otherwise we may never be 609 # collected. 610 def Remove(_, l=self._log_handler): 611 l.handlers = []
612 613 self._logger.addHandler(weakref.proxy( 614 self._log_handler, Remove)) 615 616 return self._logger
617 618 @utils.safe_property
619 - def volatile(self):
620 return (self.physical_address_space and 621 self.physical_address_space.volatile)
622 623 @utils.safe_property
624 - def repository_managers(self):
625 """The IO managers that are used to fetch profiles from the profile 626 repository. 627 628 """ 629 if self._repository_managers: 630 return self._repository_managers 631 632 # The profile path is specified in search order. 633 repository_path = (self.GetParameter("repository_path") or 634 self.GetParameter("profile_path") or []) 635 636 for path in repository_path: 637 # TODO(scudette): remove this hack for 1.6 release. Github has 638 # changed their static URL access. If the user is using an old URL 639 # we warn and correct it. 640 if path in constants.OLD_DEPRECATED_URLS: 641 self.logging.warn( 642 "Rekall's profile repository is pointing to deprecated URL " 643 "(%s). Please update your ~/.rekallrc file.", path) 644 path = constants.PROFILE_REPOSITORIES[0] 645 646 try: 647 self._repository_managers.append( 648 (path, io_manager.Factory(path, session=self))) 649 except ValueError: 650 pass 651 652 if not self._repository_managers: 653 try: 654 self.logging.warn( 655 "No usable repositories were found. " 656 "Rekall Will attempt to use the local cache. " 657 "This is likely to fail if profiles are missing locally!") 658 self._repository_managers = [ 659 (None, io_manager.DirectoryIOManager( 660 urn=cache.GetCacheDir(self), session=self))] 661 except IOError: 662 self._repository_managers = [] 663 664 return self._repository_managers
665
666 - def __enter__(self):
667 # Allow us to update the state context manager. 668 self.state.__enter__() 669 return self
670
671 - def __exit__(self, exc_type, exc_value, trace):
672 self.state.__exit__(exc_type, exc_value, trace)
673
674 - def Reset(self):
675 self.context_cache = {} 676 self.profile_cache = {} 677 self.kernel_address_space = None 678 679 # For volatile sessions we use a timed cache (which expires after a 680 # short time). 681 cache_type = self.GetParameter("cache", "memory") 682 if self.volatile: 683 cache_type = "timed" 684 685 if self.cache: 686 self.remove_flush_hook(self.cache) 687 688 self.cache = cache.Factory(self, cache_type) 689 if self.physical_address_space: 690 self.physical_address_space.ConfigureSession(self) 691 692 # Fix up the completer. This is sometimes required after the debugger 693 # steals readline focus. Typing session.Reset() fixes things again. 694 self.shell.init_completer()
695 696 @utils.safe_property
697 - def default_address_space(self):
698 return self.GetParameter("default_address_space")
699 700 @utils.safe_property
701 - def address_resolver(self):
702 """A convenience accessor for the address resolver implementation. 703 704 Note that the correct address resolver implementation depends on the 705 profile. For example, windows has its own address resolver, while Linux 706 and OSX have a different one. 707 """ 708 # Get the current process context. 709 current_context = (self.GetParameter("process_context").obj_offset or 710 "Kernel") 711 712 # Get the resolver from the cache. 713 address_resolver = self.context_cache.get(current_context) 714 if address_resolver == None: 715 # Make a new address resolver. 716 address_resolver = self.plugins.address_resolver() 717 self.context_cache[current_context] = address_resolver 718 719 return address_resolver
720
721 - def __getattr__(self, attr):
722 """This will only get called if the attribute does not exist.""" 723 return obj.NoneObject("Attribute not set")
724
725 - def HasParameter(self, item):
726 """Returns if the session has the specified parameter set. 727 728 If False, a call to GetParameter() might trigger autodetection. 729 """ 730 return (self.state.get(item) is not None or 731 self.cache.Get(item) is not None)
732
733 - def GetParameter(self, item, default=obj.NoneObject(), cached=True):
734 """Retrieves a stored parameter. 735 736 Parameters are managed by the Rekall session in two layers. The state 737 containers contains those parameters which are deliberately set by the 738 user. 739 740 Some parameters are calculated by plugins and are used in order to speed 741 up further calculations. These are cached in the state as well. 742 743 It is important to never override a user selection by the cached 744 results. Since the user must be allowed to override all parameters - for 745 example through the GUI or the command line. Therefore when resolving a 746 parameter, we first check in the state, and only if the parameter does 747 not exist, we check the cache. 748 """ 749 result = self.state.get(item) 750 if result is not None: 751 return result 752 753 # None in the state dict means that the cache is empty. This is 754 # different from a NoneObject() returned (which represents a cacheable 755 # failure). 756 if cached: 757 result = self.cache.Get(item) 758 if result is not None: 759 return result 760 761 # We don't have or didn't look in the cache for the result. See if we 762 # can get if from a hook. 763 try: 764 result = self._RunParameterHook(item) 765 if result is not None: 766 return result 767 except RecursiveHookException: 768 pass 769 770 return default
771
772 - def SetCache(self, item, value, volatile=True):
773 """Store something in the cache.""" 774 self.cache.Set(item, value, volatile=volatile)
775
776 - def SetParameter(self, item, value):
777 """Sets a session parameter. 778 779 NOTE! This method should only be used for setting user provided data. It 780 must not be used to set cached data - use SetCache() instead. Parameters 781 set with this method are not cleared as part of session.Reset() and are 782 copied to cloned sessions. 783 """ 784 self.state.Set(item, value)
785
786 - def _RunParameterHook(self, name):
787 """Launches the registered parameter hook for name.""" 788 for cls in kb.ParameterHook.classes.values(): 789 if cls.name == name and cls.is_active(self): 790 if name in self._hook_locks: 791 # This should never happen! If it does then this will block 792 # in a loop so we fail hard. 793 raise RecursiveHookException( 794 "Trying to invoke hook %s recursively!" % name) 795 796 try: 797 self._hook_locks.add(name) 798 hook = cls(session=self) 799 result = hook.calculate() 800 801 # Cache the output from the hook directly. 802 self.SetCache(name, result, volatile=hook.volatile) 803 finally: 804 self._hook_locks.remove(name) 805 806 return result
807
808 - def _CorrectKWArgs(self, kwargs):
809 """Normalize args to use _ instead of -. 810 811 So we can pass them as valid python parameters. 812 """ 813 result = {} 814 for k, v in kwargs.iteritems(): 815 result[k.replace("-", "_")] = v 816 return result
817
818 - def RunPlugin(self, plugin_obj, *args, **kwargs):
819 """Launch a plugin and its render() method automatically. 820 821 We use the pager specified in session.GetParameter("pager"). 822 823 Args: 824 plugin_obj: A string naming the plugin, or the plugin instance itself. 825 *pos_args: Args passed to the plugin if it is not an instance. 826 **kwargs: kwargs passed to the plugin if it is not an instance. 827 """ 828 kwargs = self._CorrectKWArgs(kwargs) 829 output = kwargs.pop("output", self.GetParameter("output")) 830 ui_renderer = kwargs.pop("format", None) 831 result = None 832 833 if ui_renderer is None: 834 ui_renderer = self.GetRenderer(output=output) 835 836 self.renderers.append(ui_renderer) 837 838 # Set the renderer so we can transport log messages. 839 self._log_handler.SetRenderer(ui_renderer) 840 841 try: 842 plugin_name = self._GetPluginName(plugin_obj) 843 except Exception as e: 844 raise ValueError( 845 "Invalid plugin_obj parameter (%s)." % repr(plugin)) 846 847 # On multiple calls to RunPlugin, we need to make sure the 848 # HoardingLogHandler doesn't send messages to the wrong renderer. 849 # We reset the renderer and make it hoard messages until we have the 850 # new one. 851 self.logging.debug( 852 u"Running plugin (%s) with args (%s) kwargs (%s)", 853 plugin_name, args, utils.SmartUnicode(kwargs)[:1000]) 854 855 with ui_renderer.start(plugin_name=plugin_name, kwargs=kwargs): 856 try: 857 original_plugin_obj = plugin_obj 858 plugin_obj = self._GetPluginObj(plugin_obj, *args, **kwargs) 859 if not plugin_obj: 860 raise ValueError( 861 "Invalid plugin: %s" % original_plugin_obj) 862 result = plugin_obj.render(ui_renderer) or plugin_obj 863 self.last = plugin_obj 864 except (Exception, KeyboardInterrupt) as e: 865 self._HandleRunPluginException(ui_renderer, e) 866 867 finally: 868 self.renderers.pop(-1) 869 870 # At this point, the ui_renderer will have flushed all data. 871 # Further logging will be lost. 872 return result
873
874 - def _HandleRunPluginException(self, ui_renderer, e):
875 """Handle exceptions thrown while trying to run a plugin.""" 876 _ = ui_renderer, e 877 # This method is called from exception handlers. 878 raise # pylint: disable=misplaced-bare-raise
879
880 - def _GetPluginName(self, plugin_obj):
881 """Extract the name from the plugin object.""" 882 if isinstance(plugin_obj, basestring): 883 return plugin_obj 884 885 elif utils.issubclass(plugin_obj, plugin.Command): 886 return plugin_obj.name 887 888 elif isinstance(plugin_obj, plugin.Command): 889 return plugin_obj.name
890
891 - def _GetPluginObj(self, plugin_obj, *args, **kwargs):
892 if isinstance(plugin_obj, basestring): 893 plugin_name = plugin_obj 894 895 elif utils.issubclass(plugin_obj, plugin.Command): 896 plugin_name = plugin_obj.name 897 plugin_cls = plugin_obj 898 899 elif isinstance(plugin_obj, plugin.Command): 900 return plugin_obj 901 902 else: 903 raise TypeError( 904 "First parameter should be a plugin name or instance.") 905 906 # When passed as a string this specifies a plugin name. 907 if isinstance(plugin_obj, basestring): 908 plugin_cls = getattr(self.plugins, plugin_obj, None) 909 if plugin_cls is None: 910 self.logging.error( 911 "Plugin %s is not active. Is it supported with " 912 "this profile?", plugin_name) 913 return 914 915 # Instantiate the plugin object. 916 kwargs["session"] = self 917 return plugin_cls(*args, **kwargs)
918
919 - def LoadProfile(self, name, use_cache=True):
920 """Try to load a profile directly by its name. 921 922 Args: 923 924 name: A string which represents the canonical name for the profile. We 925 ask all repositories in the repository_path to resolve this name 926 into a profile. 927 928 Returns: 929 a Profile() instance or a NoneObject() 930 931 """ 932 if not name: 933 return obj.NoneObject("No filename") 934 935 if isinstance(name, obj.Profile): 936 return name 937 938 # We only want to deal with unix paths. 939 name = name.replace("\\", "/") 940 941 try: 942 if use_cache: 943 cached_profile = self.profile_cache[name] 944 if cached_profile: 945 return cached_profile 946 947 else: 948 return obj.NoneObject( 949 "Unable to load profile %s from any repository." % 950 name) 951 952 except KeyError: 953 pass 954 955 result = None 956 try: 957 # If the name is a path we try to open it directly: 958 container = io_manager.DirectoryIOManager(os.path.dirname(name), 959 version=None, 960 session=self) 961 data = container.GetData(os.path.basename(name)) 962 if data == None: 963 raise IOError("Not found.") 964 965 result = obj.Profile.LoadProfileFromData(data, self, name=name) 966 except IOError: 967 pass 968 969 # Traverse the profile path until one works. 970 if not result: 971 # Add the last supported repository as the last fallback path. 972 for path, manager in self.repository_managers: 973 try: 974 # The inventory allows us to fail fetching the profile 975 # quickly - without making the round trip. 976 if not manager.CheckInventory(name): 977 self.logging.debug( 978 "Skipped profile %s from %s (Not in inventory)", 979 name, path) 980 continue 981 982 now = time.time() 983 result = obj.Profile.LoadProfileFromData( 984 manager.GetData(name), session=self, name=name) 985 if result: 986 self.logging.info( 987 "Loaded profile %s from %s (in %s sec)", 988 name, manager, time.time() - now) 989 break 990 991 except (IOError, KeyError) as e: 992 result = obj.NoneObject(e) 993 self.logging.debug("Could not find profile %s in %s: %s", 994 name, path, e) 995 996 continue 997 998 # Cache it for later. Note that this also caches failures so we do not 999 # retry again. 1000 self.profile_cache[name] = result 1001 if result == None: 1002 return obj.NoneObject( 1003 "Unable to load profile %s from any repository." % name) 1004 1005 return result
1006
1007 - def __unicode__(self):
1008 return u"Session"
1009
1010 - def report_progress(self, message=" %(spinner)s", *args, **kwargs):
1011 """Called by the library to report back on the progress.""" 1012 self.progress.Broadcast(message, *args, **kwargs)
1013
1014 - def GetRenderer(self, output=None):
1015 """Get a renderer for this session. 1016 1017 If a renderer is currently active we just reuse it, otherwise we 1018 instantiate the renderer specified in self.GetParameter("format"). 1019 """ 1020 # Reuse the current renderer. 1021 if self.renderers and output is None: 1022 return self.renderers[-1] 1023 1024 ui_renderer = self.GetParameter("format", "text") 1025 if isinstance(ui_renderer, basestring): 1026 ui_renderer_cls = renderer.BaseRenderer.ImplementationByName( 1027 ui_renderer) 1028 ui_renderer = ui_renderer_cls(session=self, output=output) 1029 1030 return ui_renderer
1031 1032 @utils.safe_property
1033 - def physical_address_space(self):
1034 res = self.GetParameter("physical_address_space", None) 1035 return res
1036 1037 @physical_address_space.setter
1038 - def physical_address_space(self, value):
1039 # The physical_address_space is not part of the cache because 1040 # it needs to be set first before we know which cache 1041 # fingerprint to use (getting the fingerprint depends on the 1042 # physical_address_space). 1043 self.SetParameter("physical_address_space", value) 1044 self.Reset() 1045 1046 # Ask the physical_address_space to configure this session. 1047 if value: 1048 value.ConfigureSession(self)
1049 1050 @utils.safe_property
1051 - def profile(self):
1052 # If a process context is specified, we use the profile from the process 1053 # context. 1054 process_context = self.GetParameter("process_context").obj_profile 1055 if process_context != None: 1056 return process_context 1057 1058 res = self.GetParameter("profile_obj") 1059 return res
1060 1061 @profile.setter
1062 - def profile(self, value):
1063 # Clear the profile object. Next access to it will trigger profile 1064 # auto-detection. 1065 if value == None: 1066 self.SetCache('profile_obj', value, volatile=False) 1067 1068 elif isinstance(value, basestring): 1069 with self.state: 1070 self.state.Set('profile', value) 1071 1072 elif isinstance(value, obj.Profile): 1073 self.SetCache('profile_obj', value, volatile=False) 1074 self.SetCache("profile", value.name, volatile=False) 1075 else: 1076 raise AttributeError("Type %s not allowed for profile" % value)
1077
1078 - def clone(self, **kwargs):
1079 new_state = self.state.copy() 1080 # Remove the cache from the copy so we start with a fresh cache. 1081 new_state.pop("cache", None) 1082 1083 # session_ids are automatically generated so we need to pop it. 1084 new_state.pop("session_id", None) 1085 1086 session_id = self._new_session_id() 1087 old_session_name = new_state.pop("session_name", None) 1088 new_session_name = kwargs.pop( 1089 "session_name", kwargs.get( 1090 "filename", "%s (%s)" % (old_session_name, session_id))) 1091 new_session = self.__class__( 1092 session_name=new_session_name, session_id=session_id, **new_state) 1093 new_session.Reset() 1094 new_session.locals = self.locals 1095 1096 # Now override all parameters as requested. 1097 with new_session: 1098 for k, v in kwargs.iteritems(): 1099 new_session.SetParameter(k, v) 1100 return new_session
1101
1102 - def register_flush_hook(self, owner, hook, args=()):
1103 """This hook will run when the session is closed.""" 1104 self._flush_hooks.append((owner, hook, args))
1105
1106 - def remove_flush_hook(self, owner):
1107 """Removes the flush hooks set by the owner. 1108 1109 Returns the hooks so they can be called if needed. 1110 """ 1111 owners_hooks = [] 1112 flush_hooks = [] 1113 for x in self._flush_hooks: 1114 if x[0] is owner: 1115 owners_hooks.append(x) 1116 else: 1117 flush_hooks.append(x) 1118 self._flush_hooks = flush_hooks 1119 1120 return owners_hooks
1121
1122 - def Flush(self):
1123 """Destroy this session. 1124 1125 This should be called when the session is destroyed. 1126 """ 1127 for _, hook, args in self._flush_hooks: 1128 hook(*args)
1129
1130 1131 -class DynamicNameSpace(dict):
1132 """A namespace which dynamically reflects the currently active plugins. 1133 1134 This forms the global namespace inside the ipython interpreter shell. There 1135 are some special variables prepopulated: 1136 1137 - plugins: A PluginRunnerContainer that users can use to see which plugins 1138 are active. 1139 1140 - session: A reference to the current session. 1141 """ 1142
1143 - def __init__(self, session=None, **kwargs):
1144 if session is None: 1145 raise RuntimeError("Session must be given.") 1146 1147 self.help_profile = None 1148 self.session = session 1149 1150 super(DynamicNameSpace, self).__init__( 1151 session=session, 1152 plugins=PluginRunnerContainer(session), 1153 **kwargs)
1154
1155 - def __iter__(self):
1156 res = set(super(DynamicNameSpace, self).__iter__()) 1157 res.update(self["plugins"].__dir__()) 1158 1159 return iter(res)
1160
1161 - def __delitem__(self, item):
1162 try: 1163 super(DynamicNameSpace, self).__delitem__(item) 1164 except KeyError: 1165 pass
1166
1167 - def keys(self):
1168 return list(self)
1169
1170 - def __getitem__(self, item):
1171 try: 1172 return super(DynamicNameSpace, self).__getitem__(item) 1173 except KeyError: 1174 if getattr(self["session"].plugins, item): 1175 return self._prepare_runner(item) 1176 1177 raise KeyError(item)
1178
1179 - def get(self, item, default=None):
1180 try: 1181 return self[item] 1182 except KeyError: 1183 return default
1184
1185 - def _prepare_runner(self, name):
1186 """Prepare a runner to run the given plugin.""" 1187 # Create a runner for this plugin. 1188 return PluginRunner(self["session"], name)
1189
1190 1191 -class InteractiveSession(Session):
1192 """The session allows for storing of arbitrary values and configuration. 1193 1194 This session contains a lot of convenience features which are useful for 1195 interactive use. 1196 """ 1197 1198 # A list of tuples (session_id, session) sorted by session id. This list is 1199 # shared by all session instances! TODO: Refactor into a session group. 1200 session_list = [] 1201
1202 - def __init__(self, env=None, use_config_file=True, **kwargs):
1203 """Creates an interactive session. 1204 1205 Args: 1206 env: If passed we use this dict as the local environment. 1207 1208 use_config_file: If True we merge the system's config file into the 1209 session. This helps set the correct profile paths for example. 1210 1211 kwargs: Arbitrary parameters to store in the session. 1212 1213 Returns: 1214 an interactive session object. 1215 """ 1216 # When this session was created. 1217 self._start_time = time.time() 1218 1219 # These keep track of the last run plugin. 1220 self._last_plugin = None 1221 1222 # Fill the session with helpful defaults. 1223 self.pager = obj.NoneObject("Set this to your favourite pager.") 1224 1225 # Set the session name 1226 self.session_name = kwargs.pop("session_name", u"Default session") 1227 super(InteractiveSession, self).__init__() 1228 1229 # Prepare the local namespace for the interactive session. 1230 self.locals = DynamicNameSpace( 1231 session=self, 1232 1233 # Prepopulate the namespace with our most important modules. 1234 v=self.v, 1235 1236 # Some useful modules which should be available always. 1237 sys=sys, os=os, 1238 1239 # A list of sessions. 1240 session_list=self.session_list, 1241 1242 # Pass additional environment. 1243 **(env or {}) 1244 ) 1245 1246 with self.state: 1247 self.state.Set("session_list", self.session_list) 1248 self.state.Set("session_name", self.session_name) 1249 self.state.Set("session_id", self._new_session_id()) 1250 1251 # Configure the session from the config file and kwargs. 1252 if use_config_file: 1253 with self.state: 1254 config.MergeConfigOptions(self.state, self) 1255 1256 with self.state: 1257 for k, v in kwargs.items(): 1258 self.state.Set(k, v)
1259 1260 @utils.safe_property
1261 - def session_id(self):
1262 return self.GetParameter("session_id", default=Session.session_id)
1263
1264 - def find_session(self, session_id):
1265 for session in self.session_list: 1266 if session.session_id == session_id: 1267 return session 1268 1269 return None
1270
1271 - def _new_session_id(self):
1272 new_sid = 1 1273 for session in InteractiveSession.session_list: 1274 if new_sid <= session.session_id: 1275 new_sid = session.session_id + 1 1276 1277 return new_sid
1278
1279 - def _HandleRunPluginException(self, ui_renderer, e):
1280 """Handle all exceptions thrown by logging to the console.""" 1281 1282 if isinstance(e, plugin.InvalidArgs): 1283 self.logging.fatal("Invalid Args: %s" % e) 1284 1285 elif isinstance(e, plugin.PluginError): 1286 self.logging.fatal(str(e)) 1287 1288 elif isinstance(e, (KeyboardInterrupt, plugin.Abort)): 1289 logging.error("Aborted\r\n") 1290 1291 else: 1292 error_status = traceback.format_exc() 1293 1294 # Report the error to the renderer. 1295 self.logging.fatal(error_status) 1296 1297 # If anything goes wrong, we break into a debugger here. 1298 if self.GetParameter("debug"): 1299 pdb.post_mortem(sys.exc_info()[2]) 1300 1301 # This method is called from the exception handler - this bare raise 1302 # will preserve backtraces. 1303 raise # pylint: disable=misplaced-bare-raise
1304
1305 - def v(self):
1306 """Re-execute the previous command.""" 1307 if self.last: 1308 self.RunPlugin(self.last)
1309
1310 - def lister(self, arg):
1311 for x in arg: 1312 self.printer(x)
1313
1314 - def __str__(self):
1315 return self.__unicode__()
1316
1317 - def __unicode__(self):
1318 result = u"""Rekall Memory Forensics session Started on %s. 1319 1320 Config: 1321 %s 1322 1323 Cache (%r): 1324 %s 1325 """ % (time.ctime(self._start_time), self.state, self.cache, self.cache) 1326 return result
1327
1328 - def __dir__(self):
1329 items = self.__dict__.keys() + dir(self.__class__) 1330 1331 return [x for x in items if not x.startswith("_")]
1332
1333 - def add_session(self, **kwargs):
1334 """Creates a new session and adds it to the list. 1335 1336 Returns: 1337 the new session. 1338 """ 1339 session_id = kwargs["session_id"] = self._new_session_id() 1340 if "session_name" not in kwargs: 1341 # Make a unique session name. 1342 kwargs["session_name"] = u"%s (%s)" % ( 1343 kwargs.get("filename", session_id), session_id) 1344 1345 new_session = self.__class__() 1346 new_session.locals = self.locals 1347 # pylint: disable=protected-access 1348 new_session._repository_managers = self._repository_managers 1349 new_session.profile_cache = self.profile_cache 1350 1351 with new_session: 1352 for k, v in kwargs.iteritems(): 1353 new_session.SetParameter(k, v) 1354 1355 self.session_list.append(new_session) 1356 self.session_list.sort(key=lambda x: x.session_id) 1357 1358 return new_session
1359