mirror of
https://github.com/volatilityfoundation/volatility
synced 2026-06-08 18:04:46 +00:00
Add in initial version of unified output plugin.
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
*.py[cod]
|
||||
|
||||
# Pycharm ide library
|
||||
.idea
|
||||
|
||||
*.swp
|
||||
|
||||
# C extensions
|
||||
|
||||
+12
-3
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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")
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user