Files
volatility3/volatility/framework/automagic/linux.py
T
Mike Auty 9512cbe9eb Commit metadata changeset.
Layers now accept metadata dictionaries (and chain/stack them on top of
those from lower layers).  Metadata can only be set at construction
time, and the metadata dictionary is readonly.  The hope is this will
make enumerating metadata keys across the codebase simpler.

The current metadata items that layers hold is:

architecture (Unknown | Intel32 | Intel64)
os (Unknown | Windows | Linux)
pae (bool)
page_map_offset (int)

This patchset may develop further to help enumerate all of these
(through a registration/reporting system).
2018-04-26 12:48:14 +01:00

204 lines
10 KiB
Python

import logging
import typing
from volatility.framework import interfaces, constants, validity
from volatility.framework.automagic import linux_symbol_cache
from volatility.framework.layers import intel, scanners
from volatility.framework.symbols import linux
vollog = logging.getLogger(__name__)
class LinuxSymbolFinder(interfaces.automagic.AutomagicInterface):
"""Linux symbol loader based on uname signature strings"""
priority = 40
def __init__(self,
context: interfaces.context.ContextInterface,
config_path: str) -> None:
super().__init__(context, config_path)
self._requirements = [] # type: typing.List[typing.Tuple[str, str, interfaces.configuration.ConstructableRequirementInterface]]
self._linux_banners_ = {} # type: linux_symbol_cache.LinuxBanners
@property
def _linux_banners(self) -> linux_symbol_cache.LinuxBanners:
"""Creates a cached copy of the results, but only it's been requested"""
if not self._linux_banners_:
self._linux_banners_ = linux_symbol_cache.LinuxSymbolCache.load_linux_banners()
return self._linux_banners_
def __call__(self,
context: interfaces.context.ContextInterface,
config_path: str,
requirement: interfaces.configuration.RequirementInterface,
progress_callback: validity.ProgressCallback = None) -> None:
"""Searches for LinuxSymbolRequirements and attempt to populate them"""
self._requirements = self.find_requirements(context, config_path, requirement,
(interfaces.configuration.TranslationLayerRequirement,
interfaces.configuration.SymbolRequirement),
shortcut = False)
for (path, sub_path, requirement) in self._requirements:
if (isinstance(requirement, interfaces.configuration.SymbolRequirement) and
requirement.unsatisfied(context, path)):
for (tl_path, tl_sub_path, tl_requirement) in self._requirements:
# Find the TranslationLayer sibling to the SymbolRequirement
if (isinstance(tl_requirement, interfaces.configuration.TranslationLayerRequirement) and
tl_path == path):
if context.config.get(tl_sub_path, None):
self._banner_scan(context, path, requirement, context.config[tl_sub_path],
progress_callback)
break
def _banner_scan(self,
context: interfaces.context.ContextInterface,
config_path: str,
requirement: interfaces.configuration.ConstructableRequirementInterface,
layer_name: str,
progress_callback: validity.ProgressCallback = None) -> None:
"""Accepts a context, config_path and SymbolRequirement, with a constructed layer_name
and scans the layer for linux banners"""
# Bomb out early if there's no banners
if not self._linux_banners:
return
mss = scanners.MultiStringScanner([x for x in self._linux_banners if x is not None])
layer = context.memory[layer_name]
# Check if the Stacker has already found what we're looking for
if layer.config.get('linux_banner', None):
banner_list = [(0, bytes(layer.config['linux_banner'], 'latin-1'))]
else:
# Swap to the physical layer for scanning
# TODO: Fix this so it works for layers other than just Intel
layer = context.memory[layer.config['memory_layer']]
banner_list = layer.scan(context = context, scanner = mss, progress_callback = progress_callback)
for _, banner in banner_list:
vollog.debug("Identified banner: {}".format(repr(banner)))
symbol_files = self._linux_banners.get(banner, None)
if symbol_files:
isf_path = symbol_files[0]
vollog.debug("Using symbol library: {}".format(symbol_files[0]))
clazz = "volatility.framework.symbols.linux.LinuxKernelIntermedSymbols"
# Set the discovered options
path_join = interfaces.configuration.path_join
context.config[path_join(config_path, requirement.name, "class")] = clazz
context.config[path_join(config_path, requirement.name, "isf_url")] = isf_path
# Construct the appropriate symbol table
requirement.construct(context, config_path)
break
else:
vollog.debug("Symbol library path not found: {}".format(symbol_files[0]))
# print("Kernel", banner, hex(banner_offset))
else:
vollog.debug("No existing linux banners found")
# TODO: Fallback to generic regex search?
class LintelStacker(interfaces.automagic.StackerLayerInterface):
stack_order = 12
@classmethod
def stack(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
progress_callback: validity.ProgressCallback = None) \
-> typing.Optional[interfaces.layers.DataLayerInterface]:
"""Attempts to identify linux within this layer"""
layer = context.memory[layer_name]
join = interfaces.configuration.path_join
# Bail out if we're not a physical layer
# TODO: We need a better way of doing this
if isinstance(layer, intel.Intel):
return None
dtb = None
linux_banners = linux_symbol_cache.LinuxSymbolCache.load_linux_banners()
mss = scanners.MultiStringScanner([x for x in linux_banners if x is not None])
for _, banner in layer.scan(context = context, scanner = mss, progress_callback = progress_callback):
vollog.debug("Identified banner: {}".format(repr(banner)))
symbol_files = linux_banners.get(banner, None)
if symbol_files:
isf_path = symbol_files[0]
table_name = context.symbol_space.free_table_name('LintelStacker')
table = linux.LinuxKernelIntermedSymbols(context, 'temporary.' + table_name, name = table_name,
isf_url = isf_path)
context.symbol_space.append(table)
kaslr_shift, _ = LinuxUtilities.find_aslr(context, table_name, layer_name,
progress_callback = progress_callback)
layer_class = intel.Intel # type: typing.Type
if ('init_level4_pgt' in table.symbols):
layer_class = intel.Intel32e
dtb_symbol_name = 'init_level4_pgt'
else:
dtb_symbol_name = 'swapper_pg_dir'
dtb = LinuxUtilities.virtual_to_physical_address(table.get_symbol(dtb_symbol_name).address +
kaslr_shift)
# Build the new layer
new_layer_name = context.memory.free_layer_name("IntelLayer")
config_path = join("IntelHelper", new_layer_name)
context.config[join(config_path, "memory_layer")] = layer_name
context.config[join(config_path, "page_map_offset")] = dtb
context.config[join(config_path, "linux_banner")] = str(banner, 'latin-1')
layer = layer_class(context, config_path = config_path, name = new_layer_name, os = 'Linux')
if layer:
vollog.debug("DTB was found at: 0x{:0x}".format(dtb))
break
return layer
class LinuxUtilities(object):
"""Class with multiple useful linux functions"""
@classmethod
def find_aslr(cls,
context: interfaces.context.ContextInterface,
symbol_table: str,
layer_name: str,
progress_callback: validity.ProgressCallback = None) \
-> typing.Tuple[typing.Optional[int], typing.Optional[int]]:
"""Determines the offset of the actual DTB in physical space and its symbol offset"""
init_task_symbol = symbol_table + constants.BANG + 'init_task'
table_dtb = context.symbol_space.get_symbol(init_task_symbol).address
swapper_signature = b"swapper/0\x00\x00\x00\x00\x00\x00"
module = context.module(symbol_table, layer_name, 0)
for offset in context.memory[layer_name].scan(scanner = scanners.RegExScanner(swapper_signature),
context = context, progress_callback = progress_callback):
task_symbol = module.get_type('task_struct')
image_dtb = offset - task_symbol.relative_child_offset('comm')
init_task = module.object(type_name = 'task_struct', offset = image_dtb)
if init_task.pid != 0:
continue
if hasattr(init_task, 'thread_info') and init_task.thread_info.cast('unsigned int') != 0:
continue
elif (hasattr(init_task, 'state') and init_task.state.cast('unsigned int') != 0):
continue
# This we get for free
aslr_shift = init_task.files.cast('long long unsigned int') - module.get_symbol('init_files').address
kaslr_shift = image_dtb - cls.virtual_to_physical_address(table_dtb)
if aslr_shift & 0xfff != 0 or kaslr_shift & 0xfff != 0:
continue
vollog.debug(
"Linux ASLR shift values determined: physical {:0x} virtual {:0x}".format(kaslr_shift, aslr_shift))
return kaslr_shift, aslr_shift
return None, None
@classmethod
def virtual_to_physical_address(cls, addr: int) -> int:
"""Converts a virtual linux address to a physical one (does not account of ASLR)"""
if addr > 0xffffffff80000000:
return addr - 0xffffffff80000000
return addr - 0xc0000000