mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-10 03:37:39 +02:00
Add in renderer selection and a CSV renderer.
This commit is contained in:
@@ -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]))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user