Add in renderer selection and a CSV renderer.

This commit is contained in:
Mike Auty
2019-04-07 19:07:32 +01:00
parent 5b8645bd1f
commit 772a30403f
2 changed files with 99 additions and 18 deletions
+11 -2
View File
@@ -93,6 +93,8 @@ class CommandLine(interfaces.plugins.FileConsumerInterface):
volatility.framework.require_interface_version(0, 0, 0)
renderers = dict([(x.name.lower(), x) for x in framework.class_subclasses(text_renderer.CLIRenderer)])
parser = argparse.ArgumentParser(prog = 'volatility', description = "An open-source memory forensics framework")
parser.add_argument(
"-c", "--config", help = "Load the configuration from a json file", default = None, type = str)
@@ -115,6 +117,8 @@ class CommandLine(interfaces.plugins.FileConsumerInterface):
default = "",
type = str)
parser.add_argument("-v", "--verbosity", help = "Increase output verbosity", default = 0, action = "count")
parser.add_argument(
"-l", "--log", help = "Log output to a file as well as the console", default = None, type = str)
parser.add_argument(
"-o",
"--output-dir",
@@ -123,7 +127,12 @@ class CommandLine(interfaces.plugins.FileConsumerInterface):
type = str)
parser.add_argument("-q", "--quiet", help = "Remove progress feedback", default = False, action = 'store_true')
parser.add_argument(
"-l", "--log", help = "Log output to a file as well as the console", default = None, type = str)
"-r",
"--renderer",
metavar = 'RENDERER',
help = "Determines how to render the output",
default = "quick",
choices = list(renderers))
parser.add_argument(
"-f",
"--file",
@@ -253,7 +262,7 @@ class CommandLine(interfaces.plugins.FileConsumerInterface):
json.dump(dict(constructed.build_configuration()), f, sort_keys = True, indent = 2)
# Construct and run the plugin
text_renderer.QuickTextRenderer().render(constructed.run())
renderers[args.renderer]().render(constructed.run())
except exceptions.UnsatisfiedException as excp:
self.process_exceptions(excp)
parser.exit(1, "Unable to validate the plugin requirements: {}\n".format([x for x in excp.unsatisfied]))
+88 -16
View File
@@ -23,7 +23,8 @@ import logging
import random
import string
import sys
from typing import Callable, Any, List, Tuple
from functools import wraps
from typing import Any, List, Tuple
from volatility.framework.renderers import format_hints
@@ -66,18 +67,32 @@ def hex_bytes_as_text(value: bytes) -> str:
return output
class Optional(object):
def optional(func):
def __init__(self, func: Callable[[Any], str]) -> None:
self._func = func
def __call__(self, x: Any) -> str:
@wraps(func)
def wrapped(x: Any) -> str:
if isinstance(x, interfaces.renderers.BaseAbsentValue):
if isinstance(x, renderers.NotApplicableValue):
return "N/A"
else:
return "-"
return self._func(x)
return func(x)
return wrapped
def quoted_optional(func):
@wraps(func)
def wrapped(x: Any) -> str:
result = optional(func)(x)
if result == "-" or result == "N/A":
return ""
if isinstance(x, int) and not isinstance(x, (format_hints.Hex, format_hints.Bin)):
return "{}".format(result)
return "\"{}\"".format(result)
return wrapped
def display_disassembly(disasm: interfaces.renderers.Disassembly) -> str:
@@ -106,17 +121,24 @@ def display_disassembly(disasm: interfaces.renderers.Disassembly) -> str:
return QuickTextRenderer.type_renderers[bytes](disasm.data)
class QuickTextRenderer(interfaces.renderers.Renderer):
class CLIRenderer(interfaces.renderers.Renderer):
"""Class to add specific requirements for CLI renderers"""
name = "unnamed"
class QuickTextRenderer(CLIRenderer):
type_renderers = {
format_hints.Bin: Optional(lambda x: "0b{:b}".format(x)),
format_hints.Hex: Optional(lambda x: "0x{:x}".format(x)),
format_hints.HexBytes: Optional(hex_bytes_as_text),
interfaces.renderers.Disassembly: Optional(display_disassembly),
bytes: Optional(lambda x: " ".join(["{0:2x}".format(b) for b in x])),
datetime.datetime: Optional(lambda x: x.strftime("%Y-%m-%d %H:%M:%S.%f %Z")),
'default': Optional(lambda x: "{}".format(x))
format_hints.Bin: optional(lambda x: "0b{:b}".format(x)),
format_hints.Hex: optional(lambda x: "0x{:x}".format(x)),
format_hints.HexBytes: optional(hex_bytes_as_text),
interfaces.renderers.Disassembly: optional(display_disassembly),
bytes: optional(lambda x: " ".join(["{0:2x}".format(b) for b in x])),
datetime.datetime: optional(lambda x: x.strftime("%Y-%m-%d %H:%M:%S.%f %Z")),
'default': optional(lambda x: "{}".format(x))
}
name = "quick"
def get_render_options(self):
pass
@@ -156,9 +178,59 @@ class QuickTextRenderer(interfaces.renderers.Renderer):
outfd.write("\n")
class PrettyTextRenderer(interfaces.renderers.Renderer):
class CSVRenderer(CLIRenderer):
type_renderers = {
format_hints.Bin: quoted_optional(lambda x: "0b{:b}".format(x)),
format_hints.Hex: quoted_optional(lambda x: "0x{:x}".format(x)),
format_hints.HexBytes: quoted_optional(hex_bytes_as_text),
interfaces.renderers.Disassembly: quoted_optional(display_disassembly),
bytes: quoted_optional(lambda x: " ".join(["{0:2x}".format(b) for b in x])),
datetime.datetime: quoted_optional(lambda x: x.strftime("%Y-%m-%d %H:%M:%S.%f %Z")),
'default': quoted_optional(lambda x: "{}".format(x))
}
name = "csv"
def get_render_options(self):
pass
def render(self, grid: interfaces.renderers.TreeGrid) -> None:
"""
Renders each row immediately to stdout.
Args:
grid: The TreeGrid object to render
"""
outfd = sys.stdout
line = ['"TreeDepth"']
for column in grid.columns:
# Ignore the type because namedtuples don't realize they have accessible attributes
line.append("{}".format('"' + column.name + '"'))
outfd.write("\n{}".format(",".join(line)))
def visitor(node, accumulator):
accumulator.write("\n")
# Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case
accumulator.write(str(max(0, node.path_depth - 1)) + ",")
line = []
for column in grid.columns:
renderer = self.type_renderers.get(column.type, self.type_renderers['default'])
line.append(renderer(node.values[column.index]))
accumulator.write("{}".format(",".join(line)))
return accumulator
grid.populate(visitor, outfd)
outfd.write("\n")
class PrettyTextRenderer(CLIRenderer):
type_renderers = QuickTextRenderer.type_renderers
name = "pretty"
def get_render_options(self):
pass