| Trees | Indices | Help |
|
|---|
|
|
1 #!/usr/bin/python
2
3 # Rekall Memory Forensics
4 # Copyright (C) 2012 Michael Cohen <scudette@gmail.com>
5 # Copyright 2013 Google Inc. All Rights Reserved.
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or (at
10 # your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 # General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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
52 # Allow us to suppress an arg from the --help output for those options
53 # which do not make sense on the command line.
54 if action.dest != "SUPPRESS":
55 super(RekallHelpFormatter, self).add_argument(action)
56
57
59 ignore_errors = False
60
62 kwargs["formatter_class"] = RekallHelpFormatter
63 if session == None:
64 raise RuntimeError("Session must be set")
65 self.session = session
66 super(RekallArgParser, self).__init__(**kwargs)
67
69 if self.ignore_errors:
70 return
71
72 # We trap this error especially since we launch the volshell.
73 if message == "too few arguments":
74 return
75
76 super(RekallArgParser, self).error(message)
77
79 self.ignore_errors = force
80
81 result = super(RekallArgParser, self).parse_known_args(
82 args=args, namespace=namespace)
83
84 return result
85
91
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 # Its a python file.
112 if ext in PYTHON_EXTENSIONS:
113 # Make sure python can find the file.
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 # Make sure python can find the file.
128 sys.path.insert(0, path)
129 try:
130 logging.info("Loading user plugin archive %s", path)
131 for name in zfile.namelist():
132 # Change from filename to python package name.
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 # Argparser stupidly matches short options for options it does not know yet
174 # (with parse_known_args()). This list allows us to declare placeholders to
175 # avoid the partial option matching behaviour in some cases.
176 DISAMBIGUATE_OPTIONS = [
177 "profile",
178 ]
179
180
182 """Parse some session wide args which must be done before anything else."""
183 # Register global args.
184 ConfigureCommandLineParser(config.OPTIONS, parser)
185
186 # Parse the known args.
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 # Argparser erroneously parses flags as str objects, but they are
192 # really unicode objects.
193 if isinstance(value, str):
194 value = utils.SmartUnicode(value)
195
196 # Argparse tries to interpolate defaults into the parsed data in the
197 # event that the args are not present - even when calling
198 # parse_known_args. Before we get to this point, the config system
199 # has already set the state from the config file, so if we allow
200 # argparse to set the default we would override the config file
201 # (with the defaults). We solve this by never allowing argparse
202 # itself to handle the defaults. We always set default=None, when
203 # configuring the parser, and rely on the
204 # config.MergeConfigOptions() to set the defaults.
205 if value is not None:
206 state.Set(arg, value)
207
208 # Enforce the appropriate logging level if user supplies the --verbose
209 # or --quiet command line flags.
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 # Now load the third party user plugins. These may introduce additional
222 # plugins with args.
223 if user_session.state.plugin:
224 LoadPlugins(user_session.state.plugin)
225
226 # External files might have introduced new plugins - rebuild the plugin
227 # DB.
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
259 """Apply the plugin configuration to an argparse parser.
260
261 This method is the essential glue between the abstract plugin metadata and
262 argparse.
263
264 The main intention is to de-couple the plugin's args definition from arg
265 parser's specific implementation. The plugin then conveys semantic meanings
266 about its arguments rather than argparse implementation specific
267 details. Note that args are parsed through other mechanisms in a number of
268 cases so this gives us flexibility to implement arbitrary parsing:
269
270 - Directly provided to the plugin in the constructor.
271 - Parsed from json from the web console.
272 """
273 # This is used to allow the user to break the command line arbitrarily.
274 parser.add_argument('-', dest='__dummy', action="store_true",
275 help="A do nothing arg. Useful to separate options "
276 "which take multiple args from positional. Can be "
277 "specified many times.")
278
279 try:
280 groups = parser.groups
281 except AttributeError:
282 groups = parser.groups = {
283 "None": parser.add_argument_group("Global options")
284 }
285
286 if command_metadata.plugin_cls:
287 groups[command_metadata.plugin_cls.name] = parser.add_argument_group(
288 "Plugin %s options" % command_metadata.plugin_cls.name)
289
290 for name, options in command_metadata.args.iteritems():
291 # We need to modify options to feed into argparse.
292 options = options.copy()
293
294 # Skip this option since it is hidden.
295 if options.pop("hidden", None):
296 options["help"] = argparse.SUPPRESS
297
298 # Prevent None getting into the kwargs because it upsets argparser.
299 kwargs = dict((k, v) for k, v in options.items() if v is not None)
300 name = kwargs.pop("name", None) or name
301
302 # If default is specified we assume the parameter is not required.
303 # However, defaults are not passed on to argparse in most cases, and
304 # instead applied separately through ApplyDefaults. For exceptions,
305 # see below.
306 default = kwargs.pop("default", None)
307 try:
308 required = kwargs.pop("required")
309 except KeyError:
310 required = default is None
311
312 group_name = kwargs.pop("group", None)
313 if group_name is None and command_metadata.plugin_cls:
314 group_name = command_metadata.plugin_cls.name
315
316 group = groups.get(group_name)
317 if group is None:
318 groups[group_name] = group = parser.add_argument_group(group_name)
319
320 positional_args = []
321
322 short_opt = kwargs.pop("short_opt", None)
323
324 # A positional arg is allowed to be specified without a flag.
325 if kwargs.pop("positional", None):
326 positional_args.append(name)
327
328 # If a position arg is optional we need to specify nargs=?
329 if not required:
330 kwargs["nargs"] = "?"
331
332 # Otherwise argparse wants to have - in front of the arg.
333 else:
334 if short_opt:
335 positional_args.append("-" + short_opt)
336
337 positional_args.append("--" + name)
338
339 arg_type = kwargs.pop("type", None)
340 choices = kwargs.pop("choices", [])
341 if callable(choices):
342 choices = choices()
343
344 if arg_type == "ArrayIntParser":
345 kwargs["action"] = ArrayIntParser
346 kwargs["nargs"] = "+" if required else "*"
347
348 if arg_type in ["ArrayString", "ArrayStringParser"]:
349 kwargs["action"] = ArrayStringParser
350 kwargs["nargs"] = "+" if required else "*"
351
352 elif arg_type == "IntParser":
353 kwargs["action"] = IntParser
354
355 elif arg_type == "Float":
356 kwargs["type"] = float
357
358 elif arg_type == "Boolean" or arg_type == "Bool":
359 # Argparse will assume default False for flags and not return
360 # None, which is required by ApplyDefaults to recognize an unset
361 # argument. To solve this issue, we just pass the default on.
362 kwargs["default"] = default
363 kwargs["action"] = "store_true"
364
365 # Multiple entries of choices (requires a choices paramter).
366 elif arg_type == "ChoiceArray":
367 kwargs["nargs"] = "+" if required else "*"
368 kwargs["choices"] = list(choices)
369
370 elif arg_type == "Choices":
371 kwargs["choices"] = list(choices)
372
373 # Skip option if not critical.
374 critical_arg = kwargs.pop("critical", False)
375 if critical and critical_arg:
376 group.add_argument(*positional_args, **kwargs)
377 continue
378
379 if not (critical or critical_arg):
380 group.add_argument(*positional_args, **kwargs)
381
382
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 # Parse the global and critical args from the command line.
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 # The plugin name is taken from the command line, but it is not enough to
412 # know which specific implementation will be used. For example there are 3
413 # classes implementing the pslist plugin WinPsList, LinPsList and OSXPsList.
414 plugin_name, argv = FindPlugin(argv, user_session)
415
416 # Add all critical parameters. Critical parameters are those which are
417 # common to all implementations of a certain plugin and are required in
418 # order to choose from these implementations. For example, the profile or
419 # filename are usually used to select the specific implementation of a
420 # plugin.
421 for metadata in user_session.plugins.plugin_db.MetadataByName(plugin_name):
422 ConfigureCommandLineParser(metadata, parser, critical=True)
423
424 # Parse the global and critical args from the command line.
425 ParseGlobalArgs(parser, argv, user_session)
426
427 # Find the specific implementation of the plugin that applies here. For
428 # example, we have 3 different pslist implementations depending on the
429 # specific profile loaded.
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 # Configure the arg parser for this command's options.
436 plugin_cls = command_metadata.plugin_cls
437 ConfigureCommandLineParser(command_metadata, parser)
438
439 # We handle help especially.
440 if global_flags.help:
441 parser.print_help()
442 sys.exit(-1)
443
444 # Parse the final command line.
445 result = parser.parse_args(argv)
446
447 # Apply the defaults to the parsed args.
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 ## Parser for special args.
457
459 """Class to parse ints either in hex or as ints."""
461 # Support suffixes
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
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
503 return self.parse_int(value)
504
514
515
527
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Oct 9 03:29:38 2017 | http://epydoc.sourceforge.net |