1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 """Rekall's search function.
20
21 The following queries should work and not break.
22
23 1) On Windows with --live API
24
25 * regex match on array of strings - case insensitive.
26
27 select proc, proc.environ from pslist() where proc.environ.TMP =~ "temp"
28
29 2) Format using the hex() method, using as to name columns.
30
31 select hex(VAD.start) as start, hex(VAD.end) as end, Protect from vad(proc_regex: "rekal")
32
33 3) Autoselect column names - second column can not clash with first
34 column name (should be hex, column 1).
35
36 select hex(VAD.start), hex(VAD.end), Protect from vad(proc_regex: "rekal")
37
38 4) Timestamp user function - requires a session to be passed (returns UnixTimeStamp).
39
40 select timestamp(proc.create_time) from pslist()
41
42 5) Yarascan with sub query
43
44 select * from file_yara(paths: (select path.filename from glob("c:\windows\*.exe")).filename, yara_expression: "rule r1 {strings: $a = \"Microsoft\" wide condition: any of them}")
45
46 6) Parameter interpolations:
47
48 a = "select * from file_yara(paths: ( select path.filename from glob({0})).filename, yara_expression: {1})"
49
50 plugins.search(a, query_parameters=[r"c:\windows\*.exe", "rule r1 {strings: $a = \"Microsoft\" wide condition: any of them}"])
51
52 7) WMI integration + unknown field:
53
54 select Result.Name, Result.SessionId, Result.foo from wmi("select * from Win32_Process")
55
56 select Result.Name, Result.BootDevice from wmi("select * from Win32_OperatingSystem")
57
58 8) Describe WMI dynamic query
59
60 describe wmi, dict(query="select * from Win32_Process")
61
62 9) Substitute a single string
63
64 select sub("Microsoft", "MS", Result.Name) from wmi("select * from Win32_OperatingSystem")
65
66 10) Substiture an array
67 select sub("rekal", "REKALL", proc.cmdline) from pslist()
68
69 """
70
71 __author__ = "Adam Sindelar <adamsh@google.com>"
72 import itertools
73 import re
74 import six
75
76 from efilter import api
77 from efilter import ast
78 from efilter import errors
79 from efilter import protocol
80 from efilter import query as q
81
82 from efilter.ext import row_tuple
83
84 from efilter.transforms import asdottysql
85 from efilter.transforms import solve
86 from efilter.transforms import infer_type
87
88 from efilter.protocols import applicative
89 from efilter.protocols import associative
90 from efilter.protocols import repeated
91 from efilter.protocols import structured
92
93 from rekall import obj
94 from rekall import plugin
95 from rekall import testlib
96 from rekall.plugins.response import common
97 from rekall.plugins.overlays import basic
98 from rekall.plugins.common.efilter_plugins import helpers
99 from rekall.ui import identity as identity_renderer
100 from rekall_lib import utils
109
117
125
133
142
143
144 -class FindPlugins(plugin.TypedProfileCommand, plugin.ProfileCommand):
145 """Find which plugin(s) are available to produce the desired output."""
146
147 name = "which_plugin"
148
149 type_name = None
150 producers_only = False
151
152 __args = [
153 dict(name="type_name", required=True, positional=True,
154 help="The name of the type we're looking for. "
155 "E.g.: 'proc' will find psxview, pslist, etc."),
156
157 dict(name="producers_only", required=False, type="Boolean",
158 help="Only include producers: plugins that output "
159 "only this struct and have no side effects.")
160 ]
161
191
199
200
201 -class Collect(plugin.TypedProfileCommand, plugin.ProfileCommand):
202 """Collect instances of struct of type 'type_name'.
203
204 This plugin will find all other plugins that produce 'type_name' and merge
205 all their output. For example, running collect 'proc' will give you a
206 rudimentary psxview.
207
208 This plugin is mostly used by other plugins, like netstat and psxview.
209 """
210
211 name = "collect"
212
213 type_name = None
214
215 __args = [
216 dict(name="type_name", required=True, positional=True,
217 help="The type (struct) to collect.")
218 ]
219
220 @classmethod
222 """Instantiate with suitable default arguments."""
223 return cls(None, session=session)
224
245
255
256
257 -class Lookup(plugin.TypedProfileCommand, plugin.ProfileCommand):
258 """Lookup a global in the profile.
259
260 This plugin lets the user ask for a specific global constant in the
261 active profile.
262 """
263
264 name = "lookup"
265
266 __args = [
267 dict(name="constant", required=True, positional=True,
268 help="The constant to look up in the profile."),
269 dict(name="target", positional=True, default=None,
270 help="The type of the constant."),
271 dict(name="target_args", positional=True, default=None,
272 help="The target args"),
273 ]
274
275 table_header = [
276 dict(name="field")
277 ]
278
284
287 """Wraps a plugin and its output for the purpose of EFILTER searches.
288
289 This is a helper class for the Search plugin. It lets us pretend that
290 plugins are functions to be called from inside EFILTER queries, and also
291 takes care of running the plugin and saving its output and headers.
292
293 Members:
294 plugin_cls: The type of the Command subclass.
295 rows: Output of rendering the plugin.
296 columns: How 'rows' are structured.
297 table_header: If Command is a subclass of TypedProfileCommand then this
298 will contain its table header once applied.
299 """
300 plugin_cls = None
301 plugin_obj = None
302
303 rows = None
304 columns = None
305
306 session = None
307
308
309
310
311 _applied_args = None
312
313 - def __init__(self, plugin_cls, session):
316
318 return "<CommandWrapper: %r>" % (self.plugin_cls.__name__)
319
320
321
322 - def apply(self, args, kwargs):
323 """Instantiate the plugin with given args and run it.
324
325 This caches the output of the plugin. Subsequently, table_header,
326 rows and columns will be populated.
327
328 The CommmandWrapper must not be applied twice with different
329 arguments - each instance represents a unique application.
330
331 Arguments:
332 args, kwargs: Arguments to the plugin.
333 """
334 if self._applied_args is not None:
335
336 if self._applied_args != (args, kwargs):
337 raise ValueError(
338 "%r was previously called with %r but is now being called"
339 " with %r. This should never happen."
340 % (self, self._applied_args, (args, kwargs)))
341
342 return self.rows
343
344 kwargs = kwargs.copy()
345 kwargs.pop("vars", None)
346 self._applied_args = (args, kwargs)
347
348
349 plugin_curry = getattr(self.session.plugins, self.plugin_cls.name)
350 self.plugin_obj = plugin_curry(session=self.session,
351 *args, **kwargs)
352
353 output_header = getattr(self.plugin_cls, "table_header", None)
354 collector = getattr(self.plugin_obj, "collect_as_dicts", None)
355
356 if callable(collector) and output_header is not None:
357
358
359 self.columns = output_header
360 self.rows = repeated.lazy(collector)
361 else:
362
363
364
365 renderer = identity_renderer.IdentityRenderer(session=self.session)
366 with renderer.start():
367 self.session.RunPlugin(self.plugin_cls.name, format=renderer,
368 *args, **kwargs)
369
370
371
372 self.columns = renderer.columns
373 self.rows = repeated.repeated(*list(renderer.rows))
374
375 return self.rows
376
378 """Return the return type* of this CommandWrapper.
379
380 This actually returns a dummy instance (prototype) of the plugin this
381 CommandWrapper wraps. EFILTER allows use of stand-in objects for type
382 inference. We make heavy use of prototypes to represent Rekall's
383 profile-dependent type system.
384 """
385
386 try:
387 return self.plugin_cls.GetPrototype(session=self.session)
388 except NotImplementedError:
389
390
391 return None
392
393
394
395
396 applicative.IApplicative.implicit_static(CommandWrapper)
397
398
399 -class EfilterPlugin(plugin.TypedProfileCommand, plugin.Command):
400
401 """Abstract base class for plugins that do something with queries.
402
403 Provides implementations of the basic EFILTER protocols for selecting and
404 inspecting the output of plugins. Search and Explain extend this.
405 """
406 __abstract = True
407
408 query = None
409 query_source = None
410 query_error = None
411
412 __args = [
413 dict(name="query", required=True, positional=True,
414 help="The dotty/EFILTER query to run."),
415
416 dict(name="query_parameters", type="ArrayString", positional=True,
417 help="Positional parameters for parametrized queries."),
418 ]
419
439
455
456
469
471 """Get all available plugins."""
472 result = dir(self.session.plugins)
473 result += self.scopes.keys()
474
475 return frozenset(result)
476
478 """Find the type* of 'name', which is a plugin.
479
480 * This returns a CommandWrapper which allows plugins to be called from
481 EFILTER queries as functions. EFILTER allows the use of stand-in objects
482 as proxies for actual types, so we make heavy use of plugin and struct
483 prototypes to represent Rekall's profile-dependent type system.
484 """
485 cls = self.session.plugins.plugin_db.GetActivePlugin(name).plugin_cls
486 return CommandWrapper(cls, self.session)
487
488
489
491 """Render the query parsing error in a user-friendly manner."""
492 renderer.section("Query Error")
493
494 try:
495 start = self.query_error.adjusted_start
496 end = self.query_error.adjusted_end
497 source = self.query_error.source
498 text = self.query_error.text
499 except AttributeError:
500
501
502 start = None
503 end = None
504 source = self.query_source
505 text = str(self.query_error)
506
507 if start is not None and end is not None:
508 renderer.format(
509 "EFILTER error ({}) {} at position {}-{} in query:\n{}\n\n",
510 type(self.query_error).__name__, repr(text), start, end,
511 utils.AttributedString(
512 source,
513 [dict(start=start, end=end, fg="RED", bold=True)]))
514 else:
515 renderer.format(
516 "EFILTER error ({}) {} in query:\n{}\n",
517 type(self.query_error).__name__, repr(text), source)
518
520 raise NotImplementedError()
521
522
523
524
525 structured.IStructured.implicit_dynamic(EfilterPlugin)
526
527
528 -class Search(EfilterPlugin):
529 """
530 Searches and recombines output of other plugins.
531
532 Search allows you to use the EFILTER search engine to filter, transform
533 and combine output of most Rekall plugins. The most common use for this
534 is running IOCs.
535
536 ## Some examples
537
538 * Find the process with pid 1:
539
540 ```
541 select * pslist() where proc.pid == 1
542 ```
543
544 * Sort lsof output by file descriptor:
545
546 ```
547 select * from lsof() order by fd
548 ```
549
550 * Filter and sort through lsof in one step:
551
552 ```
553 select * from lsof() where proc.name =~ "rekall" order by fd
554 ```
555
556 * Is there any proc with PID 1, that has a TCPv6 connection and
557 isn't a dead process?
558
559 ```
560 search("(any lsof where (proc.pid == 1 and fileproc.human_type == 'TCPv6'))
561 and not (any dead_procs where (proc.pid == 1))")
562 ```
563
564 Note: "ANY" is just a short hand for "SELECT ANY FROM" which does what
565 it sounds like, and returns True or False depending on whether the
566 query has any results.
567
568 You will probably need to use the *describe* plugin to help
569 discover the exact column structure.
570
571
572 * regex match on array of strings - case insensitive.
573
574 ```
575 (Windows)
576 select proc, proc.environ from pslist() where
577 proc.environ.TMP =~ "temp"
578
579 (Linux)
580 select proc, proc.environ from pslist() where
581 proc.environ.PATH =~ "home"
582 ```
583
584 * Format using the hex() method, using *as* to name columns.
585
586 ```
587 (Windows)
588 select hex(VAD.start) as start, hex(VAD.end) as end,
589 Protect from vad(proc_regex: "rekal")
590
591 (Linux)
592 select hex(start) as start, hex(end) as end, filename
593 from maps(proc_regex: "rekall")
594 ```
595
596 * Autoselect column names - second column can not clash with first
597 column name (should be hex, column 1).
598
599 ```
600 (Windows)
601 select hex(VAD.start), hex(VAD.end), Protect
602 from vad(proc_regex: "rekal")
603
604 (Linux)
605 select hex(start), hex(end), filename from maps(proc_regex: "rekall")
606 ```
607 * Timestamp user function
608
609 ```
610 select proc, timestamp(proc.create_time) from pslist()
611 ```
612
613 * Yarascan with sub query
614
615 ```
616 select * from file_yara(
617 paths: (
618 select path.filename from glob(
619 "c:\windows\*.exe")).filename,
620 yara_expression: "rule r1 {strings: $a = \"Microsoft\" wide condition: any of them}")
621 ```
622
623 On Linux:
624 ```
625 select * from file_yara(
626 paths: (
627 select path.filename from glob(
628 "/home/*/.ssh/*")).filename,
629 yara_expression: "rule r1 {strings: $a = \"ssh-rsa\" condition: any of them}")
630 ```
631
632 * Parameter interpolations:
633
634 ```
635 a = "select * from file_yara(paths: ( select path.filename from glob({0})).filename, yara_expression: {1})"
636
637 search a, [r"c:\windows\*.exe",
638 "rule r1 {strings: $a = \"Microsoft\" wide condition: any of them}"]
639 ```
640 * WMI integration + unknown field:
641
642 ```
643 select Result.Name, Result.SessionId, Result.foo
644 from wmi("select * from Win32_Process")
645
646 select Result.Name, Result.BootDevice
647 from wmi("select * from Win32_OperatingSystem")
648 ```
649
650 * Describe WMI dynamic query
651
652 ```
653 describe wmi, dict(query="select * from Win32_Process")
654 ```
655
656 * Substitute a single string
657
658 ```
659 select sub("Microsoft", "MS", Result.Name)
660 from wmi("select * from Win32_OperatingSystem")
661 ```
662 * Substiture an array
663
664 ```
665 select sub("rekal", "REKALL", proc.cmdline) from pslist()
666 ```
667 """
668 name = "search"
669
670 __args = [
671 dict(name="silent", default=False, type="Boolean",
672 help="Queries should fail silently."),
673 ]
674
676 """Return the search results without displaying them.
677
678 Returns:
679 A list of results from the query solver.
680
681 Raises:
682 EfilterError unless 'silent' flag was set.
683 """
684 try:
685 result = self.solve()
686 return repeated.getvalues(result)
687 except errors.EfilterError:
688 if self.plugin_args.silent:
689 return None
690
691 raise
692
694 """Return the search results exactly as EFILTER returns them.
695
696 Returns:
697 Depends on the query.
698
699 Raises:
700 EfilterError if anything goes wrong.
701 """
702 return solve.solve(self.query, self).value or []
703
704 @utils.safe_property
706 """Get only the first search result.
707
708 This is useful when we need to find a concrete structure for some other
709 purpose, such as finding a concrete allocator zone when writing a
710 'dump_zone' plugin.
711 """
712 try:
713 for result in self.collect():
714 return result
715 except (TypeError, ValueError):
716 return None
717
738
747
761
763
764 if not self.query:
765 return self.render_error(renderer)
766
767
768
769
770
771
772 try:
773 t = infer_type.infer_type(self.query, self)
774 except Exception:
775 t = None
776
777 if isinstance(t, CommandWrapper):
778 raise RuntimeError(
779 "%r is a plugin and must be called as a function. Try '%s()'"
780 " instead of '%s'"
781 % (t.plugin_cls, t.plugin_cls.name, t.plugin_cls.name))
782
783
784 try:
785 rows = self.collect() or []
786 except errors.EfilterError as error:
787 self.query_error = error
788 return self.render_error(renderer)
789
790
791
792
793
794
795 if isinstance(t, plugin.Command):
796 output_header = getattr(t, "table_header", None)
797 if output_header is None:
798 raise plugin.PluginError(
799 "Query is using plugin %s which is not typed." % t.name)
800
801 renderer.table_header(output_header)
802 return self._render_plugin_output(renderer, output_header, rows)
803
804
805
806
807
808
809
810
811
812 remaining_rows = iter(rows)
813 try:
814 first_row = next(remaining_rows)
815 except StopIteration:
816 renderer.format("No results.")
817 return
818
819 all_rows = itertools.chain((first_row,), remaining_rows)
820
821
822
823 if isinstance(first_row, row_tuple.RowTuple):
824 columns = [dict(name=x)
825 for x in structured.getmembers(first_row)]
826 renderer.table_header(columns, auto_widths=True)
827 return self._render_plugin_output(renderer, columns, all_rows)
828
829
830 renderer.table_header([dict(name="result")])
831 return self._render_whatever_i_guess(renderer, all_rows)
832
835 """Prints various information about a query.
836
837 Explains how a query was parsed and how it will be interpreted. It also
838 runs a full type inferencer, to attempt to determine the output of the
839 query once it's executed.
840
841 The Explain plugin can analyse a strict superset of expressions that
842 are valid in the Search plugin. It supports:
843
844 - Any search query that can be passed to Search.
845 - Expressions asking about types and members of profile types
846 (like structs).
847 """
848
849 name = "explain"
850
851
852
853
854
855 input_is_regular_query = True
856
868
874
876 yield expr, depth
877
878 if not isinstance(expr, ast.Expression):
879 return
880
881 for child in expr.children:
882 for expr_, depth in self.recurse_expr(child, depth + 1):
883 yield expr_, depth
884
886 """Render an AST node and recurse."""
887 t = infer_type.infer_type(node, self)
888
889 try:
890 name = "(%s) <%s>" % (t.__name__, type(node).__name__)
891 except AttributeError:
892 name = "(%r) <%s>" % (t, type(node).__name__)
893
894 renderer.table_row(
895 name,
896 utils.AttributedString(
897 str(query),
898 [dict(start=node.start, end=node.end, fg="RED", bold=True)]
899 ),
900 depth=depth
901 )
902
903 for child in node.children:
904 if isinstance(child, ast.Expression):
905 self._render_node(node=child, renderer=renderer, query=query,
906 depth=depth + 1)
907 else:
908 renderer.table_row(
909 "(%s) <leaf: %r>" % (type(child).__name__, child),
910 None,
911 depth=depth + 1
912 )
913
923
925 """Render analysis of the expression's return type and its members."""
926 output_type = infer_type.infer_type(self.query, self)
927
928 renderer.section("Type Analysis", width=140)
929 renderer.table_header([
930 dict(name="name", type="TreeNode", max_depth=2, width=60),
931 dict(name="type", width=40)
932 ])
933
934 renderer.table_row(self.query.source,
935 repr(output_type),
936 depth=1)
937
938 try:
939 for member in structured.getmembers(output_type):
940 subq = "(%s)[%r]" % (self.query.source, member)
941 subtype = infer_type.infer_type(q.Query(subq), self)
942 if isinstance(subtype, type):
943 subtype = subtype.__name__
944 else:
945 subtype = repr(subtype)
946
947 renderer.table_row(subq, subtype, depth=2)
948 except (NotImplementedError, TypeError, AttributeError):
949 pass
950
952 """Render query analysis if the input is a regular query.
953
954 A non-regular query could be the user asking us to explain (e.g.) a
955 struct.
956 """
957 if not self.input_is_regular_query:
958 return
959
960 original_query = self.query.source
961 canonical_query = asdottysql.asdottysql(self.query)
962
963 renderer.section("Query Analysis", width=140)
964 self.render_query(renderer, self.query)
965
966 if canonical_query != original_query:
967 renderer.section("Query Analysis (Using canonical syntax)",
968 width=140)
969 self.render_query(renderer, q.Query(canonical_query))
970
979
980
981
982
983
984
985 applicative.IApplicative.implement(
986 for_type=plugin.Command,
987 implementations={
988 applicative.apply:
989 lambda x, *args, **kwargs: x(*args, **kwargs).collect(),
990
991
992 applicative.reflect_runtime_return: lambda command: command
993 }
994 )
995
996
997
998
999
1000
1001 structured.IStructured.implement(
1002 for_type=plugin.TypedProfileCommand,
1003 implementations={
1004 structured.resolve: lambda _, __: None,
1005 structured.reflect_runtime_member:
1006 lambda c, name: c.get_column_type(name),
1007 structured.getmembers_runtime: lambda c: c.table_header.all_names
1008 }
1009 )
1010
1011
1012
1013
1014
1015
1016 associative.IAssociative.implement(
1017 for_type=plugin.TypedProfileCommand,
1018 implementations={
1019 associative.select: lambda _, __: None,
1020 associative.reflect_runtime_key:
1021 lambda c, name: c.get_column_type(name),
1022 associative.getkeys_runtime: lambda c: c.table_header.all_names
1023 }
1024 )
1025
1026
1027
1028
1029 associative.IAssociative.implement(
1030 for_type=obj.Struct,
1031 implementations={
1032 associative.select: getattr,
1033 associative.reflect_runtime_key: structured.reflect_runtime_member,
1034 associative.getkeys_runtime: structured.getmembers_runtime
1035 }
1036 )
1040 result = set((name for name, _ in item.getproperties()))
1041 result.update(["obj_offset", "obj_type", "obj_name"])
1042 return result
1043
1044
1045
1046
1047
1048 structured.IStructured.implement(
1049 for_type=obj.Struct,
1050 implementations={
1051 structured.resolve: lambda x, y: getattr(x, y, obj.NoneObject("")),
1052 structured.reflect_runtime_member:
1053 lambda s, m: type(getattr(s, m, None)),
1054 structured.getmembers_runtime: Struct_getmembers_runtime,
1055 }
1056 )
1057
1058
1059 structured.IStructured.implement(
1060 for_type=obj.NoneObject,
1061 implementations={
1062 structured.resolve: lambda x, y: x,
1063 }
1064 )
1065
1066
1067 structured.IStructured.implement(
1068 for_type=basic.Flags,
1069 implementations={
1070 structured.resolve: getattr,
1071 structured.reflect_runtime_member:
1072 lambda s, m: type(getattr(s, m, None)),
1073 structured.getmembers_runtime: lambda x: list(x.maskmap),
1074 }
1075 )
1076
1077
1078 associative.IAssociative.implement(
1079 for_type=obj.Array,
1080 implementations={
1081 associative.select: lambda obj, key: obj[key],
1082 }
1083 )
1084
1085
1086
1087 structured.IStructured.implement(
1088 for_type=obj.Array,
1089 implementations={
1090 structured.resolve: getattr
1091 }
1092 )
1096 """Delegate to target of the pointer, if any."""
1097 target_obj = ptr.deref()
1098 if not target_obj:
1099 ptr.session.logging.warn(
1100 "Attempting to access key %r of a void pointer %r.", key, ptr)
1101 if target_obj:
1102 return associative.select(target_obj, key)
1103
1104
1105
1106 associative.IAssociative.implement(
1107 for_type=obj.Pointer,
1108 implementations={
1109 associative.select: select_Pointer
1110 }
1111 )
1115 """Delegate to target of the pointer, if any."""
1116 target_obj = ptr.deref()
1117 if not target_obj:
1118 ptr.session.logging.warn(
1119 "Attempting to access member %r of a void pointer %r.", member, ptr)
1120 if target_obj:
1121 return structured.resolve(target_obj, member)
1122
1123
1124
1125 structured.IStructured.implement(
1126 for_type=obj.Pointer,
1127 implementations={
1128 structured.resolve: resolve_Pointer
1129 }
1130 )
1131
1132
1133
1134
1135
1136
1137 structured.IStructured.implement(
1138 for_type=utils.AttributeDict,
1139 implementations={
1140 structured.resolve: lambda d, m: d.get(m),
1141 structured.getmembers_runtime: lambda d: d.keys(),
1142 }
1143 )
1144
1145
1146
1147 structured.IStructured.implement(
1148 for_type=utils.SlottedObject,
1149 implementations={
1150 structured.resolve: lambda s, m: getattr(s, m, None),
1151 structured.getmembers_runtime: lambda d: d.__slots__,
1152 }
1153 )
1154