Layers: Change mapping signature to return domain length

This commit is contained in:
Mike Auty
2020-03-04 20:29:42 +00:00
committed by ikelos
parent a2ac55ba0b
commit 77be83edc4
11 changed files with 109 additions and 92 deletions
+1 -1
View File
@@ -323,7 +323,7 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
try:
kvp = vlayer.mapping(kvo, 0)
if (any([(p == kernel['mz_offset'] and layer_name == physical_layer_name)
for (_, p, _, layer_name) in kvp])):
for (_, _, p, _, layer_name) in kvp])):
valid_kernels[virtual_layer_name] = (kvo, kernel)
# Sit the virtual offset under the TranslationLayer it applies to
context.config[kvo_path] = kvo
+44 -66
View File
@@ -377,8 +377,9 @@ class TranslationLayerInterface(DataLayerInterface, metaclass = ABCMeta):
"""
@abstractmethod
def mapping(self, offset: int, length: int, ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, str]]:
"""Returns a sorted iterable of (offset, mapped_offset, length, layer)
def mapping(self, offset: int, length: int,
ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, int, str]]:
"""Returns a sorted iterable of (offset, sublength, mapped_offset, mapped_length, layer)
mappings.
ignore_errors will provide all available maps with gaps, but
@@ -394,13 +395,32 @@ class TranslationLayerInterface(DataLayerInterface, metaclass = ABCMeta):
"""Returns a list of layer names that this layer translates onto."""
return []
def _decode(self, data: bytes, mapped_offset: int, offset: int) -> bytes:
"""Decodes any necessary data."""
return data
def _decode(self, layer_name: str, mapped_offset: int, offset: int, output_length: int) -> bytes:
"""Decodes any necessary data.
def _encode(self, data: bytes, mapped_offset: int, offset: int) -> bytes:
"""Encodes any necessary data."""
return data
Args:
layer_name: The layer from which the data should be taken
value: The new value to encode
mapped_offset: The offset in the underlying layer where the data would begin
offset: The offset in the higher-layer where the data would begin
output_length: The expected length of the returned data
Returns:
The data to be read from the underlying layer."""
return self._context.layers.read(layer_name, mapped_offset, output_length)
def _encode(self, layer_name: str, mapped_offset: int, offset: int, value: bytes) -> bytes:
"""Encodes any necessary data.
Args:
layer_name: The layer from which the data should be taken
mapped_offset: The offset in the underlying layer where the data would begin
offset: The offset in the higher-layer where the data would begin
value: The new value to encode
Returns:
The data to be rewritten at mapped_offset."""
return value
# ## Read/Write functions for mapped pages
@@ -409,83 +429,41 @@ class TranslationLayerInterface(DataLayerInterface, metaclass = ABCMeta):
"""Reads an offset for length bytes and returns 'bytes' (not 'str') of
length size."""
current_offset = offset
output = [] # type: List[bytes]
for (layer_offset, mapped_offset, mapped_length, layer) in self.mapping(offset, length, ignore_errors = pad):
output = b'' # type: bytes
for (layer_offset, sublength, mapped_offset, mapped_length, layer) in self.mapping(offset,
length,
ignore_errors = pad):
if not pad and layer_offset > current_offset:
raise exceptions.InvalidAddressException(
self.name, current_offset, "Layer {} cannot map offset: {}".format(self.name, current_offset))
elif layer_offset > current_offset:
output += [b"\x00" * (layer_offset - current_offset)]
output += b"\x00" * (layer_offset - current_offset)
current_offset = layer_offset
# The layer_offset can be less than the current_offset in non-linearly mapped layers
# it does not suggest an overlap, but that the data is in an encoded block
if mapped_length > 0:
processed_data = self._decode(self._context.layers.read(layer, mapped_offset, mapped_length, pad),
mapped_offset, layer_offset)
# Chop off anything unnecessary at the start
processed_data = processed_data[current_offset - layer_offset:]
# Chop off anything unnecessary at the end
processed_data = processed_data[:length - (current_offset - offset)]
output += [processed_data]
current_offset += len(processed_data)
recovered_data = b"".join(output)
return recovered_data + b"\x00" * (length - len(recovered_data))
processed_data = self._decode(layer, mapped_offset, layer_offset, sublength)
if len(processed_data) != sublength:
raise ValueError("ProcessedData length does not match expected length of chunk")
output += processed_data
current_offset += sublength
return output + (b"\x00" * (length - len(output)))
def write(self, offset: int, value: bytes) -> None:
"""Writes a value at offset, distributing the writing across any
underlying mapping."""
current_offset = offset
length = len(value)
for (layer_offset, mapped_offset, mapped_length, layer) in self.mapping(offset, length):
for (layer_offset, sublength, mapped_offset, mapped_length, layer) in self.mapping(offset, length):
if layer_offset > current_offset:
raise exceptions.InvalidAddressException(
self.name, current_offset, "Layer {} cannot map offset: {}".format(self.name, current_offset))
original_data = self._context.layers.read(layer, mapped_offset, mapped_length)
# Always chunk the value based on the mapping
value_to_write = original_data[:current_offset - layer_offset] + value[:mapped_length -
(current_offset - layer_offset)]
value = value[mapped_length - (current_offset - layer_offset):]
encoded_value = self._encode(value_to_write, mapped_offset, layer_offset)
if len(encoded_value) != mapped_length:
raise exceptions.LayerException(self.name,
"Unable to write new value, does not map to the same dimensions")
self._context.layers.write(layer, mapped_offset, encoded_value)
current_offset += len(value_to_write)
# ## Scan implementation with knowledge of pages
value_chunk = value[layer_offset - offset:layer_offset - offset + sublength]
new_data = self._encode(layer, mapped_offset, layer_offset, value_chunk)
self._context.layers.write(layer, mapped_offset, new_data)
def _scan_iterator(self, scanner: 'ScannerInterface',
sections: Iterable[Tuple[int, int]]) -> Iterable[IteratorValue]:
"""Essentially, for paged systems we take a bunch of pages and chunk them up into scanner.page_size or
as large a chunk as possible (if there are gaps)."""
for (section_start, section_length) in sections:
# For each section, split it into scan size chunks
for chunk_start in range(section_start, section_start + section_length, scanner.chunk_size):
# Shorten it, if we're at the end of the section
chunk_length = min(section_start + section_length - chunk_start, scanner.chunk_size + scanner.overlap)
# Prev offset keeps track of the end of the previous subchunk
prev_offset = chunk_start
output = [] # type: List[Tuple[str, int, int]]
# We populate the response based on subchunks that may be mapped all over the place
for mapped in self.mapping(chunk_start, chunk_length, ignore_errors = True):
offset, mapped_offset, length, layer_name = mapped
# We need to check if the offset is next to the end of the last one (contiguous)
if offset != prev_offset:
# Only yield if we've accumulated output
if len(output):
# Yield all the (joined) items so far
# and the ending point of that subchunk (where we'd gotten to previously)
yield output, prev_offset
output = []
# Shift the marker up to the end of what we just received and add it to the output
prev_offset = offset + length
output += [(layer_name, mapped_offset, length)]
# If there's still output left, output it
if len(output):
yield output, prev_offset
current_offset += len(value_chunk)
class LayerContainer(collections.abc.Mapping):
+6 -5
View File
@@ -166,13 +166,14 @@ class Intel(linear.LinearlyMappedLayer):
# TODO: Consider reimplementing this, since calls to mapping can call is_valid
return all([
self._context.layers[layer].is_valid(mapped_offset)
for _, mapped_offset, _, layer in self.mapping(offset, length)
for _, _, mapped_offset, _, layer in self.mapping(offset, length)
])
except exceptions.InvalidAddressException:
return False
def mapping(self, offset: int, length: int, ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, str]]:
"""Returns a sorted iterable of (offset, mapped_offset, length, layer)
def mapping(self, offset: int, length: int,
ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, int, str]]:
"""Returns a sorted iterable of (offset, sublength, mapped_offset, mapped_length, layer)
mappings.
This allows translation layers to provide maps of contiguous
@@ -187,7 +188,7 @@ class Intel(linear.LinearlyMappedLayer):
if not ignore_errors:
raise
return
yield (offset, mapped_offset, length, layer_name)
yield offset, length, mapped_offset, length, layer_name
return
while length > 0:
try:
@@ -207,7 +208,7 @@ class Intel(linear.LinearlyMappedLayer):
length -= length_diff
offset += length_diff
else:
yield (offset, chunk_offset, chunk_size, layer_name)
yield offset, length, chunk_offset, chunk_size, layer_name
length -= chunk_size
offset += chunk_size
+37 -4
View File
@@ -1,5 +1,5 @@
import functools
from typing import List, Optional, Tuple
from typing import List, Optional, Tuple, Iterable
from volatility.framework import exceptions, interfaces
@@ -13,7 +13,7 @@ class LinearlyMappedLayer(interfaces.layers.TranslationLayerInterface):
def translate(self, offset: int, ignore_errors: bool = False) -> Tuple[Optional[int], Optional[str]]:
mapping = list(self.mapping(offset, 0, ignore_errors))
if len(mapping) == 1:
original_offset, mapped_offset, _, layer = mapping[0]
original_offset, _, mapped_offset, _, layer = mapping[0]
if original_offset != offset:
raise exceptions.LayerException(self.name,
"Layer {} claims to map linearly but does not".format(self.name))
@@ -34,7 +34,7 @@ class LinearlyMappedLayer(interfaces.layers.TranslationLayerInterface):
length size."""
current_offset = offset
output = [] # type: List[bytes]
for (offset, mapped_offset, mapped_length, layer) in self.mapping(offset, length, ignore_errors = pad):
for (offset, _, mapped_offset, mapped_length, layer) in self.mapping(offset, length, ignore_errors = pad):
if not pad and offset > current_offset:
raise exceptions.InvalidAddressException(
self.name, current_offset, "Layer {} cannot map offset: {}".format(self.name, current_offset))
@@ -54,7 +54,7 @@ class LinearlyMappedLayer(interfaces.layers.TranslationLayerInterface):
underlying mapping."""
current_offset = offset
length = len(value)
for (offset, mapped_offset, length, layer) in self.mapping(offset, length):
for (offset, _, mapped_offset, length, layer) in self.mapping(offset, length):
if offset > current_offset:
raise exceptions.InvalidAddressException(
self.name, current_offset, "Layer {} cannot map offset: {}".format(self.name, current_offset))
@@ -63,3 +63,36 @@ class LinearlyMappedLayer(interfaces.layers.TranslationLayerInterface):
self._context.layers.write(layer, mapped_offset, value[:length])
value = value[length:]
current_offset += length
def _scan_iterator(self, scanner: 'interfaces.layers.ScannerInterface',
sections: Iterable[Tuple[int, int]]) -> Iterable[interfaces.layers.IteratorValue]:
"""Essentially, for paged systems we take a bunch of pages and chunk them up into scanner.page_size or
as large a chunk as possible (if there are gaps)."""
for (section_start, section_length) in sections:
# For each section, split it into scan size chunks
for chunk_start in range(section_start, section_start + section_length, scanner.chunk_size):
# Shorten it, if we're at the end of the section
chunk_length = min(section_start + section_length - chunk_start, scanner.chunk_size + scanner.overlap)
# Prev offset keeps track of the end of the previous subchunk
prev_offset = chunk_start
output = [] # type: List[Tuple[str, int, int]]
# We populate the response based on subchunks that may be mapped all over the place
for mapped in self.mapping(chunk_start, chunk_length, ignore_errors = True):
offset, _, mapped_offset, mapped_length, layer_name = mapped
# We need to check if the offset is next to the end of the last one (contiguous)
if offset != prev_offset:
# Only yield if we've accumulated output
if len(output):
# Yield all the (joined) items so far
# and the ending point of that subchunk (where we'd gotten to previously)
yield output, prev_offset
output = []
# Shift the marker up to the end of what we just received and add it to the output
prev_offset = offset + mapped_length
output += [(layer_name, mapped_offset, mapped_length)]
# If there's still output left, output it
if len(output):
yield output, prev_offset
+7 -4
View File
@@ -136,8 +136,9 @@ class PdbMultiStreamFormat(linear.LinearlyMappedLayer):
def is_valid(self, offset: int, length: int = 1) -> bool:
return self.context.layers[self._base_layer].is_valid(offset, length)
def mapping(self, offset: int, length: int, ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, str]]:
yield (offset, offset, length, self._base_layer)
def mapping(self, offset: int, length: int,
ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, int, str]]:
yield offset, length, offset, length, self._base_layer
def get_stream(self, index) -> Optional['PdbMSFStream']:
self.read_streams()
@@ -182,7 +183,8 @@ class PdbMSFStream(linear.LinearlyMappedLayer):
requirements.IntRequirement(name = 'maximum_size')
]
def mapping(self, offset: int, length: int, ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, str]]:
def mapping(self, offset: int, length: int,
ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, int, str]]:
returned = 0
page_size = self._pdb_layer.page_size
while length > 0:
@@ -194,7 +196,8 @@ class PdbMSFStream(linear.LinearlyMappedLayer):
raise exceptions.InvalidAddressException(layer_name = self.name,
invalid_address = offset + returned)
else:
yield (offset + returned, (self._pages[page] * page_size) + page_position, chunk_size, self._base_layer)
yield offset + returned, chunk_size, (self._pages[page] *
page_size) + page_position, chunk_size, self._base_layer
returned += chunk_size
length -= chunk_size
+4 -3
View File
@@ -209,7 +209,8 @@ class RegistryHive(linear.LinearlyMappedLayer):
entry = table.Table[table_index]
return entry.get_block_offset() + suboffset
def mapping(self, offset: int, length: int, ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, str]]:
def mapping(self, offset: int, length: int,
ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, int, str]]:
if length < 0:
raise ValueError("Mapping length of RegistryHive must be positive or zero")
@@ -225,7 +226,7 @@ class RegistryHive(linear.LinearlyMappedLayer):
chunk_size = min(chunk_size, remaining_length, self._page_size)
try:
translated_offset = self._translate(current_offset)
response.append((current_offset, translated_offset, chunk_size, self._base_layer))
response.append((current_offset, chunk_size, translated_offset, chunk_size, self._base_layer))
except exceptions.LayerException:
if not ignore_errors:
raise
@@ -245,7 +246,7 @@ class RegistryHive(linear.LinearlyMappedLayer):
# Pass this to the lower layers for now
return all([
self.context.layers[layer].is_valid(offset, length)
for (_, offset, length, layer) in self.mapping(offset, length)
for (_, _, offset, length, layer) in self.mapping(offset, length)
])
except exceptions.InvalidAddressException:
return False
+5 -4
View File
@@ -45,7 +45,7 @@ class SegmentedLayer(linear.LinearlyMappedLayer, metaclass = ABCMeta):
try:
base_layer = self._context.layers[self._base_layer]
return all(
[base_layer.is_valid(mapped_offset) for _i, mapped_offset, _i, _s in self.mapping(offset, length)])
[base_layer.is_valid(mapped_offset) for _i, _i, mapped_offset, _i, _s in self.mapping(offset, length)])
except exceptions.InvalidAddressException:
return False
@@ -69,8 +69,9 @@ class SegmentedLayer(linear.LinearlyMappedLayer, metaclass = ABCMeta):
return self._segments[i]
raise exceptions.InvalidAddressException(self.name, offset, "Invalid address at {:0x}".format(offset))
def mapping(self, offset: int, length: int, ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, str]]:
"""Returns a sorted iterable of (offset, mapped_offset, length, layer)
def mapping(self, offset: int, length: int,
ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, int, str]]:
"""Returns a sorted iterable of (offset, length, mapped_offset, mapped_length, layer)
mappings."""
done = False
current_offset = offset
@@ -100,7 +101,7 @@ class SegmentedLayer(linear.LinearlyMappedLayer, metaclass = ABCMeta):
return
# Crop it to the amount we need left
chunk_size = min(size, length + offset - logical_offset)
yield (logical_offset, mapped_offset, chunk_size, self._base_layer)
yield logical_offset, chunk_size, mapped_offset, chunk_size, self._base_layer
current_offset += chunk_size
# Terminate if we've gone (or reached) our required limit
if current_offset >= offset + length:
@@ -132,7 +132,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
if not self.config.get('physical', self.PHYSICAL_DEFAULT):
offset = proc.vol.offset
else:
(_, offset, _, _) = list(memory.mapping(offset = proc.vol.offset, length = 0))[0]
(_, _, offset, _, _) = list(memory.mapping(offset = proc.vol.offset, length = 0))[0]
yield (0, (proc.UniqueProcessId, proc.InheritedFromUniqueProcessId,
proc.ImageFileName.cast("string", max_length = proc.ImageFileName.vol.count, errors = 'replace'),
@@ -42,7 +42,7 @@ class PsTree(pslist.PsList):
else:
layer_name = self.config['primary']
memory = self.context.layers[layer_name]
(_, offset, _, _) = list(memory.mapping(offset = proc.vol.offset, length = 0))[0]
(_, _, offset, _, _) = list(memory.mapping(offset = proc.vol.offset, length = 0))[0]
self._processes[proc.UniqueProcessId] = proc
@@ -84,7 +84,7 @@ class Strings(interfaces.plugins.PluginInterface):
if isinstance(layer, intel.Intel):
# We don't care about errors, we just wanted chunks that map correctly
for mapval in layer.mapping(0x0, layer.maximum_address, ignore_errors = True):
vpage, kpage, page_size, maplayer = mapval
vpage, _, kpage, page_size, maplayer = mapval
for val in range(kpage, kpage + page_size, 0x1000):
cur_set = reverse_map.get(kpage >> 12, set())
cur_set.add(("kernel", vpage))
@@ -107,7 +107,7 @@ class Strings(interfaces.plugins.PluginInterface):
proc_layer = self.context.layers[proc_layer_name]
if isinstance(proc_layer, linear.LinearlyMappedLayer):
for mapval in proc_layer.mapping(0x0, proc_layer.maximum_address, ignore_errors = True):
kpage, vpage, page_size, maplayer = mapval
kpage, _, vpage, page_size, maplayer = mapval
for val in range(kpage, kpage + page_size, 0x1000):
cur_set = reverse_map.get(kpage >> 12, set())
cur_set.add(("Process {}".format(process.UniqueProcessId), vpage))
+1 -1
View File
@@ -34,7 +34,7 @@ class Statistics(plugins.PluginInterface):
while page_addr < layer.maximum_address:
try:
_, _, page_size, layer_name = list(layer.mapping(page_addr, 2 * expected_page_size))[0]
_, _, _, page_size, layer_name = list(layer.mapping(page_addr, 2 * expected_page_size))[0]
if layer_name != layer.config['memory_layer']:
swap_count += 1
else: