mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-11 12:17:38 +02:00
Merge branch 'volatilityfoundation:develop' into linux_kmsg_issue_1055
This commit is contained in:
@@ -173,3 +173,140 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
|
||||
|
||||
class ADS(interfaces.plugins.PluginInterface):
|
||||
|
||||
"""Scans for Alternate Data Stream"""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
return [
|
||||
requirements.TranslationLayerRequirement(
|
||||
name="primary",
|
||||
description="Memory layer for the kernel",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0)
|
||||
),
|
||||
]
|
||||
|
||||
def _generator(self):
|
||||
layer = self.context.layers[self.config["primary"]]
|
||||
|
||||
# Yara Rule to scan for MFT Header Signatures
|
||||
rules = yarascan.YaraScan.process_yara_options(
|
||||
{"yara_rules": "/FILE0|FILE\*|BAAD/"}
|
||||
)
|
||||
|
||||
# Read in the Symbol File
|
||||
symbol_table = intermed.IntermediateSymbolTable.create(
|
||||
context=self.context,
|
||||
config_path=self.config_path,
|
||||
sub_path="windows",
|
||||
filename="mft",
|
||||
class_types={
|
||||
"MFT_ENTRY": mft.MFTEntry,
|
||||
"FILE_NAME_ENTRY": mft.MFTFileName,
|
||||
"ATTRIBUTE": mft.MFTAttribute,
|
||||
},
|
||||
)
|
||||
|
||||
# get each of the individual Field Sets
|
||||
mft_object = symbol_table + constants.BANG + "MFT_ENTRY"
|
||||
attribute_object = symbol_table + constants.BANG + "ATTRIBUTE"
|
||||
fn_object = symbol_table + constants.BANG + "FILE_NAME_ENTRY"
|
||||
|
||||
# Scan the layer for Raw MFT records and parse the fields
|
||||
for offset, _rule_name, _name, _value in layer.scan(
|
||||
context=self.context, scanner=yarascan.YaraScanner(rules=rules)
|
||||
):
|
||||
with contextlib.suppress(exceptions.PagedInvalidAddressException):
|
||||
mft_record = self.context.object(
|
||||
mft_object, offset=offset, layer_name=layer.name
|
||||
)
|
||||
# We will update this on each pass in the next loop and use it as the new offset.
|
||||
attr_base_offset = mft_record.FirstAttrOffset
|
||||
|
||||
attr = self.context.object(
|
||||
attribute_object,
|
||||
offset=offset + attr_base_offset,
|
||||
layer_name=layer.name,
|
||||
)
|
||||
|
||||
# There is no field that has a count of Attributes
|
||||
# Keep Attempting to read attributes until we get an invalid attr.AttrType
|
||||
is_ads = False
|
||||
file_name = renderers.NotAvailableValue
|
||||
# The First $DATA Attr is the 'principal' file itself not the ADS
|
||||
while attr.Attr_Header.AttrType.is_valid_choice:
|
||||
if attr.Attr_Header.AttrType.lookup() == "FILE_NAME":
|
||||
attr_data = attr.Attr_Data.cast(fn_object)
|
||||
file_name = attr_data.get_full_name()
|
||||
if attr.Attr_Header.AttrType.lookup() == "DATA":
|
||||
if is_ads:
|
||||
if not attr.Attr_Header.NonResidentFlag:
|
||||
# Resident files are the most interesting.
|
||||
if attr.Attr_Header.NameLength > 0:
|
||||
ads_name = attr.get_resident_filename()
|
||||
if not ads_name:
|
||||
ads_name = renderers.NotAvailableValue
|
||||
|
||||
content = attr.get_resident_filecontent()
|
||||
if content:
|
||||
# Preparing for Disassembly
|
||||
disasm = interfaces.renderers.BaseAbsentValue
|
||||
architecture = layer.metadata.get(
|
||||
"architecture", None
|
||||
)
|
||||
if architecture:
|
||||
disasm = interfaces.renderers.Disassembly(
|
||||
content, 0, architecture.lower()
|
||||
)
|
||||
else:
|
||||
content = renderers.NotAvailableValue
|
||||
disasm = interfaces.renderers.BaseAbsentValue
|
||||
|
||||
yield 0, (
|
||||
format_hints.Hex(attr_data.vol.offset),
|
||||
mft_record.get_signature(),
|
||||
mft_record.RecordNumber,
|
||||
attr.Attr_Header.AttrType.lookup(),
|
||||
file_name,
|
||||
ads_name,
|
||||
format_hints.HexBytes(content),
|
||||
disasm,
|
||||
)
|
||||
else:
|
||||
is_ads = True
|
||||
|
||||
# If there's no advancement the loop will never end, so break it now
|
||||
if attr.Attr_Header.Length == 0:
|
||||
break
|
||||
|
||||
# Update the base offset to point to the next attribute
|
||||
attr_base_offset += attr.Attr_Header.Length
|
||||
# Get the next attribute
|
||||
attr = self.context.object(
|
||||
attribute_object,
|
||||
offset=offset + attr_base_offset,
|
||||
layer_name=layer.name,
|
||||
)
|
||||
|
||||
def run(self):
|
||||
return renderers.TreeGrid(
|
||||
[
|
||||
("Offset", format_hints.Hex),
|
||||
("Record Type", str),
|
||||
("Record Number", int),
|
||||
("MFT Type", str),
|
||||
("Filename", str),
|
||||
("ADS Filename", str),
|
||||
("Hexdump", format_hints.HexBytes),
|
||||
("Disasm", interfaces.renderers.Disassembly),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
|
||||
@@ -18,47 +18,26 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
|
||||
"""Scans all the Virtual Address Descriptor memory maps using yara."""
|
||||
|
||||
_required_framework_version = (2, 4, 0)
|
||||
_version = (1, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
return [
|
||||
# create a list of requirements for vadyarascan
|
||||
vadyarascan_requirements = [
|
||||
requirements.ModuleRequirement(
|
||||
name="kernel",
|
||||
description="Windows kernel",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.BooleanRequirement(
|
||||
name="wide",
|
||||
description="Match wide (unicode) strings",
|
||||
default=False,
|
||||
optional=True,
|
||||
),
|
||||
requirements.StringRequirement(
|
||||
name="yara_rules", description="Yara rules (as a string)", optional=True
|
||||
),
|
||||
requirements.URIRequirement(
|
||||
name="yara_file", description="Yara rules (as a file)", optional=True
|
||||
),
|
||||
# This additional requirement is to follow suit with upstream, who feel that compiled rules could potentially be used to execute malicious code
|
||||
# As such, there's a separate option to run compiled files, as happened with yara-3.9 and later
|
||||
requirements.URIRequirement(
|
||||
name="yara_compiled_file",
|
||||
description="Yara compiled rules (as a file)",
|
||||
optional=True,
|
||||
),
|
||||
requirements.IntRequirement(
|
||||
name="max_size",
|
||||
default=0x40000000,
|
||||
description="Set the maximum size (default is 1GB)",
|
||||
optional=True,
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0)
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="yarascan", plugin=yarascan.YaraScan, version=(1, 2, 0)
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name="pid",
|
||||
element_type=int,
|
||||
@@ -67,6 +46,12 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
|
||||
),
|
||||
]
|
||||
|
||||
# get base yarascan requirements for command line options
|
||||
yarascan_requirements = yarascan.YaraScan.get_yarascan_option_requirements()
|
||||
|
||||
# return the combined requirements
|
||||
return yarascan_requirements + vadyarascan_requirements
|
||||
|
||||
def _generator(self):
|
||||
kernel = self.context.modules[self.config["kernel"]]
|
||||
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
#
|
||||
|
||||
from typing import Dict, Tuple
|
||||
import logging
|
||||
|
||||
from volatility3.framework import constants
|
||||
from volatility3.framework import objects, interfaces
|
||||
from volatility3.framework import objects, interfaces, exceptions
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class elf(objects.StructType):
|
||||
@@ -33,14 +36,23 @@ class elf(objects.StructType):
|
||||
layer_name = self.vol.layer_name
|
||||
symbol_table_name = self.get_symbol_table_name()
|
||||
# We read the MAGIC: (0x0 to 0x4) 0x7f 0x45 0x4c 0x46
|
||||
magic = self._context.object(
|
||||
symbol_table_name + constants.BANG + "unsigned long",
|
||||
layer_name=layer_name,
|
||||
offset=object_info.offset,
|
||||
)
|
||||
try:
|
||||
magic = self._context.object(
|
||||
symbol_table_name + constants.BANG + "unsigned long",
|
||||
layer_name=layer_name,
|
||||
offset=object_info.offset,
|
||||
)
|
||||
except (
|
||||
exceptions.PagedInvalidAddressException,
|
||||
exceptions.InvalidAddressException,
|
||||
) as excp:
|
||||
vollog.debug(
|
||||
f"Unable to check magic bytes for ELF file at offset {hex(object_info.offset)} in layer {layer_name}: {excp}"
|
||||
)
|
||||
return None
|
||||
|
||||
# Check validity
|
||||
if magic != 0x464C457F:
|
||||
if magic != 0x464C457F: # e.g. ELF
|
||||
return None
|
||||
|
||||
# We need to read the EI_CLASS (0x4 offset)
|
||||
@@ -72,7 +84,10 @@ class elf(objects.StructType):
|
||||
"""
|
||||
Determine whether it is a valid object
|
||||
"""
|
||||
return self._type_prefix is not None and self._hdr is not None
|
||||
if hasattr(self, "_type_prefix") and hasattr(self, "_hdr"):
|
||||
return self._type_prefix is not None and self._hdr is not None
|
||||
else:
|
||||
return False
|
||||
|
||||
def __getattr__(self, name):
|
||||
# Just redirect to the corresponding header
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
|
||||
from volatility3.framework import objects
|
||||
from volatility3.framework import objects, constants, exceptions
|
||||
|
||||
|
||||
class MFTEntry(objects.StructType):
|
||||
@@ -21,3 +21,36 @@ class MFTFileName(objects.StructType):
|
||||
"string", encoding="utf16", max_length=self.NameLength * 2, errors="replace"
|
||||
)
|
||||
return output
|
||||
|
||||
|
||||
class MFTAttribute(objects.StructType):
|
||||
"""This represents an MFT ATTRIBUTE"""
|
||||
|
||||
def get_resident_filename(self) -> str:
|
||||
# To get the resident name, we jump to relative name offset and read name length * 2 bytes of data
|
||||
try:
|
||||
name = self._context.object(
|
||||
self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "string",
|
||||
layer_name=self.vol.layer_name,
|
||||
offset=self.vol.offset + self.Attr_Header.NameOffset,
|
||||
max_length=self.Attr_Header.NameLength * 2,
|
||||
errors="replace",
|
||||
encoding="utf16",
|
||||
)
|
||||
return name
|
||||
except exceptions.InvalidAddressException:
|
||||
return None
|
||||
|
||||
def get_resident_filecontent(self) -> bytes:
|
||||
# To get the resident content, we jump to relative content offset and read name length * 2 bytes of data
|
||||
try:
|
||||
bytesobj = self._context.object(
|
||||
self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "bytes",
|
||||
layer_name=self.vol.layer_name,
|
||||
offset=self.vol.offset + self.Attr_Header.ContentOffset,
|
||||
native_layer_name=self.vol.native_layer_name,
|
||||
length=self.Attr_Header.ContentLength,
|
||||
)
|
||||
return bytesobj
|
||||
except exceptions.InvalidAddressException:
|
||||
return None
|
||||
|
||||
@@ -300,10 +300,24 @@
|
||||
"kind": "base",
|
||||
"name": "unsigned short"
|
||||
}
|
||||
},
|
||||
"ContentLength": {
|
||||
"offset": 16,
|
||||
"type": {
|
||||
"kind": "base",
|
||||
"name": "unsigned int"
|
||||
}
|
||||
},
|
||||
"ContentOffset": {
|
||||
"offset": 20,
|
||||
"type": {
|
||||
"kind": "base",
|
||||
"name": "unsigned short"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 16
|
||||
"size": 24
|
||||
},"RESIDENT_HEADER": {
|
||||
"fields": {
|
||||
"AttrSize": {
|
||||
|
||||
Reference in New Issue
Block a user