Core: Shift renderers from interfaces

This commit is contained in:
Mike Auty
2025-03-23 00:42:13 +00:00
parent 6af8cd09f0
commit c18c6cbf30
8 changed files with 121 additions and 83 deletions
+6 -6
View File
@@ -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: (
+18 -47
View File
@@ -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(
+2 -4
View File
@@ -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."""