mirror of
https://github.com/volatilityfoundation/volatility
synced 2026-06-08 18:04:46 +00:00
211 lines
7.7 KiB
Python
211 lines
7.7 KiB
Python
# Volatility
|
|
# Copyright (C) 2008 Volatile Systems
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or (at
|
|
# your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
# General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
|
|
import sys, textwrap
|
|
import volatility.debug as debug
|
|
import volatility.fmtspec as fmtspec
|
|
import volatility.addrspace as addrspace
|
|
|
|
class Command(object):
|
|
""" Base class for each plugin command """
|
|
op = ""
|
|
opts = ""
|
|
args = ""
|
|
cmdname = ""
|
|
# meta_info will be removed
|
|
meta_info = {}
|
|
# Make these class variables so they can be modified across every plugin
|
|
elide_data = True
|
|
tablesep = " "
|
|
|
|
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
|
|
command line. Options are stored in the self.opts attribute.
|
|
"""
|
|
self._config = config
|
|
self._formatlist = []
|
|
|
|
@staticmethod
|
|
def register_options(config):
|
|
"""Registers options into a config object provided"""
|
|
config.add_option("OUTPUT", default = 'text',
|
|
cache_invalidator = False,
|
|
help = "Output in this format (format support is module specific)")
|
|
|
|
config.add_option("OUTPUT-FILE", default = None,
|
|
cache_invalidator = False,
|
|
help = "write output in this file")
|
|
|
|
config.add_option("VERBOSE", default = 0, action = 'count',
|
|
cache_invalidator = False,
|
|
short_option = 'v', help = 'Verbose information')
|
|
|
|
@classmethod
|
|
def help(cls):
|
|
""" This function returns a string that will be displayed when a
|
|
user lists available plugins.
|
|
"""
|
|
try:
|
|
return textwrap.dedent(cls.__doc__)
|
|
except (AttributeError, TypeError):
|
|
return ""
|
|
|
|
@staticmethod
|
|
def is_valid_profile(profile):
|
|
return True
|
|
|
|
def calculate(self):
|
|
""" This function is responsible for performing all calculations
|
|
|
|
We should not have any output functions (e.g. print) in this
|
|
function at all.
|
|
|
|
If this function is expected to take a long time to return
|
|
some data, the function should return a generator.
|
|
"""
|
|
|
|
|
|
def execute(self):
|
|
""" Executes the plugin command."""
|
|
## Executing plugins is done in two stages - first we calculate
|
|
data = self.calculate()
|
|
|
|
## Then we render the result in some way based on the
|
|
## requested output mode:
|
|
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
|
|
else:
|
|
outfd = sys.stdout
|
|
|
|
try:
|
|
func = getattr(self, function_name)
|
|
except AttributeError:
|
|
## Try to find out what formats are supported
|
|
result = []
|
|
for x in dir(self):
|
|
if x.startswith("render_"):
|
|
_a, b = x.split("_", 1)
|
|
result.append(b)
|
|
|
|
print "Plugin {0} is unable to produce output in format {1}. Supported formats are {2}. Please send a feature request".format(self.__class__.__name__, self._config.OUTPUT, result)
|
|
return
|
|
|
|
func(outfd, data)
|
|
|
|
def _formatlookup(self, profile, code):
|
|
"""Code to turn profile specific values into format specifications"""
|
|
code = code or ""
|
|
if not code.startswith('['):
|
|
return code
|
|
|
|
# Strip off the square brackets
|
|
code = code[1:-1].lower()
|
|
if code.startswith('addr'):
|
|
spec = fmtspec.FormatSpec("#10x")
|
|
if profile.metadata.get('memory_model', '32bit') == '64bit':
|
|
spec.minwidth += 8
|
|
if 'pad' in code:
|
|
spec.fill = "0"
|
|
spec.align = spec.align if spec.align else "="
|
|
else:
|
|
# Non-padded addresses will come out as numbers,
|
|
# so titles should align >
|
|
spec.align = ">"
|
|
return spec.to_string()
|
|
|
|
# Something went wrong
|
|
debug.warning("Unknown table format specification: " + code)
|
|
return ""
|
|
|
|
def _elide(self, string, length):
|
|
"""Adds three dots in the middle of a string if it is longer than length"""
|
|
# Only elide data if we've been asked to (which we are by default)
|
|
if not self.elide_data:
|
|
return string
|
|
|
|
if length == -1:
|
|
return string
|
|
if len(string) < length:
|
|
return (" " * (length - len(string))) + string
|
|
elif len(string) == length:
|
|
return string
|
|
else:
|
|
if length < 5:
|
|
debug.error("Cannot elide a string to length less than 5")
|
|
even = ((length + 1) % 2)
|
|
length = (length - 3) / 2
|
|
return string[:length + even] + "..." + string[-length:]
|
|
|
|
def format_value(self, value, fmt):
|
|
""" Formats an individual field using the table formatting codes"""
|
|
profile = addrspace.BufferAddressSpace(self._config).profile
|
|
return ("{0:" + self._formatlookup(profile, fmt) + "}").format(value)
|
|
|
|
def table_header(self, outfd, title_format_list = None):
|
|
"""Table header renders the title row of a table
|
|
|
|
This also stores the header types to ensure
|
|
everything is formatted appropriately.
|
|
It must be a list of tuples rather than a dict for ordering purposes.
|
|
"""
|
|
titles = []
|
|
rules = []
|
|
self._formatlist = []
|
|
profile = addrspace.BufferAddressSpace(self._config).profile
|
|
|
|
for (k, v) in title_format_list:
|
|
spec = fmtspec.FormatSpec(self._formatlookup(profile, v))
|
|
# If spec.minwidth = -1, this field is unbounded length
|
|
if spec.minwidth != -1:
|
|
spec.minwidth = max(spec.minwidth, len(k))
|
|
|
|
# Get the title specification to follow the alignment of the field
|
|
titlespec = fmtspec.FormatSpec(formtype = 's', minwidth = max(spec.minwidth, len(k)))
|
|
titlespec.align = spec.align if spec.align in "<>^" else "<"
|
|
|
|
# Add this to the titles, rules, and formatspecs lists
|
|
titles.append(("{0:" + titlespec.to_string() + "}").format(k))
|
|
rules.append("-" * titlespec.minwidth)
|
|
self._formatlist.append(spec)
|
|
|
|
# Write out the titles and line rules
|
|
if outfd:
|
|
outfd.write(self.tablesep.join(titles) + "\n")
|
|
outfd.write(self.tablesep.join(rules) + "\n")
|
|
|
|
def table_row(self, outfd, *args):
|
|
"""Outputs a single row of a table"""
|
|
reslist = []
|
|
if len(args) > len(self._formatlist):
|
|
debug.error("Too many values for the table")
|
|
for index in range(len(args)):
|
|
spec = self._formatlist[index]
|
|
result = self._elide(("{0:" + spec.to_string() + "}").format(args[index]), spec.minwidth)
|
|
reslist.append(result)
|
|
outfd.write(self.tablesep.join(reslist) + "\n")
|
|
|
|
### Deprecated components
|
|
#
|
|
# This is for removal after 2.2 has been released
|
|
#
|
|
|
|
command = Command
|