1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """This module manages the command line parsing logic.
22
23 Rekall uses the argparse module for command line parsing, however this module
24 contains so many bugs it might be worth to implement our own parser in future.
25 """
26
27 __author__ = "Michael Cohen <scudette@gmail.com>"
28
29 import argparse
30 import logging
31 import re
32 import os
33 import sys
34 import zipfile
35
36 from rekall import config
37 from rekall import constants
38 from rekall import plugin
39 from rekall_lib import utils
40
41
42 config.DeclareOption("--plugin", default=[], type="ArrayStringParser",
43 help="Load user provided plugin bundle.")
44
45 config.DeclareOption(
46 "-h", "--help", default=False, type="Boolean",
47 help="Show help about global paramters.")
48
49
56
57
97
98
100 PYTHON_EXTENSIONS = [".py", ".pyo", ".pyc"]
101
102 for path in paths:
103 if not os.access(path, os.R_OK):
104 logging.error("Unable to find %s", path)
105 continue
106
107 path = os.path.abspath(path)
108 directory, filename = os.path.split(path)
109 module_name, ext = os.path.splitext(filename)
110
111
112 if ext in PYTHON_EXTENSIONS:
113
114 sys.path.insert(0, directory)
115
116 try:
117 logging.info("Loading user plugin %s", path)
118 __import__(module_name)
119 except Exception as e:
120 logging.error("Error loading user plugin %s: %s", path, e)
121 finally:
122 sys.path.pop(0)
123
124 elif ext == ".zip":
125 zfile = zipfile.ZipFile(path)
126
127
128 sys.path.insert(0, path)
129 try:
130 logging.info("Loading user plugin archive %s", path)
131 for name in zfile.namelist():
132
133 module_name, ext = os.path.splitext(name)
134 if ext in PYTHON_EXTENSIONS:
135 module_name = module_name.replace("/", ".").replace(
136 "\\", ".")
137
138 try:
139 __import__(module_name.strip("\\/"))
140 except Exception as e:
141 logging.error("Error loading user plugin %s: %s",
142 path, e)
143
144 finally:
145 sys.path.pop(0)
146
147 else:
148 logging.error("Plugin %s has incorrect extension.", path)
149
150
152 """Truncate the argv list at the first sign of a plugin name.
153
154 At this stage we do not know which module is valid, or its options. The
155 syntax of the command line is:
156
157 rekal -x -y -z plugin_name -a -b -c
158
159 Where -x -y -z are global options, and -a -b -c are plugin option. We only
160 want to parse up to the plugin name.
161 """
162 short_argv = [argv[0]]
163 for item in argv[1:]:
164 for plugin_cls in plugin.Command.classes.values():
165 if plugin_cls.name == item:
166 return short_argv
167
168 short_argv.append(item)
169
170 return short_argv
171
172
173
174
175
176 DISAMBIGUATE_OPTIONS = [
177 "profile",
178 ]
179
180
182 """Parse some session wide args which must be done before anything else."""
183
184 ConfigureCommandLineParser(config.OPTIONS, parser)
185
186
187 known_args, unknown_args = parser.parse_known_args(args=argv)
188
189 with user_session.state as state:
190 for arg, value in vars(known_args).items():
191
192
193 if isinstance(value, str):
194 value = utils.SmartUnicode(value)
195
196
197
198
199
200
201
202
203
204
205 if value is not None:
206 state.Set(arg, value)
207
208
209
210 verbose_flag = getattr(known_args, "verbose", None)
211 quiet_flag = getattr(known_args, "quiet", None)
212
213 if verbose_flag and quiet_flag:
214 raise ValueError("Cannot set both --verbose and --quiet!")
215
216 if verbose_flag:
217 state.Set("logging_level", "DEBUG")
218 elif quiet_flag:
219 state.Set("logging_level", "CRITICAL")
220
221
222
223 if user_session.state.plugin:
224 LoadPlugins(user_session.state.plugin)
225
226
227
228 user_session.plugins.plugin_db.Rebuild()
229
230 return known_args, unknown_args
231
232
234 """Search the argv for the first occurrence of a valid plugin name.
235
236 Returns a mutated argv where the plugin is moved to the front. If a plugin
237 is not found we assume the plugin is "shell" (i.e. the interactive session).
238
239 This maintains backwards compatibility with the old global/plugin specific
240 options. In the current implementation, the plugin name should probably come
241 first:
242
243 rekal pslist -v -f foo.elf --pid 4
244
245 but this still works:
246
247 rekal -v -f foo.elf pslist --pid 4
248 """
249 result = argv[:]
250 for i, item in enumerate(argv):
251 if item in user_session.plugins.plugin_db.db:
252 result.pop(i)
253 return item, result
254
255 return "shell", result
256
257
381
382
383 -def parse_args(argv=None, user_session=None, global_arg_cb=None):
384 """Parse the args from the command line argv.
385
386 Args:
387 argv: The args to process.
388 user_session: The session we work with.
389 global_arg_cb: A callback that will be used to process global
390 args. Global args are those which affect the state of the
391 Rekall framework and must be processed prior to any plugin
392 specific args. In essence these flags control which plugins
393 can be available.
394 """
395 if argv is None:
396 argv = sys.argv[1:]
397
398 parser = RekallArgParser(
399 description=constants.BANNER,
400 conflict_handler='resolve',
401 add_help=True,
402 session=user_session,
403 epilog="When no module is provided, drops into interactive mode",
404 formatter_class=RekallHelpFormatter)
405
406
407 global_flags, unknown_flags = ParseGlobalArgs(parser, argv, user_session)
408 if global_arg_cb:
409 global_arg_cb(global_flags, unknown_flags)
410
411
412
413
414 plugin_name, argv = FindPlugin(argv, user_session)
415
416
417
418
419
420
421 for metadata in user_session.plugins.plugin_db.MetadataByName(plugin_name):
422 ConfigureCommandLineParser(metadata, parser, critical=True)
423
424
425 ParseGlobalArgs(parser, argv, user_session)
426
427
428
429
430 command_metadata = user_session.plugins.Metadata(plugin_name)
431 if not command_metadata:
432 raise plugin.PluginError(
433 "Plugin %s is not available for this configuration" % plugin_name)
434
435
436 plugin_cls = command_metadata.plugin_cls
437 ConfigureCommandLineParser(command_metadata, parser)
438
439
440 if global_flags.help:
441 parser.print_help()
442 sys.exit(-1)
443
444
445 result = parser.parse_args(argv)
446
447
448 result = utils.AttributeDict(vars(result))
449 result.pop("__dummy", None)
450
451 command_metadata.ApplyDefaults(result)
452
453 return plugin_cls, result
454
455
456
457
459 """Class to parse ints either in hex or as ints."""
461
462 multiplier = 1
463 m = re.search("(.*)(Mb|mb|kb|m|M|k|g|G|Gb)", value)
464 if m:
465 value = m.group(1)
466 suffix = m.group(2).lower()
467 if suffix in ("gb", "g"):
468 multiplier = 1024 * 1024 * 1024
469 elif suffix in ("mb", "m"):
470 multiplier = 1024 * 1024
471 elif suffix in ("kb", "k"):
472 multiplier = 1024
473
474 try:
475 if value.startswith("0x"):
476 value = int(value, 16) * multiplier
477 else:
478 value = int(value) * multiplier
479 except ValueError:
480 raise argparse.ArgumentError(self, "Invalid integer value")
481
482 return value
483
484 - def __call__(self, parser, namespace, values, option_string=None):
485 if isinstance(values, basestring):
486 values = self.parse_int(values)
487 setattr(namespace, self.dest, values)
488
489
491 """Parse input as a comma separated list of integers.
492
493 We support input in the following forms:
494
495 --pid 1,2,3,4,5
496
497 --pid 1 2 3 4 5
498
499 --pid 0x1 0x2 0x3
500 """
501
504
505 - def __call__(self, parser, namespace, values, option_string=None):
506 result = []
507 if isinstance(values, basestring):
508 values = [values]
509
510 for value in values:
511 result.extend([self.Validate(x) for x in value.split(",")])
512
513 setattr(namespace, self.dest, result or None)
514
515
517 - def __call__(self, parser, namespace, values, option_string=None):
518 result = []
519
520 if isinstance(values, basestring):
521 values = [values]
522
523 for value in values:
524 result.extend([x for x in value.split(",")])
525
526 setattr(namespace, self.dest, result)
527