Package rekall :: Package ui :: Module json_renderer
[frames] | no frames]

Source Code for Module rekall.ui.json_renderer

  1  # Rekall Memory Forensics 
  2  # Copyright (C) 2012 Michael Cohen 
  3  # Copyright 2014 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 a JSON render. 
 21   
 22  A renderer is used by plugins to produce formatted output. 
 23   
 24  This code is tested in plugins/tools/render_test.py 
 25  """ 
 26  import copy 
 27  import json 
 28  import pdb 
 29  import sys 
 30   
 31  from rekall import constants 
 32  from rekall.ui import renderer as renderer_module 
 33  from rekall_lib import utils 
34 35 36 -class DecodingError(KeyError):
37 """Raised if there is a decoding error."""
38
39 40 -class EncodingError(KeyError):
41 """Raised if we can not encode the object properly."""
42
43 44 -class RobustEncoder(json.JSONEncoder):
45 - def __init__(self, logging=None, **_):
46 super(RobustEncoder, self).__init__(separators=(',', ':')) 47 if logging is None: 48 self.logging = None 49 else: 50 self.logging = logging.getChild("json.encoder.robust")
51
52 - def default(self, o):
53 if self.logging: 54 self.logging.error( 55 "Unable to encode %r (%s) as json, replacing with None", o, 56 type(o)) 57 return None
58
59 60 -def CacheableState(func):
61 """A decorator which caches objects in the renderer's LRU. 62 63 This applies to StateBasedObjectRenderer state dicts, which must have a 64 unique id member. 65 """ 66 67 def DecodeFromJsonSafe(self, value, options): 68 obj_id = None 69 try: 70 obj_id = value.get("id") 71 result = self.renderer.cache.Get(obj_id) 72 except KeyError: 73 result = func(self, value, options) 74 if obj_id is not None: 75 self.renderer.cache.Put(obj_id, result) 76 77 return result
78 79 return DecodeFromJsonSafe 80
81 82 -class JsonObjectRenderer(renderer_module.ObjectRenderer):
83 """An ObjectRenderer for Json encoding. 84 85 For the JsonRenderer we convert objects into json safe python primitives 86 (These must be json serializable). 87 """ 88 renderers = ["JsonRenderer"] 89 90 @classmethod
91 - def cache_key_from_object(cls, item):
92 """Get the cache key from the object.""" 93 try: 94 return item._object_id # pylint: disable=protected-access 95 except AttributeError: 96 # For unregistered objects we can not cache them (Note: The python 97 # id() method is useless because it does not actually guarantee 98 # unique id.). 99 return None
100 101 @classmethod
102 - def FromEncoded(cls, item, renderer):
103 """Get an JsonObjectRenderer class to parse the encoded item.""" 104 if isinstance(item, dict): 105 obj_renderer = item.get("obj_renderer") 106 if obj_renderer is not None: 107 return cls.ImplementationByClass(obj_renderer) 108 109 mro = item.get("mro") 110 if mro is not None: 111 return cls.FromMRO(mro, renderer) 112 113 return cls.ForTarget(item, renderer)
114 115 @staticmethod
116 - def GetImplementationFromMRO(base_class, value):
117 """Get the class referred to by the head of the value's MRO.""" 118 class_name = value["mro"].split(":")[0] 119 120 for cls in base_class.__subclasses__(): 121 if class_name == cls.__name__: 122 return cls
123
124 - def _encode_value(self, item, **options):
125 object_renderer_cls = self.ForTarget(item, self.renderer) 126 127 result = object_renderer_cls( 128 session=self.session, 129 renderer=self.renderer).EncodeToJsonSafe(item, **options) 130 131 return result
132
133 - def _decode_value(self, item, options):
134 object_renderer_cls = self.FromEncoded(item, self.renderer) 135 136 return object_renderer_cls( 137 session=self.session, 138 renderer=self.renderer).DecodeFromJsonSafe(item, options)
139
140 - def render_row(self, item, **options):
141 """The Json object renderer returns a json safe object for encoding.""" 142 self.EncodeToJsonSafe(item, **options)
143
144 - def Summary(self, item, **options):
145 """Returns the object formatted as a string.""" 146 _ = item 147 _ = options 148 return ""
149
150 - def EncodeToJsonSafe(self, item, **options):
151 """Convert the item into a JSON safe item. 152 153 JSON is only capable of encoding some simple types (dict, list, int, 154 float, unicode strings etc). This method is called to convert the item 155 to one of these representations. Note that this method will be called on 156 the ObjectRenderer instance with a renders_type attribute which appears 157 on the item's MRO. 158 159 Args: 160 item: A python object derived from the class mentioned in the 161 renders_type attribite. 162 163 Returns: 164 A JSON serializable object (e.g. dict, list, unicode string etc). 165 """ 166 if item == None: 167 return None 168 169 # If it is a plain dict we just use it as is. 170 elif item.__class__ is dict: 171 # Assume keys are strings. 172 result = {} 173 for k, v in item.items(): 174 result[k] = self._encode_value(v, **options) 175 176 return result 177 178 # Mark encoded lists so we know they are encoded. 179 elif isinstance(item, list): 180 return list(self._encode_value(x, **options) for x in item) 181 182 elif isinstance(item, tuple): 183 return tuple(self._encode_value(x, **options) for x in item) 184 185 # Encode json safe items literally. 186 if isinstance(item, (unicode, int, long, float)): 187 return item 188 189 # This will encode unknown objects as None. We do not raise an error 190 # here in order to succeed in the encoding of arbitrary data. For 191 # example, the session object may contain all kinds of unserializable 192 # objects but we want to ensure we can serialize the session (albeit 193 # with the loss of some of the attributes). 194 self.session.logging.error( 195 "Unable to encode objects of type %s", type(item)) 196 if "strict" in options: 197 raise EncodingError( 198 "Unable to encode objects of type %s" % type(item)) 199 200 return None
201
202 - def DecodeFromJsonSafe(self, value, options):
203 """Decode the item from its Json safe representation. 204 205 This should essentially be the reverse of EncodeToJsonSafe(). Each 206 ObjectRenderer class should implement this method to invert 207 EncodeToJsonSafe(). 208 209 Args: 210 value: The json safe object to decode. 211 options: A dict which will receive any options encoded by the encoder. 212 213 Returns: 214 A python object. 215 """ 216 if value == None: 217 return None 218 219 if value.__class__ is dict: 220 result = dict() 221 for k, v in value.iteritems(): 222 result[k] = self._decode_value(v, options) 223 224 return result 225 226 if value.__class__ is list: 227 return list(self._decode_value(x, options) for x in value) 228 229 if value.__class__ is tuple: 230 return tuple(self._decode_value(x, options) for x in value) 231 232 # Decode json safe items literally. 233 if isinstance(value, (unicode, int, long, float)): 234 return value 235 236 return value
237
238 239 -class StateBasedObjectRenderer(JsonObjectRenderer):
240 """An object renderer which serializes an object to a dict.""" 241 renders_type = "" # Baseclass - does not act by itself. 242 243 @classmethod
244 - def cache_key(cls, item):
245 """Get the decoding cache key from the json safe encoding.""" 246 return item.get("id")
247
248 - def GetState(self, item, **_):
249 _ = item 250 return {}
251 252 @CacheableState
253 - def DecodeFromJsonSafe(self, value, options):
254 value.pop("id", None) 255 return super(StateBasedObjectRenderer, self).DecodeFromJsonSafe( 256 value, options)
257
258 - def EncodeToJsonSafe(self, item, details=False, **options):
259 state = self.GetState(item, **options) 260 if state.__class__ is not dict: 261 raise EncodingError( 262 "%s.GetState method must return a plain dict." % 263 self.__class__.__name__) 264 265 # Store the mro of the item. 266 if not "mro" in state: 267 # Respect what the object renderer asserts about the object's MRO 268 # (mainly to make delegation work). 269 state["mro"] = ":".join(self.get_mro(item)) 270 271 # Store an object ID for this item to ensure that the decoder can re-use 272 # objects if possible. The ID is globally unique for this object and 273 # does not change. 274 try: 275 object_id = item._object_id # pylint: disable=protected-access 276 state["id"] = object_id 277 except AttributeError: 278 pass 279 280 # Add the details view if required. 281 if details: 282 state["details"] = unicode(repr(item)) 283 284 return super(StateBasedObjectRenderer, self).EncodeToJsonSafe( 285 state, **options)
286
287 288 -class StringRenderer(StateBasedObjectRenderer):
289 # Json is not able to encode strings, we therefore must implement a proper 290 # encoder/decoder. 291 renders_type = "str" 292
293 - def DecodeFromJsonSafe(self, value, options):
294 result = value.get("str") 295 if result is None: 296 result = value.get("b64").decode("base64") 297 else: 298 return result.encode("utf8") 299 300 return result
301
302 - def GetState(self, item, **_):
303 try: 304 # If the string happens to be unicode safe we dont need to 305 # encode it, but we still must mark it with a "*" to ensure the 306 # decoder replaces it with a plain string. 307 return dict(str=unicode(item, "utf8")) 308 except UnicodeError: 309 # If we failed to encode it into utf8 we must base64 encode it. 310 return dict(b64=unicode(item.encode("base64")).rstrip("\n"))
311
312 - def EncodeToJsonSafe(self, item, **options):
313 # In many cases we receive a string but it can be represented as unicode 314 # object. To make it easier all round its better to continue handling it 315 # as a unicode object for JSON purposes. 316 try: 317 return item.decode("utf8", "strict") 318 except UnicodeError: 319 return super(StringRenderer, self).EncodeToJsonSafe( 320 item, **options)
321
322 323 -class BaseObjectRenderer(StateBasedObjectRenderer):
324 renders_type = "BaseObject" 325 326 @CacheableState
327 - def DecodeFromJsonSafe(self, value, options):
328 value = super(BaseObjectRenderer, self).DecodeFromJsonSafe( 329 value, options) 330 331 profile = value.pop("profile", None) 332 if profile: 333 value.pop("mro", None) 334 335 return profile.Object(**value)
336
337 - def GetState(self, item, **_):
338 return dict(offset=item.obj_offset, 339 type_name=unicode(item.obj_type), 340 name=unicode(item.obj_name), 341 vm=item.obj_vm, 342 profile=item.obj_profile)
343
344 345 -class JSTreeNodeRenderer(StateBasedObjectRenderer):
346 renders_type = "TreeNode" 347
348 - def DecodeFromJsonSafe(self, state, options):
349 state = super(JSTreeNodeRenderer, self).DecodeFromJsonSafe( 350 state, options) 351 352 result = state.pop("child") 353 options.update(state) 354 355 return result
356
357 - def GetState(self, item, **options):
358 result = options 359 result["child"] = item 360 result["type_name"] = u"TreeNode" 361 362 return result
363
364 365 -class JsonEncoder(object):
366 - def __init__(self, session=None, renderer=None):
367 self.renderer = renderer 368 self.session = session 369 370 self.cache = utils.FastStore(100)
371
372 - def Encode(self, item, **options):
373 """Convert item to a json safe object.""" 374 # Get a Json Safe item. 375 object_renderer = JsonObjectRenderer.ForTarget(item, self.renderer)( 376 session=self.session, renderer=self.renderer) 377 378 # First check the cache. 379 cache_key = object_renderer.cache_key_from_object(item) 380 try: 381 # The contents of this cache are guaranteed to be json safe so we 382 # can copy them. 383 if cache_key is not None: 384 return copy.deepcopy(self.cache.Get(cache_key)) 385 except KeyError: 386 pass 387 388 json_safe_item = object_renderer.EncodeToJsonSafe(item, **options) 389 390 self.cache.Put(cache_key, json_safe_item) 391 return json_safe_item
392
393 394 -class _Empty(object):
395 """An empty class to access the real instance later.""" 396
397 - def __init__(self, session):
398 self.session = session
399
400 401 -class JsonDecoder(object):
402 """A Decoder for JSON encoded data.""" 403
404 - def __init__(self, session, renderer):
405 self.session = session 406 self.renderer = renderer
407
408 - def Decode(self, item, options=None):
409 if options is None: 410 options = {} 411 412 # Find the correct ObjectRenderer that we can use to decode this item. 413 object_renderer_cls = None 414 if isinstance(item, dict): 415 obj_renderer = item.get("obj_renderer") 416 if obj_renderer is not None: 417 object_renderer_cls = JsonObjectRenderer.ImplementationByClass( 418 obj_renderer) 419 420 else: 421 mro = item.get("mro") 422 if mro is not None: 423 object_renderer_cls = JsonObjectRenderer.FromMRO( 424 mro, self.renderer) 425 426 if object_renderer_cls is None: 427 object_renderer_cls = JsonObjectRenderer.ForTarget( 428 item, self.renderer) 429 430 object_renderer = object_renderer_cls( 431 session=self.session, 432 renderer=self.renderer) 433 434 key = object_renderer_cls.cache_key(item) 435 if key is None: 436 return object_renderer.DecodeFromJsonSafe(item, options) 437 438 try: 439 result = self.renderer.cache.Get(key) 440 except KeyError: 441 try: 442 result = object_renderer.DecodeFromJsonSafe(item, options) 443 except Exception as e: 444 pdb.post_mortem() 445 446 self.session.logging.error( 447 "Failed to decode %s: %s", repr(item)[:1000], e) 448 if self.session.GetParameter("debug"): 449 pdb.post_mortem() 450 451 result = None 452 453 self.renderer.cache.Put(key, result) 454 455 return result
456
457 458 -class JsonRenderer(renderer_module.BaseRenderer):
459 """Render the output as a json object. 460 461 The JSON output is designed to be streamed to a remote end - that is results 462 are sent incrementally as soon as they are available. The receiver can then 463 process the results as they come, rendering them to screen or GUI. 464 465 The data is essentially a list of commands. 466 467 Each command is a list. The first parameter is the command name, further 468 parameters are the args to the command. 469 470 Currently the following commands are supported: 471 472 m: This is a metadata, followed by a dict of various metadata. 473 474 s: Start a new section. Followed by section name. 475 476 f: A free format text line. Followed by format string and a list of 477 parameters. Parameters are dicts encoded using the lexicon. 478 479 t: Start a new table. Followed by Table headers. Followed by a list of lists 480 (human_name, name, formatstring). 481 482 r: A table row. Followed by a list of dicts for each row cell. Each row cell 483 is encoded using the lexicon for both keys and values. 484 485 p: A progress message. Followed by a single string which is the formatted 486 message. 487 488 L: Log message sent via session.logging logger. 489 """ 490 491 name = "json" 492 493 progress_interval = 1 494 495 # This will hold a list of JSON commands to buffer them before they are 496 # written to the json file. 497 data = None 498 499 spinner = r"/-\|" 500 last_spin = 0 501
502 - def __init__(self, output=None, send_message_callback=None, **kwargs):
503 super(JsonRenderer, self).__init__(**kwargs) 504 505 self.send_message_callback = send_message_callback 506 507 # Allow the user to dump all output to a file. 508 self.output = output 509 510 # This keeps a list of object renderers which we will use for each 511 # column. 512 self.object_renderers = [] 513 514 fd = None 515 if self.output: 516 if hasattr(self.output, "write") and hasattr(self.output, "flush"): 517 fd = self.output 518 else: 519 # This overwrites the output file with a new json message. 520 fd = open(self.output, "wb") 521 522 if fd == None: 523 fd = self.session.fd 524 525 if fd == None: 526 fd = sys.stdout 527 528 self.fd = fd 529 self.encoder = JsonEncoder(session=self.session, renderer=self) 530 self.decoder = JsonDecoder(session=self.session, renderer=self) 531 532 # A general purpose cache for encoders and decoders. 533 self.cache = utils.FastStore(100) 534 self.data = []
535
536 - def start(self, plugin_name=None, kwargs=None):
537 super(JsonRenderer, self).start(plugin_name=plugin_name, kwargs=kwargs) 538 539 # Save some metadata. 540 self.metadata = dict(plugin_name=unicode(plugin_name), 541 tool_name="rekall", 542 cookie=self._object_id, 543 tool_version=constants.VERSION, 544 session=self.encoder.Encode(self.session)) 545 self.SendMessage( 546 ["m", self.metadata]) 547 548 return self
549
550 - def SendMessage(self, statement):
551 self.data.append(statement)
552
553 - def format(self, formatstring, *args):
554 statement = ["f", unicode(formatstring)] 555 for arg in args: 556 # Just store the statement in the output. 557 statement.append(self.encoder.Encode(arg)) 558 559 self.SendMessage(statement)
560
561 - def section(self, name=None, **kwargs):
562 kwargs["name"] = name 563 self.SendMessage(["s", self.encoder.Encode(kwargs)])
564
565 - def report_error(self, message):
566 self.SendMessage(["e", message])
567
568 - def table_header(self, columns=None, **options):
569 super(JsonRenderer, self).table_header(columns=columns, **options) 570 571 self.object_renderers = [ 572 column_spec.get("type") for column_spec in self.table.column_specs] 573 574 self.SendMessage(["t", self.table.column_specs, options])
575
576 - def table_row(self, *args, **kwargs):
577 result = [] 578 for i, arg in enumerate(args): 579 result.append(self.encoder.Encode( 580 arg, type=self.object_renderers[i])) 581 582 self.SendMessage(["r", result, kwargs])
583
584 - def write_data_stream(self):
585 if self.data: 586 # Just dump out the json object. 587 self.fd.write(json.dumps(self.data, cls=RobustEncoder, 588 separators=(',', ':'), 589 logging=self.session.logging)) 590 self.fd.flush()
591
592 - def flush(self):
593 self.write_data_stream() 594 595 # We store the data here. 596 self.data = []
597
598 - def end(self):
599 # Send a special message marking end of the rendering sequence. 600 self.SendMessage(["x"]) 601 602 super(JsonRenderer, self).end() 603 self.flush()
604
605 - def RenderProgress(self, message=" %(spinner)s", *args, **kwargs):
606 if super(JsonRenderer, self).RenderProgress(): 607 if "%(" in message: 608 self.last_spin += 1 609 kwargs["spinner"] = self.spinner[ 610 self.last_spin % len(self.spinner)] 611 612 formatted_message = message % kwargs 613 elif args: 614 format_args = [] 615 for arg in args: 616 if callable(arg): 617 format_args.append(arg()) 618 else: 619 format_args.append(arg) 620 621 formatted_message = message % tuple(format_args) 622 else: 623 formatted_message = message 624 625 self.SendMessage(["p", formatted_message]) 626 627 return True
628
629 - def Log(self, record):
630 log_message = { 631 "msg": record.getMessage(), 632 "level": record.levelname, 633 "name": record.name, 634 "time": record.created, 635 } 636 self.SendMessage(["L", log_message])
637
638 - def encode(self, obj):
639 """Convenience method for fast encoding of objects. 640 641 Args: 642 obj: An arbitrary object which should be encoded. 643 644 Returns: 645 a Json serializable data object. 646 """ 647 return self.encoder.Encode(obj)
648
649 - def decode(self, data):
650 """Decode a json representation into an object.""" 651 return self.decoder.Decode(data)
652