mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-26 19:44:50 +02:00
Add type-annotations for reamining layer files.
This commit is contained in:
@@ -1,20 +1,25 @@
|
||||
import logging
|
||||
import math
|
||||
import struct
|
||||
import typing
|
||||
|
||||
from volatility.framework import exceptions, interfaces
|
||||
from volatility.framework.configuration import requirements
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
IteratorValue = typing.Tuple[typing.List[typing.Tuple[str, int, int]], int]
|
||||
|
||||
|
||||
class classproperty(object):
|
||||
"""Class property decorator"""
|
||||
"""Class property decorator
|
||||
|
||||
def __init__(self, func):
|
||||
Note this will change the return type """
|
||||
|
||||
def __init__(self, func: typing.Callable[[typing.Any], typing.Any]) -> None:
|
||||
self._func = func
|
||||
|
||||
def __get__(self, owner_self, owner_cls):
|
||||
def __get__(self, _owner_self, owner_cls: typing.Type) -> typing.Any:
|
||||
return self._func(owner_cls)
|
||||
|
||||
|
||||
@@ -32,7 +37,10 @@ class Intel(interfaces.layers.TranslationLayerInterface):
|
||||
_structure = [('page directory', 10, False),
|
||||
('page table', 10, True)]
|
||||
|
||||
def __init__(self, context, config_path, name):
|
||||
def __init__(self,
|
||||
context: interfaces.context.ContextInterface,
|
||||
config_path: str,
|
||||
name: str) -> None:
|
||||
super().__init__(context, config_path, name)
|
||||
self._base_layer = self._check_type(self.config["memory_layer"], str)
|
||||
self._page_map_offset = self._check_type(self.config["page_map_offset"], int)
|
||||
@@ -42,31 +50,31 @@ class Intel(interfaces.layers.TranslationLayerInterface):
|
||||
self._index_shift = int(math.ceil(math.log2(struct.calcsize(self._entry_format))))
|
||||
|
||||
@classproperty
|
||||
def page_size(cls):
|
||||
def page_size(cls) -> int:
|
||||
"""Page size for the intel address space.
|
||||
|
||||
All Intel address spaces work on 4096 byte pages"""
|
||||
return 1 << cls._page_size_in_bits
|
||||
|
||||
@classproperty
|
||||
def bits_per_register(cls):
|
||||
def bits_per_register(cls) -> int:
|
||||
"""Returns the bits_per_register to determine the range of an IntelTranslationLayer"""
|
||||
return cls._bits_per_register
|
||||
|
||||
@classproperty
|
||||
def minimum_address(cls):
|
||||
def minimum_address(cls) -> int: # type: ignore
|
||||
return 0
|
||||
|
||||
@classproperty
|
||||
def maximum_address(cls):
|
||||
def maximum_address(cls) -> int: # type: ignore
|
||||
return (1 << cls._maxvirtaddr) - 1
|
||||
|
||||
@classproperty
|
||||
def structure(cls):
|
||||
def structure(cls) -> typing.List[typing.Tuple[str, int, bool]]:
|
||||
return cls._structure
|
||||
|
||||
@staticmethod
|
||||
def _mask(value, high_bit, low_bit):
|
||||
def _mask(value: int, high_bit: int, low_bit: int) -> int:
|
||||
"""Returns the bits of a value between highbit and lowbit inclusive"""
|
||||
high_mask = (2 ** (high_bit + 1)) - 1
|
||||
low_mask = (2 ** low_bit) - 1
|
||||
@@ -75,11 +83,11 @@ class Intel(interfaces.layers.TranslationLayerInterface):
|
||||
return value & mask
|
||||
|
||||
@staticmethod
|
||||
def _page_is_valid(entry):
|
||||
def _page_is_valid(entry: int) -> bool:
|
||||
"""Returns whether a particular page is valid based on its entry"""
|
||||
return entry & 1
|
||||
return bool(entry & 1)
|
||||
|
||||
def _translate(self, offset):
|
||||
def _translate(self, offset: int) -> typing.Tuple[int, int, str]:
|
||||
"""Translates a specific offset based on paging tables
|
||||
|
||||
Returns the translated offset, the contiguous pagesize that the translated address lives in and the layer_name that the address lives in
|
||||
@@ -121,7 +129,7 @@ class Intel(interfaces.layers.TranslationLayerInterface):
|
||||
page = self._mask(entry, self._maxphyaddr - 1, position + 1) | self._mask(offset, position, 0)
|
||||
return page, 1 << (position + 1), self._base_layer
|
||||
|
||||
def is_valid(self, offset, length = 1):
|
||||
def is_valid(self, offset: int, length: int = 1) -> bool:
|
||||
"""Returns whether the address offset can be translated to a valid address"""
|
||||
try:
|
||||
# TODO: Consider reimplementing this, since calls to mapping can call is_valid
|
||||
@@ -130,12 +138,14 @@ class Intel(interfaces.layers.TranslationLayerInterface):
|
||||
except exceptions.InvalidAddressException:
|
||||
return False
|
||||
|
||||
def mapping(self, offset, length, ignore_errors = False):
|
||||
def mapping(self,
|
||||
offset: int,
|
||||
length: int,
|
||||
ignore_errors: bool = False) -> typing.Iterable[typing.Tuple[int, int, int, str]]:
|
||||
"""Returns a sorted iterable of (offset, mapped_offset, length, layer) mappings
|
||||
|
||||
This allows translation layers to provide maps of contiguous regions in one layer
|
||||
"""
|
||||
result = []
|
||||
if length == 0:
|
||||
if ignore_errors and not self.is_valid(offset):
|
||||
raise StopIteration
|
||||
@@ -156,13 +166,13 @@ class Intel(interfaces.layers.TranslationLayerInterface):
|
||||
offset += chunk_size
|
||||
|
||||
@property
|
||||
def dependencies(self):
|
||||
def dependencies(self) -> typing.List[str]:
|
||||
"""Returns a list of the lower layer names that this layer is dependent upon"""
|
||||
# TODO: Add in the whole buffalo
|
||||
return [self._base_layer]
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
def get_requirements(cls) -> typing.List[interfaces.configuration.RequirementInterface]:
|
||||
return [requirements.TranslationLayerRequirement(name = 'memory_layer',
|
||||
optional = False),
|
||||
requirements.TranslationLayerRequirement(name = 'swap_layer',
|
||||
@@ -174,10 +184,14 @@ class Intel(interfaces.layers.TranslationLayerInterface):
|
||||
requirements.StringRequirement(name = 'linux_banner',
|
||||
optional = True)]
|
||||
|
||||
def _scan_iterator(self, scanner, min_address, max_address):
|
||||
def _scan_iterator(self,
|
||||
scanner: interfaces.layers.ScannerInterface,
|
||||
min_address: int,
|
||||
max_address: int) \
|
||||
-> typing.Iterable[IteratorValue]:
|
||||
previous = None
|
||||
data_to_scan = []
|
||||
scanned_pairs = set()
|
||||
data_to_scan = [] # type: typing.List[typing.Tuple[str, int, int]]
|
||||
scanned_pairs = set() # type: typing.Set[typing.Tuple[int, int]]
|
||||
chunk_end = min_address
|
||||
while chunk_end <= max_address:
|
||||
try:
|
||||
@@ -206,7 +220,13 @@ class Intel(interfaces.layers.TranslationLayerInterface):
|
||||
previous = address
|
||||
chunk_end += chunk_size
|
||||
|
||||
def _scan_chunk(self, scanner, min_address, max_address, progress, iterator_value):
|
||||
# We ignore the type due to the iterator_value, actually it only needs to match the output from _scan_iterator
|
||||
def _scan_chunk(self,
|
||||
scanner: interfaces.layers.ScannerInterface,
|
||||
min_address: int,
|
||||
max_address: int,
|
||||
progress: interfaces.layers.ProgressValue,
|
||||
iterator_value: IteratorValue) -> typing.List[typing.Any]:
|
||||
data_to_scan, chunk_end = iterator_value
|
||||
data = b''
|
||||
for layer_name, address, chunk_size in data_to_scan:
|
||||
@@ -219,7 +239,11 @@ class Intel(interfaces.layers.TranslationLayerInterface):
|
||||
progress.value = chunk_end
|
||||
return list(scanner(data, chunk_end - len(data_to_scan)))
|
||||
|
||||
def _scan_metric(self, _scanner, min_address, max_address, value):
|
||||
def _scan_metric(self,
|
||||
_scanner: interfaces.layers.ScannerInterface,
|
||||
min_address: int,
|
||||
max_address: int,
|
||||
value: int) -> float:
|
||||
return max(0, ((value - min_address) * 100) / (max_address - min_address))
|
||||
|
||||
|
||||
@@ -254,7 +278,7 @@ class Intel32e(Intel):
|
||||
|
||||
class WindowsMixin(object):
|
||||
@staticmethod
|
||||
def _page_is_valid(entry):
|
||||
def _page_is_valid(entry: int) -> bool:
|
||||
"""Returns whether a particular page is valid based on its entry
|
||||
|
||||
Windows uses additional "available" bits to store flags
|
||||
@@ -264,7 +288,7 @@ class WindowsMixin(object):
|
||||
|
||||
For more information, see Windows Internals (6th Ed, Part 2, pages 268-269)
|
||||
"""
|
||||
return (entry & 1) or ((entry & 1 << 11) and not entry & 1 << 10)
|
||||
return bool((entry & 1) or ((entry & 1 << 11) and not entry & 1 << 10))
|
||||
|
||||
|
||||
### These must be full separate classes so that JSON configs re-create them properly
|
||||
|
||||
@@ -5,8 +5,9 @@ Created on 6 Apr 2016
|
||||
"""
|
||||
|
||||
import struct
|
||||
import typing
|
||||
|
||||
from volatility.framework import exceptions, interfaces
|
||||
from volatility.framework import exceptions, interfaces, validity
|
||||
from volatility.framework.layers import segmented
|
||||
|
||||
|
||||
@@ -29,13 +30,16 @@ class LimeLayer(segmented.SegmentedLayer):
|
||||
# XXX move this to a custom SymbolSpace?
|
||||
_header_struct = struct.Struct('<IIQQQ')
|
||||
|
||||
def __init__(self, context, config_path, name):
|
||||
def __init__(self,
|
||||
context: interfaces.context.ContextInterface,
|
||||
config_path: str,
|
||||
name: str) -> None:
|
||||
super().__init__(context, config_path, name)
|
||||
|
||||
# We must run this on creation in order to get the right min/maxaddr in case scanning is our first action
|
||||
self._load_segments()
|
||||
|
||||
def _load_segments(self):
|
||||
def _load_segments(self) -> None:
|
||||
base_layer = self._context.memory[self._base_layer]
|
||||
base_maxaddr = base_layer.maximum_address
|
||||
maxaddr = 0
|
||||
@@ -61,7 +65,9 @@ class LimeLayer(segmented.SegmentedLayer):
|
||||
self._segments = segments
|
||||
|
||||
@classmethod
|
||||
def _check_header(cls, base_layer, offset = 0):
|
||||
def _check_header(cls,
|
||||
base_layer: interfaces.layers.DataLayerInterface,
|
||||
offset: int = 0) -> typing.Tuple[int, int]:
|
||||
header_data = base_layer.read(offset, cls._header_struct.size)
|
||||
(magic, version, start, end, reserved) = cls._header_struct.unpack(header_data)
|
||||
if magic != cls.MAGIC:
|
||||
@@ -75,11 +81,15 @@ class LimeStacker(interfaces.automagic.StackerLayerInterface):
|
||||
stack_order = 10
|
||||
|
||||
@classmethod
|
||||
def stack(cls, context, layer_name, progress_callback = None):
|
||||
def stack(cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
layer_name: str,
|
||||
progress_callback: validity.ProgressCallback = None) \
|
||||
-> typing.Optional[interfaces.layers.DataLayerInterface]:
|
||||
try:
|
||||
LimeLayer._check_header(context.memory[layer_name])
|
||||
except LimeFormatException:
|
||||
return
|
||||
return None
|
||||
new_name = context.memory.free_layer_name("LimeLayer")
|
||||
context.config[interfaces.configuration.path_join(new_name, "base_layer")] = layer_name
|
||||
return LimeLayer(context, new_name, new_name)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import typing
|
||||
|
||||
from volatility.framework import exceptions, interfaces, layers
|
||||
from volatility.framework.configuration import requirements
|
||||
|
||||
@@ -8,26 +10,30 @@ class BufferDataLayer(interfaces.layers.DataLayerInterface):
|
||||
provides = {"type": "physical"}
|
||||
priority = 10
|
||||
|
||||
def __init__(self, context, config_path, name, buffer):
|
||||
def __init__(self,
|
||||
context: interfaces.context.ContextInterface,
|
||||
config_path: str,
|
||||
name: str,
|
||||
buffer: bytes) -> None:
|
||||
super().__init__(context, config_path, name)
|
||||
self._buffer = self._check_type(buffer, bytes)
|
||||
|
||||
@property
|
||||
def maximum_address(self):
|
||||
def maximum_address(self) -> int:
|
||||
"""Returns the largest available address in the space"""
|
||||
return len(self._buffer) - 1
|
||||
|
||||
@property
|
||||
def minimum_address(self):
|
||||
def minimum_address(self) -> int:
|
||||
"""Returns the smallest available address in the space"""
|
||||
return 0
|
||||
|
||||
def is_valid(self, offset, length = 1):
|
||||
def is_valid(self, offset: int, length: int = 1) -> bool:
|
||||
"""Returns whether the offset is valid or not"""
|
||||
return (self.minimum_address <= offset <= self.maximum_address and
|
||||
self.minimum_address <= offset + length - 1 <= self.maximum_address)
|
||||
return bool(self.minimum_address <= offset <= self.maximum_address and
|
||||
self.minimum_address <= offset + length - 1 <= self.maximum_address)
|
||||
|
||||
def read(self, address, length, pad = False):
|
||||
def read(self, address: int, length: int, pad: bool = False) -> bytes:
|
||||
"""Reads the data from the buffer"""
|
||||
if not self.is_valid(address, length):
|
||||
invalid_address = address
|
||||
@@ -37,13 +43,13 @@ class BufferDataLayer(interfaces.layers.DataLayerInterface):
|
||||
"Offset outside of the buffer boundaries")
|
||||
return self._buffer[address:address + length]
|
||||
|
||||
def write(self, address, data):
|
||||
def write(self, address: int, data: bytes):
|
||||
"""Writes the data from to the buffer"""
|
||||
self._check_type(data, bytes)
|
||||
self._buffer = self._buffer[:address] + data + self._buffer[address + len(data):]
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
def get_requirements(cls) -> typing.List[interfaces.configuration.RequirementInterface]:
|
||||
# No real requirements (only the buffer). Need to figure out if there's a better way of representing this
|
||||
return [requirements.BytesRequirement(name = 'buffer', description = "The direct bytes to interact with",
|
||||
optional = False)]
|
||||
@@ -55,23 +61,26 @@ class FileLayer(interfaces.layers.DataLayerInterface):
|
||||
provides = {"type": "physical"}
|
||||
priority = 20
|
||||
|
||||
def __init__(self, context, config_path, name):
|
||||
def __init__(self,
|
||||
context: interfaces.context.ContextInterface,
|
||||
config_path: str,
|
||||
name: str) -> None:
|
||||
super().__init__(context, config_path, name)
|
||||
|
||||
self._location = self.config["location"]
|
||||
self._accessor = layers.ResourceAccessor()
|
||||
self._file_ = None
|
||||
self._size = None
|
||||
self._size = None # type: typing.Optional[int]
|
||||
# Instantiate the file to throw exceptions if the file doesn't open
|
||||
_ = self._file
|
||||
|
||||
@property
|
||||
def location(self):
|
||||
def location(self) -> str:
|
||||
"""Returns the location on which this Layer abstracts"""
|
||||
return self._location
|
||||
|
||||
@property
|
||||
def _file(self):
|
||||
def _file(self) -> typing.IO[typing.Any]:
|
||||
"""Property to prevent the initializer storing an unserializable open file (for context cloning)"""
|
||||
# FIXME: Add "+" to the mode once we've determined whether write mode is enabled
|
||||
mode = "rb"
|
||||
@@ -80,7 +89,7 @@ class FileLayer(interfaces.layers.DataLayerInterface):
|
||||
return self._file_
|
||||
|
||||
@property
|
||||
def maximum_address(self):
|
||||
def maximum_address(self) -> int:
|
||||
"""Returns the largest available address in the space"""
|
||||
# Zero based, so we return the size of the file minus 1
|
||||
if self._size:
|
||||
@@ -92,18 +101,18 @@ class FileLayer(interfaces.layers.DataLayerInterface):
|
||||
return self._size
|
||||
|
||||
@property
|
||||
def minimum_address(self):
|
||||
def minimum_address(self) -> int:
|
||||
"""Returns the smallest available address in the space"""
|
||||
return 0
|
||||
|
||||
def is_valid(self, offset, length = 1):
|
||||
def is_valid(self, offset: int, length: int = 1) -> bool:
|
||||
"""Returns whether the offset is valid or not"""
|
||||
if length <= 0:
|
||||
raise TypeError("Length must be positive")
|
||||
return (self.minimum_address <= offset <= self.maximum_address and
|
||||
self.minimum_address <= offset + length - 1 <= self.maximum_address)
|
||||
return bool(self.minimum_address <= offset <= self.maximum_address and
|
||||
self.minimum_address <= offset + length - 1 <= self.maximum_address)
|
||||
|
||||
def read(self, offset, length, pad = False):
|
||||
def read(self, offset: int, length: int, pad: bool = False) -> bytes:
|
||||
"""Reads from the file at offset for length"""
|
||||
if not self.is_valid(offset, length):
|
||||
invalid_address = offset
|
||||
@@ -122,7 +131,7 @@ class FileLayer(interfaces.layers.DataLayerInterface):
|
||||
self.name + " file")
|
||||
return data
|
||||
|
||||
def write(self, offset, data):
|
||||
def write(self, offset: int, data: bytes) -> None:
|
||||
"""Writes to the file
|
||||
|
||||
This will technically allow writes beyond the extent of the file
|
||||
@@ -136,7 +145,7 @@ class FileLayer(interfaces.layers.DataLayerInterface):
|
||||
self._file.seek(offset)
|
||||
self._file.write(data)
|
||||
|
||||
def __getstate__(self):
|
||||
def __getstate__(self) -> typing.Dict[str, typing.Any]:
|
||||
"""Do not store the open _file_ attribute, our property will ensure the file is open when needed
|
||||
|
||||
This is necessary for multi-processing
|
||||
@@ -144,10 +153,10 @@ class FileLayer(interfaces.layers.DataLayerInterface):
|
||||
self._file_ = None
|
||||
return self.__dict__
|
||||
|
||||
def destroy(self):
|
||||
def destroy(self) -> None:
|
||||
"""Closes the file handle"""
|
||||
self._file.close()
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
def get_requirements(cls) -> typing.List[interfaces.configuration.RequirementInterface]:
|
||||
return [requirements.StringRequirement(name = 'location', optional = False)]
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import logging
|
||||
import os.path as os_path
|
||||
import typing
|
||||
|
||||
from volatility.framework import constants, exceptions, interfaces
|
||||
from volatility.framework import constants, exceptions, interfaces, objects
|
||||
from volatility.framework.configuration import requirements
|
||||
from volatility.framework.configuration.requirements import IntRequirement
|
||||
from volatility.framework.interfaces.configuration import TranslationLayerRequirement
|
||||
@@ -19,7 +20,11 @@ class RegistryInvalidIndex(exceptions.LayerException):
|
||||
|
||||
|
||||
class RegistryHive(interfaces.layers.TranslationLayerInterface):
|
||||
def __init__(self, context, config_path, name, os = "Unknown"):
|
||||
def __init__(self,
|
||||
context: interfaces.context.ContextInterface,
|
||||
config_path: str,
|
||||
name: str,
|
||||
os: str = "Unknown") -> None:
|
||||
super().__init__(context, config_path, name, os)
|
||||
|
||||
self._base_layer = self.config["base_layer"]
|
||||
@@ -51,23 +56,23 @@ class RegistryHive(interfaces.layers.TranslationLayerInterface):
|
||||
"Invalid registry base_block length: {}".format(self._base_block.Length))
|
||||
|
||||
@property
|
||||
def address_mask(self):
|
||||
def address_mask(self) -> int:
|
||||
"""Return a mask that allows for the volatile bit to be set"""
|
||||
return super().address_mask | 0x80000000
|
||||
|
||||
@property
|
||||
def root_cell_offset(self):
|
||||
def root_cell_offset(self) -> int:
|
||||
"""Returns the offset for the root cell in this hive"""
|
||||
return self._base_block.RootCell
|
||||
|
||||
def get_cell(self, cell_offset):
|
||||
def get_cell(self, cell_offset: int) -> 'objects.Struct':
|
||||
"""Returns the appropriate Cell value for a cell offset"""
|
||||
# This would be an _HCELL containing CELL_DATA, but to save time we skip the size of the HCELL
|
||||
cell = self._context.object(symbol = self._table_name + constants.BANG + "_CELL_DATA", offset = cell_offset + 4,
|
||||
layer_name = self.name)
|
||||
return cell
|
||||
|
||||
def get_node(self, cell_offset):
|
||||
def get_node(self, cell_offset: int) -> 'objects.Struct':
|
||||
"""Returns the appropriate Node, interpreted from the Cell based on its Signature"""
|
||||
cell = self.get_cell(cell_offset)
|
||||
signature = cell.cast('string', max_length = 2, encoding = 'latin-1')
|
||||
@@ -89,13 +94,13 @@ class RegistryHive(interfaces.layers.TranslationLayerInterface):
|
||||
"Unknown Signature {} (0x{:x}) at offset {}".format(signature, cell.u.KeyNode.Signature, cell_offset))
|
||||
return cell
|
||||
|
||||
def get_key(self, key):
|
||||
def get_key(self, key: str) -> interfaces.objects.ObjectInterface:
|
||||
"""Gets a specific registry key by key path"""
|
||||
node_key = self.get_node(self.root_cell_offset)
|
||||
if key.endswith("\\"):
|
||||
key = key[:-1]
|
||||
key_array = key.split('\\')
|
||||
found_key = []
|
||||
found_key = [] # type: typing.List[str]
|
||||
while key_array and node_key:
|
||||
for subkey in node_key.get_subkeys():
|
||||
if subkey.helper_name == key_array[0]:
|
||||
@@ -105,10 +110,12 @@ class RegistryHive(interfaces.layers.TranslationLayerInterface):
|
||||
else:
|
||||
node_key = None
|
||||
if not node_key:
|
||||
raise KeyError("Key {} not found under {}", key_array[0], found_key.join('\\'))
|
||||
raise KeyError("Key {} not found under {}", key_array[0], '\\'.join(found_key))
|
||||
return node_key
|
||||
|
||||
def visit_nodes(self, visitor, node = None):
|
||||
def visit_nodes(self,
|
||||
visitor: typing.Callable[[objects.Struct], None],
|
||||
node: typing.Optional[objects.Struct] = None) -> None:
|
||||
"""Applies a callable (visitor) to all nodes within the registry tree from a given node"""
|
||||
if not node:
|
||||
node = self.get_node(self.root_cell_offset)
|
||||
@@ -117,7 +124,7 @@ class RegistryHive(interfaces.layers.TranslationLayerInterface):
|
||||
self.visit_nodes(visitor, node)
|
||||
|
||||
@staticmethod
|
||||
def _mask(value, high_bit, low_bit):
|
||||
def _mask(value: int, high_bit: int, low_bit: int) -> int:
|
||||
"""Returns the bits of a value between highbit and lowbit inclusive"""
|
||||
high_mask = (2 ** (high_bit + 1)) - 1
|
||||
low_mask = (2 ** low_bit) - 1
|
||||
@@ -125,12 +132,13 @@ class RegistryHive(interfaces.layers.TranslationLayerInterface):
|
||||
# print(high_bit, low_bit, bin(mask), bin(value))
|
||||
return value & mask
|
||||
|
||||
def get_requirements(cls):
|
||||
@classmethod
|
||||
def get_requirements(cls) -> typing.List[interfaces.configuration.RequirementInterface]:
|
||||
return [IntRequirement(name = 'hive_offset', description = '', default = 0, optional = False),
|
||||
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"),
|
||||
TranslationLayerRequirement(name = 'base_layer', optional = False)]
|
||||
|
||||
def _translate(self, offset):
|
||||
def _translate(self, offset: int) -> int:
|
||||
"""Translates a single cell index to a cell memory offset and the suboffset within it"""
|
||||
|
||||
# Ignore the volatile bit when determining maxaddr validity
|
||||
@@ -147,7 +155,10 @@ class RegistryHive(interfaces.layers.TranslationLayerInterface):
|
||||
entry = table.Table[table_index]
|
||||
return entry.helper_block_offset + suboffset
|
||||
|
||||
def mapping(self, offset, length, ignore_errors = False):
|
||||
def mapping(self,
|
||||
offset: int,
|
||||
length: int,
|
||||
ignore_errors: bool = False) -> typing.Iterable[typing.Tuple[int, int, int, str]]:
|
||||
|
||||
# TODO: Check the offset and offset + length are not outside the norms
|
||||
if (length < 0):
|
||||
@@ -177,19 +188,19 @@ class RegistryHive(interfaces.layers.TranslationLayerInterface):
|
||||
return response
|
||||
|
||||
@property
|
||||
def dependencies(self):
|
||||
def dependencies(self) -> typing.List[str]:
|
||||
"""Returns a list of layer names that this layer translates onto"""
|
||||
return [self.config['base_layer']]
|
||||
|
||||
def is_valid(self, offset, length = 1):
|
||||
def is_valid(self, offset: int, length: int = 1) -> bool:
|
||||
"""Returns a boolean based on whether the offset is valid or not"""
|
||||
# TODO: Fix me
|
||||
return True
|
||||
|
||||
@property
|
||||
def minimum_address(self):
|
||||
def minimum_address(self) -> int:
|
||||
return self._minaddr
|
||||
|
||||
@property
|
||||
def maximum_address(self):
|
||||
def maximum_address(self) -> int:
|
||||
return self._maxaddr
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import typing
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from bisect import bisect_right
|
||||
|
||||
@@ -11,32 +12,35 @@ class SegmentedLayer(interfaces.layers.TranslationLayerInterface, metaclass = AB
|
||||
In the documentation "mapped address" or "mapped offset" refers to an offset once it has been mapped to the underlying layer
|
||||
"""
|
||||
|
||||
def __init__(self, context, config_path, name):
|
||||
def __init__(self,
|
||||
context: interfaces.configuration.ContextInterface,
|
||||
config_path: str,
|
||||
name: str) -> None:
|
||||
super().__init__(context, config_path = config_path, name = name)
|
||||
|
||||
self._base_layer = self.config["base_layer"]
|
||||
self._segments = []
|
||||
self._minaddr = None
|
||||
self._maxaddr = None
|
||||
self._segments = [] # type: typing.List[typing.Tuple[int, int, int]]
|
||||
self._minaddr = None # type: typing.Optional[int]
|
||||
self._maxaddr = None # type: typing.Optional[int]
|
||||
|
||||
self._load_segments()
|
||||
|
||||
@abstractmethod
|
||||
def _load_segments(self):
|
||||
def _load_segments(self) -> None:
|
||||
"""Populates the _segments variable
|
||||
|
||||
Segments must be (address, mapped address, length) and must be sorted by address when this method exits
|
||||
"""
|
||||
|
||||
def is_valid(self, offset, length = 1):
|
||||
def is_valid(self, offset: int, length: int = 1) -> bool:
|
||||
"""Returns whether the address offset can be translated to a valid address"""
|
||||
try:
|
||||
return all([self._context.memory[self._base_layer].is_valid(mapped_offset) for _, mapped_offset, _, _ in
|
||||
return all([self._context.memory[self._base_layer].is_valid(mapped_offset) for _i, mapped_offset, _i, _s in
|
||||
self.mapping(offset, length)])
|
||||
except exceptions.InvalidAddressException:
|
||||
return False
|
||||
|
||||
def _find_segment(self, offset, next = False):
|
||||
def _find_segment(self, offset: int, next: bool = False) -> typing.Tuple[int, int, int]:
|
||||
"""Finds the segment containing a given offset
|
||||
|
||||
Returns the segment tuple (offset, mapped_offset, length)
|
||||
@@ -57,7 +61,8 @@ class SegmentedLayer(interfaces.layers.TranslationLayerInterface, metaclass = AB
|
||||
return self._segments[i]
|
||||
raise exceptions.InvalidAddressException(self.name, offset, "Invalid address at {:0x}".format(offset))
|
||||
|
||||
def mapping(self, offset, length, ignore_errors = False):
|
||||
def mapping(self, offset: int, length: int, ignore_errors: bool = False) \
|
||||
-> typing.Iterable[typing.Tuple[int, int, int, str]]:
|
||||
"""Returns a sorted iterable of (offset, mapped_offset, length, layer) mappings"""
|
||||
done = False
|
||||
current_offset = offset
|
||||
@@ -94,7 +99,7 @@ class SegmentedLayer(interfaces.layers.TranslationLayerInterface, metaclass = AB
|
||||
done = True
|
||||
|
||||
@property
|
||||
def minimum_address(self):
|
||||
def minimum_address(self) -> int:
|
||||
if not self._segments:
|
||||
raise ValueError("SegmentedLayer must contain some segments")
|
||||
if self._minaddr is None:
|
||||
@@ -103,7 +108,7 @@ class SegmentedLayer(interfaces.layers.TranslationLayerInterface, metaclass = AB
|
||||
return self._minaddr
|
||||
|
||||
@property
|
||||
def maximum_address(self):
|
||||
def maximum_address(self) -> int:
|
||||
if not self._segments:
|
||||
raise ValueError("SegmentedLayer must contain some segments")
|
||||
if self._maxaddr is None:
|
||||
@@ -112,11 +117,11 @@ class SegmentedLayer(interfaces.layers.TranslationLayerInterface, metaclass = AB
|
||||
return self._maxaddr
|
||||
|
||||
@property
|
||||
def dependencies(self):
|
||||
def dependencies(self) -> typing.List[str]:
|
||||
"""Returns a list of the lower layers that this layer is dependent upon"""
|
||||
return [self._base_layer]
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
def get_requirements(cls) -> typing.List[interfaces.configuration.RequirementInterface]:
|
||||
return [requirements.TranslationLayerRequirement(name = 'base_layer',
|
||||
optional = False)]
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import os
|
||||
import struct
|
||||
import typing
|
||||
|
||||
from volatility.framework import interfaces
|
||||
from volatility.framework import interfaces, validity
|
||||
from volatility.framework.configuration import requirements
|
||||
from volatility.framework.layers import physical, segmented
|
||||
from volatility.framework.symbols import native
|
||||
@@ -14,7 +15,10 @@ class VmwareLayer(segmented.SegmentedLayer):
|
||||
header_structure = "<4sII"
|
||||
group_structure = "64sQQ"
|
||||
|
||||
def __init__(self, context, config_path, name):
|
||||
def __init__(self,
|
||||
context: interfaces.context.ContextInterface,
|
||||
config_path: str,
|
||||
name: str) -> None:
|
||||
# Construct these so we can use self.config
|
||||
self._context = context
|
||||
self._config_path = config_path
|
||||
@@ -23,11 +27,11 @@ class VmwareLayer(segmented.SegmentedLayer):
|
||||
# Then call the super, which will call load_segments (which needs the base_layer before it'll work)
|
||||
super().__init__(context, config_path = config_path, name = name)
|
||||
|
||||
def _load_segments(self):
|
||||
def _load_segments(self) -> None:
|
||||
"""Loads up the segments from the meta_layer"""
|
||||
self._read_header()
|
||||
|
||||
def _read_header(self):
|
||||
def _read_header(self) -> None:
|
||||
"""Checks the vmware header to make sure it's valid"""
|
||||
if "vmware" not in self._context.symbol_space:
|
||||
self._context.symbol_space.append(native.NativeTable("vmware", native.std_ctypes))
|
||||
@@ -86,11 +90,11 @@ class VmwareLayer(segmented.SegmentedLayer):
|
||||
self._segments.append((offset, mapped_offset, length))
|
||||
|
||||
@property
|
||||
def dependencies(self):
|
||||
def dependencies(self) -> typing.List[str]:
|
||||
return [self._base_layer, self._meta_layer]
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
def get_requirements(cls) -> typing.List[interfaces.configuration.RequirementInterface]:
|
||||
"""This vmware translation layer always requires a separate metadata layer"""
|
||||
return [requirements.TranslationLayerRequirement(name = 'base_layer',
|
||||
optional = False),
|
||||
@@ -101,10 +105,14 @@ class VmwareLayer(segmented.SegmentedLayer):
|
||||
|
||||
class VmwareStacker(interfaces.automagic.StackerLayerInterface):
|
||||
@classmethod
|
||||
def stack(cls, context, layer_name, progress_callback = None):
|
||||
def stack(cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
layer_name: str,
|
||||
progress_callback: validity.ProgressCallback = None) \
|
||||
-> typing.Optional[interfaces.layers.DataLayerInterface]:
|
||||
"""Attempt to stack this based on the starting information"""
|
||||
if not isinstance(context.memory[layer_name], physical.FileLayer):
|
||||
return
|
||||
return None
|
||||
location = context.memory[layer_name].location
|
||||
if location.endswith(".vmem"):
|
||||
vmss = location[:-5] + ".vmss"
|
||||
@@ -119,10 +127,11 @@ class VmwareStacker(interfaces.automagic.StackerLayerInterface):
|
||||
context.config[interfaces.configuration.path_join(current_config_path, "location")] = vmss
|
||||
context.memory.add_layer(physical.FileLayer(context, current_config_path, current_layer_name))
|
||||
else:
|
||||
return
|
||||
return None
|
||||
new_layer_name = context.memory.free_layer_name("VmwareLayer")
|
||||
context.config[interfaces.configuration.path_join(current_config_path, "base_layer")] = layer_name
|
||||
context.config[
|
||||
interfaces.configuration.path_join(current_config_path, "meta_layer")] = current_layer_name
|
||||
new_layer = VmwareLayer(context, current_config_path, new_layer_name)
|
||||
return new_layer
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user