Merge branch 'volatilityfoundation:develop' into library

This commit is contained in:
j-t-1
2024-12-17 19:22:48 +00:00
committed by GitHub
18 changed files with 97 additions and 172 deletions
@@ -27,7 +27,7 @@ To create a symbol table please refer to :ref:`symbol-tables:Mac or Linux symbol
Listing plugins
---------------
The following is a sample of the linux plugins available for volatility3, it is not complete and more more plugins may
The following is a sample of the linux plugins available for volatility3, it is not complete and more plugins may
be added. For a complete reference, please see the volatility 3 :doc:`list of plugins <volatility3.plugins>`.
For plugin requests, please create an issue with a description of the requested plugin.
@@ -40,7 +40,7 @@ For plugin requests, please create an issue with a description of the requested
linux.check_creds.Check_creds
linux.check_idt.Check_idt
.. note:: Here the the command is piped to grep and head in-order to provide the start of the list of linux plugins.
.. note:: Here the the command is piped to grep and head to provide the start of the list of linux plugins.
Using plugins
@@ -80,9 +80,9 @@ Thanks go to `stuxnet <https://github.com/stuxnet999/>`_ for providing this memo
The above command helps us to find the memory dump's kernel version and the distribution version. Now using the above banner we can search for the needed ISF file from the ISF server.
If ISF file cannot be found then, follow the instructions on :ref:`getting-started-linux-tutorial:Procedure to create symbol tables for linux`. After that, place the ISF file under the ``volatility3/symbols/linux`` directory.
If an ISF file cannot be found then, follow the instructions on :ref:`getting-started-linux-tutorial:Procedure to create symbol tables for linux`. After that, place the ISF file under the ``volatility3/symbols/linux`` directory.
.. tip:: Use the banner text which is most repeated to search from ISF Server.
.. tip:: Use the banner text which is most repeated to search on the ISF Server.
linux.pslist
~~~~~~~~~~~~
@@ -157,7 +157,7 @@ linux.pstree
***** 1548 1266 gsd-keyboard
***** 1550 1266 gsd-media-keys
``linux.pstree`` helps us to display the parent child relationships between processes.
``linux.pstree`` helps us to display the parent-child relationships between processes.
linux.bash
~~~~~~~~~~
+4 -4
View File
@@ -37,7 +37,7 @@ For plugin requests, please create an issue with a description of the requested
mac.check_sysctl.Check_sysctl
mac.check_trap_table.Check_trap_table
.. note:: Here the the command is piped to grep and head in-order to provide the start of the list of macOS plugins.
.. note:: Here the the command is piped to grep and head to provide the start of the list of macOS plugins.
Using plugins
@@ -78,7 +78,7 @@ Thanks go to `stuxnet <https://github.com/stuxnet999/>`_ for providing this memo
The above command helps us to find the memory dump's Darwin kernel version. Now using the above banner we can search for the needed ISF file.
If ISF file cannot be found then, follow the instructions on :ref:`getting-started-mac-tutorial:Procedure to create symbol tables for macOS`. After that, place the ISF file under the ``volatility3/symbols`` directory.
If an ISF file cannot be found then, follow the instructions on :ref:`getting-started-mac-tutorial:Procedure to create symbol tables for macOS`. After that, place the ISF file under the ``volatility3/symbols`` directory.
mac.pslist
~~~~~~~~~~
@@ -125,7 +125,7 @@ mac.pstree
337 1 system_installd
* 455 337 update_dyld_shar
``mac.pstree`` helps us to display the parent child relationships between processes.
``mac.pstree`` helps us to display the parent-child relationships between processes.
mac.ifconfig
~~~~~~~~~~~~
@@ -150,4 +150,4 @@ mac.ifconfig
utun0 False
utun0 fe80:5::2a95:bb15:87e3:977c False
we can use the ``mac.ifconfig`` plugin to get information about the configuration of the network interfaces of the host under investigation.
We can use the ``mac.ifconfig`` plugin to get information about the configuration of the network interfaces of the host under investigation.
@@ -15,19 +15,19 @@ Memory can be acquired using a number of tools, below are some examples but othe
Listing Plugins
---------------
The following is a sample of the windows plugins available for volatility3, it is not complete and more more plugins may
The following is a sample of the windows plugins available for volatility3, it is not complete and more plugins may
be added. For a complete reference, please see the volatility 3 :doc:`list of plugins <volatility3.plugins>`.
For plugin requests, please create an issue with a description of the requested plugin.
.. code-block:: shell-session
$ python3 vol.py --help | grep windows | head -n 5
$ python3 vol.py --help | grep windows | head -n 4
windows.bigpools.BigPools
windows.cmdline.CmdLine
windows.crashinfo.Crashinfo
windows.dlllist.DllList
.. note:: Here the the command is piped to grep and head in-order to provide the start of a list of the available windows plugins.
.. note:: Here the the command is piped to grep and head to provide the start of a list of the available windows plugins.
Using plugins
-------------
@@ -95,9 +95,9 @@ windows.pstree
** 616 504 svchost.exe 0xfa8002b86ab0 13 314 0 False 2022-02-07 16:32:16.000000 N/A
** 624 504 svchost.exe 0xfa8002410630 10 350 0 False 2022-02-07 16:30:14.000000 N/A
``windows.pstree`` helps to display the parent child relationships between processes.
``windows.pstree`` helps to display the parent-child relationships between processes.
.. note:: Here the the command is piped to head in-order to provide smaller output, here listing only the first 20.
.. note:: Here the the command is piped to head to provide smaller output, here listing only the first 20.
windows.hashdump
~~~~~~~~~~~~~~~~
@@ -116,9 +116,3 @@ windows.hashdump
Dennis 1003 aad3b435b51404eeaad3b435b51404ee cf96684bbc7877920adaa9663698bf54
``windows.hashdump`` helps to list the hashes of the users in the system.
+7 -9
View File
@@ -58,7 +58,7 @@ Options
EXTEND. Extensions must be of the form **configuration.item.name=value**
-p PLUGIN_DIRS, --plugin-dirs PLUGIN_DIRS
Specified a semi-colon separated list of paths that contain directories
Specified as a semi-colon separated list of paths that contain directories
where plugins may be found. These paths are searched before the default
paths when loading python files for plugins. This can therefore be used
to override built-in plugins. NOTE: All python code within this directory
@@ -67,12 +67,12 @@ Options
-s SYMBOL_DIRS, --symbol-dirs SYMBOL_DIRS
SYMBOL_DIRS is a semi-colon separated list of paths that contain symbol
files or symbol zip packs. Symbols must be within a particular directory
structure if they depending on the operating system of the symbols,
structure if they depend on the operating system of the symbols,
whilst symbol packs must be in the root of the directory and named after
the after the operating system to which they apply.
the operating system to which they apply.
-v, --verbose
A flag which can be used multiple times, each time increasing the level of
A flag which can be used multiple times (up to six, -vvvvvv), each time increasing the level of
detail in the logs produced.
-l LOG, --log LOG
@@ -87,7 +87,7 @@ Options
-q, --quiet
When present, this flag mutes the progress feedback for operations. This
can be beneficial when piping the output directly to a file or another
tool. This also removes the
tool.
-r RENDERER, --renderer RENDERER
Specifies the output format in which to display results. The default is
@@ -120,9 +120,7 @@ Options
Change the default path used to store the cache.
--offline
Do not search online for additional JSON files.
Run offline mode (defaults to false) and for
remote windows symbol tables, linux/mac banner repositories.
Run offline mode (defaults to false). Do not search online for additional JSON files, remote windows symbol tables, nor linux/mac banner repositories.
--single-location SINGLE_LOCATION
This specifies a URL which will be downloaded if necessary, and built
@@ -152,7 +150,7 @@ but can be overridden by creating a JSON file (`%APPDATA%/volatility3/vol.json`
systems, or `~/.config/volatility3/vol.json` or `volshell.json` for all others).
The format of this file is a JSON dictionary, containing the options above and their value.
It should be noted that the ordering is (`<` means is overridden by):
It should be noted that the ordering is (`x < y` means `x` is overridden by `y`):
`in-built default value < config file value < command line parameter`
+20
View File
@@ -632,6 +632,26 @@ def test_linux_vmayarascan_yara_string(image, volatility, python):
assert rc == 0
def test_linux_page_cache_files(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.pagecache.Files",
image,
volatility,
python,
pluginargs=["--find", "/etc/passwd"],
)
out = out.lower()
assert out.count(b"\n") > 4
# inode_num inode_addr ... file_path
assert re.search(
rb"146829\s0x88001ab5c270.*?/etc/passwd",
out,
)
assert rc == 0
# MAC
+5 -5
View File
@@ -49,12 +49,12 @@ def hex_bytes_as_text(value: bytes, width: int = 16) -> str:
output += "\n"
printables = ""
# Handle leftovers when the lenght is not mutiple of width
# Handle leftovers when the length is not mutiple of width
if printables:
padding = width - len(printables)
output += " " * (padding)
output += " " * padding
output += printables
output += " " * (padding)
output += " " * padding
return output
@@ -132,7 +132,7 @@ def display_disassembly(disasm: interfaces.renderers.Disassembly) -> str:
for i in disasm_types[disasm.architecture].disasm(
disasm.data, disasm.offset
):
output += f"\n0x{i.address:x}:\t{i.mnemonic}\t{i.op_str}"
output += f"\n{i.address:#x}:\t{i.mnemonic}\t{i.op_str}"
return output
return QuickTextRenderer._type_renderers[bytes](disasm.data)
@@ -342,7 +342,7 @@ class PrettyTextRenderer(CLIRenderer):
column_separator = " | "
tree_indent_column = "".join(
random.choice(string.ascii_uppercase + string.digits) for _ in range(20)
random.choices(string.ascii_uppercase + string.digits, k=20)
)
max_column_widths = dict(
[(column.name, len(column.name)) for column in grid.columns]
+12
View File
@@ -143,6 +143,18 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface):
and init_task.state.cast("unsigned int") != 0
):
continue
elif init_task.active_mm.cast("long unsigned int") == module.get_symbol(
"init_mm"
).address and init_task.tasks.next.cast(
"long unsigned int"
) == init_task.tasks.prev.cast(
"long unsigned int"
):
# The idle task steals `mm` from previously running task, i.e.,
# `init_mm` is only used as long as no CPU has ever been idle.
# This catches cases where we found a fragment of the
# unrelocated ELF file instead of the running kernel.
continue
# This we get for free
aslr_shift = (
@@ -529,6 +529,8 @@ class VersionRequirement(interfaces.configuration.RequirementInterface):
component: Type[interfaces.configuration.VersionableInterface] = None,
version: Optional[Tuple[int, ...]] = None,
) -> None:
if version is None:
raise TypeError("Version cannot be None")
if description is None:
description = f"Version {'.'.join([str(x) for x in version])} dependency on {component.__module__}.{component.__name__} unmet"
super().__init__(
@@ -537,8 +539,6 @@ class VersionRequirement(interfaces.configuration.RequirementInterface):
if component is None:
raise TypeError("Component cannot be None")
self._component: Type[interfaces.configuration.VersionableInterface] = component
if version is None:
raise TypeError("Version cannot be None")
self._version = version
def unsatisfied(
+2 -5
View File
@@ -73,9 +73,7 @@ class Intel(linear.LinearlyMappedLayer):
)
# These can vary depending on the type of space
self._index_shift = int(
math.ceil(math.log2(struct.calcsize(self._entry_format)))
)
self._index_shift = math.ceil(math.log2(struct.calcsize(self._entry_format)))
@classproperty
@functools.lru_cache()
@@ -125,7 +123,6 @@ class Intel(linear.LinearlyMappedLayer):
high_mask = (1 << (high_bit + 1)) - 1
low_mask = (1 << low_bit) - 1
mask = high_mask ^ low_mask
# print(high_bit, low_bit, bin(mask), bin(value))
return value & mask
@staticmethod
@@ -147,7 +144,7 @@ class Intel(linear.LinearlyMappedLayer):
return self._mask(addr, self._maxvirtaddr, 0) + self._canonical_prefix
def decanonicalize(self, addr: int) -> int:
"""Removes canonicalization to ensure an adress fits within the correct range if it has been canonicalized
"""Removes canonicalization to ensure an address fits within the correct range if it has been canonicalized
This will produce an address outside the range if the canonicalization is incorrect
"""
@@ -389,7 +389,7 @@ class InodePages(plugins.PluginInterface):
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -412,9 +412,10 @@ class InodePages(plugins.PluginInterface):
description="Inode address",
optional=True,
),
requirements.StringRequirement(
requirements.BooleanRequirement(
name="dump",
description="Output file path",
description="Extract inode content",
default=False,
optional=True,
),
]
@@ -436,7 +437,7 @@ class InodePages(plugins.PluginInterface):
"""
if not inode.is_reg:
vollog.error("The inode is not a regular file")
return
return None
# By using truncate/seek, provided the filesystem supports it, a sparse file will be
# created, saving both disk space and I/O time.
@@ -471,7 +472,7 @@ class InodePages(plugins.PluginInterface):
if self.config["inode"] and self.config["find"]:
vollog.error("Cannot use --inode and --find simultaneously")
return
return None
if self.config["find"]:
inodes_iter = Files.get_inodes(
@@ -487,15 +488,15 @@ class InodePages(plugins.PluginInterface):
inode = vmlinux.object("inode", self.config["inode"], absolute=True)
else:
vollog.error("You must use either --inode or --find")
return
return None
if not inode.is_valid():
vollog.error("Invalid inode at 0x%x", inode.vol.offset)
return
return None
if not inode.is_reg:
vollog.error("The inode is not a regular file")
return
return None
inode_size = inode.i_size
for page_obj in inode.get_pages():
@@ -519,9 +520,13 @@ class InodePages(plugins.PluginInterface):
yield 0, fields
if self.config["dump"]:
filename = self.config["dump"]
vollog.info("[*] Writing inode at 0x%x to '%s'", inode.vol.offset, filename)
self.write_inode_content_to_file(inode, filename, self.open, vmlinux_layer)
open_method = self.open
inode_address = inode.vol.offset
filename = open_method.sanitize_filename(f"inode_0x{inode_address:x}.dmp")
vollog.info("[*] Writing inode at 0x%x to '%s'", inode_address, filename)
self.write_inode_content_to_file(
inode, filename, open_method, vmlinux_layer
)
def run(self):
headers = [
@@ -48,7 +48,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
name="driverirp", plugin=driverirp.DriverIrp, version=(1, 0, 0)
),
requirements.PluginRequirement(
name="handles", plugin=handles.Handles, version=(1, 0, 0)
name="handles", plugin=handles.Handles, version=(2, 0, 0)
),
]
@@ -69,7 +69,7 @@ class DumpFiles(interfaces.plugins.PluginInterface):
name="pslist", component=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="handles", component=handles.Handles, version=(1, 0, 0)
name="handles", component=handles.Handles, version=(2, 0, 0)
),
]
@@ -3,9 +3,9 @@
#
import logging
from typing import List, Optional, Dict
from typing import Dict, List, Optional
from volatility3.framework import constants, exceptions, renderers, interfaces, symbols
from volatility3.framework import constants, exceptions, interfaces, renderers, symbols
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
@@ -13,23 +13,15 @@ from volatility3.plugins.windows import pslist, psscan
vollog = logging.getLogger(__name__)
try:
import capstone
has_capstone = True
except ImportError:
has_capstone = False
class Handles(interfaces.plugins.PluginInterface):
"""Lists process open handles."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 2)
_version = (2, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._sar_value = None
self._type_map = None
self._cookie = None
self._level_mask = 7
@@ -62,21 +54,6 @@ class Handles(interfaces.plugins.PluginInterface):
),
]
def _decode_pointer(self, value, magic):
"""Windows encodes pointers to objects and decodes them on the fly
before using them.
This function mimics the decoding routine so we can generate the
proper pointer values as well.
"""
value = value & 0xFFFFFFFFFFFFFFF8
value = value >> magic
# if (value & (1 << 47)):
# value = value | 0xFFFF000000000000
return value
def _get_item(self, handle_table_entry, handle_value):
"""Given a handle table entry (_HANDLE_TABLE_ENTRY) structure from a
process' handle table, determine where the corresponding object's
@@ -100,24 +77,11 @@ class Handles(interfaces.plugins.PluginInterface):
)
if is_64bit:
if handle_table_entry.LowValue == 0:
if handle_table_entry.ObjectPointerBits == 0:
return None
magic = self.find_sar_value()
offset = handle_table_entry.ObjectPointerBits << 4
# is this the right thing to raise here?
if magic is None:
if has_capstone:
raise AttributeError(
"Unable to find the SAR value for decoding handle table pointers"
)
else:
raise exceptions.MissingModuleException(
"capstone",
"Requires capstone to find the SAR value for decoding handle table pointers",
)
offset = self._decode_pointer(handle_table_entry.LowValue, magic)
else:
if handle_table_entry.InfoTable == 0:
return None
@@ -135,78 +99,6 @@ class Handles(interfaces.plugins.PluginInterface):
object_header.HandleValue = handle_value
return object_header
def find_sar_value(self):
"""Locate ObpCaptureHandleInformationEx if it exists in the sample.
Once found, parse it for the SAR value that we need to decode
pointers in the _HANDLE_TABLE_ENTRY which allows us to find the
associated _OBJECT_HEADER.
"""
DEFAULT_SAR_VALUE = 0x10 # to be used only when decoding fails
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"]]
virtual_layer_name = kernel.layer_name
kvo = self.context.layers[virtual_layer_name].config[
"kernel_virtual_offset"
]
ntkrnlmp = self.context.module(
kernel.symbol_table_name, layer_name=virtual_layer_name, offset=kvo
)
try:
func_addr = ntkrnlmp.get_symbol("ObpCaptureHandleInformationEx").address
except exceptions.SymbolError:
vollog.debug("Unable to locate ObpCaptureHandleInformationEx symbol")
return 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.warning(
f"Failed to read {hex(num_bytes_to_read)} bytes at symbol {hex(func_addr_to_read)}. Unable to decode SAR value. Failing back to a common value of {hex(DEFAULT_SAR_VALUE)}"
)
self._sar_value = DEFAULT_SAR_VALUE
return self._sar_value
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.warning(
f"Failed to to locate SAR value having parsed {instruction_count} instructions, failing back to a common value of {hex(DEFAULT_SAR_VALUE)}"
)
self._sar_value = DEFAULT_SAR_VALUE
return self._sar_value
@classmethod
def get_type_map(
cls,
@@ -73,7 +73,7 @@ class IndirectSystemCalls(direct_system_calls.DirectSystemCalls):
@staticmethod
def _indirect_syscall_block_target(
proc_layer: interfaces.layers.DataLayerInterface, inst: capstone._cs_insn
proc_layer: interfaces.layers.DataLayerInterface, inst
) -> Optional[int]:
"""
This function determines the address of a jmp in the following form:
@@ -139,7 +139,7 @@ class PoolScanner(plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="handles", plugin=handles.Handles, version=(1, 0, 0)
name="handles", plugin=handles.Handles, version=(2, 0, 0)
),
]
@@ -62,7 +62,7 @@ class PsXView(plugins.PluginInterface):
name="thrdscan", component=thrdscan.ThrdScan, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="handles", component=handles.Handles, version=(1, 0, 0)
name="handles", component=handles.Handles, version=(2, 0, 0)
),
requirements.BooleanRequirement(
name="physical-offsets",
@@ -1107,9 +1107,16 @@ class dentry(objects.StructType):
walk_member = "d_sib"
list_head_member = self.d_children
elif self.has_member("d_child") and self.has_member("d_subdirs"):
# 2.5.0 <= kernels < 6.8
# 3.19.0 <= kernels < 6.8
walk_member = "d_child"
list_head_member = self.d_subdirs
elif self.has_member("d_u") and self.has_member("d_subdirs"):
# kernels < 3.19
# Actually, 'd_u.d_child' but to_list() doesn't support something like that.
# Since, it's an union, everything is at the same offset than 'd_u'.
walk_member = "d_u"
list_head_member = self.d_subdirs
else:
raise exceptions.VolatilityException("Unsupported dentry type")
+1 -1
View File
@@ -27,7 +27,7 @@ class ProducerMetadata(interfaces.symbols.MetadataInterface):
@property
def version(self) -> Optional[Tuple[int]]:
"""Returns the version of the ISF file producer"""
version = self.version_string()
version = self.version_string
if not version:
return None
if all(x in "0123456789." for x in version):