1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.")
96 """Raised when a hook is invoked recursively."""
97
100 """A runner for a specific plugin."""
101
102 - def __init__(self, session, plugin_name):
106
112
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
124
136
139
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
162
165 """Like a PluginContainer but returns plugin runners."""
166
173
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
215 session = None
216
217
218 _lock = False
219 _pending_parameters = None
220 _pending_hooks = None
221
222 _loaded_filename = None
223
224 - def __init__(self, session=None, **kwargs):
234
236 return "<Configuration Object>"
237
259
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
270
271 os.environ["HOME"] = home
272 return home
273
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
285 self.Set('base_filename', os.path.basename(filename))
286
287
288 if self.session:
289 self.session.Reset()
290
291
292
293 if 'profile' not in parameters:
294
295
296 del self['profile']
297 self['filename'] = filename
298
299 return filename
300
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
308 for context in self.session.context_cache.values():
309 context.reset()
310
311 return set(tracked)
312
314
315 self.session.profile_cache = {}
316
317 return profile_path
318
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
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
368
373
377
388
389 - def Set(self, attr, value):
390 hook = getattr(self, "_set_%s" % attr, None)
391 if hook:
392
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
410
412 self._lock -= 1
413
414 return self
415
416 - def __exit__(self, exc_type, exc_value, trace):
441
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
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
470
473
476
477 - def Broadcast(self, message, *args, **kwargs):
478 for handler in self.callbacks.values():
479 handler(message, *args, **kwargs)
480
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
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
502 """Sets the renderer so messages can be delivered."""
503 self.renderer = renderer_obj
504 self.Flush()
505
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
516 """Base session.
517
518 This session contains the bare minimum to use rekall.
519 """
520
521
522
523
524
525
526
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
539 _address_resolver = None
540
541
542
543 session_id = 0
544
545
546 privileged = False
547
549 self.progress = ProgressDispatcher()
550
551
552 self.profile_cache = {}
553
554
555
556
557 self.plugins = PluginContainer(self)
558
559
560
561
562
563 self.context_cache = {}
564 self._repository_managers = []
565
566
567
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
575 self.logger = kwargs.pop("logger", None)
576 self._logger = None
577
578
579 Session.session_id += 1
580
581
582 self.last = None
583
584
585 self._hook_locks = set()
586
587
588 self._flush_hooks = []
589
590 self.renderers = []
591
592 @utils.safe_property
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
600
601 self._logger = logging.getLogger(logger_name)
602
603
604
605 self._log_handler = HoardingLogHandler()
606
607
608
609
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
622
623 @utils.safe_property
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
633 repository_path = (self.GetParameter("repository_path") or
634 self.GetParameter("profile_path") or [])
635
636 for path in repository_path:
637
638
639
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
667
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
695
696 @utils.safe_property
699
700 @utils.safe_property
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
709 current_context = (self.GetParameter("process_context").obj_offset or
710 "Kernel")
711
712
713 address_resolver = self.context_cache.get(current_context)
714 if address_resolver == None:
715
716 address_resolver = self.plugins.address_resolver()
717 self.context_cache[current_context] = address_resolver
718
719 return address_resolver
720
722 """This will only get called if the attribute does not exist."""
723 return obj.NoneObject("Attribute not set")
724
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
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
754
755
756 if cached:
757 result = self.cache.Get(item)
758 if result is not None:
759 return result
760
761
762
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):
775
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
807
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
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
848
849
850
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
871
872 return result
873
875 """Handle exceptions thrown while trying to run a plugin."""
876 _ = ui_renderer, e
877
878 raise
879
890
918
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
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
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
970 if not result:
971
972 for path, manager in self.repository_managers:
973 try:
974
975
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
999
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
1009
1011 """Called by the library to report back on the progress."""
1012 self.progress.Broadcast(message, *args, **kwargs)
1013
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
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
1034 res = self.GetParameter("physical_address_space", None)
1035 return res
1036
1037 @physical_address_space.setter
1049
1050 @utils.safe_property
1060
1061 @profile.setter
1063
1064
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
1081 new_state.pop("cache", None)
1082
1083
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
1097 with new_session:
1098 for k, v in kwargs.iteritems():
1099 new_session.SetParameter(k, v)
1100 return new_session
1101
1103 """This hook will run when the session is closed."""
1104 self._flush_hooks.append((owner, hook, args))
1105
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
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
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):
1154
1160
1166
1169
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
1186 """Prepare a runner to run the given plugin."""
1187
1188 return PluginRunner(self["session"], name)
1189
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
1199
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
1217 self._start_time = time.time()
1218
1219
1220 self._last_plugin = None
1221
1222
1223 self.pager = obj.NoneObject("Set this to your favourite pager.")
1224
1225
1226 self.session_name = kwargs.pop("session_name", u"Default session")
1227 super(InteractiveSession, self).__init__()
1228
1229
1230 self.locals = DynamicNameSpace(
1231 session=self,
1232
1233
1234 v=self.v,
1235
1236
1237 sys=sys, os=os,
1238
1239
1240 session_list=self.session_list,
1241
1242
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
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
1263
1270
1278
1304
1306 """Re-execute the previous command."""
1307 if self.last:
1308 self.RunPlugin(self.last)
1309
1311 for x in arg:
1312 self.printer(x)
1313
1316
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
1332
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
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
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