mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-12 12:47:39 +02:00
Core: Shift renderers from interfaces
This commit is contained in:
@@ -114,7 +114,7 @@ def quoted_optional(func: Callable) -> Callable:
|
||||
return wrapped
|
||||
|
||||
|
||||
def display_disassembly(disasm: interfaces.renderers.Disassembly) -> str:
|
||||
def display_disassembly(disasm: renderers.Disassembly) -> str:
|
||||
"""Renders a disassembly renderer type into string format.
|
||||
|
||||
Args:
|
||||
@@ -156,12 +156,12 @@ class LayerDataRenderer(CLITypeRenderer):
|
||||
self.display_hex = True
|
||||
self.display_ascii = True
|
||||
|
||||
def render(data: Union[interfaces.renderers.LayerData, BaseAbsentValue]):
|
||||
def render(data: Union[renderers.LayerData, BaseAbsentValue]):
|
||||
if isinstance(data, BaseAbsentValue):
|
||||
# FIXME: Do something cleverer here
|
||||
return ""
|
||||
|
||||
context_byte_len = self.context_byte_len if not data.no_context else 0
|
||||
context_byte_len = self.context_byte_len if not data.no_surrounding else 0
|
||||
|
||||
layer = data.context.layers[data.layer_name]
|
||||
# Map of the holes
|
||||
@@ -230,9 +230,9 @@ class CLIRenderer(interfaces.renderers.Renderer):
|
||||
format_hints.Hex: CLITypeRenderer(lambda x: f"0x{x:x}"),
|
||||
format_hints.HexBytes: CLITypeRenderer(hex_bytes_as_text),
|
||||
format_hints.MultiTypeData: CLITypeRenderer(multitypedata_as_text),
|
||||
interfaces.renderers.Disassembly: CLITypeRenderer(display_disassembly),
|
||||
renderers.Disassembly: CLITypeRenderer(display_disassembly),
|
||||
bytes: CLITypeRenderer(lambda x: " ".join(f"{b:02x}" for b in x)),
|
||||
interfaces.renderers.LayerData: LayerDataRenderer(),
|
||||
renderers.LayerData: LayerDataRenderer(),
|
||||
datetime.datetime: CLITypeRenderer(
|
||||
lambda x: x.strftime("%Y-%m-%d %H:%M:%S.%f %Z")
|
||||
),
|
||||
@@ -523,7 +523,7 @@ class PrettyTextRenderer(CLIRenderer):
|
||||
class JsonRenderer(CLIRenderer):
|
||||
_type_renderers = {
|
||||
format_hints.HexBytes: quoted_optional(hex_bytes_as_text),
|
||||
interfaces.renderers.Disassembly: quoted_optional(display_disassembly),
|
||||
renderers.Disassembly: quoted_optional(display_disassembly),
|
||||
format_hints.MultiTypeData: quoted_optional(multitypedata_as_text),
|
||||
bytes: optional(lambda x: " ".join(f"{b:02x}" for b in x)),
|
||||
datetime.datetime: lambda x: (
|
||||
|
||||
@@ -9,8 +9,8 @@ renderer interface which can interact with a TreeGrid to produce
|
||||
suitable output.
|
||||
"""
|
||||
|
||||
import dataclasses
|
||||
import datetime
|
||||
import warnings
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from collections import abc
|
||||
from typing import (
|
||||
@@ -31,9 +31,19 @@ from typing import Dict
|
||||
from volatility3.framework import interfaces
|
||||
|
||||
|
||||
class BasicType:
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method for rendering basic types"""
|
||||
return str(self)
|
||||
|
||||
|
||||
class BaseAbsentValue:
|
||||
"""Class that represents values which are not present for some reason."""
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method for rendering basic types"""
|
||||
return "-"
|
||||
|
||||
|
||||
class Column(NamedTuple):
|
||||
name: str
|
||||
@@ -136,7 +146,7 @@ class TreeNode(abc.Sequence, metaclass=ABCMeta):
|
||||
"""
|
||||
|
||||
|
||||
class Disassembly:
|
||||
class Disassembly(BasicType):
|
||||
"""A class to indicate that the bytes provided should be disassembled
|
||||
(based on the architecture)"""
|
||||
|
||||
@@ -145,6 +155,10 @@ class Disassembly:
|
||||
def __init__(
|
||||
self, data: bytes, offset: int = 0, architecture: str = "intel64"
|
||||
) -> None:
|
||||
warnings.warn(
|
||||
f"interfaces.renderers.Disassembly is now renderers.Disassembly",
|
||||
FutureWarning,
|
||||
)
|
||||
self.data = data
|
||||
self.architecture = None
|
||||
if architecture in self.possible_architectures:
|
||||
@@ -158,40 +172,6 @@ class Disassembly:
|
||||
return str(self.data)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class LayerData:
|
||||
"""Layer data
|
||||
|
||||
This requires the contex to be passed in, in case plugins want to use multiple contexts
|
||||
and to ensure the TreeGrid interface doesn't change, since this would break all existing plugins
|
||||
"""
|
||||
|
||||
context: "interfaces.context.ContextInterface"
|
||||
layer_name: str
|
||||
offset: int
|
||||
length: int
|
||||
no_surrounding: bool = False
|
||||
|
||||
@staticmethod
|
||||
def from_object(
|
||||
object: "interfaces.objects.ObjectInterface",
|
||||
size: Optional[int] = None,
|
||||
no_surrounding: bool = True,
|
||||
):
|
||||
return LayerData(
|
||||
context=object._context,
|
||||
layer_name=object.vol.layer_name,
|
||||
offset=object.vol.offset,
|
||||
length=size or object.vol.size,
|
||||
no_surrounding=no_surrounding,
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method of rendering"""
|
||||
data = self.context.layers[self.layer_name].read(self.offset, self.length, True)
|
||||
return str(data)
|
||||
|
||||
|
||||
# We don't class these off a shared base, because the BaseTypes must only
|
||||
# contain the types that the validator will accept (which would not include the base)
|
||||
|
||||
@@ -203,8 +183,7 @@ BaseTypes = Union[
|
||||
Type[bytes],
|
||||
Type[datetime.datetime],
|
||||
Type[BaseAbsentValue],
|
||||
Type[Disassembly],
|
||||
Type[LayerData],
|
||||
Type[BasicType],
|
||||
]
|
||||
ColumnsType = List[Tuple[str, BaseTypes]]
|
||||
VisitorSignature = Callable[[TreeNode, _Type], _Type]
|
||||
@@ -224,15 +203,7 @@ class TreeGrid(metaclass=ABCMeta):
|
||||
"""
|
||||
|
||||
# TODO: Figure out why this isn't just BaseTypes (which includes AbsentValues'
|
||||
base_types: ClassVar[Tuple] = (
|
||||
int,
|
||||
str,
|
||||
float,
|
||||
bytes,
|
||||
datetime.datetime,
|
||||
Disassembly,
|
||||
LayerData,
|
||||
)
|
||||
base_types: ClassVar[Tuple] = (int, str, float, bytes, datetime.datetime, BasicType)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@@ -18,7 +18,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
"""Lists process memory ranges that potentially contain injected code."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 2)
|
||||
_version = (1, 0, 3)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -76,9 +76,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
else:
|
||||
architecture = "intel64"
|
||||
|
||||
disasm = interfaces.renderers.Disassembly(
|
||||
data, vma.vm_start, architecture
|
||||
)
|
||||
disasm = renderers.Disassembly(data, vma.vm_start, architecture)
|
||||
|
||||
yield (
|
||||
0,
|
||||
@@ -106,7 +104,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
("Path", str),
|
||||
("Protection", str),
|
||||
("Hexdump", format_hints.HexBytes),
|
||||
("Disasm", interfaces.renderers.Disassembly),
|
||||
("Disasm", renderers.Disassembly),
|
||||
],
|
||||
self._generator(
|
||||
pslist.PsList.list_tasks(
|
||||
|
||||
@@ -68,9 +68,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
else:
|
||||
architecture = "intel64"
|
||||
|
||||
disasm = interfaces.renderers.Disassembly(
|
||||
data, vma.links.start, architecture
|
||||
)
|
||||
disasm = renderers.Disassembly(data, vma.links.start, architecture)
|
||||
|
||||
yield (
|
||||
0,
|
||||
@@ -99,7 +97,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
("End", format_hints.Hex),
|
||||
("Protection", str),
|
||||
("Hexdump", format_hints.HexBytes),
|
||||
("Disasm", interfaces.renderers.Disassembly),
|
||||
("Disasm", renderers.Disassembly),
|
||||
],
|
||||
self._generator(
|
||||
list_tasks(self.context, self.config["kernel"], filter_func=filter_func)
|
||||
|
||||
@@ -103,7 +103,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
symbol_table: str,
|
||||
proc: interfaces.objects.ObjectInterface,
|
||||
) -> Generator[
|
||||
Tuple[interfaces.objects.ObjectInterface, interfaces.renderers.LayerData],
|
||||
Tuple[interfaces.objects.ObjectInterface, renderers.LayerData],
|
||||
None,
|
||||
None,
|
||||
]:
|
||||
@@ -177,7 +177,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
)
|
||||
start = vad.get_start()
|
||||
length = 64
|
||||
data = interfaces.renderers.LayerData(
|
||||
data = renderers.LayerData(
|
||||
context=context,
|
||||
layer_name=proc_layer_name,
|
||||
offset=start,
|
||||
@@ -223,9 +223,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
else:
|
||||
architecture = "intel64"
|
||||
|
||||
disasm = interfaces.renderers.Disassembly(
|
||||
data, vad.get_start(), architecture
|
||||
)
|
||||
disasm = renderers.Disassembly(data, vad.get_start(), architecture)
|
||||
|
||||
file_output = "Disabled"
|
||||
if self.config["dump"]:
|
||||
@@ -281,8 +279,8 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
("PrivateMemory", int),
|
||||
("File output", str),
|
||||
("Notes", str),
|
||||
("Hexdump", interfaces.renderers.LayerData),
|
||||
("Disasm", interfaces.renderers.Disassembly),
|
||||
("Hexdump", renderers.LayerData),
|
||||
("Disasm", renderers.Disassembly),
|
||||
],
|
||||
self._generator(
|
||||
pslist.PsList.list_processes(
|
||||
|
||||
@@ -74,7 +74,7 @@ class MBRScan(interfaces.plugins.PluginInterface):
|
||||
partition_table_object = symbol_table + constants.BANG + "PARTITION_TABLE"
|
||||
|
||||
# Define Signature and Data Length
|
||||
mbr_signature = b"\x55\xAA"
|
||||
mbr_signature = b"\x55\xaa"
|
||||
mbr_length = 0x200
|
||||
bootcode_length = 0x1B8
|
||||
|
||||
@@ -120,9 +120,7 @@ class MBRScan(interfaces.plugins.PluginInterface):
|
||||
renderers.NotApplicableValue(),
|
||||
renderers.NotApplicableValue(),
|
||||
renderers.NotApplicableValue(),
|
||||
interfaces.renderers.Disassembly(
|
||||
bootcode, 0, architecture
|
||||
),
|
||||
renderers.Disassembly(bootcode, 0, architecture),
|
||||
),
|
||||
)
|
||||
else:
|
||||
@@ -146,10 +144,8 @@ class MBRScan(interfaces.plugins.PluginInterface):
|
||||
renderers.NotApplicableValue(),
|
||||
renderers.NotApplicableValue(),
|
||||
renderers.NotApplicableValue(),
|
||||
interfaces.renderers.Disassembly(
|
||||
bootcode, 0, architecture
|
||||
),
|
||||
interfaces.renderers.LayerData(
|
||||
renderers.Disassembly(bootcode, 0, architecture),
|
||||
renderers.LayerData(
|
||||
context=self.context,
|
||||
layer_name=layer.name,
|
||||
offset=mbr_start_offset,
|
||||
@@ -238,7 +234,7 @@ class MBRScan(interfaces.plugins.PluginInterface):
|
||||
("Bootable", bool),
|
||||
("PartitionType", str),
|
||||
("SectorInSize", format_hints.Hex),
|
||||
("Disasm", interfaces.renderers.Disassembly),
|
||||
("Disasm", renderers.Disassembly),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
@@ -262,8 +258,8 @@ class MBRScan(interfaces.plugins.PluginInterface):
|
||||
("EndingCHS", int),
|
||||
("EndingSector", int),
|
||||
("SectorInSize", format_hints.Hex),
|
||||
("Disasm", interfaces.renderers.Disassembly),
|
||||
("Bootcode", interfaces.renderers.LayerData),
|
||||
("Disasm", renderers.Disassembly),
|
||||
("Bootcode", renderers.LayerData),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
|
||||
@@ -223,7 +223,7 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
|
||||
content = attr.get_resident_filecontent()
|
||||
if content:
|
||||
content = interfaces.renderers.LayerData.from_object(content)
|
||||
content = renderers.LayerData.from_object(content)
|
||||
else:
|
||||
content = renderers.NotAvailableValue()
|
||||
|
||||
@@ -387,7 +387,7 @@ class ADS(interfaces.plugins.PluginInterface):
|
||||
("MFT Type", str),
|
||||
("Filename", str),
|
||||
("ADS Filename", str),
|
||||
("Hexdump", interfaces.renderers.LayerData),
|
||||
("Hexdump", renderers.LayerData),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
@@ -453,7 +453,7 @@ class ResidentData(interfaces.plugins.PluginInterface):
|
||||
("Record Number", int),
|
||||
("MFT Type", str),
|
||||
("Filename", str),
|
||||
("Hexdump", interfaces.renderers.LayerData),
|
||||
("Hexdump", renderers.LayerData),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ or file or graphical output
|
||||
"""
|
||||
import collections
|
||||
import collections.abc
|
||||
import dataclasses
|
||||
import datetime
|
||||
import logging
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, TypeVar, Union
|
||||
@@ -22,16 +23,28 @@ class UnreadableValue(interfaces.renderers.BaseAbsentValue):
|
||||
"""Class that represents values which are empty because the data cannot be
|
||||
read."""
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method for rendering basic types"""
|
||||
return "-"
|
||||
|
||||
|
||||
class UnparsableValue(interfaces.renderers.BaseAbsentValue):
|
||||
"""Class that represents values which are empty because the data cannot be
|
||||
interpreted correctly."""
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method for rendering basic types"""
|
||||
return "-"
|
||||
|
||||
|
||||
class NotApplicableValue(interfaces.renderers.BaseAbsentValue):
|
||||
"""Class that represents values which are empty because they don't make
|
||||
sense for this node."""
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method for rendering basic types"""
|
||||
return "N/A"
|
||||
|
||||
|
||||
class NotAvailableValue(interfaces.renderers.BaseAbsentValue):
|
||||
"""Class that represents values which cannot be provided now (but might in
|
||||
@@ -45,6 +58,70 @@ class NotAvailableValue(interfaces.renderers.BaseAbsentValue):
|
||||
in preference, and only if neither fits should this be used.
|
||||
"""
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method for rendering basic types"""
|
||||
return "N/A"
|
||||
|
||||
|
||||
##########
|
||||
### Basic Types
|
||||
|
||||
|
||||
class Disassembly(interfaces.renderers.BasicType):
|
||||
"""A class to indicate that the bytes provided should be disassembled
|
||||
(based on the architecture)"""
|
||||
|
||||
possible_architectures = ["intel", "intel64", "arm", "arm64"]
|
||||
|
||||
def __init__(
|
||||
self, data: bytes, offset: int = 0, architecture: str = "intel64"
|
||||
) -> None:
|
||||
self.data = data
|
||||
self.architecture = None
|
||||
if architecture in self.possible_architectures:
|
||||
self.architecture = architecture
|
||||
if not isinstance(offset, int):
|
||||
raise TypeError("Offset must be an integer type")
|
||||
self.offset = offset
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method of rendering"""
|
||||
return str(self.data)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class LayerData(interfaces.renderers.BasicType):
|
||||
"""Layer data
|
||||
|
||||
This requires the contex to be passed in, in case plugins want to use multiple contexts
|
||||
and to ensure the TreeGrid interface doesn't change, since this would break all existing plugins
|
||||
"""
|
||||
|
||||
context: "interfaces.context.ContextInterface"
|
||||
layer_name: str
|
||||
offset: int
|
||||
length: int
|
||||
no_surrounding: bool = False
|
||||
|
||||
@staticmethod
|
||||
def from_object(
|
||||
object: "interfaces.objects.ObjectInterface",
|
||||
size: Optional[int] = None,
|
||||
no_surrounding: bool = True,
|
||||
):
|
||||
return LayerData(
|
||||
context=object._context,
|
||||
layer_name=object.vol.layer_name,
|
||||
offset=object.vol.offset,
|
||||
length=size or object.vol.size,
|
||||
no_surrounding=no_surrounding,
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method of rendering"""
|
||||
data = self.context.layers[self.layer_name].read(self.offset, self.length, True)
|
||||
return str(data)
|
||||
|
||||
|
||||
class TreeNode(interfaces.renderers.TreeNode):
|
||||
"""Class representing a particular node in a tree grid."""
|
||||
|
||||
Reference in New Issue
Block a user