Add in initial version of unified output plugin.

This commit is contained in:
Mike Auty
2014-09-08 10:05:29 +01:00
parent e7b45292b7
commit 0c2871d7ce
5 changed files with 230 additions and 3 deletions
+3
View File
@@ -1,5 +1,8 @@
*.py[cod]
# Pycharm ide library
.idea
*.swp
# C extensions
+12 -3
View File
@@ -22,6 +22,7 @@ import volatility.debug as debug
import volatility.fmtspec as fmtspec
import volatility.obj as obj
import volatility.registry as registry
import volatility.renderers as renderers
import volatility.addrspace as addrspace
class Command(object):
@@ -38,7 +39,7 @@ class Command(object):
def __init__(self, config, *_args, **_kwargs):
""" Constructor uses args as an initializer. It creates an instance
of OptionParser, populates the options, and finally parses the
of OptionParser, populates the options, and finally parses the
command line. Options are stored in the self.opts attribute.
"""
self._config = config
@@ -88,7 +89,7 @@ class Command(object):
""" Executes the plugin command."""
# Check we can support the plugins
profs = registry.get_plugin_classes(obj.Profile)
# force user to give a profile if a plugin
# force user to give a profile if a plugin
# other than kdbgscan or imageinfo are given:
if self.__class__.__name__.lower() in ["kdbgscan", "imageinfo"] and self._config.PROFILE == None:
self._config.update("PROFILE", "WinXPSP2x86")
@@ -107,7 +108,7 @@ class Command(object):
function_name = "render_{0}".format(self._config.OUTPUT)
if self._config.OUTPUT_FILE:
outfd = open(self._config.OUTPUT_FILE, 'w')
# TODO: We should probably check that this won't blat over an existing file
# TODO: We should probably check that this won't blat over an existing file
else:
outfd = sys.stdout
@@ -217,3 +218,11 @@ class Command(object):
result = self._elide(("{0:" + spec.to_string() + "}").format(args[index]), spec.minwidth)
reslist.append(result)
outfd.write(self.tablesep.join(reslist) + "\n")
def render_text(self, outfd, data):
if not hasattr(self, "unified_output"):
raise NotImplementedError("Render text using the unified output format has not been implemented for this plugin.")
output = self.unified_output(data)
if isinstance(output, renderers.TreeGrid):
pass
+123
View File
@@ -0,0 +1,123 @@
"""Renderers
Renderers display the unified output format in some manner (be it text or file or graphical output"""
import collections
from volatility import validity, fmtspec
class TreeRow(validity.ValidityRoutines):
"""Class providing the interface for an individual Row of the TreeGrid"""
def __init__(self, treegrid, values):
self.type_check(treegrid, TreeGrid)
if not isinstance(self, TreeGrid):
self.type_check(values, list)
treegrid.validate_values(values)
self._treegrid = treegrid
self._children = []
self._values = values
def add_child(self, child):
"""Appends a child to the current Row"""
self.type_check(child, TreeRow)
self._children += [child]
def insert_child(self, child, position):
"""Inserts a child at a specific position in the current Row"""
self.type_check(child, TreeRow)
self._children = self._children[:position] + [child] + self._children[:position]
def clear(self):
"""Removes all children from this row
:rtype : None
"""
self._children = []
@property
def values(self):
"""The individual cell values of the row"""
return self._values
@property
def children(self):
"""Returns an iterator of the children of the current row
:rtype : iterator of TreeRows
"""
for child in self._children:
yield child
def iterator(self, level = 0):
"""Returns an iterator of all rows with their depths
:type level: int
:param level: Indicates the depth of the current iterator
"""
yield (level, self)
for child in self.children:
for grandchild in child.iterator(level + 1):
yield grandchild
Column = collections.namedtuple('Column', ['index', 'name', 'type', 'format'])
class TreeGrid(TreeRow):
"""Class providing the interface for a TreeGrid (which contains TreeRows)"""
simple_types = set((int, str, float, bytes))
def __init__(self, columns):
"""Constructs a TreeGrid object using a specific set of columns
The TreeGrid itself is a root element, that can have children but no values.
The format_hint is a suggestion to the renderer as to how the field should be portrayed as a string,
but it should be noted that the renderer is not under obligation to use it.
:param columns: A list of column tuples made up of (name, type and format_hint).
"""
self.type_check(columns, list)
converted_columns = []
for (name, column_type, column_format) in columns:
is_simple_type = False
for stype in self.simple_types:
is_simple_type = is_simple_type or issubclass(column_type, stype)
if not is_simple_type:
raise TypeError("Column " + name + "'s type " + column_type.__class__.__name__ +
" is not a simple type")
if isinstance(column_format, str):
column_format = fmtspec.FormatSpecification.from_specification(column_format)
if not (column_format is None or isinstance(column_format, fmtspec.FormatSpecification)):
raise TypeError(
"Column " + name + "'s format " + repr(column_format) + " is not an accepted formatter.")
converted_columns.append(Column(len(converted_columns), name, column_type, column_format))
self._columns = converted_columns
# We can use the special type None because we're the top level node without values
TreeRow.__init__(self, self, None)
@property
def columns(self):
"""Returns list of tuples of (name, type and format_hint)"""
for column in self._columns:
yield column
def validate_values(self, values):
"""Takes a list of values and verified them against the column types"""
if len(values) != len(self._columns):
raise ValueError("The length of the values provided does not match the number of columns.")
for column in self._columns:
if not isinstance(values[column.index], column.type):
raise TypeError("Column type " + str(column.index) + " is incorrect.")
def iterator(self, level = 0):
"""Returns an iterator of all rows with their depths
:type level: int
:param level: Indicates the depth of the current iterator
"""
for child in self.children:
for grandchild in child.iterator(level + 1):
yield grandchild
+53
View File
@@ -0,0 +1,53 @@
__author__ = 'mike'
import sys
from volatility.framework.interfaces import renderers as interface
from volatility.framework import renderers
class TextRenderer(interface.Renderer):
def get_render_options(self):
# FIXME: Fill in the docstring and provide render_options
pass
def __init__(self, options):
interface.Renderer.__init__(self, options)
self._options = options
self._column_widths = []
def render(self, grid):
"""Renders a text grid based on the contents of each element"""
self.type_check(grid, renderers.TreeGrid)
# FIXME: Separator should come from options
sep = " | "
max_level = -1
column_maximum_widths = [max(len(column.name), column.format.width) for column in grid.columns]
for (level, row) in grid.iterator():
max_level = max(max_level, level)
column_maximum_widths = [max(column_maximum_widths[column.index], len(
("{0:" + column.format.to_string() + "}").format(row.values[column.index]))) for column in grid.columns]
for column in grid.columns:
column.format.width = column_maximum_widths[column.index]
column.format.fill = ' '
# column.format.align = '<'
# Run through the values and determine their maximum lengths, perhaps build up a two dimensional array
# Then print out the headers and the values at their appropriate spacings
# Potentially warn if the output is likely to be longer than the display area.
headers = [("{0:" + renderers.FormatSpecification(width = column_maximum_widths[column.index], fill = ' ',
align = '^').to_string() + "}").format(column.name) for column
in
grid.columns]
print(sep.join(headers))
for (level, row) in grid.iterator():
row_text = []
for column in grid.columns:
row_text.append(("{:" + column.format.to_string() + "}").format(row.values[column.index]))
line = sep.join(row_text)
sys.stdout.write(line + "\n")
+39
View File
@@ -0,0 +1,39 @@
"""
Created on 4 May 2013
@author: mike
"""
class ValidityRoutines(object):
"""Class to hold all validation routines, such as type checking"""
def type_check(self, value, valid_type):
"""Checks that value is an instance of valid_type, and returns value if it is, or throws a TypeError otherwise
:param value: The value of which to validate the type
:type value: object
:param valid_type: The type against which to validate
:type valid_type: type
"""
assert isinstance(value, valid_type), self.__class__.__name__ + " expected " + \
valid_type.__name__ + ", not " + type(value).__name__
return value
def class_check(self, klass, valid_class):
"""Checks that class is an instance of valid_class, and returns klass if it is, or throws a TypeError otherwise
:param klass: Class to validate
:type klass: class
:param valid_class: Valid class against which to check class validity
:type valid_class: class
"""
assert issubclass(klass, valid_class), self.__class__.__name__ + " expected " + \
valid_class.__name__ + ", not " + klass.__name__
def confirm(self, assertion, error):
"""Acts like an assertion, but will not be disabled when __debug__ is disabled"""
if not assertion:
if error is None:
error = "An unspecified Assertion was not met in " + self.__class__.__name__
raise AssertionError(error)