mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-08-31 20:29:46 +02:00
Merge branch 'volatilityfoundation:develop' into hsarkey/windows-dlllist
This commit is contained in:
@@ -6,7 +6,7 @@ jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: psf/black@stable
|
||||
with:
|
||||
options: "--check --diff --verbose"
|
||||
|
||||
@@ -20,9 +20,9 @@ jobs:
|
||||
matrix:
|
||||
python-version: ["3.7"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
python setup.py bdist_wheel
|
||||
|
||||
- name: Archive dist
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: volatility3-pypi
|
||||
path: |
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
host: [ ubuntu-latest, windows-latest ]
|
||||
python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
|
||||
@@ -8,9 +8,9 @@ jobs:
|
||||
matrix:
|
||||
python-version: ["3.7"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ authors:
|
||||
identifiers:
|
||||
- type: url
|
||||
value: 'https://github.com/volatilityfoundation/volatility3'
|
||||
description: Volatility 3 source code respository
|
||||
description: Volatility 3 source code repository
|
||||
repository-code: 'https://github.com/volatilityfoundation/volatility3'
|
||||
url: 'https://github.com/volatilityfoundation/volatility3'
|
||||
abstract: >-
|
||||
|
||||
+4
-17
@@ -1,22 +1,9 @@
|
||||
# The following packages are required for core functionality.
|
||||
pefile>=2023.2.7
|
||||
|
||||
# The following packages are optional.
|
||||
# If certain packages are not necessary, place a comment (#) at the start of the line.
|
||||
|
||||
# This is required for the yara plugins
|
||||
yara-python>=3.8.0
|
||||
|
||||
# This is required for several plugins that perform malware analysis and disassemble code.
|
||||
# It can also improve accuracy of Windows 8 and later memory samples.
|
||||
capstone>=3.0.5
|
||||
|
||||
# This is required by plugins that decrypt passwords, password hashes, etc.
|
||||
pycryptodome
|
||||
-r requirements.txt
|
||||
|
||||
# This can improve error messages regarding improperly configured ISF files,
|
||||
# but is only recommended for development
|
||||
jsonschema>=2.3.0
|
||||
|
||||
# This is required for memory acquisition via leechcore/pcileech.
|
||||
leechcorepyc>=2.4.0
|
||||
# Used to build executable file
|
||||
pyinstaller>=6.5.0
|
||||
pyinstaller-hooks-contrib>=2024.3
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
# The following packages are required for core functionality.
|
||||
pefile>=2023.2.7
|
||||
# Include the minimal requirements
|
||||
-r requirements-minimal.txt
|
||||
|
||||
# The following packages are optional.
|
||||
# If certain packages are not necessary, place a comment (#) at the start of the line.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import shutil
|
||||
@@ -189,6 +190,16 @@ def test_windows_svcscan(image, volatility, python):
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_windows_thrdscan(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("windows.thrdscan.ThrdScan", image, volatility, python)
|
||||
# find pid 4 (of system process) which starts with lowest tids
|
||||
assert out.find(b"\t4\t8") != -1
|
||||
assert out.find(b"\t4\t12") != -1
|
||||
assert out.find(b"\t4\t16") != -1
|
||||
#assert out.find(b"this raieses AssertionError") != -1
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_windows_privileges(image, volatility, python):
|
||||
rc, out, err = runvol_plugin(
|
||||
"windows.privileges.Privs", image, volatility, python, pluginargs=["--pid", "4"]
|
||||
@@ -331,6 +342,32 @@ def test_linux_tty_check(image, volatility, python):
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_library_list(image, volatility, python):
|
||||
rc, out, err = runvol_plugin(
|
||||
"linux.library_list.LibraryList", image, volatility, python
|
||||
)
|
||||
|
||||
assert re.search(
|
||||
rb"NetworkManager\s2363\s0x7f52cdda0000\s/lib/x86_64-linux-gnu/libnss_files.so.2",
|
||||
out,
|
||||
)
|
||||
assert re.search(
|
||||
rb"gnome-settings-\s3807\s0x7f7e660b5000\s/lib/x86_64-linux-gnu/libbz2.so.1.0",
|
||||
out,
|
||||
)
|
||||
assert re.search(
|
||||
rb"gdu-notificatio\s3878\s0x7f25ce33e000\s/usr/lib/x86_64-linux-gnu/libXau.so.6",
|
||||
out,
|
||||
)
|
||||
assert re.search(
|
||||
rb"bash\s8600\s0x7fe78a85f000\s/lib/x86_64-linux-gnu/libnss_files.so.2",
|
||||
out,
|
||||
)
|
||||
|
||||
assert out.count(b"\n") >= 2677
|
||||
assert rc == 0
|
||||
|
||||
|
||||
# MAC
|
||||
|
||||
|
||||
|
||||
+34
-11
@@ -264,12 +264,14 @@ class CommandLine:
|
||||
file_logger.setFormatter(file_formatter)
|
||||
rootlog.addHandler(file_logger)
|
||||
vollog.info("Logging started")
|
||||
|
||||
self.order_extra_verbose_levels()
|
||||
if partial_args.verbosity < 3:
|
||||
if partial_args.verbosity < 1:
|
||||
sys.tracebacklimit = None
|
||||
console.setLevel(30 - (partial_args.verbosity * 10))
|
||||
console.setLevel(logging.WARNING - (partial_args.verbosity * 10))
|
||||
else:
|
||||
console.setLevel(10 - (partial_args.verbosity - 2))
|
||||
console.setLevel(logging.DEBUG - (partial_args.verbosity - 2))
|
||||
|
||||
for level, msg in delayed_logs:
|
||||
vollog.log(level, msg)
|
||||
@@ -695,6 +697,17 @@ class CommandLine:
|
||||
)
|
||||
context.config[extended_path] = value
|
||||
|
||||
def order_extra_verbose_levels(self):
|
||||
for level, level_value in enumerate(
|
||||
[
|
||||
constants.LOGLEVEL_V,
|
||||
constants.LOGLEVEL_VV,
|
||||
constants.LOGLEVEL_VVV,
|
||||
constants.LOGLEVEL_VVVV,
|
||||
]
|
||||
):
|
||||
logging.addLevelName(level_value, f"DETAIL {level+1}")
|
||||
|
||||
def file_handler_class_factory(self, direct=True):
|
||||
output_dir = self.output_dir
|
||||
|
||||
@@ -703,19 +716,17 @@ class CommandLine:
|
||||
"""Gets the final filename"""
|
||||
if output_dir is None:
|
||||
raise TypeError("Output directory is not a string")
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
pref_name_array = self.preferred_filename.split(".")
|
||||
filename, extension = (
|
||||
os.path.join(output_dir, ".".join(pref_name_array[:-1])),
|
||||
pref_name_array[-1],
|
||||
)
|
||||
output_filename = f"{filename}.{extension}"
|
||||
output_filename = os.path.join(output_dir, self.preferred_filename)
|
||||
filename, extension = os.path.splitext(output_filename)
|
||||
|
||||
counter = 1
|
||||
while os.path.exists(output_filename):
|
||||
output_filename = f"{filename}-{counter}.{extension}"
|
||||
output_filename = f"{filename}-{counter}{extension}"
|
||||
counter += 1
|
||||
|
||||
return output_filename
|
||||
|
||||
class CLIMemFileHandler(io.BytesIO, CLIFileHandler):
|
||||
@@ -778,8 +789,16 @@ class CommandLine:
|
||||
if self._file.closed:
|
||||
return None
|
||||
|
||||
self._file.close()
|
||||
output_filename = self._get_final_filename()
|
||||
|
||||
# Update the filename, which may have changed if a file with
|
||||
# the same name already existed. This needs to be done before
|
||||
# closing the file, otherwise FileHandlerInterface will raise
|
||||
# an exception. Also, the preferred_filename setter only allows
|
||||
# a specific set of characters, where '/' is not in that list
|
||||
self.preferred_filename = os.path.basename(output_filename)
|
||||
|
||||
self._file.close()
|
||||
os.rename(self._name, output_filename)
|
||||
|
||||
if direct:
|
||||
@@ -827,7 +846,11 @@ class CommandLine:
|
||||
requirement,
|
||||
volatility3.framework.configuration.requirements.ListRequirement,
|
||||
):
|
||||
additional["type"] = requirement.element_type
|
||||
# Allow a list of integers, specified with the convenient 0x hexadecimal format
|
||||
if requirement.element_type == int:
|
||||
additional["type"] = lambda x: int(x, 0)
|
||||
else:
|
||||
additional["type"] = requirement.element_type
|
||||
nargs = "*" if requirement.optional else "+"
|
||||
additional["nargs"] = nargs
|
||||
elif isinstance(
|
||||
|
||||
@@ -197,10 +197,11 @@ class VolShell(cli.CommandLine):
|
||||
vollog.addHandler(file_logger)
|
||||
vollog.info("Logging started")
|
||||
|
||||
self.order_extra_verbose_levels()
|
||||
if partial_args.verbosity < 3:
|
||||
console.setLevel(30 - (partial_args.verbosity * 10))
|
||||
console.setLevel(logging.WARNING - (partial_args.verbosity * 10))
|
||||
else:
|
||||
console.setLevel(10 - (partial_args.verbosity - 2))
|
||||
console.setLevel(logging.DEBUG - (partial_args.verbosity - 2))
|
||||
|
||||
for level, msg in delayed_logs:
|
||||
vollog.log(level, msg)
|
||||
|
||||
@@ -223,8 +223,14 @@ def list_plugins() -> Dict[str, Type[interfaces.plugins.PluginInterface]]:
|
||||
return plugin_list
|
||||
|
||||
|
||||
def clear_cache(complete=False):
|
||||
def clear_cache(complete=True):
|
||||
try:
|
||||
if complete:
|
||||
glob_pattern = "*.cache"
|
||||
for cache_filename in glob.glob(
|
||||
os.path.join(constants.CACHE_PATH, glob_pattern)
|
||||
):
|
||||
os.unlink(cache_filename)
|
||||
os.unlink(os.path.join(constants.CACHE_PATH, constants.IDENTIFIERS_FILENAME))
|
||||
except FileNotFoundError:
|
||||
vollog.log(constants.LOGLEVEL_VVVV, "Attempting to clear a non-existant cache")
|
||||
|
||||
@@ -159,7 +159,10 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
|
||||
# This we get for free
|
||||
aslr_shift = (
|
||||
init_task.files.cast("long unsigned int")
|
||||
int.from_bytes(
|
||||
init_task.files.cast("bytes", length=init_task.files.vol.size),
|
||||
byteorder=init_task.files.vol.data_format.byteorder,
|
||||
)
|
||||
- module.get_symbol("init_files").address
|
||||
)
|
||||
kaslr_shift = init_task_address - cls.virtual_to_physical_address(
|
||||
|
||||
@@ -44,7 +44,7 @@ BANG = "!"
|
||||
|
||||
# We use the SemVer 2.0.0 versioning scheme
|
||||
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
|
||||
VERSION_MINOR = 6 # Number of changes that only add to the interface
|
||||
VERSION_MINOR = 7 # Number of changes that only add to the interface
|
||||
VERSION_PATCH = 1 # Number of changes that do not change the interface
|
||||
VERSION_SUFFIX = ""
|
||||
|
||||
@@ -59,14 +59,18 @@ PACKAGE_VERSION = (
|
||||
AUTOMAGIC_CONFIG_PATH = "automagic"
|
||||
"""The root section within the context configuration for automagic values"""
|
||||
|
||||
LOGLEVEL_INFO = 20
|
||||
"""Logging level for information data, showed when use the requests any logging: -v"""
|
||||
LOGLEVEL_DEBUG = 10
|
||||
"""Logging level for debugging data, showed when the user requests more logging detail: -vv"""
|
||||
LOGLEVEL_V = 9
|
||||
"""Logging level for a single -v"""
|
||||
"""Logging level for the lowest "extra" level of logging: -vvv"""
|
||||
LOGLEVEL_VV = 8
|
||||
"""Logging level for -vv"""
|
||||
"""Logging level for two levels of detail: -vvvv"""
|
||||
LOGLEVEL_VVV = 7
|
||||
"""Logging level for -vvv"""
|
||||
"""Logging level for three levels of detail: -vvvvv"""
|
||||
LOGLEVEL_VVVV = 6
|
||||
"""Logging level for -vvvv"""
|
||||
"""Logging level for four levels of detail: -vvvvvv"""
|
||||
|
||||
CACHE_PATH = os.path.join(os.path.expanduser("~"), ".cache", "volatility3")
|
||||
"""Default path to store cached data"""
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
|
||||
Linux-specific values that aren't found in debug symbols
|
||||
"""
|
||||
from enum import IntEnum
|
||||
|
||||
KERNEL_NAME = "__kernel__"
|
||||
|
||||
# arch/x86/include/asm/page_types.h
|
||||
PAGE_SHIFT = 12
|
||||
"""The value hard coded from the Linux Kernel (hence not extracted from the layer itself)"""
|
||||
|
||||
# include/linux/sched.h
|
||||
@@ -281,3 +280,25 @@ CAPABILITIES = (
|
||||
)
|
||||
|
||||
ELF_MAX_EXTRACTION_SIZE = 1024 * 1024 * 1024 * 4 - 1
|
||||
|
||||
|
||||
class ELF_IDENT(IntEnum):
|
||||
"""ELF header e_ident indexes"""
|
||||
|
||||
EI_MAG0 = 0
|
||||
EI_MAG1 = 1
|
||||
EI_MAG2 = 2
|
||||
EI_MAG3 = 3
|
||||
EI_CLASS = 4
|
||||
EI_DATA = 5
|
||||
EI_VERSION = 6
|
||||
EI_OSABI = 7
|
||||
EI_PAD = 8
|
||||
|
||||
|
||||
class ELF_CLASS(IntEnum):
|
||||
"""ELF header class types"""
|
||||
|
||||
ELFCLASSNONE = 0
|
||||
ELFCLASS32 = 1
|
||||
ELFCLASS64 = 2
|
||||
|
||||
@@ -256,12 +256,13 @@ class Module(interfaces.context.ModuleInterface):
|
||||
if not absolute:
|
||||
offset += self._offset
|
||||
|
||||
# Ensure we don't use a layer_name other than the module's, why would anyone do that?
|
||||
if "layer_name" in kwargs:
|
||||
del kwargs["layer_name"]
|
||||
# We have to allow using an alternative layer name due to pool scanners switching
|
||||
# to the memory layer for scanning samples prior to Windows 10.
|
||||
layer_name = kwargs.pop("layer_name", self._layer_name)
|
||||
|
||||
return self._context.object(
|
||||
object_type=object_type,
|
||||
layer_name=self._layer_name,
|
||||
layer_name=layer_name,
|
||||
offset=offset,
|
||||
native_layer_name=native_layer_name or self._native_layer_name,
|
||||
**kwargs,
|
||||
|
||||
@@ -60,7 +60,7 @@ class FileHandlerInterface(io.RawIOBase):
|
||||
@staticmethod
|
||||
def sanitize_filename(filename: str) -> str:
|
||||
"""Sanititizes the filename to ensure only a specific whitelist of characters is allowed through"""
|
||||
allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.- ()[]\{\}!$%^:#~?<>,|"
|
||||
allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.- ()[]{}!$%^:#~?<>,|"
|
||||
result = ""
|
||||
for char in filename:
|
||||
if char in allowed:
|
||||
|
||||
@@ -96,12 +96,13 @@ class WindowsCrashDump32Layer(segmented.SegmentedLayer):
|
||||
def get_summary_header(self) -> interfaces.objects.ObjectInterface:
|
||||
return self.context.object(
|
||||
self._crash_common_table_name + constants.BANG + "_SUMMARY_DUMP",
|
||||
offset=0x1000 * self.headerpages,
|
||||
offset=self._page_size * self.headerpages,
|
||||
layer_name=self._base_layer,
|
||||
)
|
||||
|
||||
def _load_segments(self) -> None:
|
||||
"""Loads up the segments from the meta_layer."""
|
||||
"""Loads up the segments from the meta_layer.
|
||||
A segment is a set of contiguous memory pages."""
|
||||
|
||||
segments = []
|
||||
|
||||
@@ -119,70 +120,87 @@ class WindowsCrashDump32Layer(segmented.SegmentedLayer):
|
||||
for run in header.PhysicalMemoryBlockBuffer.Run:
|
||||
segments.append(
|
||||
(
|
||||
run.BasePage * 0x1000,
|
||||
offset * 0x1000,
|
||||
run.PageCount * 0x1000,
|
||||
run.PageCount * 0x1000,
|
||||
run.BasePage * self._page_size,
|
||||
offset * self._page_size,
|
||||
run.PageCount * self._page_size,
|
||||
run.PageCount * self._page_size,
|
||||
)
|
||||
)
|
||||
offset += run.PageCount
|
||||
|
||||
elif self.dump_type == 0x05:
|
||||
summary_header = self.get_summary_header()
|
||||
first_bit = None # First bit in a run
|
||||
first_offset = 0 # File offset of first bit
|
||||
last_bit_seen = 0 # Most recent bit processed
|
||||
offset = summary_header.HeaderSize # Size of file headers
|
||||
buffer_char = summary_header.get_buffer_char()
|
||||
buffer_long = summary_header.get_buffer_long()
|
||||
|
||||
for outer_index in range(0, ((summary_header.BitmapSize + 31) // 32)):
|
||||
if buffer_long[outer_index] == 0:
|
||||
if first_bit is not None:
|
||||
last_bit = ((outer_index - 1) * 32) + 31
|
||||
segment_length = (last_bit - first_bit + 1) * 0x1000
|
||||
seg_first_bit = None # First bit in a run
|
||||
seg_first_offset = 0 # File offset of first bit
|
||||
offset = (
|
||||
summary_header.HeaderSize
|
||||
) # Offset to the start of actual memory dump
|
||||
ulong_bitmap_array = summary_header.get_buffer_long()
|
||||
# outer_index points to a 32 bits array inside a list of arrays,
|
||||
# each bit indicating a page mapping state
|
||||
for outer_index in range(0, ulong_bitmap_array.vol.count):
|
||||
ulong_bitmap = ulong_bitmap_array[outer_index]
|
||||
# All pages in this 32 bits array are mapped (speedup iteration process)
|
||||
if ulong_bitmap == 0xFFFFFFFF:
|
||||
# New segment
|
||||
if seg_first_bit is None:
|
||||
seg_first_offset = offset
|
||||
seg_first_bit = outer_index * 32
|
||||
offset += 32 * self._page_size
|
||||
# No pages in this 32 bits array are mapped (speedup iteration process)
|
||||
elif ulong_bitmap == 0:
|
||||
# End of segment
|
||||
if seg_first_bit is not None:
|
||||
last_bit = (outer_index - 1) * 32 + 31
|
||||
segment_length = (
|
||||
last_bit - seg_first_bit + 1
|
||||
) * self._page_size
|
||||
segments.append(
|
||||
(
|
||||
first_bit * 0x1000,
|
||||
first_offset,
|
||||
seg_first_bit * self._page_size,
|
||||
seg_first_offset,
|
||||
segment_length,
|
||||
segment_length,
|
||||
)
|
||||
)
|
||||
first_bit = None
|
||||
elif buffer_long[outer_index] == 0xFFFFFFFF:
|
||||
if first_bit is None:
|
||||
first_offset = offset
|
||||
first_bit = outer_index * 32
|
||||
offset = offset + (32 * 0x1000)
|
||||
seg_first_bit = None
|
||||
# Some pages in this 32 bits array are mapped and some aren't
|
||||
else:
|
||||
for inner_index in range(0, 32):
|
||||
bit_addr = outer_index * 32 + inner_index
|
||||
if (buffer_char[bit_addr >> 3] >> (bit_addr & 0x7)) & 1:
|
||||
if first_bit is None:
|
||||
first_offset = offset
|
||||
first_bit = bit_addr
|
||||
offset = offset + 0x1000
|
||||
for inner_bit_position in range(0, 32):
|
||||
current_bit = outer_index * 32 + inner_bit_position
|
||||
page_mapped = ulong_bitmap & (1 << inner_bit_position)
|
||||
if page_mapped:
|
||||
# New segment
|
||||
if seg_first_bit is None:
|
||||
seg_first_offset = offset
|
||||
seg_first_bit = current_bit
|
||||
offset += self._page_size
|
||||
else:
|
||||
if first_bit is not None:
|
||||
# End of segment
|
||||
if seg_first_bit is not None:
|
||||
segment_length = (
|
||||
(bit_addr - 1) - first_bit + 1
|
||||
) * 0x1000
|
||||
current_bit - 1 - seg_first_bit + 1
|
||||
) * self._page_size
|
||||
segments.append(
|
||||
(
|
||||
first_bit * 0x1000,
|
||||
first_offset,
|
||||
seg_first_bit * self._page_size,
|
||||
seg_first_offset,
|
||||
segment_length,
|
||||
segment_length,
|
||||
)
|
||||
)
|
||||
first_bit = None
|
||||
last_bit_seen = (outer_index * 32) + 31
|
||||
seg_first_bit = None
|
||||
last_bit_seen = outer_index * 32 + 31
|
||||
|
||||
if first_bit is not None:
|
||||
segment_length = (last_bit_seen - first_bit + 1) * 0x1000
|
||||
if seg_first_bit is not None:
|
||||
segment_length = (last_bit_seen - seg_first_bit + 1) * self._page_size
|
||||
segments.append(
|
||||
(first_bit * 0x1000, first_offset, segment_length, segment_length)
|
||||
(
|
||||
seg_first_bit * self._page_size,
|
||||
seg_first_offset,
|
||||
segment_length,
|
||||
segment_length,
|
||||
)
|
||||
)
|
||||
else:
|
||||
vollog.log(
|
||||
@@ -261,11 +279,15 @@ class WindowsCrashDumpStacker(interfaces.automagic.StackerLayerInterface):
|
||||
progress_callback: constants.ProgressCallback = None,
|
||||
) -> Optional[interfaces.layers.DataLayerInterface]:
|
||||
for layer in [WindowsCrashDump32Layer, WindowsCrashDump64Layer]:
|
||||
with contextlib.suppress(WindowsCrashDumpFormatException):
|
||||
try:
|
||||
layer.check_header(context.layers[layer_name])
|
||||
new_name = context.layers.free_layer_name(layer.__name__)
|
||||
context.config[
|
||||
interfaces.configuration.path_join(new_name, "base_layer")
|
||||
] = layer_name
|
||||
return layer(context, new_name, new_name)
|
||||
except WindowsCrashDumpFormatException as excp:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV, f"Exception reading crashdump: {excp}"
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -6,9 +6,11 @@ import struct
|
||||
from typing import Optional
|
||||
|
||||
from volatility3.framework import exceptions, interfaces, constants
|
||||
from volatility3.framework.constants.linux import ELF_CLASS
|
||||
from volatility3.framework.layers import segmented
|
||||
from volatility3.framework.symbols import intermed
|
||||
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -21,7 +23,7 @@ class Elf64Layer(segmented.SegmentedLayer):
|
||||
|
||||
_header_struct = struct.Struct("<IBBB")
|
||||
MAGIC = 0x464C457F # "\x7fELF"
|
||||
ELF_CLASS = 2
|
||||
ELF_CLASS = ELF_CLASS.ELFCLASS64
|
||||
|
||||
def __init__(
|
||||
self, context: interfaces.context.ContextInterface, config_path: str, name: str
|
||||
@@ -50,8 +52,17 @@ class Elf64Layer(segmented.SegmentedLayer):
|
||||
offset=ehdr.e_phoff + (pindex * ehdr.e_phentsize),
|
||||
)
|
||||
# We only want PT_TYPES with valid sizes
|
||||
try:
|
||||
ptype = phdr.p_type.description
|
||||
except ValueError:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping unknown ELF program header type: {phdr.p_type}",
|
||||
)
|
||||
continue
|
||||
|
||||
if (
|
||||
phdr.p_type.lookup() == "PT_LOAD"
|
||||
ptype == "PT_LOAD"
|
||||
and phdr.p_filesz == phdr.p_memsz
|
||||
and phdr.p_filesz > 0
|
||||
):
|
||||
|
||||
@@ -67,6 +67,12 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
math.ceil(math.log2(struct.calcsize(self._entry_format)))
|
||||
)
|
||||
|
||||
@classproperty
|
||||
@functools.lru_cache()
|
||||
def page_shift(cls) -> int:
|
||||
"""Page shift for the intel memory layers."""
|
||||
return cls._page_size_in_bits
|
||||
|
||||
@classproperty
|
||||
@functools.lru_cache()
|
||||
def page_size(cls) -> int:
|
||||
@@ -76,6 +82,12 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
"""
|
||||
return 1 << cls._page_size_in_bits
|
||||
|
||||
@classproperty
|
||||
@functools.lru_cache()
|
||||
def page_mask(cls) -> int:
|
||||
"""Page mask for the intel memory layers."""
|
||||
return ~(cls.page_size - 1)
|
||||
|
||||
@classproperty
|
||||
@functools.lru_cache()
|
||||
def bits_per_register(cls) -> int:
|
||||
|
||||
@@ -151,6 +151,12 @@ class ResourceAccessor(object):
|
||||
raise excp
|
||||
else:
|
||||
raise excp
|
||||
except ValueError as excp:
|
||||
# Reraise errors such as proxy auth errors as offline exception errors
|
||||
# Example Proxy auth error - ValueError: AbstractDigestAuthHandler does not support the following scheme: 'Negotiate'
|
||||
vollog.info(f"Cannot access {url} due to {excp} - Setting OFFLINE")
|
||||
constants.OFFLINE = True
|
||||
raise exceptions.OfflineException(url)
|
||||
except exceptions.OfflineException:
|
||||
vollog.info(f"Not accessing {url} in offline mode")
|
||||
raise
|
||||
|
||||
@@ -152,7 +152,7 @@ class NonLinearlySegmentedLayer(
|
||||
raise ValueError("SegmentedLayer must contain some segments")
|
||||
if self._maxaddr is None:
|
||||
mapped, _, length, _ = self._segments[-1]
|
||||
self._maxaddr = mapped + length
|
||||
self._maxaddr = mapped + length - 1
|
||||
return self._maxaddr
|
||||
|
||||
@property
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Optional
|
||||
from volatility3.framework import constants, interfaces, exceptions
|
||||
from volatility3.framework.layers import elf
|
||||
from volatility3.framework.symbols import intermed
|
||||
from volatility3.framework.constants.linux import ELF_CLASS
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
@@ -14,7 +15,7 @@ class XenCoreDumpLayer(elf.Elf64Layer):
|
||||
|
||||
_header_struct = struct.Struct("<IBBB")
|
||||
MAGIC = 0x464C457F # "\x7fELF"
|
||||
ELF_CLASS = 2
|
||||
ELF_CLASS = ELF_CLASS.ELFCLASS64
|
||||
|
||||
def __init__(
|
||||
self, context: interfaces.context.ContextInterface, config_path: str, name: str
|
||||
@@ -115,12 +116,10 @@ class XenCoreDumpLayer(elf.Elf64Layer):
|
||||
)
|
||||
)
|
||||
elif p2m_data and pfn_data:
|
||||
raise elf.ElfFormatException(
|
||||
self.name, f"Both P2M and PFN in Xen Core Dump"
|
||||
)
|
||||
raise elf.ElfFormatException(self.name, "Both P2M and PFN in Xen Core Dump")
|
||||
else:
|
||||
raise elf.ElfFormatException(
|
||||
self.name, f"Neither P2M nor PFN in Xen Core Dump"
|
||||
self.name, "Neither P2M nor PFN in Xen Core Dump"
|
||||
)
|
||||
|
||||
if len(segments) == 0:
|
||||
|
||||
@@ -18,7 +18,7 @@ class LayerWriter(plugins.PluginInterface):
|
||||
default_block_size = 0x500000
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (2, 0, 0)
|
||||
_version = (2, 0, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -77,7 +77,7 @@ class LayerWriter(plugins.PluginInterface):
|
||||
|
||||
file_handle = open_method(preferred_name)
|
||||
for i in range(0, layer.maximum_address, chunk_size):
|
||||
current_chunk_size = min(chunk_size, layer.maximum_address - i)
|
||||
current_chunk_size = min(chunk_size, layer.maximum_address + 1 - i)
|
||||
data = layer.read(i, current_chunk_size, pad=True)
|
||||
file_handle.write(data)
|
||||
if progress_callback:
|
||||
@@ -95,7 +95,7 @@ class LayerWriter(plugins.PluginInterface):
|
||||
if not self.config["layers"]:
|
||||
self.config["layers"] = []
|
||||
for name in self.context.layers:
|
||||
if not self.context.layers[name].metadata.get("mapped", False):
|
||||
if "mapped" not in self.context.layers[name].metadata:
|
||||
self.config["layers"] = [name]
|
||||
|
||||
for name in self.config["layers"]:
|
||||
@@ -103,7 +103,8 @@ class LayerWriter(plugins.PluginInterface):
|
||||
if name not in self.context.layers:
|
||||
yield 0, (f"Layer Name {name} does not exist",)
|
||||
else:
|
||||
output_name = self.config.get("output", ".".join([name, "raw"]))
|
||||
default_output_name = f"{name}.raw"
|
||||
output_name = self.config.get("output", default_output_name)
|
||||
try:
|
||||
file_handle = self.write_layer(
|
||||
self.context,
|
||||
@@ -114,10 +115,12 @@ class LayerWriter(plugins.PluginInterface):
|
||||
progress_callback=self._progress_callback,
|
||||
)
|
||||
file_handle.close()
|
||||
|
||||
# Update the filename, which may have changed if a file
|
||||
# with the same name already existed.
|
||||
output_name = file_handle.preferred_filename
|
||||
except IOError as excp:
|
||||
yield 0, (
|
||||
f"Layer cannot be written to {self.config['output_name']}: {excp}",
|
||||
)
|
||||
yield 0, (f"Layer cannot be written to {output_name}: {excp}",)
|
||||
|
||||
yield 0, (f"Layer has been written to {output_name}",)
|
||||
|
||||
|
||||
@@ -14,8 +14,10 @@ from volatility3.framework.objects import utility
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.framework.symbols import intermed
|
||||
from volatility3.framework.symbols.linux.extensions import elf
|
||||
from volatility3.framework.constants.linux import ELF_MAX_EXTRACTION_SIZE
|
||||
from volatility3.plugins.linux import pslist
|
||||
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -23,7 +25,7 @@ class Elfs(plugins.PluginInterface):
|
||||
"""Lists all memory mapped ELF files for all processes."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (2, 0, 0)
|
||||
_version = (2, 0, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -82,12 +84,20 @@ class Elfs(plugins.PluginInterface):
|
||||
)
|
||||
|
||||
if not elf_object.is_valid():
|
||||
vollog.debug("ELF object to be dumped is not valid")
|
||||
return None
|
||||
|
||||
sections = {}
|
||||
# TODO: Apply more effort to reconstruct ELF, e.g.: https://github.com/enbarberis/core2ELF64 ?
|
||||
for phdr in elf_object.get_program_headers():
|
||||
if phdr.p_type != 1: # PT_LOAD = 1
|
||||
try:
|
||||
if phdr.p_type.description != "PT_LOAD":
|
||||
continue
|
||||
except ValueError:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping unknown ELF program header type: {phdr.p_type}",
|
||||
)
|
||||
continue
|
||||
|
||||
start = phdr.p_vaddr
|
||||
@@ -95,18 +105,18 @@ class Elfs(plugins.PluginInterface):
|
||||
end = start + size
|
||||
|
||||
# Use complete memory pages for dumping
|
||||
# If start isn't a multiple of 4096, stick to the highest multiple < start
|
||||
# If end isn't a multiple of 4096, stick to the lowest multiple > end
|
||||
if start % 4096:
|
||||
start = start & ~0xFFF
|
||||
# If start isn't a multiple of a page, stick to the highest multiple < start
|
||||
# If end isn't a multiple of a page, stick to the lowest multiple > end
|
||||
if start % proc_layer.page_size:
|
||||
start = start & proc_layer.page_mask
|
||||
|
||||
if end % 4096:
|
||||
end = (end & ~0xFFF) + 4096
|
||||
if end % proc_layer.page_size:
|
||||
end = (end & proc_layer.page_mask) + proc_layer.page_size
|
||||
|
||||
real_size = end - start
|
||||
|
||||
# Check if ELF has a legitimate size
|
||||
if real_size < 0 or real_size > constants.linux.ELF_MAX_EXTRACTION_SIZE:
|
||||
if real_size < 0 or real_size > ELF_MAX_EXTRACTION_SIZE:
|
||||
raise ValueError(f"The claimed size of the ELF is invalid: {real_size}")
|
||||
|
||||
sections[start] = real_size
|
||||
@@ -140,12 +150,7 @@ class Elfs(plugins.PluginInterface):
|
||||
|
||||
for vma in task.mm.get_vma_iter():
|
||||
hdr = proc_layer.read(vma.vm_start, 4, pad=True)
|
||||
if not (
|
||||
hdr[0] == 0x7F
|
||||
and hdr[1] == 0x45
|
||||
and hdr[2] == 0x4C
|
||||
and hdr[3] == 0x46
|
||||
):
|
||||
if hdr != b"\x7fELF":
|
||||
continue
|
||||
|
||||
path = vma.get_name(self.context, task)
|
||||
|
||||
@@ -66,7 +66,7 @@ class ABCKmsg(ABC):
|
||||
self._config = config
|
||||
self.vmlinux = context.modules[self._config["kernel"]]
|
||||
self.layer_name = self.vmlinux.layer_name # type: ignore
|
||||
self.long_unsigned_int_size = self.vmlinux.get_type("long unsigned int").size
|
||||
self.long_unsigned_int_size = self.vmlinux.get_type("pointer").size
|
||||
|
||||
@classmethod
|
||||
def run_all(
|
||||
@@ -198,7 +198,9 @@ class ABCKmsg(ABC):
|
||||
class Kmsg_pre_3_5(ABCKmsg):
|
||||
"""The kernel ring buffer (log_buf) is a char array that sequentially stores
|
||||
log lines, each separated by newline (LF) characters. i.e:
|
||||
<6>[ 9565.250411] line1!\n<6>[ 9565.250412] line2\n...
|
||||
|
||||
<6>[ 9565.250411] line1!\\n<6>[ 9565.250412] line2\\n...
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
# This file is Copyright 2024 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
|
||||
from typing import Iterable, Tuple
|
||||
|
||||
from volatility3.framework import interfaces, renderers, constants, exceptions
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.framework.symbols import intermed
|
||||
from volatility3.framework.symbols.linux.extensions import elf
|
||||
from volatility3.plugins.linux import pslist
|
||||
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LibraryList(interfaces.plugins.PluginInterface):
|
||||
"""Enumerate libraries loaded into processes"""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
_version = (1, 0, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
return [
|
||||
requirements.ModuleRequirement(
|
||||
name="kernel",
|
||||
description="Linux kernel",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(2, 2, 0)
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name="pids",
|
||||
description="Filter on specific process IDs",
|
||||
element_type=int,
|
||||
optional=True,
|
||||
),
|
||||
]
|
||||
|
||||
def _get_libdl_libraries(
|
||||
self, proc_layer_name: str, vma_start: int
|
||||
) -> interfaces.objects.ObjectInterface:
|
||||
"""Get the ELF link map objects for the given VMA address
|
||||
|
||||
Args:
|
||||
proc_layer_name (str): Name of the process layer
|
||||
vma_start (int): VMA start address
|
||||
|
||||
Yields:
|
||||
ELF link map objects for the given VMA address
|
||||
"""
|
||||
elf_table_name = intermed.IntermediateSymbolTable.create(
|
||||
self.context,
|
||||
self.config_path,
|
||||
"linux",
|
||||
"elf",
|
||||
class_types=elf.class_types,
|
||||
)
|
||||
elf_object = self.context.object(
|
||||
elf_table_name + constants.BANG + "Elf",
|
||||
offset=vma_start,
|
||||
layer_name=proc_layer_name,
|
||||
)
|
||||
|
||||
if not elf_object or not elf_object.is_valid():
|
||||
return None
|
||||
|
||||
kernel = self.context.modules[self.config["kernel"]]
|
||||
|
||||
try:
|
||||
for link_map in elf_object.get_link_maps(kernel.symbol_table_name):
|
||||
if link_map.l_addr and link_map.l_name:
|
||||
yield link_map
|
||||
except exceptions.InvalidAddressException:
|
||||
# Protection against memory smear in this VMA
|
||||
pass
|
||||
|
||||
def _get_libdl_maps(
|
||||
self, task: interfaces.objects.ObjectInterface, proc_layer_name: str
|
||||
) -> interfaces.objects.ObjectInterface:
|
||||
"""Get the ELF link maps objects for a task
|
||||
|
||||
Args:
|
||||
task (task_struct): A reference task
|
||||
proc_layer_name (str): Name of the process layer
|
||||
|
||||
Yields:
|
||||
ELF link map objects
|
||||
"""
|
||||
|
||||
link_map_seen = set()
|
||||
for vma in task.mm.get_vma_iter():
|
||||
for link_map in self._get_libdl_libraries(proc_layer_name, vma.vm_start):
|
||||
if link_map.l_addr in link_map_seen:
|
||||
continue
|
||||
|
||||
yield link_map
|
||||
link_map_seen.add(link_map.l_addr)
|
||||
|
||||
def _get_task_libraries(
|
||||
self, task: interfaces.objects.ObjectInterface
|
||||
) -> Tuple[int, str]:
|
||||
"""Get the task libraries from the ELF headers found within the memory maps
|
||||
|
||||
Args:
|
||||
task (task_struct): The reference task
|
||||
|
||||
Yields:
|
||||
Tuples with a ELF link map address and name
|
||||
"""
|
||||
proc_layer_name = task.add_process_layer()
|
||||
if not proc_layer_name:
|
||||
return
|
||||
|
||||
for elf_link_map in self._get_libdl_maps(task, proc_layer_name):
|
||||
name = elf_link_map.get_name()
|
||||
if not name:
|
||||
continue
|
||||
yield elf_link_map.l_addr, name
|
||||
|
||||
def _get_tasks_libraries(
|
||||
self,
|
||||
tasks: Iterable[interfaces.objects.ObjectInterface],
|
||||
) -> Iterable[Tuple[str, int, int, str]]:
|
||||
"""Get the task libraries from the ELF headers found within the memory maps for
|
||||
all the tasks.
|
||||
|
||||
Args:
|
||||
tasks: An iterable of tasks
|
||||
|
||||
Yields:
|
||||
Tuples with a task name, task tgid, an ELF link map address and name
|
||||
"""
|
||||
for task in tasks:
|
||||
task_name = utility.array_to_string(task.comm)
|
||||
for linkmap_addr, linkmap_name in self._get_task_libraries(task):
|
||||
yield task_name, task.tgid, linkmap_addr, linkmap_name
|
||||
|
||||
def _format_fields(self, fields):
|
||||
task_name, task_pid, addr, name = fields
|
||||
return task_name, task_pid, format_hints.Hex(addr), name
|
||||
|
||||
def _generator(
|
||||
self, tasks: Iterable[interfaces.objects.ObjectInterface]
|
||||
) -> Iterable[Tuple[int, Tuple]]:
|
||||
for fields in self._get_tasks_libraries(tasks):
|
||||
yield 0, self._format_fields(fields)
|
||||
|
||||
def run(self):
|
||||
pids = self.config.get("pids")
|
||||
pid_filter = pslist.PsList.create_pid_filter(pids)
|
||||
tasks = pslist.PsList.list_tasks(
|
||||
self.context, self.config["kernel"], filter_func=pid_filter
|
||||
)
|
||||
|
||||
headers = [
|
||||
("Name", str),
|
||||
("Pid", int),
|
||||
("LoadAddress", format_hints.Hex),
|
||||
("Path", str),
|
||||
]
|
||||
|
||||
return renderers.TreeGrid(headers, self._generator(tasks))
|
||||
@@ -83,11 +83,11 @@ class PsList(interfaces.plugins.PluginInterface):
|
||||
cls, task: interfaces.objects.ObjectInterface, decorate_comm: bool = False
|
||||
) -> Tuple[int, int, int, str]:
|
||||
"""Extract the fields needed for the final output
|
||||
|
||||
Args:
|
||||
task: A task object from where to get the fields.
|
||||
decorate_comm: If True, it decorates the comm string of
|
||||
- User threads: in curly brackets,
|
||||
- Kernel threads: in square brackets
|
||||
decorate_comm: If True, it decorates the comm string of user threads in curly brackets,
|
||||
and of Kernel threads in square brackets.
|
||||
Defaults to False.
|
||||
Returns:
|
||||
A tuple with the fields to show in the plugin output.
|
||||
@@ -142,6 +142,8 @@ class PsList(interfaces.plugins.PluginInterface):
|
||||
file_output = str(file_handle.preferred_filename)
|
||||
file_handle.close()
|
||||
break
|
||||
else:
|
||||
file_output = "VMA start matching task start_code not found"
|
||||
return file_output
|
||||
|
||||
def _generator(
|
||||
|
||||
@@ -83,7 +83,7 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
|
||||
sock: Kernel generic `sock` object
|
||||
|
||||
Returns a tuple with:
|
||||
sock: The respective kernel's \*_sock object for that socket family
|
||||
sock: The respective kernel's \\*_sock object for that socket family
|
||||
sock_stat: A tuple with the source and destination (address and port) along with its state string
|
||||
socket_filter: A dictionary with information about the socket filter
|
||||
"""
|
||||
@@ -501,7 +501,7 @@ class Sockstat(plugins.PluginInterface):
|
||||
family: Socket family string (AF_UNIX, AF_INET, etc)
|
||||
sock_type: Socket type string (STREAM, DGRAM, etc)
|
||||
protocol: Protocol string (UDP, TCP, etc)
|
||||
sock_fields: A tuple with the \*_sock object, the sock stats and the extended info dictionary
|
||||
sock_fields: A tuple with the \\*_sock object, the sock stats and the extended info dictionary
|
||||
"""
|
||||
vmlinux = context.modules[symbol_table]
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
# This file is Copyright 2024 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
|
||||
from volatility3.framework import interfaces, renderers, exceptions
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.objects import utility
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Dmesg(interfaces.plugins.PluginInterface):
|
||||
"""Prints the kernel log buffer."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
return [
|
||||
requirements.ModuleRequirement(
|
||||
name="kernel",
|
||||
description="Kernel module for the OS",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_kernel_log_buffer(
|
||||
cls, context: interfaces.context.ContextInterface, kernel_module_name: str
|
||||
):
|
||||
"""
|
||||
Online documentation :
|
||||
- https://github.com/apple-open-source/macos/blob/master/xnu/bsd/sys/msgbuf.h
|
||||
- https://github.com/apple-open-source/macos/blob/ea4cd5a06831aca49e33df829d2976d6de5316ec/xnu/bsd/kern/subr_log.c#L751
|
||||
Volatility 2 plugin :
|
||||
- https://github.com/volatilityfoundation/volatility/blob/master/volatility/plugins/mac/dmesg.py
|
||||
"""
|
||||
|
||||
kernel = context.modules[kernel_module_name]
|
||||
if not kernel.has_symbol("msgbufp"):
|
||||
raise exceptions.SymbolError(
|
||||
"msgbufp",
|
||||
kernel.symbol_table_name,
|
||||
'The provided symbol table does not include the "msgbufp" symbol. This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt.',
|
||||
)
|
||||
|
||||
msgbufp = kernel.object_from_symbol(symbol_name="msgbufp")
|
||||
msg_size = msgbufp.msg_size # max buffer size
|
||||
msg_bufx = msgbufp.msg_bufx # write index of the msg_bufc circular buffer
|
||||
msg_bufc = msgbufp.msg_bufc
|
||||
# msg_bufc is circular, meaning that if its size exceeds msg_size,
|
||||
# msg_bufx will point to the beginning of the buffer and start overwriting.
|
||||
msg_bufc_data: str = utility.pointer_to_string(msg_bufc, msg_size)
|
||||
# Avoid OOB reads
|
||||
msg_bufx = msg_bufx if msg_bufx <= msg_size else 0
|
||||
# We directly take into account the case where the write buffer did a loop,
|
||||
# as older messages will start at msg_bufx offset (not overwritten yet).
|
||||
dmesg = msg_bufc_data[msg_bufx:]
|
||||
dmesg += msg_bufc_data[:msg_bufx]
|
||||
|
||||
# Yield each line
|
||||
for dmesg_line in dmesg.splitlines():
|
||||
yield (dmesg_line,)
|
||||
|
||||
def _generator(self):
|
||||
for value in self.get_kernel_log_buffer(
|
||||
context=self.context, kernel_module_name=self.config["kernel"]
|
||||
):
|
||||
yield (0, value)
|
||||
|
||||
def run(self):
|
||||
return renderers.TreeGrid(
|
||||
[
|
||||
("line", str),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
@@ -2,17 +2,23 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
|
||||
from volatility3.framework import renderers, interfaces
|
||||
from volatility3.framework import renderers, interfaces, exceptions
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.plugins.mac import pslist
|
||||
from typing import Callable, Generator, Type, Optional
|
||||
import logging
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Maps(interfaces.plugins.PluginInterface):
|
||||
"""Lists process memory ranges that potentially contain injected code."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 1, 0)
|
||||
MAXSIZE_DEFAULT = 1024 * 1024 * 1024 # 1 Gb
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -31,14 +37,152 @@ class Maps(interfaces.plugins.PluginInterface):
|
||||
element_type=int,
|
||||
optional=True,
|
||||
),
|
||||
requirements.BooleanRequirement(
|
||||
name="dump",
|
||||
description="Extract listed memory segments",
|
||||
default=False,
|
||||
optional=True,
|
||||
),
|
||||
requirements.ListRequirement(
|
||||
name="address",
|
||||
description="Process virtual memory addresses to include "
|
||||
"(all other VMA sections are excluded). This can be any "
|
||||
"virtual address within the VMA section. Virtual addresses "
|
||||
"must be separated by a space.",
|
||||
element_type=int,
|
||||
optional=True,
|
||||
),
|
||||
requirements.IntRequirement(
|
||||
name="maxsize",
|
||||
description="Maximum size for dumped VMA sections "
|
||||
"(all the bigger sections will be ignored)",
|
||||
default=cls.MAXSIZE_DEFAULT,
|
||||
optional=True,
|
||||
),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def list_vmas(
|
||||
cls,
|
||||
task: interfaces.objects.ObjectInterface,
|
||||
filter_func: Callable[
|
||||
[interfaces.objects.ObjectInterface], bool
|
||||
] = lambda _: True,
|
||||
) -> Generator[interfaces.objects.ObjectInterface, None, None]:
|
||||
"""Lists the Virtual Memory Areas of a specific process.
|
||||
|
||||
Args:
|
||||
task: task object from which to list the vma
|
||||
filter_func: Function to take a vma and return False if it should be filtered out
|
||||
|
||||
Returns:
|
||||
Yields vmas based on the task and filtered based on the filter function
|
||||
"""
|
||||
for vma in task.get_map_iter():
|
||||
if filter_func(vma):
|
||||
yield vma
|
||||
else:
|
||||
vollog.debug(
|
||||
f"Excluded vma at offset {vma.vol.offset:#x} for pid {task.p_pid} due to filter_func"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def vma_dump(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
task: interfaces.objects.ObjectInterface,
|
||||
vm_start: int,
|
||||
vm_end: int,
|
||||
open_method: Type[interfaces.plugins.FileHandlerInterface],
|
||||
maxsize: int = MAXSIZE_DEFAULT,
|
||||
) -> Optional[interfaces.plugins.FileHandlerInterface]:
|
||||
"""Extracts the complete data for VMA as a FileInterface.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
task: an task_struct instance
|
||||
vm_start: The start virtual address from the vma to dump
|
||||
vm_end: The end virtual address from the vma to dump
|
||||
open_method: class to provide context manager for opening the file
|
||||
maxsize: Max size of VMA section (default MAXSIZE_DEFAULT)
|
||||
|
||||
Returns:
|
||||
An open FileInterface object containing the complete data for the task or None in the case of failure
|
||||
"""
|
||||
pid = task.p_pid
|
||||
|
||||
try:
|
||||
proc_layer_name = task.add_process_layer()
|
||||
except exceptions.InvalidAddressException as excp:
|
||||
vollog.debug(
|
||||
"Process {}: invalid address {} in layer {}".format(
|
||||
pid, excp.invalid_address, excp.layer_name
|
||||
)
|
||||
)
|
||||
return None
|
||||
vm_size = vm_end - vm_start
|
||||
|
||||
# check if vm_size is negative, this should never happen.
|
||||
if vm_size < 0:
|
||||
vollog.warning(
|
||||
f"Skip virtual memory dump for pid {pid} between {vm_start:#x}-{vm_end:#x} as {vm_size} is negative."
|
||||
)
|
||||
return None
|
||||
# check if vm_size is larger than the maxsize limit, and therefore is not saved out.
|
||||
if maxsize <= vm_size:
|
||||
vollog.warning(
|
||||
f"Skip virtual memory dump for pid {pid} between {vm_start:#x}-{vm_end:#x} as {vm_size} is larger than maxsize limit of {maxsize}"
|
||||
)
|
||||
return None
|
||||
proc_layer = context.layers[proc_layer_name]
|
||||
file_name = f"pid.{pid}.vma.{vm_start:#x}-{vm_end:#x}.dmp"
|
||||
try:
|
||||
file_handle = open_method(file_name)
|
||||
chunk_size = 1024 * 1024 * 10
|
||||
offset = vm_start
|
||||
while offset < vm_start + vm_size:
|
||||
to_read = min(chunk_size, vm_start + vm_size - offset)
|
||||
data = proc_layer.read(offset, to_read, pad=True)
|
||||
file_handle.write(data)
|
||||
offset += to_read
|
||||
except Exception as excp:
|
||||
vollog.debug(f"Unable to dump virtual memory {file_name}: {excp}")
|
||||
return None
|
||||
return file_handle
|
||||
|
||||
def _generator(self, tasks):
|
||||
address_list = self.config.get("address", None)
|
||||
if not address_list:
|
||||
# do not filter as no address_list was supplied
|
||||
vma_filter_func = lambda _: True
|
||||
else:
|
||||
# filter for any vm_start that matches the supplied address config
|
||||
def vma_filter_function(task: interfaces.objects.ObjectInterface) -> bool:
|
||||
addrs_in_vma = [
|
||||
addr
|
||||
for addr in address_list
|
||||
if task.links.start <= addr <= task.links.end
|
||||
]
|
||||
|
||||
# if any of the user supplied addresses would fall within this vma return true
|
||||
return bool(addrs_in_vma)
|
||||
|
||||
vma_filter_func = vma_filter_function
|
||||
|
||||
for task in tasks:
|
||||
process_name = utility.array_to_string(task.p_comm)
|
||||
process_pid = task.p_pid
|
||||
|
||||
for vma in task.get_map_iter():
|
||||
for vma in self.list_vmas(task, filter_func=vma_filter_func):
|
||||
try:
|
||||
vm_start = vma.links.start
|
||||
vm_end = vma.links.end
|
||||
except AttributeError:
|
||||
vollog.debug(
|
||||
f"Unable to find the vm_start and vm_end for vma at {vma.vol.offset:#x} for pid {process_pid}"
|
||||
)
|
||||
continue
|
||||
|
||||
path = vma.get_path(
|
||||
self.context,
|
||||
self.context.modules[self.config["kernel"]].symbol_table_name,
|
||||
@@ -46,15 +190,32 @@ class Maps(interfaces.plugins.PluginInterface):
|
||||
if path == "":
|
||||
path = vma.get_special_path()
|
||||
|
||||
file_output = "Disabled"
|
||||
if self.config["dump"]:
|
||||
file_output = "Error outputting file"
|
||||
file_handle = self.vma_dump(
|
||||
self.context,
|
||||
task,
|
||||
vm_start,
|
||||
vm_end,
|
||||
self.open,
|
||||
self.config["maxsize"],
|
||||
)
|
||||
|
||||
if file_handle:
|
||||
file_handle.close()
|
||||
file_output = file_handle.preferred_filename
|
||||
|
||||
yield (
|
||||
0,
|
||||
(
|
||||
process_pid,
|
||||
process_name,
|
||||
format_hints.Hex(vma.links.start),
|
||||
format_hints.Hex(vma.links.end),
|
||||
format_hints.Hex(vm_start),
|
||||
format_hints.Hex(vm_end),
|
||||
vma.get_perms(),
|
||||
path,
|
||||
file_output,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -72,6 +233,7 @@ class Maps(interfaces.plugins.PluginInterface):
|
||||
("End", format_hints.Hex),
|
||||
("Protection", str),
|
||||
("Map Name", str),
|
||||
("File output", str),
|
||||
],
|
||||
self._generator(
|
||||
list_tasks(self.context, self.config["kernel"], filter_func=filter_func)
|
||||
|
||||
@@ -183,16 +183,16 @@ class Timeliner(interfaces.plugins.PluginInterface):
|
||||
plugin_name,
|
||||
self._sanitize_body_format(item),
|
||||
self._text_format(
|
||||
times.get(TimeLinerType.ACCESSED, "")
|
||||
times.get(TimeLinerType.ACCESSED, "0")
|
||||
),
|
||||
self._text_format(
|
||||
times.get(TimeLinerType.MODIFIED, "")
|
||||
times.get(TimeLinerType.MODIFIED, "0")
|
||||
),
|
||||
self._text_format(
|
||||
times.get(TimeLinerType.CHANGED, "")
|
||||
times.get(TimeLinerType.CHANGED, "0")
|
||||
),
|
||||
self._text_format(
|
||||
times.get(TimeLinerType.CREATED, "")
|
||||
times.get(TimeLinerType.CREATED, "0")
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
# This file is Copyright 2023 Volatility Foundation and licensed under the Volatility Software License 1.0
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
|
||||
import enum
|
||||
import logging
|
||||
import os
|
||||
import struct
|
||||
from typing import Dict, List
|
||||
|
||||
from volatility3.framework import constants, exceptions, interfaces, renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.interfaces import configuration, plugins
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.framework.symbols import intermed
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VMCSTest(enum.IntFlag):
|
||||
VMCS_ABORT_INVALID = enum.auto()
|
||||
VMCS_LINK_PTR_IS_NOT_FS = enum.auto()
|
||||
VMCS_HOST_CR4_NO_VTX = enum.auto()
|
||||
VMCS_CR3_IS_ZERO = enum.auto()
|
||||
VMCS_GUEST_CR4_RESERVED = enum.auto()
|
||||
|
||||
|
||||
class PageStartScanner(interfaces.layers.ScannerInterface):
|
||||
def __init__(self, signatures: List[bytes], page_size: int = 0x1000):
|
||||
super().__init__()
|
||||
if not len(signatures):
|
||||
raise ValueError("No signatures passed to constructor")
|
||||
self._siglen = len(signatures[0])
|
||||
for item in signatures:
|
||||
if len(item) != self._siglen:
|
||||
raise ValueError(
|
||||
"Signatures of different lengths passed to PageStartScanner"
|
||||
)
|
||||
self._signatures = signatures
|
||||
self._page_size = page_size
|
||||
|
||||
def __call__(self, data: bytes, data_offset: int):
|
||||
"""Scans only the start of every page, to see whether a signature is present or not"""
|
||||
for page_start in range(
|
||||
data_offset % self._page_size, len(data), self._page_size
|
||||
):
|
||||
if data[page_start : page_start + self._siglen] in self._signatures:
|
||||
yield (
|
||||
page_start + data_offset,
|
||||
data[page_start : page_start + self._siglen],
|
||||
)
|
||||
|
||||
|
||||
class Vmscan(plugins.PluginInterface):
|
||||
"""Scans for Intel VT-d structues and generates VM volatility configs for them"""
|
||||
|
||||
_required_framework_version = (2, 2, 0)
|
||||
_version = (1, 0, 0)
|
||||
|
||||
STRICTLY_REQUIRED_TESTS = {
|
||||
VMCSTest.VMCS_ABORT_INVALID,
|
||||
VMCSTest.VMCS_LINK_PTR_IS_NOT_FS,
|
||||
VMCSTest.VMCS_HOST_CR4_NO_VTX,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
return [
|
||||
requirements.TranslationLayerRequirement(
|
||||
name="primary", description="Physical base memory layer"
|
||||
),
|
||||
requirements.IntRequirement(
|
||||
name="log-threshold",
|
||||
description="Number of criteria failed to log to debug output",
|
||||
default=2,
|
||||
optional=True,
|
||||
),
|
||||
]
|
||||
|
||||
# Scan for VMCS structures based on the known VMCS structures
|
||||
# found in symbols/vmcs directory
|
||||
|
||||
def _gather_vmcs_structures(
|
||||
self, context: interfaces.context.ContextInterface, config_path: str
|
||||
) -> Dict[bytes, str]:
|
||||
"""Enumerate all JSON files containing VMCS information and return the structures
|
||||
Signatures can be generated using data extracted using the vmcs_layout tool at
|
||||
https://github.com/google/rekall/tree/master/tools/linux/vmcs_layout
|
||||
|
||||
Args:
|
||||
context: The volatility context to work against
|
||||
config_path: The location to store symbol table configurations under
|
||||
|
||||
Returns:
|
||||
A dictionary of pattern bytes to the string representation of the architecture
|
||||
"""
|
||||
filenames = intermed.IntermediateSymbolTable.file_symbol_url(
|
||||
os.path.join("generic", "vmcs")
|
||||
)
|
||||
table_names = []
|
||||
for filename in filenames:
|
||||
base_name = os.path.basename(filename).split(".")[0]
|
||||
table_name = intermed.IntermediateSymbolTable.create(
|
||||
context,
|
||||
configuration.path_join(config_path, "vmcs"),
|
||||
os.path.join("generic", "vmcs"),
|
||||
filename=base_name,
|
||||
)
|
||||
table_names.append(table_name)
|
||||
|
||||
result = {}
|
||||
for table_name in table_names:
|
||||
symbol_table = context.symbol_space[table_name]
|
||||
revision_id = struct.pack(
|
||||
"<I", int(symbol_table.get_symbol("revision_id").constant_data)
|
||||
)
|
||||
result[revision_id] = table_name
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def _verify_vmcs_page(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
vmcs: interfaces.objects.ObjectInterface,
|
||||
) -> List[str]:
|
||||
"""Runs tests to verify whether a block of data is a VMCS page
|
||||
Some tests based on the Hypervisor Memory Forensics paper by
|
||||
Mariano Graziano, Andrea Lanzi and Davide Balzarotti
|
||||
|
||||
Args:
|
||||
context: The volatility context to be used for this call
|
||||
vmcs: The instantiated VMCS object to verify
|
||||
|
||||
Returns:
|
||||
The list of failed criteria that the VMCS did not meet
|
||||
"""
|
||||
|
||||
# The VMCS should have been constructed on the physical layer (even a nested VMCS)
|
||||
physical_layer_name = vmcs.vol.layer_name
|
||||
|
||||
failed_tests: VMCSTest = VMCSTest(0)
|
||||
# The abort field must be valid (generally 0, although other abort codes may exist)
|
||||
if context.layers[physical_layer_name].read(vmcs.vol.offset + 4, 4) not in [
|
||||
b"\x00\x00\x00\x00"
|
||||
]:
|
||||
failed_tests |= VMCSTest.VMCS_ABORT_INVALID
|
||||
# The vmcs link pointer is supposed to always be set
|
||||
if vmcs.vmcs_link_ptr != 0xFFFFFFFFFFFFFFFF:
|
||||
failed_tests |= VMCSTest.VMCS_LINK_PTR_IS_NOT_FS
|
||||
# To have a VMCS the host needs the VTx bit set in CR4, this can false positive often when all bits are set
|
||||
if (vmcs.host_cr4 & 1 << 13) == 0:
|
||||
failed_tests |= VMCSTest.VMCS_HOST_CR4_NO_VTX
|
||||
# The guest CR3 is *exceptionally* unlikely to be 0 and the guest cr4 is likely to have some bits unset
|
||||
if (vmcs.guest_cr3 == 0) or (vmcs.host_cr3 == 0):
|
||||
failed_tests |= VMCSTest.VMCS_CR3_IS_ZERO
|
||||
# CR4 registers have certain bits reserved that should not be set
|
||||
if vmcs.guest_cr4 & 0xFFFFFFFFFF889000:
|
||||
failed_tests |= VMCSTest.VMCS_GUEST_CR4_RESERVED
|
||||
|
||||
if failed_tests and failed_tests.name:
|
||||
failed_list = failed_tests.name.split("|")
|
||||
return failed_list
|
||||
|
||||
return []
|
||||
|
||||
def _generator(self):
|
||||
# Gather VMCS structures
|
||||
structures = self._gather_vmcs_structures(self.context, self.config_path)
|
||||
# Scan memory for them
|
||||
layer = self.context.layers[self.config["primary"]]
|
||||
|
||||
# Try to move down to the highest physical layer
|
||||
if layer.config.get("memory_layer"):
|
||||
layer = self.context.layers[layer.config["memory_layer"]]
|
||||
|
||||
# Run the scan
|
||||
for offset, match in layer.scan(
|
||||
self.context,
|
||||
PageStartScanner(list(structures.keys())),
|
||||
self._progress_callback,
|
||||
):
|
||||
try:
|
||||
vmcs = self.context.object(
|
||||
structures[match] + constants.BANG + "_VMCS",
|
||||
layer.name,
|
||||
offset=offset,
|
||||
)
|
||||
failed_list = self._verify_vmcs_page(self.context, vmcs)
|
||||
if not failed_list:
|
||||
yield (
|
||||
0,
|
||||
(
|
||||
structures[match],
|
||||
format_hints.Hex(vmcs.vol.offset),
|
||||
format_hints.Hex(vmcs.ept),
|
||||
format_hints.Hex(vmcs.guest_cr3),
|
||||
),
|
||||
)
|
||||
if len(failed_list) <= self.config["log-threshold"]:
|
||||
vollog.debug(
|
||||
f"Potential {structures[match]} VMCS found at {vmcs.vol.offset:x} with failed criteria: {failed_list}"
|
||||
)
|
||||
except (exceptions.InvalidAddressException, AttributeError):
|
||||
# Not what we're looking for
|
||||
continue
|
||||
|
||||
def run(self):
|
||||
return renderers.TreeGrid(
|
||||
[
|
||||
("Architecture", str),
|
||||
("VMCS Physical offset", format_hints.Hex),
|
||||
("EPT", format_hints.Hex),
|
||||
("Guest CR3", format_hints.Hex),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
@@ -81,7 +81,10 @@ class DriverIrp(interfaces.plugins.PluginInterface):
|
||||
address
|
||||
)
|
||||
|
||||
module_found = False
|
||||
|
||||
for module_name, symbol_generator in module_symbols:
|
||||
module_found = True
|
||||
symbols_found = False
|
||||
|
||||
for symbol in symbol_generator:
|
||||
@@ -111,6 +114,19 @@ class DriverIrp(interfaces.plugins.PluginInterface):
|
||||
),
|
||||
)
|
||||
|
||||
if not module_found:
|
||||
yield (
|
||||
0,
|
||||
(
|
||||
format_hints.Hex(driver.vol.offset),
|
||||
driver_name,
|
||||
MAJOR_FUNCTIONS[i],
|
||||
format_hints.Hex(address),
|
||||
renderers.NotAvailableValue(),
|
||||
renderers.NotAvailableValue(),
|
||||
),
|
||||
)
|
||||
|
||||
def run(self):
|
||||
return renderers.TreeGrid(
|
||||
[
|
||||
|
||||
@@ -244,6 +244,8 @@ class DumpFiles(interfaces.plugins.PluginInterface):
|
||||
symbol_table=kernel.symbol_table_name,
|
||||
)
|
||||
|
||||
dumped_files = set()
|
||||
|
||||
for proc in procs:
|
||||
try:
|
||||
object_table = proc.ObjectTable
|
||||
@@ -267,6 +269,10 @@ class DumpFiles(interfaces.plugins.PluginInterface):
|
||||
if not file_re.search(name):
|
||||
continue
|
||||
|
||||
if file_obj.vol.offset in dumped_files:
|
||||
continue
|
||||
dumped_files.add(file_obj.vol.offset)
|
||||
|
||||
for result in self.process_file_object(
|
||||
self.context, kernel.layer_name, self.open, file_obj
|
||||
):
|
||||
@@ -303,6 +309,10 @@ class DumpFiles(interfaces.plugins.PluginInterface):
|
||||
if not file_re.search(name):
|
||||
continue
|
||||
|
||||
if file_obj.vol.offset in dumped_files:
|
||||
continue
|
||||
dumped_files.add(file_obj.vol.offset)
|
||||
|
||||
for result in self.process_file_object(
|
||||
self.context, kernel.layer_name, self.open, file_obj
|
||||
):
|
||||
|
||||
@@ -25,7 +25,7 @@ class Handles(interfaces.plugins.PluginInterface):
|
||||
"""Lists process open handles."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -145,6 +145,9 @@ class Handles(interfaces.plugins.PluginInterface):
|
||||
|
||||
if self._sar_value is None:
|
||||
if not has_capstone:
|
||||
vollog.debug(
|
||||
"capstone module is missing, unable to create disassembly of ObpCaptureHandleInformationEx"
|
||||
)
|
||||
return None
|
||||
kernel = self.context.modules[self.config["kernel"]]
|
||||
|
||||
@@ -159,25 +162,46 @@ class Handles(interfaces.plugins.PluginInterface):
|
||||
try:
|
||||
func_addr = ntkrnlmp.get_symbol("ObpCaptureHandleInformationEx").address
|
||||
except exceptions.SymbolError:
|
||||
vollog.debug("Unable to locate ObpCaptureHandleInformationEx symbol")
|
||||
return None
|
||||
|
||||
data = self.context.layers.read(virtual_layer_name, kvo + func_addr, 0x200)
|
||||
if data is None:
|
||||
try:
|
||||
func_addr_to_read = kvo + func_addr
|
||||
num_bytes_to_read = 0x200
|
||||
vollog.debug(
|
||||
f"ObpCaptureHandleInformationEx symbol located at {hex(func_addr_to_read)}"
|
||||
)
|
||||
data = self.context.layers.read(
|
||||
virtual_layer_name, func_addr_to_read, num_bytes_to_read
|
||||
)
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug(
|
||||
f"Failed to read {hex(num_bytes_to_read)} bytes at symbol {hex(func_addr_to_read)}"
|
||||
)
|
||||
return None
|
||||
|
||||
md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64)
|
||||
|
||||
instruction_count = 0
|
||||
for address, size, mnemonic, op_str in md.disasm_lite(
|
||||
data, kvo + func_addr
|
||||
):
|
||||
# print("{} {} {} {}".format(address, size, mnemonic, op_str))
|
||||
|
||||
instruction_count += 1
|
||||
if mnemonic.startswith("sar"):
|
||||
# if we don't want to parse op strings, we can disasm the
|
||||
# single sar instruction again, but we use disasm_lite for speed
|
||||
self._sar_value = int(op_str.split(",")[1].strip(), 16)
|
||||
vollog.debug(
|
||||
f"SAR located at {hex(address)} with value of {hex(self._sar_value)}"
|
||||
)
|
||||
break
|
||||
|
||||
if self._sar_value is None:
|
||||
vollog.debug(
|
||||
f"Failed to to locate SAR value having parsed {instruction_count} instructions"
|
||||
)
|
||||
|
||||
return self._sar_value
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# This file is Copyright 2024 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
|
||||
|
||||
from volatility3.framework import constants, exceptions, interfaces, renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import format_hints
|
||||
@@ -5,12 +11,14 @@ from volatility3.framework.symbols import intermed
|
||||
from volatility3.framework.symbols.windows.extensions import pe
|
||||
from volatility3.plugins.windows import pslist, vadinfo
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LdrModules(interfaces.plugins.PluginInterface):
|
||||
"""Lists the loaded modules in a particular windows memory image."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -71,7 +79,11 @@ class LdrModules(interfaces.plugins.PluginInterface):
|
||||
# Filter out VADs that do not start with a MZ header
|
||||
if dos_header.e_magic != 0x5A4D:
|
||||
continue
|
||||
except exceptions.PagedInvalidAddressException:
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping vad at {hex(dos_header.vol.offset)} due to InvalidAddressException",
|
||||
)
|
||||
continue
|
||||
|
||||
mapped_files[vad.get_start()] = vad.get_file_name()
|
||||
|
||||
@@ -155,12 +155,12 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
|
||||
for proc in procs:
|
||||
# by default, "Notes" column will be set to N/A
|
||||
notes = renderers.NotApplicableValue()
|
||||
process_name = utility.array_to_string(proc.ImageFileName)
|
||||
|
||||
for vad, data in self.list_injections(
|
||||
self.context, kernel.layer_name, kernel.symbol_table_name, proc
|
||||
):
|
||||
notes = renderers.NotApplicableValue()
|
||||
# Check for unique headers and update "Notes" column if criteria is met
|
||||
if data[0:2] in refined_criteria:
|
||||
notes = refined_criteria[data[0:2]]
|
||||
|
||||
@@ -38,7 +38,7 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
|
||||
# Yara Rule to scan for MFT Header Signatures
|
||||
rules = yarascan.YaraScan.process_yara_options(
|
||||
{"yara_rules": "/FILE0|FILE\*|BAAD/"}
|
||||
{"yara_rules": "/FILE0|FILE\\*|BAAD/"}
|
||||
)
|
||||
|
||||
# Read in the Symbol File
|
||||
@@ -197,7 +197,7 @@ class ADS(interfaces.plugins.PluginInterface):
|
||||
|
||||
# Yara Rule to scan for MFT Header Signatures
|
||||
rules = yarascan.YaraScan.process_yara_options(
|
||||
{"yara_rules": "/FILE0|FILE\*|BAAD/"}
|
||||
{"yara_rules": "/FILE0|FILE\\*|BAAD/"}
|
||||
)
|
||||
|
||||
# Read in the Symbol File
|
||||
@@ -264,9 +264,10 @@ class ADS(interfaces.plugins.PluginInterface):
|
||||
disasm = interfaces.renderers.Disassembly(
|
||||
content, 0, architecture.lower()
|
||||
)
|
||||
content = format_hints.HexBytes(content)
|
||||
else:
|
||||
content = renderers.NotAvailableValue
|
||||
disasm = interfaces.renderers.BaseAbsentValue
|
||||
content = renderers.NotAvailableValue()
|
||||
disasm = interfaces.renderers.BaseAbsentValue()
|
||||
|
||||
yield 0, (
|
||||
format_hints.Hex(attr_data.vol.offset),
|
||||
@@ -275,7 +276,7 @@ class ADS(interfaces.plugins.PluginInterface):
|
||||
attr.Attr_Header.AttrType.lookup(),
|
||||
file_name,
|
||||
ads_name,
|
||||
format_hints.HexBytes(content),
|
||||
content,
|
||||
disasm,
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -222,6 +222,24 @@ class PoolScanner(plugins.PluginInterface):
|
||||
type_name=symbol_table + constants.BANG + "_EPROCESS",
|
||||
object_type="Process",
|
||||
size=(600, None),
|
||||
skip_type_test=True,
|
||||
page_type=PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE,
|
||||
),
|
||||
# threads on windows before windows8
|
||||
PoolConstraint(
|
||||
b"Thr\xe5", # -> “protected” allocation, MSB is set.
|
||||
type_name=symbol_table + constants.BANG + "_ETHREAD",
|
||||
object_type="Thread",
|
||||
size=(600, None), # -> 0x0258 - size of struct in win5.1
|
||||
skip_type_test=True,
|
||||
page_type=PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE,
|
||||
),
|
||||
# threads on windows starting with windows8
|
||||
PoolConstraint(
|
||||
b"Thre",
|
||||
type_name=symbol_table + constants.BANG + "_ETHREAD",
|
||||
object_type="Thread",
|
||||
size=(600, None), # -> 0x0258 - size of struct in win5.1
|
||||
page_type=PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE,
|
||||
),
|
||||
# files on windows before windows 8
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
# This file is Copyright 2024 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
|
||||
from typing import List
|
||||
|
||||
from volatility3.framework import constants, exceptions, interfaces, renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.plugins.windows import ssdt
|
||||
from volatility3.plugins.windows.registry import hivelist
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GetCellRoutine(interfaces.plugins.PluginInterface):
|
||||
"""Reports registry hives with a hooked GetCellRoutine handler"""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
return [
|
||||
requirements.ModuleRequirement(
|
||||
name="kernel",
|
||||
description="Windows kernel",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
|
||||
),
|
||||
]
|
||||
|
||||
def _generator(self):
|
||||
kernel = self.context.modules[self.config["kernel"]]
|
||||
|
||||
collection = ssdt.SSDT.build_module_collection(
|
||||
self.context, kernel.layer_name, kernel.symbol_table_name
|
||||
)
|
||||
|
||||
# walk each hive and validate that the GetCellRoutine handler
|
||||
# is inside of the kernel (ntoskrnl)
|
||||
for hive_object in hivelist.HiveList.list_hives(
|
||||
context=self.context,
|
||||
base_config_path=self.config_path,
|
||||
layer_name=kernel.layer_name,
|
||||
symbol_table=kernel.symbol_table_name,
|
||||
):
|
||||
hive = hive_object.hive
|
||||
|
||||
try:
|
||||
cellroutine = hive.GetCellRoutine
|
||||
except exceptions.InvalidAddressException:
|
||||
continue
|
||||
|
||||
module_symbols = list(
|
||||
collection.get_module_symbols_by_absolute_location(cellroutine)
|
||||
)
|
||||
|
||||
if module_symbols:
|
||||
for module_name, _ in module_symbols:
|
||||
# GetCellRoutine handlers should only be in the kernel
|
||||
if module_name not in constants.windows.KERNEL_MODULE_NAMES:
|
||||
yield (
|
||||
0,
|
||||
(
|
||||
format_hints.Hex(hive.vol.offset),
|
||||
hive_object.get_name() or "",
|
||||
module_name,
|
||||
format_hints.Hex(cellroutine),
|
||||
),
|
||||
)
|
||||
# Doesn't map to any module...
|
||||
else:
|
||||
yield (
|
||||
0,
|
||||
(
|
||||
format_hints.Hex(hive.vol.offset),
|
||||
hive_object.get_name() or "",
|
||||
renderers.NotAvailableValue(),
|
||||
format_hints.Hex(cellroutine),
|
||||
),
|
||||
)
|
||||
|
||||
def run(self):
|
||||
return renderers.TreeGrid(
|
||||
[
|
||||
("Hive Offset", renderers.format_hints.Hex),
|
||||
("Hive Name", str),
|
||||
("GetCellRoutine Module", str),
|
||||
("GetCellRoutine Handler", renderers.format_hints.Hex),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
@@ -0,0 +1,141 @@
|
||||
##
|
||||
## plugin for testing addition of threads scan support to poolscanner.py
|
||||
##
|
||||
import logging
|
||||
import datetime
|
||||
from typing import Iterable
|
||||
|
||||
from volatility3.framework import renderers, interfaces, exceptions
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.plugins.windows import poolscanner
|
||||
from volatility3.plugins import timeliner
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
"""Scans for windows threads."""
|
||||
|
||||
# version 2.6.0 adds support for scanning for 'Ethread' structures by pool tags
|
||||
_required_framework_version = (2, 6, 0)
|
||||
_version = (1, 0, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
return [
|
||||
requirements.ModuleRequirement(
|
||||
name="kernel",
|
||||
description="Windows kernel",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
|
||||
),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def scan_threads(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
layer_name: str,
|
||||
symbol_table: str,
|
||||
) -> Iterable[interfaces.objects.ObjectInterface]:
|
||||
"""Scans for threads using the poolscanner module and constraints.
|
||||
|
||||
Args:
|
||||
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
|
||||
|
||||
Returns:
|
||||
A list of _ETHREAD objects found by scanning memory for the "Thre" / "Thr\\xE5" pool signatures
|
||||
"""
|
||||
|
||||
constraints = poolscanner.PoolScanner.builtin_constraints(
|
||||
symbol_table, [b"Thr\xe5", b"Thre"]
|
||||
)
|
||||
|
||||
for result in poolscanner.PoolScanner.generate_pool_scan(
|
||||
context, layer_name, symbol_table, constraints
|
||||
):
|
||||
_constraint, mem_object, _header = result
|
||||
yield mem_object
|
||||
|
||||
def _generator(self):
|
||||
kernel = self.context.modules[self.config["kernel"]]
|
||||
|
||||
for ethread in self.scan_threads(
|
||||
self.context, kernel.layer_name, kernel.symbol_table_name
|
||||
):
|
||||
try:
|
||||
thread_offset = ethread.vol.offset
|
||||
owner_proc_pid = ethread.Cid.UniqueProcess
|
||||
thread_tid = ethread.Cid.UniqueThread
|
||||
thread_start_addr = ethread.StartAddress
|
||||
thread_create_time = (
|
||||
ethread.get_create_time()
|
||||
) # datetime.datetime object / volatility3.framework.renderers.UnparsableValue object
|
||||
thread_exit_time = (
|
||||
ethread.get_exit_time()
|
||||
) # datetime.datetime object / volatility3.framework.renderers.UnparsableValue object
|
||||
except (ValueError, exceptions.InvalidAddressException):
|
||||
vollog.debug(
|
||||
"Thread :{}, invalid address {} in layer {}".format(
|
||||
thread_tid, thread_start_addr, kernel.layer_name
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
yield (
|
||||
0,
|
||||
(
|
||||
format_hints.Hex(thread_offset),
|
||||
owner_proc_pid,
|
||||
thread_tid,
|
||||
format_hints.Hex(thread_start_addr),
|
||||
thread_create_time,
|
||||
thread_exit_time,
|
||||
),
|
||||
)
|
||||
|
||||
def generate_timeline(self):
|
||||
for row in self._generator():
|
||||
_depth, row_data = row
|
||||
row_dict = {}
|
||||
(
|
||||
row_dict["Offset"],
|
||||
row_dict["PID"],
|
||||
row_dict["TID"],
|
||||
row_dict["StartAddress"],
|
||||
row_dict["CreateTime"],
|
||||
row_dict["ExitTime"],
|
||||
) = row_data
|
||||
|
||||
# Skip threads with no creation time
|
||||
# - mainly system process threads
|
||||
if not isinstance(row_dict["CreateTime"], datetime.datetime):
|
||||
continue
|
||||
description = f"Thread: Tid {row_dict['TID']} in Pid {row_dict['PID']} (Offset {row_dict['Offset']})"
|
||||
|
||||
# yield created time, and if there is exit time, yield it too.
|
||||
yield (description, timeliner.TimeLinerType.CREATED, row_dict["CreateTime"])
|
||||
if isinstance(row_dict["ExitTime"], datetime.datetime):
|
||||
yield (
|
||||
description,
|
||||
timeliner.TimeLinerType.MODIFIED,
|
||||
row_dict["ExitTime"],
|
||||
)
|
||||
|
||||
def run(self):
|
||||
return renderers.TreeGrid(
|
||||
[
|
||||
("Offset", format_hints.Hex),
|
||||
("PID", int),
|
||||
("TID", int),
|
||||
("StartAddress", format_hints.Hex),
|
||||
("CreateTime", datetime.datetime),
|
||||
("ExitTime", datetime.datetime),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
@@ -78,7 +78,7 @@ class VirtMap(interfaces.plugins.PluginInterface):
|
||||
)
|
||||
else:
|
||||
raise exceptions.SymbolError(
|
||||
None, module.name, "Required structures not found"
|
||||
"SystemVaRegions", module.name, "Required structures not found"
|
||||
)
|
||||
elif module.has_symbol("MiSystemVaType"):
|
||||
system_range_start = module.object(
|
||||
@@ -99,7 +99,7 @@ class VirtMap(interfaces.plugins.PluginInterface):
|
||||
)
|
||||
else:
|
||||
raise exceptions.SymbolError(
|
||||
None, module.name, "Required structures not found"
|
||||
"MiVisibleState", module.name, "Required structures not found"
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
@@ -270,8 +270,8 @@
|
||||
"d_tag": {
|
||||
"offset": 0,
|
||||
"type": {
|
||||
"kind": "base",
|
||||
"name": "long long"
|
||||
"kind": "enum",
|
||||
"name": "DtypeEnum64"
|
||||
}
|
||||
},
|
||||
"d_ptr": {
|
||||
@@ -699,8 +699,8 @@
|
||||
"d_tag": {
|
||||
"offset": 0,
|
||||
"type": {
|
||||
"kind": "base",
|
||||
"name": "long"
|
||||
"kind": "enum",
|
||||
"name": "DtypeEnum32"
|
||||
}
|
||||
},
|
||||
"d_ptr": {
|
||||
@@ -905,11 +905,139 @@
|
||||
"PT_PHDR": 6,
|
||||
"PT_TLS": 7,
|
||||
"PT_LOOS": 1610612736,
|
||||
"PT_GNU_EH_FRAME": 1685382480,
|
||||
"PT_GNU_STACK": 1685382481,
|
||||
"PT_GNU_RELRO": 1685382482,
|
||||
"PT_GNU_PROPERTY": 1685382483,
|
||||
"PT_HIOS": 1879048191,
|
||||
"PT_LOWPROC": 1879048192,
|
||||
"PT_HIPROC": 2147483647
|
||||
},
|
||||
"size": 4
|
||||
},
|
||||
"DtypeEnum32": {
|
||||
"base": "long",
|
||||
"constants": {
|
||||
"DT_NULL": 0,
|
||||
"DT_NEEDED": 1,
|
||||
"DT_PLTRELSZ": 2,
|
||||
"DT_PLTGOT": 3,
|
||||
"DT_HASH": 4,
|
||||
"DT_STRTAB": 5,
|
||||
"DT_SYMTAB": 6,
|
||||
"DT_RELA": 7,
|
||||
"DT_RELASZ": 8,
|
||||
"DT_RELAENT": 9,
|
||||
"DT_STRSZ": 10,
|
||||
"DT_SYMENT": 11,
|
||||
"DT_INIT": 12,
|
||||
"DT_FINI": 13,
|
||||
"DT_SONAME": 14,
|
||||
"DT_RPATH": 15,
|
||||
"DT_SYMBOLIC": 16,
|
||||
"DT_REL": 17,
|
||||
"DT_RELSZ": 18,
|
||||
"DT_RELENT": 19,
|
||||
"DT_PLTREL": 20,
|
||||
"DT_DEBUG": 21,
|
||||
"DT_TEXTREL": 22,
|
||||
"DT_JMPREL": 23,
|
||||
"DT_BIND_NOW": 24,
|
||||
"DT_INIT_ARRAY": 25,
|
||||
"DT_FINI_ARRAY": 26,
|
||||
"DT_INIT_ARRAYSZ": 27,
|
||||
"DT_FINI_ARRAYSZ": 28,
|
||||
"DT_RUNPATH": 29,
|
||||
"DT_FLAGS": 30,
|
||||
"DT_ENCODING": 32,
|
||||
"DT_PREINIT_ARRAYSZ": 33,
|
||||
"DT_SYMTAB_SHNDX": 34,
|
||||
"DT_RELRSZ": 35,
|
||||
"DT_RELR": 36,
|
||||
"DT_RELRENT": 37,
|
||||
"DT_NUM": 38,
|
||||
"OLD_DT_LOOS": 1610612736,
|
||||
"DT_LOOS": 1610612749,
|
||||
"DT_HIOS": 1879044096,
|
||||
"DT_VALRNGLO": 1879047424,
|
||||
"DT_VALRNGHI": 1879047679,
|
||||
"DT_ADDRRNGLO": 1879047680,
|
||||
"DT_GNU_HASH": 1879047925,
|
||||
"DT_ADDRRNGHI": 1879047935,
|
||||
"DT_VERSYM": 1879048176,
|
||||
"DT_RELACOUNT": 1879048185,
|
||||
"DT_RELCOUNT": 1879048186,
|
||||
"DT_FLAGS_1": 1879048187,
|
||||
"DT_VERDEF": 1879048188,
|
||||
"DT_VERDEFNUM": 1879048189,
|
||||
"DT_VERNEED": 1879048190,
|
||||
"DT_VERNEEDNUM": 1879048191,
|
||||
"DT_LOPROC": 1879048192,
|
||||
"DT_HIPROC": 2147483647
|
||||
},
|
||||
"size": 4
|
||||
},
|
||||
"DtypeEnum64": {
|
||||
"base": "long long",
|
||||
"constants": {
|
||||
"DT_NULL": 0,
|
||||
"DT_NEEDED": 1,
|
||||
"DT_PLTRELSZ": 2,
|
||||
"DT_PLTGOT": 3,
|
||||
"DT_HASH": 4,
|
||||
"DT_STRTAB": 5,
|
||||
"DT_SYMTAB": 6,
|
||||
"DT_RELA": 7,
|
||||
"DT_RELASZ": 8,
|
||||
"DT_RELAENT": 9,
|
||||
"DT_STRSZ": 10,
|
||||
"DT_SYMENT": 11,
|
||||
"DT_INIT": 12,
|
||||
"DT_FINI": 13,
|
||||
"DT_SONAME": 14,
|
||||
"DT_RPATH": 15,
|
||||
"DT_SYMBOLIC": 16,
|
||||
"DT_REL": 17,
|
||||
"DT_RELSZ": 18,
|
||||
"DT_RELENT": 19,
|
||||
"DT_PLTREL": 20,
|
||||
"DT_DEBUG": 21,
|
||||
"DT_TEXTREL": 22,
|
||||
"DT_JMPREL": 23,
|
||||
"DT_BIND_NOW": 24,
|
||||
"DT_INIT_ARRAY": 25,
|
||||
"DT_FINI_ARRAY": 26,
|
||||
"DT_INIT_ARRAYSZ": 27,
|
||||
"DT_FINI_ARRAYSZ": 28,
|
||||
"DT_RUNPATH": 29,
|
||||
"DT_FLAGS": 30,
|
||||
"DT_ENCODING": 32,
|
||||
"DT_PREINIT_ARRAYSZ": 33,
|
||||
"DT_SYMTAB_SHNDX": 34,
|
||||
"DT_RELRSZ": 35,
|
||||
"DT_RELR": 36,
|
||||
"DT_RELRENT": 37,
|
||||
"DT_NUM": 38,
|
||||
"OLD_DT_LOOS": 1610612736,
|
||||
"DT_LOOS": 1610612749,
|
||||
"DT_HIOS": 1879044096,
|
||||
"DT_VALRNGLO": 1879047424,
|
||||
"DT_VALRNGHI": 1879047679,
|
||||
"DT_ADDRRNGLO": 1879047680,
|
||||
"DT_GNU_HASH": 1879047925,
|
||||
"DT_ADDRRNGHI": 1879047935,
|
||||
"DT_VERSYM": 1879048176,
|
||||
"DT_RELACOUNT": 1879048185,
|
||||
"DT_RELCOUNT": 1879048186,
|
||||
"DT_FLAGS_1": 1879048187,
|
||||
"DT_VERDEF": 1879048188,
|
||||
"DT_VERDEFNUM": 1879048189,
|
||||
"DT_VERNEED": 1879048190,
|
||||
"DT_VERNEEDNUM": 1879048191,
|
||||
"DT_LOPROC": 1879048192,
|
||||
"DT_HIPROC": 2147483647
|
||||
},
|
||||
"size": 8
|
||||
}
|
||||
},
|
||||
"base_types": {
|
||||
@@ -958,9 +1086,9 @@
|
||||
},
|
||||
"metadata": {
|
||||
"producer": {
|
||||
"version": "0.0.1",
|
||||
"name": "ikelos-by-hand",
|
||||
"datetime": "2019-10-21T22:52:00"
|
||||
"version": "0.0.2",
|
||||
"name": "gcmoreira-by-hand",
|
||||
"datetime": "2024-02-19T14:37:00"
|
||||
},
|
||||
"format": "6.1.0"
|
||||
}
|
||||
|
||||
@@ -7,14 +7,13 @@ import logging
|
||||
import socket as socket_module
|
||||
from typing import Generator, Iterable, Iterator, Optional, Tuple, List
|
||||
|
||||
from volatility3.framework import constants
|
||||
from volatility3.framework import constants, exceptions, objects, interfaces, symbols
|
||||
from volatility3.framework.constants.linux import SOCK_TYPES, SOCK_FAMILY
|
||||
from volatility3.framework.constants.linux import IP_PROTOCOLS, IPV6_PROTOCOLS
|
||||
from volatility3.framework.constants.linux import TCP_STATES, NETLINK_PROTOCOLS
|
||||
from volatility3.framework.constants.linux import ETH_PROTOCOLS, BLUETOOTH_STATES
|
||||
from volatility3.framework.constants.linux import BLUETOOTH_PROTOCOLS, SOCKET_STATES
|
||||
from volatility3.framework.constants.linux import CAPABILITIES
|
||||
from volatility3.framework import exceptions, objects, interfaces, symbols
|
||||
from volatility3.framework.layers import linear
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.framework.symbols import generic, linux, intermed
|
||||
@@ -707,7 +706,8 @@ class vm_area_struct(objects.StructType):
|
||||
def get_page_offset(self) -> int:
|
||||
if self.vm_file == 0:
|
||||
return 0
|
||||
return self.vm_pgoff << constants.linux.PAGE_SHIFT
|
||||
parent_layer = self._context.layers[self.vol.layer_name]
|
||||
return self.vm_pgoff << parent_layer.page_shift
|
||||
|
||||
def get_name(self, context, task):
|
||||
if self.vm_file != 0:
|
||||
@@ -736,7 +736,7 @@ class vm_area_struct(objects.StructType):
|
||||
elif flags_str == "r-x" and self.vm_file.dereference().vol.offset == 0:
|
||||
ret = True
|
||||
elif proclayer and "x" in flags_str:
|
||||
for i in range(self.vm_start, self.vm_end, 1 << constants.linux.PAGE_SHIFT):
|
||||
for i in range(self.vm_start, self.vm_end, proclayer.page_size):
|
||||
try:
|
||||
if proclayer.is_dirty(i):
|
||||
vollog.warning(
|
||||
@@ -1137,17 +1137,17 @@ class vfsmount(objects.StructType):
|
||||
"""Helper to make sure it is comparing two pointers to 'vfsmount'.
|
||||
|
||||
Depending on the kernel version, the calling object (self) could be
|
||||
a 'vfsmount \*' (<3.3.8) or a 'vfsmount' (>=3.3.8). This way we trust
|
||||
a 'vfsmount \\*' (<3.3.8) or a 'vfsmount' (>=3.3.8). This way we trust
|
||||
in the framework "auto" dereferencing ability to assure that when we
|
||||
reach this point 'self' will be a 'vfsmount' already and self.vol.offset
|
||||
a 'vfsmount \*' and not a 'vfsmount \*\*'. The argument must be a 'vfsmount \*'.
|
||||
a 'vfsmount \\*' and not a 'vfsmount \\*\\*'. The argument must be a 'vfsmount \\*'.
|
||||
Typically, it's called from do_get_path().
|
||||
|
||||
Args:
|
||||
vfsmount_ptr (vfsmount \*): A pointer to a 'vfsmount'
|
||||
vfsmount_ptr (vfsmount *): A pointer to a 'vfsmount'
|
||||
|
||||
Raises:
|
||||
exceptions.VolatilityException: If vfsmount_ptr is not a 'vfsmount \*'
|
||||
exceptions.VolatilityException: If vfsmount_ptr is not a 'vfsmount \\*'
|
||||
|
||||
Returns:
|
||||
bool: 'True' if the given argument points to the the same 'vfsmount'
|
||||
|
||||
@@ -6,6 +6,10 @@ from typing import Dict, Tuple
|
||||
import logging
|
||||
|
||||
from volatility3.framework import constants
|
||||
from volatility3.framework.constants.linux import (
|
||||
ELF_IDENT,
|
||||
ELF_CLASS,
|
||||
)
|
||||
from volatility3.framework import objects, interfaces, exceptions
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
@@ -59,13 +63,15 @@ class elf(objects.StructType):
|
||||
ei_class = self._context.object(
|
||||
symbol_table_name + constants.BANG + "unsigned char",
|
||||
layer_name=layer_name,
|
||||
offset=object_info.offset + 0x4,
|
||||
offset=object_info.offset + ELF_IDENT.EI_CLASS,
|
||||
)
|
||||
|
||||
if ei_class == 1:
|
||||
if ei_class == ELF_CLASS.ELFCLASS32:
|
||||
self._type_prefix = "Elf32_"
|
||||
elif ei_class == 2:
|
||||
self._ei_class_size = 32
|
||||
elif ei_class == ELF_CLASS.ELFCLASS64:
|
||||
self._type_prefix = "Elf64_"
|
||||
self._ei_class_size = 64
|
||||
else:
|
||||
raise ValueError(f"Unsupported ei_class value {ei_class}")
|
||||
|
||||
@@ -140,36 +146,137 @@ class elf(objects.StructType):
|
||||
)
|
||||
return section_headers
|
||||
|
||||
def get_link_maps(self, kernel_symbol_table_name):
|
||||
"""Get the ELF link map objects for the given VMA address
|
||||
|
||||
Args:
|
||||
kernel_symbol_table_name (str): Kernel symbol table name
|
||||
|
||||
Yields:
|
||||
The ELF link map objects
|
||||
"""
|
||||
got_entry_size = self._ei_class_size // 8
|
||||
|
||||
elf_symbol_table = self.get_symbol_table_name()
|
||||
|
||||
link_maps_seen = set()
|
||||
for phdr in self.get_program_headers():
|
||||
try:
|
||||
if phdr.p_type.description != "PT_DYNAMIC":
|
||||
continue
|
||||
except ValueError:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping unknown ELF program header type: {phdr.p_type}",
|
||||
)
|
||||
continue
|
||||
|
||||
for dsec in phdr.dynamic_sections():
|
||||
try:
|
||||
if dsec.d_tag.description != "DT_PLTGOT":
|
||||
continue
|
||||
except ValueError:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping unknown ELF dynamic section type: {dsec.d_tag}",
|
||||
)
|
||||
continue
|
||||
|
||||
got_start = dsec.d_ptr
|
||||
|
||||
# link_map is stored at the second GOT entry
|
||||
link_map_addr = got_start + got_entry_size
|
||||
|
||||
# It needs the kernel symbol table to create a pointer
|
||||
link_map_ptr = self._context.object(
|
||||
kernel_symbol_table_name + constants.BANG + "pointer",
|
||||
offset=link_map_addr,
|
||||
layer_name=self.vol.layer_name,
|
||||
)
|
||||
if not link_map_ptr:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Invalid ELF link map pointer at 0x{link_map_addr:x}",
|
||||
)
|
||||
continue
|
||||
|
||||
linkmap_symname = (
|
||||
elf_symbol_table + constants.BANG + self._type_prefix + "LinkMap"
|
||||
)
|
||||
try:
|
||||
link_map = self._context.object(
|
||||
object_type=linkmap_symname,
|
||||
offset=link_map_ptr,
|
||||
layer_name=self.vol.layer_name,
|
||||
)
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Invalid ELF link map address at 0x{link_map_ptr:x}",
|
||||
)
|
||||
continue
|
||||
|
||||
while link_map and link_map.vol.offset != 0:
|
||||
if link_map.vol.offset in link_maps_seen:
|
||||
break
|
||||
link_maps_seen.add(link_map.vol.offset)
|
||||
|
||||
yield link_map
|
||||
|
||||
try:
|
||||
link_map = self._context.object(
|
||||
object_type=linkmap_symname,
|
||||
offset=link_map.l_next,
|
||||
layer_name=self.vol.layer_name,
|
||||
)
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"ELF link map linked list is corrupt at 0x{self.vol.offset:x}",
|
||||
)
|
||||
break
|
||||
|
||||
def _find_symbols(self):
|
||||
dt_strtab = None
|
||||
dt_symtab = None
|
||||
dt_strent = None
|
||||
|
||||
for phdr in self.get_program_headers():
|
||||
# Find PT_DYNAMIC segment
|
||||
try:
|
||||
# Find PT_DYNAMIC segment
|
||||
if str(phdr.p_type.description) != "PT_DYNAMIC":
|
||||
if phdr.p_type.description != "PT_DYNAMIC":
|
||||
continue
|
||||
except ValueError:
|
||||
# If the p_type value is outside the ones declared in the enumeration, an
|
||||
# exception is raised
|
||||
return None
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping unknown ELF program header type: {phdr.p_type}",
|
||||
)
|
||||
continue
|
||||
|
||||
# This section contains pointers to the strtab, symtab, and strent sections
|
||||
for dsec in phdr.dynamic_sections():
|
||||
if dsec.d_tag == 5:
|
||||
try:
|
||||
dtag = dsec.d_tag.description
|
||||
except ValueError:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping unknown ELF dynamic section type: {dsec.d_tag}",
|
||||
)
|
||||
continue
|
||||
|
||||
if dtag == "DT_STRTAB":
|
||||
dt_strtab = dsec.d_ptr
|
||||
|
||||
elif dsec.d_tag == 6:
|
||||
elif dtag == "DT_SYMTAB":
|
||||
dt_symtab = dsec.d_ptr
|
||||
|
||||
elif dsec.d_tag == 11:
|
||||
elif dtag == "DT_SYMENT":
|
||||
# Size of the symtab symbol entry
|
||||
dt_strent = dsec.d_ptr
|
||||
|
||||
break
|
||||
|
||||
if dt_strtab is None or dt_symtab is None or dt_strent is None:
|
||||
if not (dt_strtab and dt_symtab and dt_strent):
|
||||
return None
|
||||
|
||||
self._cached_symtab = dt_symtab
|
||||
@@ -274,19 +381,31 @@ class elf_phdr(objects.StructType):
|
||||
def get_vaddr(self):
|
||||
offset = self.__getattr__("p_vaddr")
|
||||
|
||||
if self._parent_e_type == 3: # ET_DYN
|
||||
offset = self._parent_offset + offset
|
||||
try:
|
||||
if self._parent_e_type.description == "ET_DYN":
|
||||
offset = self._parent_offset + offset
|
||||
except ValueError:
|
||||
# Unknown ELF object file type. Anyway, if the ELF object file type is not a
|
||||
# shared object (ET_DYN), the virtual address is 'p_vaddr'.
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping unknown ELF object type: {self._parent_e_type}",
|
||||
)
|
||||
|
||||
return offset
|
||||
|
||||
def dynamic_sections(self):
|
||||
# sanity check
|
||||
try:
|
||||
if str(self.p_type.description) != "PT_DYNAMIC":
|
||||
if self.p_type.description != "PT_DYNAMIC":
|
||||
return None
|
||||
except ValueError:
|
||||
# If the value is outside the ones declared in the enumeration, an
|
||||
# exception is raised
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Skipping unknown ELF program header type: {self.p_type}",
|
||||
)
|
||||
return None
|
||||
|
||||
# the buffer of array starts at elf_base + our virtual address ( offset )
|
||||
@@ -314,10 +433,30 @@ class elf_phdr(objects.StructType):
|
||||
break
|
||||
|
||||
|
||||
class elf_linkmap(objects.StructType):
|
||||
def get_name(self):
|
||||
try:
|
||||
buf = self._context.layers.read(self.vol.layer_name, self.l_name, 256)
|
||||
except exceptions.PagedInvalidAddressException:
|
||||
# Protection against memory smear
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Invalid l_name address for ELF link map at 0x{self.vol.offset:x}",
|
||||
)
|
||||
return None
|
||||
|
||||
idx = buf.find(b"\x00")
|
||||
if idx != -1:
|
||||
buf = buf[:idx]
|
||||
return buf.decode()
|
||||
|
||||
|
||||
class_types = {
|
||||
"Elf": elf,
|
||||
"Elf64_Phdr": elf_phdr,
|
||||
"Elf32_Phdr": elf_phdr,
|
||||
"Elf32_Sym": elf_sym,
|
||||
"Elf64_Sym": elf_sym,
|
||||
"Elf32_LinkMap": elf_linkmap,
|
||||
"Elf64_LinkMap": elf_linkmap,
|
||||
}
|
||||
|
||||
@@ -21,12 +21,14 @@ class MacKernelIntermedSymbols(intermed.IntermediateSymbolTable):
|
||||
self.set_type_class("vm_map_object", extensions.vm_map_object)
|
||||
self.set_type_class("socket", extensions.socket)
|
||||
self.set_type_class("inpcb", extensions.inpcb)
|
||||
self.set_type_class("queue_entry", extensions.queue_entry)
|
||||
self.set_type_class("ifnet", extensions.ifnet)
|
||||
self.set_type_class("sockaddr_dl", extensions.sockaddr_dl)
|
||||
self.set_type_class("sockaddr", extensions.sockaddr)
|
||||
self.set_type_class("sysctl_oid", extensions.sysctl_oid)
|
||||
self.set_type_class("kauth_scope", extensions.kauth_scope)
|
||||
# https://developer.apple.com/documentation/kernel/queue_head_t
|
||||
self.set_type_class("queue_entry", extensions.queue_entry)
|
||||
self.optional_set_type_class("queue_head_t", extensions.queue_entry)
|
||||
|
||||
|
||||
class MacUtilities(interfaces.configuration.VersionableInterface):
|
||||
|
||||
@@ -490,22 +490,24 @@ class queue_entry(objects.StructType):
|
||||
|
||||
for attr in ["next", "prev"]:
|
||||
with contextlib.suppress(exceptions.InvalidAddressException):
|
||||
n = getattr(self, attr).dereference().cast(type_name)
|
||||
|
||||
while n is not None and n.vol.offset != list_head:
|
||||
if n.vol.offset in seen:
|
||||
queue_element = getattr(self, attr).dereference().cast(type_name)
|
||||
while (
|
||||
queue_element is not None
|
||||
and queue_element.vol.offset != list_head.vol.offset
|
||||
):
|
||||
if queue_element.vol.offset in seen:
|
||||
break
|
||||
|
||||
yield n
|
||||
yield queue_element
|
||||
|
||||
seen.add(n.vol.offset)
|
||||
seen.add(queue_element.vol.offset)
|
||||
|
||||
yielded = yielded + 1
|
||||
if yielded == max_size:
|
||||
return None
|
||||
|
||||
n = (
|
||||
getattr(n.member(attr=member_name), attr)
|
||||
queue_element = (
|
||||
getattr(queue_element.member(attr=member_name), attr)
|
||||
.dereference()
|
||||
.cast(type_name)
|
||||
)
|
||||
|
||||
@@ -492,9 +492,47 @@ class KMUTANT(objects.StructType, pool.ExecutiveObject):
|
||||
return header.NameInfo.Name.String # type: ignore
|
||||
|
||||
|
||||
class ETHREAD(objects.StructType):
|
||||
class ETHREAD(objects.StructType, pool.ExecutiveObject):
|
||||
"""A class for executive thread objects."""
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
"""Determine if the object is valid."""
|
||||
|
||||
try:
|
||||
# validation by TID:
|
||||
if self.Cid.UniqueThread % 4 != 0: # NT tids are divisible by 4
|
||||
return False
|
||||
|
||||
# validation by PID of parent process:
|
||||
if self.Cid.UniqueProcess % 4 != 0:
|
||||
return False
|
||||
|
||||
# validation by thread creation time:
|
||||
if (
|
||||
self.Cid.UniqueProcess != 4
|
||||
): # The System process (PID 4) has no create time
|
||||
ctime = self.get_create_time()
|
||||
if not isinstance(ctime, datetime.datetime):
|
||||
return False
|
||||
|
||||
if not (1998 < ctime.year < 2030):
|
||||
return False
|
||||
|
||||
except exceptions.InvalidAddressException:
|
||||
return False
|
||||
|
||||
# passed all validations
|
||||
return True
|
||||
|
||||
def get_create_time(self):
|
||||
# For Windows XPs
|
||||
if self.has_member("ThreadsProcess"):
|
||||
return conversion.wintime_to_datetime(self.CreateTime.QuadPart >> 3)
|
||||
return conversion.wintime_to_datetime(self.CreateTime.QuadPart)
|
||||
|
||||
def get_exit_time(self):
|
||||
return conversion.wintime_to_datetime(self.ExitTime.QuadPart)
|
||||
|
||||
def owning_process(self) -> interfaces.objects.ObjectInterface:
|
||||
"""Return the EPROCESS that owns this thread."""
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
{
|
||||
"base_types": {
|
||||
"pointer": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 8
|
||||
},
|
||||
"unsigned char": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"unsigned long": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 4
|
||||
},
|
||||
"unsigned long long": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 8
|
||||
},
|
||||
"unsigned short": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 2
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"metadata": {
|
||||
"format": "6.1.0",
|
||||
"producer": {
|
||||
"datetime": "2021-07-31T17:37:28.313255",
|
||||
"name": "vmextract-by-hand",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
},
|
||||
"symbols": {
|
||||
"revision_id": {
|
||||
"address": 0,
|
||||
"constant_data": "MTg="
|
||||
}
|
||||
},
|
||||
"user_types": {
|
||||
"_VMCS": {
|
||||
"fields": {
|
||||
"ept": {
|
||||
"offset": 320,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"executive_vmcs_ptr": {
|
||||
"offset": 208,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"guest_cr3": {
|
||||
"offset": 528,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"guest_cr4": {
|
||||
"offset": 536,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"guest_pdpte": {
|
||||
"offset": 544,
|
||||
"type": {
|
||||
"count": 4,
|
||||
"kind": "array",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"guest_physical_addr": {
|
||||
"offset": 328,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"host_cr3": {
|
||||
"offset": 816,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"host_cr4": {
|
||||
"offset": 824,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"vmcs_link_ptr": {
|
||||
"offset": 248,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"vpid": {
|
||||
"offset": 206,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned short"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 4096
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
{
|
||||
"base_types": {
|
||||
"pointer": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 8
|
||||
},
|
||||
"unsigned char": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"unsigned long": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 4
|
||||
},
|
||||
"unsigned long long": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 8
|
||||
},
|
||||
"unsigned short": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 2
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"metadata": {
|
||||
"format": "6.1.0",
|
||||
"producer": {
|
||||
"datetime": "2021-07-16T16:21:01.062423",
|
||||
"name": "vmextract-by-hand",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
},
|
||||
"symbols": {
|
||||
"revision_id": {
|
||||
"address": 0,
|
||||
"constant_data": "NA=="
|
||||
}
|
||||
},
|
||||
"user_types": {
|
||||
"_VMCS": {
|
||||
"fields": {
|
||||
"ept": {
|
||||
"offset": 320,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"executive_vmcs_ptr": {
|
||||
"offset": 208,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"guest_cr3": {
|
||||
"offset": 528,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"guest_cr4": {
|
||||
"offset": 536,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"guest_pdpte": {
|
||||
"offset": 544,
|
||||
"type": {
|
||||
"count": 4,
|
||||
"kind": "array",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"guest_physical_addr": {
|
||||
"offset": 328,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"host_cr3": {
|
||||
"offset": 816,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"host_cr4": {
|
||||
"offset": 824,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"vmcs_link_ptr": {
|
||||
"offset": 248,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"vpid": {
|
||||
"offset": 206,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned short"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 4096
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user