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

Source Code for Module rekall.plugin

  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  """Plugins allow the core rekall system to be extended.""" 
 21   
 22  __author__ = "Michael Cohen <scudette@gmail.com>" 
 23   
 24  import collections 
 25  import copy 
 26  import re 
 27  import StringIO 
 28   
 29  from rekall import config 
 30  from rekall import obj 
 31  from rekall.ui import text as text_renderer 
 32   
 33  from rekall_lib import registry 
 34  from rekall_lib import utils 
35 36 37 -class Error(Exception):
38 """Raised for plugin errors."""
39
40 41 -class PluginError(Error):
42 """An error occured in a plugin."""
43
44 45 -class InvalidArgs(Error):
46 """Invalid arguments."""
47
48 49 -class Abort(Error):
50 """Signal aborting of the plugin."""
51
52 53 -class CommandOption(object):
54 """An option specification.""" 55
56 - def __init__(self, name=None, default=None, type="String", choices=None, 57 help="", positional=False, required=False, override=False, 58 hidden=False):
59 self.name = name 60 self._default = default 61 self.type = type 62 self.help = help 63 self._choices = choices 64 self.required = required 65 self.positional = positional 66 self.override = override 67 self.hidden = hidden
68 69 @utils.safe_property
70 - def default(self):
71 if callable(self._default): 72 return self._default() 73 74 return self._default
75 76 @utils.safe_property
77 - def choices(self):
78 if callable(self._choices): 79 return list(self._choices()) 80 81 return self._choices
82
83 - def add_argument(self, parser):
84 """Add ourselves to the parser.""" 85 prefix = "" if self.positional else "--" 86 parser.add_argument(prefix + self.name, default=self.default, 87 type=self.type, help=self.help, 88 positional=self.positional, hidden=self.hidden, 89 required=self.required, choices=self.choices)
90
91 - def parse(self, value, session):
92 """Parse the value as passed.""" 93 if value is None: 94 if self.default is None: 95 # Default values for various types. 96 if self.type == "AddressSpace": 97 return session.GetParameter("default_address_space") 98 99 if self.type in ["ArrayStringParser", "ArrayString", 100 "ArrayIntParser", "Array"]: 101 return [] 102 103 if self.type in ["Bool", "Boolean"]: 104 return False 105 106 elif self.type == "RegEx": 107 if isinstance(self.default, basestring): 108 return re.compile(self.default) 109 110 return self.default 111 112 # Validate the parsed type. We only support a small set of types right 113 # now, so this is good enough. 114 115 # Handle addresses specifically though the address resolver. 116 if self.type == "Address" or self.type == "SymbolAddress": 117 value = session.address_resolver.get_address_by_name(value) 118 119 elif self.type == "IntParser": 120 if isinstance(value, basestring): 121 value = int(value, 0) 122 else: 123 value = int(value) 124 125 elif self.type == "Choices": 126 if value not in self.choices: 127 raise TypeError("Arg %s must be one of %s" % ( 128 self.name, self.choices)) 129 130 elif self.type == "ChoiceArray": 131 if isinstance(value, basestring): 132 value = [value] # pylint: disable=redefined-variable-type 133 134 for item in value: 135 if item not in self.choices: 136 raise TypeError("Arg %s must be one of %s" % ( 137 self.name, self.choices)) 138 139 elif self.type in ["ArrayString", "ArrayStringParser"]: 140 if isinstance(value, basestring): 141 value = [value] 142 143 if not isinstance(value, collections.Iterable): 144 raise TypeError("Arg %s must be a list of strings" % self.name) 145 146 for item in value: 147 if not isinstance(item, basestring): 148 raise TypeError("Arg %s must be a list of strings" % 149 self.name) 150 151 elif self.type == "Array": 152 if isinstance(value, basestring): 153 value = [value] 154 155 if not isinstance(value, collections.Iterable): 156 raise TypeError("Arg %s must be a list of strings" % self.name) 157 158 elif self.type == "RegEx": 159 if isinstance(value, basestring): 160 value = re.compile(value, re.I) 161 162 elif self.type == "ArrayIntParser": 163 try: 164 value = [int(value)] # pylint: disable=redefined-variable-type 165 except (ValueError, TypeError): 166 result = [] 167 for x in value: 168 # RowTuple are treated especially in order to simplify 169 # Efilter syntax. 170 if x.__class__.__name__ == "RowTuple": 171 if len(x) != 1: 172 raise PluginError( 173 "Subselect must only select a single row when " 174 "expanding into a list.") 175 x = int(x[0]) 176 else: 177 x = int(x) 178 result.append(x) 179 value = result 180 181 # Allow address space to be specified. 182 elif self.type == "AddressSpace": 183 load_as = session.plugins.load_as(session=session) 184 value = load_as.ResolveAddressSpace(value) 185 186 return value
187
188 189 -class ModeBasedActiveMixin(object):
190 # Specify this mode to decleratively activate this class. To make this work, 191 # you will need to define a kb.ParameterHook() that can calculate if the 192 # session is running in the specified mode. 193 mode = None 194 195 @classmethod
196 - def is_active(cls, session):
197 """Checks we are active. 198 199 This method will be called with the session to check if this specific 200 class is active. This mechanism allows multiple implementations to all 201 share the same name, as long as only one is actually active. For 202 example, we can have a linux, windows and mac version of plugins with 203 the "pslist" name. 204 205 This mixin provides the mixed class with a basic is_active() method 206 which honors a mode member defined on the class and all its 207 subclasses. The mode is additive (meaning each class and its subclasses 208 are only active if the mode is active). 209 """ 210 for subclass in cls.__mro__: 211 mode = getattr(subclass, "mode", None) 212 213 if isinstance(mode, basestring): 214 if not session.GetParameter(mode): 215 return False 216 217 elif isinstance(mode, (list, tuple)): 218 for i in mode: 219 if not session.GetParameter(i): 220 return False 221 222 return True
223
224 225 226 -class Command(ModeBasedActiveMixin):
227 """A command can be run from the rekall command line. 228 229 Commands can be automatically imported into the shell's namespace and are 230 expected to produce textual (or other) output. 231 232 In order to define a new command simply extend this class. 233 """ 234 235 # these attribute are not inherited. 236 237 # The name of this command (The command will be registered under this 238 # name). If empty, the command will not be imported into the namespace but 239 # will still be available from the Factory below. 240 __name = "" 241 242 # Name of the category of this command. This is used when showing help and 243 # in the UI. 244 __category = "" 245 246 # This class will not be registered (but extensions will). 247 __abstract = True 248 __metaclass__ = registry.MetaclassRegistry 249 250 # This declares that this plugin only exists in the interactive session. 251 interactive = False 252 253 # This declares that the plugin should not be called upon to collect 254 # structs - the default behavior. 255 producer = False 256 257 # This will hold the error status from running this plugin. 258 error_status = None 259 260 mode = None 261 262 @classmethod
263 - def args(cls, parser):
264 """Declare the command line args this plugin needs."""
265 266 @classmethod
267 - def GetPrototype(cls, session):
268 """Return an instance of this plugin with suitable default arguments. 269 270 In most general applications, types are declared at compile time and 271 remain immutable, or at least available throughout the program's 272 lifecycle. Rekall, on the other hand, leave many of the decisions 273 usually made at type declaration time until late in the runtime, 274 when the profile data is available. For this reason, in many of the 275 cases when other applications would interrogate classes (for attributes 276 and properties, among other things), in Rekall we must interrogate 277 their instances, which have access to profile data. In order to 278 make this possible slightly earlier in the runtime than when running 279 the plugin, we introduce the concept of prototypes, which are 280 instances of the plugin or struct with the current session and profile 281 available, but with no data or arguments set. 282 283 Arguments: 284 session 285 286 Returns: 287 And instance of this Command with suitable default arguments. 288 """ 289 try: 290 return cls(session=session, ignore_required=True) 291 except (TypeError, ValueError): 292 raise NotImplementedError("Subclasses must override GetPrototype " 293 "if they require arguments.")
294 295 @registry.classproperty
296 - def name(cls): # pylint: disable=no-self-argument
297 return getattr(cls, "_%s__name" % cls.__name__, None)
298
299 - def __init__(self, ignore_required=False, **kwargs):
300 """The constructor for this command. 301 302 Commands can take arbitrary named args and have access to the running 303 session. 304 305 Args: 306 session: The session we will use. Many options are taken from the 307 session by default, if not provided. This allows users to omit 308 specifying many options. 309 310 ignore_required: If this is true plugin constructors must allow the 311 plugin to be instantiated with no parameters. All parameter 312 validation shall be disabled and construction must succeed. 313 """ 314 session = kwargs.pop("session", None) 315 if kwargs: 316 raise InvalidArgs("Invalid arguments: %s" % unicode(kwargs.keys())) 317 318 super(Command, self).__init__(**kwargs) 319 320 if session == None: 321 raise InvalidArgs("A session must be provided.") 322 323 self.session = session 324 self.ignore_required = ignore_required
325
326 - def get_plugin(self, name, **kwargs):
327 """Returns an instance of the named plugin. 328 329 The new plugin will initialized with the current session and optional 330 kwargs. 331 Args: 332 name: The generic name of the plugin (i.e. the __name attribute, 333 e.g. pslist). 334 kwargs: Extra args to use for instantiating the plugin. 335 """ 336 for cls in self.classes.values(): 337 if cls.name == name and cls.is_active(self.session): 338 return cls(session=self.session, profile=self.profile, 339 **kwargs)
340
341 - def __str__(self):
342 """Render into a string using the text renderer.""" 343 fd = StringIO.StringIO() 344 ui_renderer = text_renderer.TextRenderer( 345 session=self.session, fd=fd) 346 347 with ui_renderer.start(plugin_name=self.name): 348 self.render(ui_renderer) 349 350 return fd.getvalue()
351
352 - def __repr__(self):
353 return "Plugin: %s (%s)" % (self.name, self.__class__.__name__)
354
355 - def __iter__(self):
356 """Make plugins that define collect iterable, as convenience. 357 358 Because this: 359 for x in session.plugins.get_some_data(): 360 # do stuff 361 362 Is nicer than this: 363 for x in session.plugins.get_some_data().collect(): 364 # do stuff 365 """ 366 if callable(getattr(self, "collect", None)): 367 for x in self.collect(): 368 if x: 369 yield x 370 371 else: 372 raise TypeError("%r is not iterable." % self)
373
374 - def render(self, renderer):
375 """Produce results on the renderer given. 376 377 Each plugin should implement this method to produce output on the 378 renderer. The framework will initialize the plugin and provide it with 379 some kind of renderer to write output on. The plugin should not assume 380 that the renderer is actually TextRenderer, only that the methods 381 defined in the BaseRenderer exist. 382 383 Args: 384 renderer: A renderer based at rekall.ui.renderer.BaseRenderer. 385 """
386 387 @classmethod
388 - def GetActiveClasses(cls, session):
389 """Return only the active commands based on config.""" 390 for command_cls in cls.classes.values(): 391 if command_cls.is_active(session): 392 yield command_cls
393
394 395 -class ProfileCommand(Command):
396 """A baseclass for all commands which require a profile.""" 397 398 __abstract = True 399 400 PROFILE_REQUIRED = True 401 402 @classmethod
403 - def args(cls, metadata):
404 # Top level args. 405 metadata.add_argument( 406 "-p", "--profile", critical=True, hidden=True, 407 help="Name of the profile to load. This is the " 408 "filename of the profile found in the profiles " 409 "directory. Profiles are searched in the profile " 410 "path order.") 411 412 metadata.add_requirement("profile")
413 414 @classmethod
415 - def is_active(cls, session):
416 if cls.PROFILE_REQUIRED: 417 # Note! This will trigger profile autodetection if this plugin is 418 # needed. This might be slightly unexpected: When command line 419 # completing the available plugins we will trigger profile 420 # autodetection in order to determine which plugins are active. 421 profile = (session.profile != None and 422 super(ProfileCommand, cls).is_active(session)) 423 424 return profile 425 426 else: 427 return super(ProfileCommand, cls).is_active(session)
428
429 - def __init__(self, profile=None, **kwargs):
430 """Baseclass for all plugins which accept a profile. 431 432 Args: 433 profile: The kernel profile to use for this command. 434 """ 435 super(ProfileCommand, self).__init__(**kwargs) 436 437 # If a profile was provided we must set it into the session and then use 438 # it. (The new profile must control the presence of other dependent 439 # plugins and so forms part of the session's state.). 440 if profile is not None: 441 self.session.profile = profile 442 443 # If the session already has a profile, use it. 444 if self.session.HasParameter("profile_obj"): 445 self.profile = self.session.profile 446 447 # If the profile is required but the session has nothing yet, force 448 # autodetection. 449 elif self.PROFILE_REQUIRED: 450 # Force autodetection... 451 self.profile = self.session.profile 452 453 # Nothing found... bail out! 454 if not self.profile: 455 raise PluginError( 456 "Profile could not detected. " 457 "Try specifying one explicitly.") 458 else: 459 self.profile = obj.NoneObject("No profile")
460
461 462 -class PluginHeader(object):
463 header = None 464 by_name = None 465
466 - def __init__(self, *columns):
467 self.by_name = {} 468 469 for column in columns: 470 if not isinstance(column, dict): 471 raise TypeError("Plugins declaring table header ahead of " 472 "time MUST do so using the new format (" 473 "using dicts, NOT tuples). Table header %r " 474 "is invalid." % columns) 475 476 name = column.get("name") 477 if not name: 478 raise ValueError( 479 "Plugins declaring table headers ahead of " 480 "time MUST specify 'name' for each column. " 481 "Table header %r is invalid." % (columns,)) 482 483 self.by_name[name] = column 484 485 self.header = copy.deepcopy(columns)
486 487 @utils.safe_property
488 - def types_in_output(self):
489 """What types of thing does this plugin output? 490 491 Returns a set of declared types, each type being either a class object 492 or a string name of the class (for profile types, mostly). 493 494 This helps the self-documentation features find plugins based on their 495 declared headers. It's also used by 'collect' to find producers. 496 """ 497 for column in self.header: 498 t = column.get("type") 499 if t: 500 yield t
501
502 - def __iter__(self):
503 return iter(self.header)
504
505 - def __getitem__(self, idx):
506 return self.header[idx]
507
508 - def fill_dict(self, row):
509 """Fills out dict with all the declared columns.""" 510 for header in self.header: 511 column_name = header["name"] 512 if column_name not in row: 513 row[column_name] = None 514 515 return row
516
517 - def dictify(self, row):
518 """Convert an ordered row into a dict. 519 520 Uses the internal column order to map row names to the dict. 521 """ 522 result = {} 523 for idx, header in enumerate(self.header): 524 column_name = header["name"] 525 526 try: 527 result[column_name] = row[idx] 528 except IndexError: 529 result[column_name] = None 530 531 return result
532 533 @utils.safe_property
534 - def all_names(self):
535 return set(self.by_name.iterkeys())
536
537 - def find_column(self, name):
538 """Get the column spec in 'name'.""" 539 return self.by_name.get(name)
540
541 -class ArgsParserMixin(object):
542 """A Mixin which provides argument parsing and validation.""" 543 # Each plugin mixin should define a list of CommandOption instances with 544 # this name (__args). The constructor will collect these definitions into a 545 # self.args parameter available for the plugins at runtime. 546 __args = [] 547 548 # This will contain the parsed constructor args after the plugin is 549 # instantiated. 550 plugin_args = None 551
552 - def __init__(self, *pos_args, **kwargs):
553 self.ignore_required = kwargs.get("ignore_required", False) 554 555 # If this is set we do not enforce required args. This is useful when 556 # callers want to instantiate a plugin in order to use its methods as a 557 # utility. 558 if self.plugin_args is None: 559 self.plugin_args = utils.AttributeDict() 560 561 # Collect args in the declared order (basically follow the mro 562 # backwards). 563 definitions = [] 564 definitions_classes = {} 565 for cls in self.__class__.__mro__: 566 args_definition = getattr(cls, "_%s__args" % cls.__name__, []) 567 for definition in args_definition: 568 # Definitions can be just simple dicts. 569 if isinstance(definition, dict): 570 definition = CommandOption(**definition) 571 572 # We have seen this arg before. 573 previous_definition = definitions_classes.get(definition.name) 574 if previous_definition: 575 # Since we traverse the definition in reverse MRO order, 576 # later definitions should be masked by earlier (more 577 # derived) definitions. 578 continue 579 580 definitions_classes[definition.name] = cls 581 definitions.append(definition) 582 583 # Handle positional args by consuming them off the pos_args array in 584 # definition order. This allows positional args to be specified either 585 # by position, or by keyword. 586 positional_args = [x for x in definitions if x.positional] 587 if len(positional_args) < len(pos_args): 588 raise TypeError("Too many positional args provided.") 589 590 for pos_arg, definition in zip(pos_args, positional_args): 591 # If the positional arg is also defined as a keyword arg this is a 592 # bug. 593 if definition.name in kwargs: 594 raise TypeError( 595 "Positional Args %s is also supplied as a keyword arg." % 596 definition.name) 597 598 kwargs[definition.name] = pos_arg 599 600 # Collect all the declared args and parse them. 601 for definition in definitions: 602 value = kwargs.pop(definition.name, None) 603 if (value is None and definition.required and 604 not self.ignore_required): 605 raise InvalidArgs("%s is required." % definition.name) 606 607 self.plugin_args[definition.name] = definition.parse( 608 value, session=kwargs.get("session")) 609 610 super(ArgsParserMixin, self).__init__(**kwargs)
611
612 613 -class TypedProfileCommand(ArgsParserMixin):
614 """Mixin that provides the plugin with standardized table output.""" 615 616 # Subclasses must override. Has to be a list of column specifications 617 # (i.e. list of dicts specifying the columns). 618 table_header = None 619 table_options = {} 620 621 __args = [ 622 dict(name="verbosity", default=1, type="IntParser", 623 help="An integer reflecting the amount of desired output: " 624 "0 = quiet, 10 = noisy."), 625 ] 626
627 - def __init__(self, *pos_args, **kwargs):
628 super(TypedProfileCommand, self).__init__(*pos_args, **kwargs) 629 if isinstance(self.table_header, (list, tuple)): 630 self.table_header = PluginHeader(*self.table_header) 631 632 # Switch off hidden column when verbosity is high. 633 if self.plugin_args.verbosity > 1: 634 for descriptor in self.table_header: 635 descriptor["hidden"] = False 636 637 super(TypedProfileCommand, self).__init__(*pos_args, **kwargs)
638 639 @classmethod
640 - def args(cls, parser):
641 super(TypedProfileCommand, cls).args(parser) 642 643 # Collect all the declared args and add them to the parser. 644 for cls_i in cls.__mro__: 645 args_definition = getattr(cls_i, "_%s__args" % cls_i.__name__, []) 646 for definition in args_definition: 647 if isinstance(definition, dict): 648 definition = CommandOption(**definition) 649 650 # Allow derived classes to override args from base classes. 651 if definition.name in parser.args: 652 continue 653 654 definition.add_argument(parser)
655
656 - def column_types(self):
657 """Returns instances for each column definition. 658 659 The actual objects that are returned when the plugin runs are often 660 determined at run time because they depend on the profile loaded. 661 662 This method is used in order to introspect the types of each column 663 without actually running the plugin. A plugin must provide an instance 664 for each column without running any code. This allows interospectors to 665 learn about the output format before running the actual plugin. 666 667 Note that this method should almost always be overloaded. We try to do 668 our best here but it is not ideal. Ultimately all plugins will override 669 this method and just declare a column_types() method. 670 """ 671 self.session.logging.warn( 672 "FIXME: Plugin %s (%s) does not produce typed output. " 673 "Please define a column_types() method.", 674 self.name, self.__class__.__name__) 675 676 result = {} 677 columns = [] 678 for column in self.table_header: 679 column_name = column["name"] 680 681 columns.append(column_name) 682 result[column_name] = None 683 684 try: 685 for row_count, row in enumerate(self.collect()): 686 if isinstance(row, dict): 687 result.update(row) 688 689 elif isinstance(row, (tuple, list)): 690 for item, column_name in zip(row, columns): 691 if result.get(column_name) is None: 692 result[column_name] = item 693 694 # One row is sometimes sufficient to figure out types, but 695 # sometimes a plugin will send None as some of its columns 696 # so we try a few more rows. 697 if None not in result.values() or row_count > 5: 698 break 699 700 except (NotImplementedError, TypeError): 701 pass 702 703 return result
704
705 - def collect(self):
706 """Collect data that will be passed to renderer.table_row.""" 707 raise NotImplementedError()
708
709 - def collect_as_dicts(self):
710 for row in self.collect(): 711 # Its already a dict. 712 if isinstance(row, dict): 713 yield self.table_header.fill_dict(row) 714 else: 715 yield self.table_header.dictify(row)
716 717 # Row members which control some output. 718 ROW_OPTIONS = set( 719 ["depth", 720 "annotation", 721 "highlight", 722 "nowrap", 723 "hex_width"] 724 )
725 - def render(self, renderer, **options):
726 table_options = self.table_options.copy() 727 table_options.update(options) 728 729 output_style = self.session.GetParameter("output_style") 730 if output_style == "full": 731 table_options["hidden"] = False 732 733 renderer.table_header(self.table_header, **table_options) 734 for row in self.collect(): 735 if isinstance(row, (list, tuple)): 736 renderer.table_row(*row, **options) 737 else: 738 new_row = [] 739 for column in self.table_header: 740 new_row.append( 741 row.pop(column["name"], None) 742 ) 743 744 if set(row) - self.ROW_OPTIONS: 745 raise RuntimeError( 746 "Plugin produced more data than defined columns (%s)." % 747 (list(row),)) 748 749 renderer.table_row(*new_row, **row)
750
751 - def reflect(self, member):
752 column = self.table_header.by_name.get(member) 753 if not column: 754 raise KeyError("Plugin %r has no column %r." % (self, member)) 755 756 t = column.get("type") 757 758 if isinstance(t, type): 759 return t 760 761 if not t: 762 return None 763 764 if isinstance(t, basestring): 765 return self.profile.object_classes.get(t)
766
767 - def getkeys(self):
768 return self.table_header.keys()
769
770 - def get_column(self, name):
771 for row in self.collect_as_dicts(): 772 yield row[name]
773
774 - def get_column_type(self, name):
775 column = self.table_header.find_column(name) 776 if not column: 777 return 778 779 type_name = column.get("type") 780 781 # If we don't have a type then we have to actually get the instance from 782 # the profile, which will cause a type to be generated at runtime. 783 return self.session.profile.GetPrototype(type_name)
784
785 786 -class Producer(TypedProfileCommand):
787 """Finds and outputs structs of a particular type. 788 789 Producers are very simple plugins that output only a single column 790 which contains a struct of 'type_name'. A good example of a producer are 791 the individual pslist enumeration methods. 792 """ 793 794 # The type of the structs that's returned out of collect and render. 795 type_name = None 796 797 # Declare that this plugin may be called upon to collect structs. 798 producer = True 799 800 @registry.classproperty 801 @registry.memoize
802 - def table_header(self):
803 return PluginHeader(dict(type=self.type_name, name=self.type_name))
804
805 - def collect(self):
806 raise NotImplementedError()
807
808 - def produce(self):
809 """Like collect, but yields the first column instead of whole row.""" 810 for row in self.collect(): 811 yield row[0]
812
813 814 -class CachedProducer(Producer):
815 """A producer backed by a cached session parameter hook.""" 816 817 @utils.safe_property
818 - def hook_name(self):
819 """By convention, the hook name should be the same as our name.""" 820 # Override if you really want to. 821 return self.name
822
823 - def collect(self):
824 for offset in self.session.GetParameter(self.hook_name): 825 yield [self.session.profile.Object( 826 type_name=self.type_name, 827 offset=offset)]
828
829 830 -class KernelASMixin(object):
831 """A mixin for those plugins which require a valid kernel address space. 832 833 This class ensures a valid kernel AS exists or an exception is raised. 834 """ 835 836 __args = [ 837 dict(name="dtb", type="IntParser", default=None, hidden=True, 838 help="The DTB physical address.") 839 ] 840
841 - def __init__(self, *args, **kwargs):
842 """A mixin for plugins which require a valid kernel address space. 843 844 Args: 845 dtb: A potential dtb to be used. 846 """ 847 super(KernelASMixin, self).__init__(*args, **kwargs) 848 849 # If the dtb is specified use that as the kernel address space. 850 if self.plugin_args.dtb is not None: 851 self.kernel_address_space = ( 852 self.session.kernel_address_space.__class__( 853 base=self.physical_address_space, 854 dtb=self.plugin_args.dtb)) 855 else: 856 # Try to load the AS from the session if possible. 857 self.kernel_address_space = self.session.kernel_address_space 858 859 if self.kernel_address_space == None: 860 # Try to guess the AS 861 self.session.plugins.load_as().GetVirtualAddressSpace() 862 863 self.kernel_address_space = self.session.kernel_address_space 864 865 if self.kernel_address_space == None: 866 raise PluginError("kernel_address_space not specified.")
867
868 869 -class PhysicalASMixin(object):
870 """A mixin for those plugins which require a valid physical address space. 871 872 This class ensures a valid physical AS exists or an exception is raised. 873 """ 874 875 PHYSICAL_AS_REQUIRED = True 876 877 @classmethod
878 - def args(cls, metadata):
879 super(PhysicalASMixin, cls).args(metadata) 880 metadata.add_requirement("physical_address_space")
881
882 - def __init__(self, *args, **kwargs):
883 """A mixin for those plugins requiring a physical address space. 884 885 Args: 886 physical_address_space: The physical address space to use. If not 887 specified we use the following options: 888 889 1) session.physical_address_space, 890 891 2) Guess using the load_as() plugin, 892 893 3) Use session.kernel_address_space.base. 894 895 """ 896 super(PhysicalASMixin, self).__init__(*args, **kwargs) 897 self.physical_address_space = self.session.physical_address_space 898 899 if not self.physical_address_space: 900 # Try to guess the AS 901 self.session.plugins.load_as().GetPhysicalAddressSpace() 902 self.physical_address_space = self.session.physical_address_space 903 904 if self.PHYSICAL_AS_REQUIRED and not self.physical_address_space: 905 raise PluginError("Physical address space is not set. " 906 "(Try plugins.load_as)")
907
908 909 -class PrivilegedMixIn(object):
910 - def __init__(self, **kwargs):
911 super(PrivilegedMixIn, self).__init__(**kwargs) 912 if not self.session.privileged: 913 raise PluginError( 914 "Live analysis is only available for interactive or " 915 "privileged sessions.")
916
917 918 -class DataInterfaceMixin(object):
919 """This declares a plugin to present a table-like data interface.""" 920 921 COLUMNS = ()
922
923 924 -class PluginOutput(dict):
925 plugin_cls = DataInterfaceMixin
926
927 928 -class PluginMetadataDatabase(object):
929 """A database of all the currently registered plugin's metadata.""" 930
931 - def __init__(self, session):
932 if session == None: 933 raise RuntimeError("Session must be set") 934 935 self.session = session 936 self.Rebuild()
937
938 - def Rebuild(self):
939 self.db = {} 940 941 for plugin_cls in Command.classes.itervalues(): 942 plugin_name = plugin_cls.name 943 self.db.setdefault(plugin_name, []).append( 944 config.CommandMetadata(plugin_cls))
945
946 - def MetadataByName(self, name):
947 """Return all Implementations that implement command name.""" 948 for command_metadata in self.db[name]: 949 yield command_metadata
950
951 - def GetActivePlugin(self, plugin_name):
952 results = [] 953 for command_metadata in self.db.get(plugin_name, []): 954 plugin_cls = command_metadata.plugin_cls 955 if plugin_cls.is_active(self.session): 956 results.append(command_metadata) 957 958 # We assume there can only be one active plugin implementation. It 959 # is an error to have multiple implementations active at the same 960 # time. 961 if len(results) > 1: 962 raise RuntimeError("Multiple plugin implementations for %s: %s" % ( 963 plugin_name, [x.plugin_cls for x in results])) 964 965 if results: 966 return results[0] 967 968 return obj.NoneObject("Plugin not active")
969
970 - def Serialize(self):
971 result = {} 972 for name in self.db: 973 command_metadata = self.GetActivePlugin(name) 974 if command_metadata: 975 result[name] = command_metadata.Metadata() 976 977 return result
978
979 - def GetRequirments(self, command_name):
980 result = set() 981 for metadata in self.db[command_name]: 982 result.update(metadata.requirements) 983 984 return result
985