Merge branch 'volatilityfoundation:develop' into develop

This commit is contained in:
Samuel Zurowski
2022-03-26 16:30:10 -04:00
committed by GitHub
26 changed files with 656 additions and 89 deletions
+1 -1
View File
@@ -300,7 +300,7 @@ This will mean that when a specific structure is loaded from the symbol_space, i
`StructType`, but instead is instantiated using the NewStructureClass, meaning new methods can be called directly on it.
If the situation really calls for an entirely new object, that isn't covered by one of the existing
:py:class:`~volatility3.framework.objects.PrimativeObject` objects (such as
:py:class:`~volatility3.framework.objects.PrimitiveObject` objects (such as
:py:class:`~volatility3.framework.objects.Integer`,
:py:class:`~volatility3.framework.objects.Boolean`,
:py:class:`~volatility3.framework.objects.Float`,
+1 -1
View File
@@ -145,7 +145,7 @@ Struct, Structure
Symbol
This is used in many different contexts, as a short term for many things. Within Volatility, a symbol is a
construct that usually encompasses a specific type :ref:`type<Type>` at a specfific :ref:`offset<Offset>`,
construct that usually encompasses a specific type :ref:`type<Type>` at a specific :ref:`offset<Offset>`,
representing a particular instance of that type within the memory of a compiled and running program. An example
would be the location in memory of a list of active tcp endpoints maintained by the networking stack
within an operating system.
+2 -2
View File
@@ -206,9 +206,9 @@ information may not be provided.
The plugin then takes the process's ``BaseDllName`` value, and calls :py:meth:`~volatility3.framework.symbols.windows.extensions.UNICODE_STRING.get_string` on it. All structure attributes,
as defined by the symbols, are directly accessible and use the case-style of the symbol library it came from (in Windows,
attributes are CamelCase), such as ``entry.BaseDllName`` in this instance. Any attribtues not defined by the symbol but added
attributes are CamelCase), such as ``entry.BaseDllName`` in this instance. Any attributes not defined by the symbol but added
by Volatility extensions cannot be properties (in case they overlap with the attributes defined in the symbol libraries)
and are therefore always methods and prepended with ``get_``, in this example ``BaseDllName.get_string()``.
and are therefore always methods and pretended with ``get_``, in this example ``BaseDllName.get_string()``.
Finally, ``FullDllName`` is populated. These operations read from memory, and as such, the memory image may be unable to
read the data at a particular offset. This will cause an exception to be thrown. In Volatility 3, exceptions are thrown
+12 -2
View File
@@ -19,6 +19,7 @@ import os
import sys
import tempfile
import traceback
from datetime import datetime
from typing import Any, Dict, Type, Union
from urllib import parse, request
@@ -157,6 +158,10 @@ class CommandLine:
help = "Write configuration JSON file out to config.json",
default = False,
action = 'store_true')
parser.add_argument("--save-config",
help = "Save configuration JSON file to a file",
default = None,
type = str)
parser.add_argument("--clear-cache",
help = "Clears out all short-term cached items",
default = False,
@@ -320,8 +325,13 @@ class CommandLine:
self.file_handler_class_factory())
if args.write_config:
vollog.debug("Writing out configuration data to config.json")
with open("config.json", "w") as f:
vollog.warning('Use of --write-config has been deprecated, replaced by --save-config <filename>')
args.save_config = 'config.json'
if args.save_config:
vollog.debug("Writing out configuration data to {args.save_config}")
if os.path.exists(os.path.abspath(args.save_config)):
parser.error(f"Cannot write configuration: file {args.save_config} already exists")
with open(args.save_config, "w") as f:
json.dump(dict(constructed.build_configuration()), f, sort_keys = True, indent = 2)
except exceptions.UnsatisfiedException as excp:
self.process_unsatisfied_exceptions(excp)
+24 -23
View File
@@ -1,6 +1,7 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import csv
import datetime
import json
import logging
@@ -8,7 +9,7 @@ import random
import string
import sys
from functools import wraps
from typing import Callable, Any, List, Tuple, Dict
from typing import Any, Callable, Dict, List, Tuple
from volatility3.framework import interfaces, renderers
from volatility3.framework.renderers import format_hints
@@ -66,7 +67,6 @@ def multitypedata_as_text(value: format_hints.MultiTypeData) -> str:
def optional(func: Callable) -> Callable:
@wraps(func)
def wrapped(x: Any) -> str:
if isinstance(x, interfaces.renderers.BaseAbsentValue):
@@ -80,7 +80,6 @@ def optional(func: Callable) -> Callable:
def quoted_optional(func: Callable) -> Callable:
@wraps(func)
def wrapped(x: Any) -> str:
result = optional(func)(x)
@@ -102,7 +101,7 @@ def display_disassembly(disasm: interfaces.renderers.Disassembly) -> str:
disasm: Input disassembly objects
Returns:
A string as rendererd by capstone where available, otherwise output as if it were just bytes
A string as rendered by capstone where available, otherwise output as if it were just bytes
"""
if CAPSTONE_PRESENT:
@@ -193,16 +192,17 @@ class NoneRenderer(CLIRenderer):
if not grid.populated:
grid.populate(lambda x, y: True, True)
class CSVRenderer(CLIRenderer):
_type_renderers = {
format_hints.Bin: quoted_optional(lambda x: f"0b{x:b}"),
format_hints.Hex: quoted_optional(lambda x: f"0x{x:x}"),
format_hints.HexBytes: quoted_optional(hex_bytes_as_text),
format_hints.MultiTypeData: quoted_optional(multitypedata_as_text),
interfaces.renderers.Disassembly: quoted_optional(display_disassembly),
bytes: quoted_optional(lambda x: " ".join([f"{b:02x}" for b in x])),
datetime.datetime: quoted_optional(lambda x: x.strftime("%Y-%m-%d %H:%M:%S.%f %Z")),
'default': quoted_optional(lambda x: f"{x}")
format_hints.Bin: optional(lambda x: f"0b{x:b}"),
format_hints.Hex: optional(lambda x: f"0x{x:x}"),
format_hints.HexBytes: optional(hex_bytes_as_text),
format_hints.MultiTypeData: optional(multitypedata_as_text),
interfaces.renderers.Disassembly: optional(display_disassembly),
bytes: optional(lambda x: " ".join([f"{b:02x}" for b in x])),
datetime.datetime: optional(lambda x: x.strftime("%Y-%m-%d %H:%M:%S.%f %Z")),
'default': optional(lambda x: f"{x}")
}
name = "csv"
@@ -219,28 +219,28 @@ class CSVRenderer(CLIRenderer):
"""
outfd = sys.stdout
line = ['"TreeDepth"']
header_list = ['TreeDepth']
for column in grid.columns:
# Ignore the type because namedtuples don't realize they have accessible attributes
line.append("{}".format('"' + column.name + '"'))
outfd.write(f"{','.join(line)}")
header_list.append(f"{column.name}")
writer = csv.DictWriter(outfd, header_list)
writer.writeheader()
def visitor(node: interfaces.renderers.TreeNode, accumulator):
accumulator.write("\n")
# Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case
accumulator.write(str(max(0, node.path_depth - 1)) + ",")
line = []
row = {'TreeDepth': str(max(0, node.path_depth - 1))}
for column_index in range(len(grid.columns)):
column = grid.columns[column_index]
renderer = self._type_renderers.get(column.type, self._type_renderers['default'])
line.append(renderer(node.values[column_index]))
accumulator.write(f"{','.join(line)}")
row[f'{column.name}'] = renderer(node.values[column_index])
accumulator.writerow(row)
return accumulator
if not grid.populated:
grid.populate(visitor, outfd)
grid.populate(visitor, writer)
else:
grid.visit(node = None, function = visitor, initial_accumulator = outfd)
grid.visit(node = None, function = visitor, initial_accumulator = writer)
outfd.write("\n")
@@ -274,7 +274,8 @@ class PrettyTextRenderer(CLIRenderer):
max_column_widths = dict([(column.name, len(column.name)) for column in grid.columns])
def visitor(
node: interfaces.renderers.TreeNode, accumulator: List[Tuple[int, Dict[interfaces.renderers.Column, bytes]]]
node: interfaces.renderers.TreeNode,
accumulator: List[Tuple[int, Dict[interfaces.renderers.Column, bytes]]]
) -> List[Tuple[int, Dict[interfaces.renderers.Column, bytes]]]:
# Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case
max_column_widths[tree_indent_column] = max(max_column_widths.get(tree_indent_column, 0), node.path_depth)
+11 -2
View File
@@ -85,6 +85,10 @@ class VolShell(cli.CommandLine):
help = "Write configuration JSON file out to config.json",
default = False,
action = 'store_true')
parser.add_argument("--save-config",
help = "Save configuration JSON file to a file",
default = None,
type = str)
parser.add_argument("--clear-cache",
help = "Clears out all short-term cached items",
default = False,
@@ -234,8 +238,13 @@ class VolShell(cli.CommandLine):
self.file_handler_class_factory())
if args.write_config:
vollog.debug("Writing out configuration data to config.json")
with open("config.json", "w") as f:
vollog.warning('Use of --write-config has been deprecated, replaced by --save-config <filename>')
args.save_config = 'config.json'
if args.save_config:
vollog.debug("Writing out configuration data to {args.save_config}")
if os.path.exists(os.path.abspath(args.save_config)):
parser.error(f"Cannot write configuration: file {args.save_config} already exists")
with open(args.save_config, "w") as f:
json.dump(dict(constructed.build_configuration()), f, sort_keys = True, indent = 2)
except exceptions.UnsatisfiedException as excp:
self.process_unsatisfied_exceptions(excp)
+59 -22
View File
@@ -28,9 +28,9 @@ The self-referential indices for older versions of windows are listed below:
"""
import logging
import struct
from typing import Generator, List, Optional, Tuple, Type, Iterable
from typing import Generator, Iterable, List, Optional, Tuple, Type
from volatility3.framework import interfaces, layers, constants
from volatility3.framework import constants, interfaces, layers
from volatility3.framework.configuration import requirements
from volatility3.framework.layers import intel
@@ -116,10 +116,27 @@ class DtbSelfRefPae(DtbSelfReferential):
mask = 0x3FFFFFFFFFF000,
reserved_bits = 0x0)
def __call__(self, *args, **kwargs):
dtb = super().__call__(*args, **kwargs)
@staticmethod
def _and_bytes(abytes, bbytes):
return bytes([a & b for a, b in zip(abytes[::-1], bbytes[::-1])][::-1])
def __call__(self, data: bytes, data_offset: int, page_offset: int) -> Optional[Tuple[int, int]]:
dtb = super().__call__(data, data_offset, page_offset)
if dtb:
return dtb[0] - 0x4000, dtb[1]
# Find the top page
top_pae_page = dtb[0] - 0x4000
# The top page should map to the next four pages after it
# Build what we expect the page table to be
expected_table = b''.join([struct.pack(self.ptr_struct, top_pae_page + (i * 0x1000)) for i in range(1, 5)])
# Mask off the page bits of top level page map
page_table_mask = b"\x00\xf0\xff\xff\xff\xff\xff\xff" * 4
page_table = data[top_pae_page - data_offset: top_pae_page - data_offset + (4 * self.ptr_size)]
# Compare them
anded_bytes = self._and_bytes(page_table, page_table_mask)
if (anded_bytes == expected_table):
return top_pae_page, dtb[1]
# Return None since the dtb value *isn't* None
return None
return dtb
@@ -202,30 +219,50 @@ class WindowsIntelStacker(interfaces.automagic.StackerLayerInterface):
for description, tests, sections in cls.test_sets:
vollog.debug(description)
# There is a very high chance that the DTB will live in these very narrow segments, assuming we couldn't find them previously
hits = context.layers[layer_name].scan(context,
PageMapScanner(tests = tests),
sections = sections,
progress_callback = progress_callback)
hits = base_layer.scan(context,
PageMapScanner(tests = tests),
sections = sections,
progress_callback = progress_callback)
# Flatten the generator
def sort_by_tests(x):
"""Key used to sort by tests"""
return tests.index(x[0]), x[1]
def get_max_pointer(page_table, test, ptr_size: int):
"""Determines a pointer from a page_table"""
max_ptr = 0
for index in range(0, len(page_table), ptr_size):
pointer = struct.unpack(test.ptr_struct, page_table[index:index + ptr_size])[0]
# Make sure the pointer is valid, ignore large pages which would require more calculation
if pointer & 0x1 and not pointer & 0x80:
max_ptr = max(max_ptr, (pointer ^ (pointer & 0xfff)) % test.layer_type.maximum_address)
return max_ptr
hits = sorted(list(hits), key = sort_by_tests)
if hits:
# TODO: Decide which to use if there are multiple options
test, page_map_offset = hits[0]
vollog.debug(f"{test.__class__.__name__} test succeeded at {hex(page_map_offset)}")
new_layer_name = context.layers.free_layer_name("IntelLayer")
config_path = interfaces.configuration.path_join("IntelHelper", new_layer_name)
context.config[interfaces.configuration.path_join(config_path, "memory_layer")] = layer_name
context.config[interfaces.configuration.path_join(config_path, "page_map_offset")] = page_map_offset
# TODO: Need to determine the layer type (chances are high it's x64, hence this default)
layer = test.layer_type(context,
config_path = config_path,
name = new_layer_name,
metadata = {'os': 'Windows'})
for test, page_map_offset in hits:
# Turn the page tables into integers and find the largest one
page_table = base_layer.read(page_map_offset, 0x1000)
ptr_size = struct.calcsize(test.ptr_struct)
max_pointer = get_max_pointer(page_table, test, ptr_size)
if max_pointer <= base_layer.maximum_address:
vollog.debug(f"{test.__class__.__name__} test succeeded at {hex(page_map_offset)}")
new_layer_name = context.layers.free_layer_name("IntelLayer")
config_path = interfaces.configuration.path_join("IntelHelper", new_layer_name)
context.config[interfaces.configuration.path_join(config_path, "memory_layer")] = layer_name
context.config[
interfaces.configuration.path_join(config_path, "page_map_offset")] = page_map_offset
layer = test.layer_type(context,
config_path = config_path,
name = new_layer_name,
metadata = {'os': 'Windows'})
break
else:
vollog.debug(
f"Max pointer for hit with test {test.__class__.__name__} not met: {hex(max_pointer)} > {hex(base_layer.maximum_address)}")
if layer is not None and config_path:
break
if layer is not None and config_path:
+3 -2
View File
@@ -9,7 +9,7 @@ volatility This includes default scanning block sizes, etc.
import enum
import os.path
import sys
from typing import Optional, Callable
from typing import Callable, Optional
import volatility3.framework.constants.linux
import volatility3.framework.constants.windows
@@ -63,7 +63,7 @@ LOGLEVEL_VVVV = 6
CACHE_PATH = os.path.join(os.path.expanduser("~"), ".cache", "volatility3")
"""Default path to store cached data"""
if sys.platform == 'windows':
if sys.platform == 'win32':
CACHE_PATH = os.path.join(os.environ.get("APPDATA", os.path.expanduser("~")), "volatility3")
os.makedirs(CACHE_PATH, exist_ok = True)
@@ -80,6 +80,7 @@ ProgressCallback = Optional[Callable[[float, str], None]]
OS_CATEGORIES = ['windows', 'mac', 'linux']
class Parallelism(enum.IntEnum):
"""An enumeration listing the different types of parallelism applied to
volatility."""
+1 -1
View File
@@ -141,7 +141,7 @@ class Context(interfaces.context.ContextInterface):
layer_name: The layer within the context in which the module exists
offset: The offset at which the module exists in the layer
native_layer_name: The default native layer for objects constructed by the module
size: The size, in bytes, that the module occupys from offset location within the layer named layer_name
size: The size, in bytes, that the module occupies from offset location within the layer named layer_name
"""
if size:
return SizedModule.create(self,
@@ -73,7 +73,7 @@ class HierarchicalDict(collections.abc.Mapping):
separator: str = CONFIG_SEPARATOR) -> None:
"""
Args:
initial_dict: A dictionary to populate the HierachicalDict with initially
initial_dict: A dictionary to populate the HierarchicalDict with initially
separator: A custom hierarchy separator (defaults to CONFIG_SEPARATOR)
"""
if not (isinstance(separator, str) and len(separator) == 1):
+1 -1
View File
@@ -129,7 +129,7 @@ class ContextInterface(metaclass = ABCMeta):
layer_name: The layer the module is associated with (which layer the module lives within)
offset: The initial/base offset of the module (used as the offset for relative symbols)
native_layer_name: The default native_layer_name to use when the module constructs objects
size: The size, in bytes, that the module occupys from offset location within the layer named layer_name
size: The size, in bytes, that the module occupies from offset location within the layer named layer_name
Returns:
A module object
@@ -1,7 +1,7 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
"""All plugins output a TreeGrid object which must then be rendered (eithe by a
"""All plugins output a TreeGrid object which must then be rendered (either by a
GUI, or as text output, html output or in some other form.
This module defines both the output format (:class:`TreeGrid`) and the
@@ -31,7 +31,7 @@ class BytesScanner(layers.ScannerInterface):
class RegExScanner(layers.ScannerInterface):
"""A scanner that can be provided with a bytes-object regular expression pattern
The scanner will scqn all blocks for the regular expression and report the absolute offset of any finds
The scanner will scan all blocks for the regular expression and report the absolute offset of any finds
The default flags include DOTALL, since the searches are through binary data and the newline character should
have no specific significance in such searches"""
@@ -95,7 +95,7 @@ class MultiStringScanner(layers.ScannerInterface):
else:
suffixes.append(re.escape(bytes([entry])))
else:
# If we've fininshed one of the strings at this point, remember it for later
# If we've finished one of the strings at this point, remember it for later
finished = True
if len(suffixes) == 1:
+2 -2
View File
@@ -206,7 +206,7 @@ class Bytes(PrimitiveObject, bytes):
length: int = 1,
**kwargs) -> 'Bytes':
"""Creates the appropriate class and returns it so that the native type
is inherritted.
is inherited.
The only reason the kwargs is added, is so that the
inheriting types can override __init__ without needing to
@@ -704,7 +704,7 @@ class AggregateType(interfaces.objects.ObjectInterface):
tmp_list[member] = (relative_offset, new_child)
# If there's trouble with mutability, consider making update_vol return a clone with the changes
# (there will be a few other places that will be necessary) and/or making these part of the
# permanent dictionaries rather than the non-clonable ones
# permanent dictionaries rather than the non-cloneable ones
template.update_vol(members = tmp_list)
@classmethod
@@ -0,0 +1,8 @@
# This file is Copyright 2022 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
"""All core mac plugins.
These modules should only be imported from volatility3.plugins NOT
volatility3.framework.plugins
"""
@@ -9,7 +9,7 @@ from volatility3.framework.symbols import mac
class Ifconfig(plugins.PluginInterface):
"""Lists loaded kernel modules"""
"""Lists network interface information for all devices"""
_required_framework_version = (2, 0, 0)
+1 -1
View File
@@ -12,7 +12,7 @@ from volatility3.framework.symbols import mac
class Mount(plugins.PluginInterface):
"""A module containing a collection of plugins that produce data typically
foundin Mac's mount command"""
found in Mac's mount command"""
_required_framework_version = (2, 0, 0)
@@ -4,10 +4,10 @@
import binascii
import hashlib
import logging
from struct import unpack, pack
from typing import List, Tuple, Optional
from struct import pack, unpack
from typing import List, Optional, Tuple
from Crypto.Cipher import ARC4, DES, AES
from Crypto.Cipher import AES, ARC4, DES
from Crypto.Hash import MD5
from volatility3.framework import interfaces, renderers
@@ -28,7 +28,7 @@ class Hashdump(interfaces.plugins.PluginInterface):
def get_requirements(cls):
return [
requirements.ModuleRequirement(name = 'kernel', description = 'Windows kernel',
architectures = ["Intel32", "Intel64"]),
architectures = ["Intel32", "Intel64"]),
requirements.PluginRequirement(name = 'hivelist', plugin = hivelist.HiveList, version = (1, 0, 0))
]
@@ -63,7 +63,8 @@ class Hashdump(interfaces.plugins.PluginInterface):
def get_hive_key(cls, hive: registry.RegistryHive, key: str):
result = None
try:
result = hive.get_key(key)
if hive:
result = hive.get_key(key)
except KeyError:
vollog.info(
f"Unable to load the required registry key {hive.get_name()}\\{key} from this memory image")
@@ -132,7 +133,7 @@ class Hashdump(interfaces.plugins.PluginInterface):
rc4_key = md5.digest()
rc4 = ARC4.new(rc4_key)
hbootkey = rc4.encrypt(sam_data[0x80:0xA0]) # lgtm [py/weak-cryptographic-algorithm]
hbootkey = rc4.encrypt(sam_data[0x80:0xA0]) # lgtm [py/weak-cryptographic-algorithm]
return hbootkey
elif revision == 3:
# AES encrypted
@@ -151,7 +152,7 @@ class Hashdump(interfaces.plugins.PluginInterface):
des2 = DES.new(des_k2, DES.MODE_ECB)
cipher = AES.new(hbootkey[:16], AES.MODE_CBC, salt)
obfkey = cipher.decrypt(enc_hash)
return des1.decrypt(obfkey[:8]) + des2.decrypt(obfkey[8:16]) # lgtm [py/weak-cryptographic-algorithm]
return des1.decrypt(obfkey[:8]) + des2.decrypt(obfkey[8:16]) # lgtm [py/weak-cryptographic-algorithm]
@classmethod
def get_user_hashes(cls, user: registry.CM_KEY_NODE, samhive: registry.RegistryHive,
@@ -229,9 +230,9 @@ class Hashdump(interfaces.plugins.PluginInterface):
md5.update(hbootkey[:0x10] + pack("<L", rid) + lmntstr)
rc4_key = md5.digest()
rc4 = ARC4.new(rc4_key)
obfkey = rc4.encrypt(enc_hash) # lgtm [py/weak-cryptographic-algorithm]
obfkey = rc4.encrypt(enc_hash) # lgtm [py/weak-cryptographic-algorithm]
return des1.decrypt(obfkey[:8]) + des2.decrypt(obfkey[8:]) # lgtm [py/weak-cryptographic-algorithm]
return des1.decrypt(obfkey[:8]) + des2.decrypt(obfkey[8:]) # lgtm [py/weak-cryptographic-algorithm]
@classmethod
def get_user_name(cls, user: registry.CM_KEY_NODE, samhive: registry.RegistryHive) -> Optional[bytes]:
@@ -253,13 +254,9 @@ class Hashdump(interfaces.plugins.PluginInterface):
# replaces the dump_hashes method in vol2
def _generator(self, syshive: registry.RegistryHive, samhive: registry.RegistryHive):
if syshive is None:
vollog.debug("SYSTEM address is None: Did you use the correct profile?")
yield (0, (renderers.NotAvailableValue(), renderers.NotAvailableValue(), renderers.NotAvailableValue(),
renderers.NotAvailableValue()))
vollog.debug("SYSTEM address is None: No system hive found")
if samhive is None:
vollog.debug("SAM address is None: Did you use the correct profile?")
yield (0, (renderers.NotAvailableValue(), renderers.NotAvailableValue(), renderers.NotAvailableValue(),
renderers.NotAvailableValue()))
vollog.debug("SAM address is None: No SAM hive found")
bootkey = self.get_bootkey(syshive)
hbootkey = self.get_hbootkey(samhive, bootkey)
if hbootkey:
@@ -53,7 +53,7 @@ class Malfind(interfaces.plugins.PluginInterface):
"""
CHUNK_SIZE = 0x1000
all_zero_page = "\x00" * CHUNK_SIZE
all_zero_page = b"\x00" * CHUNK_SIZE
offset = 0
vad_length = vad.get_end() - vad.get_start()
@@ -0,0 +1,200 @@
# This file is Copyright 2022 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
import hashlib
from typing import Iterator, List, Tuple
from volatility3.framework import constants, exceptions, interfaces, renderers, symbols
from volatility3.framework.configuration import requirements
from volatility3.framework.layers import scanners
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows.extensions import mbr
vollog = logging.getLogger(__name__)
class MBRScan(interfaces.plugins.PluginInterface):
"""Scans for and parses potential Master Boot Records (MBRs)"""
_required_framework_version = (2, 0, 1)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls)-> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(name = 'kernel', description = 'Windows kernel',
architectures = ["Intel32", "Intel64"]),
requirements.BooleanRequirement(name = 'full',
description ="It analyzes and provides all the information in the partition entry and bootcode hexdump. (It returns a lot of information, so we recommend you render it in CSV.)",
default = False,
optional = True)
]
@classmethod
def get_hash(cls, data:bytes) -> str:
return hashlib.md5(data).hexdigest()
def _generator(self) -> Iterator[Tuple]:
kernel = self.context.modules[self.config['kernel']]
physical_layer_name = self.context.layers[kernel.layer_name].config.get('memory_layer', None)
# Decide of Memory Dump Architecture
layer = self.context.layers[physical_layer_name]
architecture = "intel" if not symbols.symbol_table_is_64bit(self.context, kernel.symbol_table_name) else "intel64"
# Read in the Symbol File
symbol_table = intermed.IntermediateSymbolTable.create(context = self.context,
config_path = self.config_path,
sub_path = "windows",
filename = "mbr",
class_types = {
'PARTITION_TABLE': mbr.PARTITION_TABLE,
'PARTITION_ENTRY': mbr.PARTITION_ENTRY
})
partition_table_object = symbol_table + constants.BANG + "PARTITION_TABLE"
# Define Signature and Data Length
mbr_signature = b"\x55\xAA"
mbr_length = 0x200
bootcode_length = 0x1B8
# Scan the Layer for Raw Master Boot Record (MBR) and parse the fields
for offset, _value in layer.scan(context = self.context, scanner = scanners.MultiStringScanner(patterns = [mbr_signature])):
try:
mbr_start_offset = offset - (mbr_length - len(mbr_signature))
partition_table = self.context.object(partition_table_object, offset = mbr_start_offset, layer_name = layer.name)
# Extract only BootCode
full_mbr = layer.read(mbr_start_offset, mbr_length, pad = True)
bootcode = full_mbr[:bootcode_length]
all_zeros = None
if bootcode:
all_zeros = bootcode.count(b"\x00") == len(bootcode)
if not all_zeros:
partition_entries = [
partition_table.FirstEntry, partition_table.SecondEntry,
partition_table.ThirdEntry, partition_table.FourthEntry
]
if not self.config.get("full", True):
yield (0, (
format_hints.Hex(offset),
partition_table.get_disk_signature(),
self.get_hash(bootcode),
self.get_hash(full_mbr),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
interfaces.renderers.Disassembly(bootcode, 0, architecture)
))
else:
yield (0, (
format_hints.Hex(offset),
partition_table.get_disk_signature(),
self.get_hash(bootcode),
self.get_hash(full_mbr),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
interfaces.renderers.Disassembly(bootcode, 0, architecture),
format_hints.HexBytes(bootcode)
))
for partition_index, partition_entry_object in enumerate(partition_entries, start=1):
if not self.config.get("full", True):
yield (1, (
format_hints.Hex(offset),
partition_table.get_disk_signature(),
self.get_hash(bootcode),
self.get_hash(full_mbr),
partition_index,
partition_entry_object.is_bootable(),
partition_entry_object.get_partition_type(),
format_hints.Hex(partition_entry_object.get_size_in_sectors()),
renderers.NotApplicableValue()
))
else:
yield (1, (
format_hints.Hex(offset),
partition_table.get_disk_signature(),
self.get_hash(bootcode),
self.get_hash(full_mbr),
partition_index,
partition_entry_object.is_bootable(),
format_hints.Hex(partition_entry_object.get_bootable_flag()),
partition_entry_object.get_partition_type(),
format_hints.Hex(partition_entry_object.PartitionType),
format_hints.Hex(partition_entry_object.get_starting_lba()),
partition_entry_object.get_starting_cylinder(),
partition_entry_object.get_starting_chs(),
partition_entry_object.get_starting_sector(),
partition_entry_object.get_ending_cylinder(),
partition_entry_object.get_ending_chs(),
partition_entry_object.get_ending_sector(),
format_hints.Hex(partition_entry_object.get_size_in_sectors()),
renderers.NotApplicableValue(),
renderers.NotApplicableValue()
))
else:
vollog.log(constants.LOGLEVEL_VVVV, f"Not a valid MBR: Data all zeroed out : {format_hints.Hex(offset)}")
continue
except exceptions.PagedInvalidAddressException as excp:
vollog.log(constants.LOGLEVEL_VVVV, f"Invalid address identified in guessed MBR: {hex(excp.invalid_address)}")
continue
def run(self)-> renderers.TreeGrid:
if not self.config.get("full", True):
return renderers.TreeGrid([
("Potential MBR at Physical Offset", format_hints.Hex),
("Disk Signature", str),
("Bootcode MD5", str),
("Full MBR MD5", str),
("PartitionIndex", int),
("Bootable", bool),
("PartitionType", str),
("SectorInSize", format_hints.Hex),
("Disasm", interfaces.renderers.Disassembly)
], self._generator())
else:
return renderers.TreeGrid([
("Potential MBR at Physical Offset", format_hints.Hex),
("Disk Signature", str),
("Bootcode MD5", str),
("Full MBR MD5", str),
("PartitionIndex", int),
("Bootable", bool),
("BootFlag", format_hints.Hex),
("PartitionType", str),
("PartitionTypeRaw", format_hints.Hex),
("StartingLBA", format_hints.Hex),
("StartingCylinder", int),
("StartingCHS", int),
("StartingSector", int),
("EndingCylinder", int),
("EndingCHS", int),
("EndingSector", int),
("SectorInSize", format_hints.Hex),
("Disasm", interfaces.renderers.Disassembly),
("Bootcode", format_hints.HexBytes)
], self._generator())
@@ -55,14 +55,14 @@ class Privs(interfaces.plugins.PluginInterface):
try:
process_token = task.Token.dereference().cast("_TOKEN")
except exceptions.InvalidAddressException:
vollog.log(constants.LOGLEVEL_VVV, 'Skeep invalid token.')
vollog.log(constants.LOGLEVEL_VVV, 'Skip invalid token.')
continue
for value, present, enabled, default in process_token.privileges():
# Skip privileges whose bit positions cannot be
# translated to a privilege name
if not self.privilege_info.get(int(value)):
vollog.log(constants.LOGLEVEL_VVV, f'Skeep invalid privilege ({value}).')
vollog.log(constants.LOGLEVEL_VVV, f'Skip invalid privilege ({value}).')
continue
name, desc = self.privilege_info.get(int(value))
@@ -85,7 +85,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
proc: the process object with phisical address
proc: the process object with physical address
Returns:
A process object on virtual address layer
@@ -1,7 +1,7 @@
# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import volatility3.framework.symbols.windows.extensions.pool
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows import extensions
from volatility3.framework.symbols.windows.extensions import registry, pool
@@ -0,0 +1,62 @@
# This file is Copyright 2022 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from volatility3.framework import objects
class PARTITION_TABLE(objects.StructType):
def get_disk_signature(self) -> str:
"""Get Disk Signature (GUID)."""
return "{0:02x}-{1:02x}-{2:02x}-{3:02x}".format(
self.DiskSignature[0],
self.DiskSignature[1],
self.DiskSignature[2],
self.DiskSignature[3]
)
class PARTITION_ENTRY(objects.StructType):
def get_bootable_flag(self) -> int:
"""Get Bootable Flag."""
return self.BootableFlag
def is_bootable(self) -> bool:
"""Check Bootable Partition."""
return False if not (self.get_bootable_flag() == 0x80) else True
def get_partition_type(self) -> str:
"""Get Partition Type."""
return self.PartitionType.lookup() if self.PartitionType.is_valid_choice else "Not Defined PartitionType"
def get_starting_chs(self) -> int:
"""Get Starting CHS (Cylinder Header Sector) Address."""
return self.StartingCHS[0]
def get_ending_chs(self) -> int:
"""Get Ending CHS (Cylinder Header Sector) Address."""
return self.EndingCHS[0]
def get_starting_sector(self) -> int:
"""Get Starting Sector."""
return self.StartingCHS[1] % 64
def get_ending_sector(self) -> int:
"""Get Ending Sector."""
return self.EndingCHS[1] % 64
def get_starting_cylinder(self) -> int:
"""Get Starting Cylinder."""
return (self.StartingCHS[1] - self.get_starting_sector()) * 4 + self.StartingCHS[2]
def get_ending_cylinder(self) -> int:
"""Get Ending Cylinder."""
return (self.EndingCHS[1] - self.get_ending_sector()) * 4 + self.EndingCHS[2]
def get_starting_lba(self) -> int:
"""Get Starting LBA (Logical Block Addressing)."""
return self.StartingLBA
def get_size_in_sectors(self) -> int:
"""Get Size in Sectors."""
return self.SizeInSectors
@@ -0,0 +1,240 @@
{
"metadata": {
"producer": {
"version": "0.0.1",
"name": "Donghyun Kim (@digitalisx99)",
"comment": "Using structures defined in File System Forensic Analysis pg 88+",
"datetime": "2022-03-05T10:53:00"
},
"format": "6.1.0"
},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned long long": {
"kind": "int",
"size": 8,
"signed": false,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": true,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"int": {
"kind": "int",
"size": 4,
"signed": true,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "int",
"size": 1,
"signed": false,
"endian": "little"
},
"wchar": {
"kind": "int",
"size": 2,
"signed": true,
"endian": "little"
}
},
"symbols": {},
"enums": {
"PartitionTypes": {
"base": "unsigned char",
"constants": {
"Empty": 0,
"FAT12,CHS": 1,
"FAT16 16-32MB,CHS": 4,
"Microsoft Extended": 5,
"FAT16 32MB,CHS": 6,
"NTFS": 7,
"FAT32,CHS": 11,
"FAT32,LBA": 12,
"FAT16, 32MB-2GB,LBA": 14,
"Microsoft Extended, LBA": 15,
"Hidden FAT12,CHS": 17,
"Hidden FAT16,16-32MB,CHS": 20,
"Hidden FAT16,32MB-2GB,CHS": 22,
"AST SmartSleep Partition": 24,
"Hidden FAT32,CHS": 27,
"Hidden FAT32,LBA": 28,
"Hidden FAT16,32MB-2GB,LBA": 30,
"PQservice": 39,
"Plan 9 partition": 57,
"PartitionMagic recovery partition": 60,
"Microsoft MBR,Dynamic Disk": 66,
"GoBack partition": 68,
"Novell": 81,
"CP/M": 82,
"Unix System V": 99,
"PC-ARMOUR protected partition": 100,
"Solaris x86 or Linux Swap": 130,
"Linux": 131,
"Hibernation": 132,
"Linux Extended": 133,
"NTFS Volume Set": 134,
"NTFS Volume Set": 135,
"BSD/OS": 159,
"Hibernation": 160,
"Hibernation": 161,
"FreeBSD": 165,
"OpenBSD": 166,
"Mac OSX": 168,
"NetBSD": 169,
"Mac OSX Boot": 171,
"MacOS X HFS": 175,
"BSDI": 183,
"BSDI Swap": 184,
"Boot Wizard hidden": 187,
"Solaris 8 boot partition": 190,
"CP/M-86": 216,
"Dell PowerEdge Server utilities (FAT fs)": 222,
"DG/UX virtual disk manager partition": 223,
"BeOS BFS": 235,
"EFI GPT Disk": 238,
"EFI System Partition": 239,
"VMWare File System": 251,
"VMWare Swap": 252
},
"size": 1
}
},
"user_types": {
"PARTITION_ENTRY":{
"fields": {
"BootableFlag": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned char"
}
},
"StartingCHS": {
"offset": 1,
"type": {
"count": 3,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
}
},
"PartitionType": {
"offset": 4,
"type": {
"kind": "enum",
"name": "PartitionTypes"
}
},
"EndingCHS": {
"offset": 5,
"type": {
"count": 3,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
}
},
"StartingLBA": {
"offset": 8,
"type": {
"kind": "base",
"name": "unsigned int"
}
},
"SizeInSectors": {
"offset": 12,
"type": {
"kind": "base",
"name": "unsigned int"
}
}
},
"kind": "struct",
"size": 16
},
"PARTITION_TABLE":{
"fields":{
"DiskSignature": {
"offset": 440,
"type": {
"count": 4,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
}
},
"Unused": {
"offset": 444,
"type": {
"kind": "base",
"name": "unsigned short"
}
},
"FirstEntry":{
"offset": 446,
"type": {
"kind": "struct",
"name": "PARTITION_ENTRY"
}
},
"SecondEntry":{
"offset": 462,
"type": {
"kind": "struct",
"name": "PARTITION_ENTRY"
}
},
"ThirdEntry":{
"offset": 478,
"type": {
"kind": "struct",
"name": "PARTITION_ENTRY"
}
},
"FourthEntry":{
"offset": 494,
"type": {
"kind": "struct",
"name": "PARTITION_ENTRY"
}
},
"Signature":{
"offset": 510,
"type": {
"kind": "base",
"name": "unsigned short"
}
}
},
"kind": "struct",
"size": 512
}
}
}
@@ -134,7 +134,7 @@
"kind": "base",
"name": "unsigned char"
}
}
}
},
"UpdateSequenceOffset": {
"offset": 4,
@@ -192,7 +192,7 @@
"name": "unsigned int"
}
},
"AlocatedSize": {
"AllocatedSize": {
"offset": 28,
"type":{
"kind": "base",
@@ -270,7 +270,8 @@
"offset": 8,
"type": {
"kind": "base",
"name": "unsigned char" }
"name": "unsigned char"
}
},
"NameLength": {
"offset": 9,
@@ -322,7 +323,8 @@
"offset": 8,
"type": {
"kind": "base",
"name": "unsigned short" }
"name": "unsigned short"
}
}
},
"kind": "struct",