#1476 - merge develop

This commit is contained in:
Dave Lassalle
2025-03-10 13:56:08 -05:00
135 changed files with 228710 additions and 2115 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: psf/black@stable
+1 -1
View File
@@ -15,7 +15,7 @@ on:
jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
strategy:
matrix:
python-version: ["3.8"]
+1 -1
View File
@@ -23,7 +23,7 @@ on:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
permissions:
actions: read
contents: read
+5 -5
View File
@@ -3,7 +3,7 @@ on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
strategy:
matrix:
python-version: ["3.8"]
@@ -43,12 +43,12 @@ jobs:
- name: Testing...
run: |
# VolShell
pytest ./test/test_volatility.py --volatility=volshell.py --image-dir=./test_images -k test_windows_volshell -v
pytest ./test/test_volatility.py --volatility=volshell.py --image-dir=./test_images -k test_linux_volshell -v
pytest ./test/plugins/windows/windows.py --volatility=volshell.py --image-dir=./test_images -k test_windows_volshell -v
pytest ./test/plugins/linux/linux.py --volatility=volshell.py --image-dir=./test_images -k test_linux_volshell -v
# Volatility
pytest ./test/test_volatility.py --volatility=vol.py --image-dir=./test_images -k "test_windows and not test_windows_volshell" -v
pytest ./test/test_volatility.py --volatility=vol.py --image-dir=./test_images -k "test_linux and not test_linux_volshell" -v
pytest ./test/plugins/windows/windows.py --volatility=vol.py --image-dir=./test_images -k "test_windows and not test_windows_volshell" -v
pytest ./test/plugins/linux/linux.py --volatility=vol.py --image-dir=./test_images -k "test_linux and not test_linux_volshell" -v
- name: Clean up post-test
run: |
+1 -1
View File
@@ -35,7 +35,7 @@ The latest stable version of Volatility will always be the `stable` branch of th
git clone https://github.com/volatilityfoundation/volatility3.git
cd volatility3/
python3 -m venv venv && . venv/bin/activate
pip install -e .[dev]
pip install -e ".[dev]"
```
## Quick Start
+12 -12
View File
@@ -13,7 +13,7 @@ There is scope for this, in order to run multiple plugins (see `Writing plugins
is to provide a parameterized `classmethod` within the plugin, which will allow the method to yield whatever kind of output it will
generate and take whatever parameters it might need.
This is how processes are listed, which is an often used function. The code lives within the
As an example, an often used function is listing processes. The code lives within the
:py:class:`~volatility3.plugins.windows.pslist.PsList` plugin but can be used by other plugins by providing the
appropriate parameters (see
:py:meth:`~volatility3.plugins.windows.pslist.PsList.list_processes`).
@@ -36,8 +36,8 @@ each plugin in order to populate the context's configuration correctly based on
between plugins). Once the automagics have been constructed, the plugin can be instantiated using the helper function
:py:func:`~volatility3.framework.plugins.construct_plugin` providing:
* the base context (containing the configuration and any already loaded layers or symbol tables),
* the plugin class to run,
* the base context (containing the configuration and any already loaded layers or symbol tables)
* the plugin class to run
* the configuration path within the context for the plugin
* any callback to determine progress in lengthy operations
* an open method for the plugin to create files during the run
@@ -58,7 +58,7 @@ ContextManager, so it can be used by the python `with` keyword). This is set on
that can be set on the filename, and a :py:class:`~volatility3.framework.interfaces.plugins.FileHandlerInterface` is the result.
This mimics an `IO[bytes]` object, which closely mimics a standard python file-like object.
As such code for outputting to a file would be expected to look something like:
As such, code for outputting to a file would be expected to look something like:
.. code-block:: python
@@ -77,7 +77,7 @@ Scanners are objects that adhere to the :py:class:`~volatility3.framework.interf
passed to the :py:meth:`~volatility3.framework.interfaces.layers.TranslationLayerInterface.scan` method on layers which will
divide the provided range of sections (or the entire layer
if none are provided) and call the :py:meth:`~volatility3.framework.interfaces.layers.ScannerInterface`'s call method
method with each chunk as a parameter, ensuring a suitable amount of overlap (as defined by the scanner).
with each chunk as a parameter, ensuring a suitable amount of overlap (as defined by the scanner).
The offset of the chunk, within the layer, is also provided as a parameter.
Scanners can technically maintain state, but it is not recommended since the ordering that the chunks are scanned is
@@ -92,18 +92,18 @@ Empirically it was found that scanners are typically not the most time intensive
extensive scanning) and so parallelism does not offer significant gains. As such, parallelism is not enabled by default
but interfaces can easily enable parallelism when desired.
Writing/Using Intermediate Symbol Format Files
----------------------------------------------
Writing / Using Intermediate Symbol Format Files
------------------------------------------------
It can occasionally be useful to create a data file containing the static structures that can create a
:py:class:`~volatility3.framework.interfaces.objects.Template` to be instantiated on a layer.
Volatility has all the machinery necessary to construct these for you from properly formatted JSON data.
The JSON format is documented by the JSON schema files located in schemas. These are versioned using standard .so
The JSON format is documented by the JSON schema files located in the schemas directory. These are versioned using standard .so
library versioning, so they may not increment as expected. Each schema lists an available version that can be used,
which specifies five different sections:
* Base_types - These are the basic type names that will make up the native/primitive types
* Base_types - These are the basic type names that will make up the native / primitive types
* User_types - These are the standard definitions of type structures, most will go here
* Symbols - These list offsets that are associated with specific names (and can be associated with specific type names)
* Enums - Enumerations that offer a number of choices
@@ -180,7 +180,7 @@ of data. Each chunk contains the following information, in order:
**layer_name**
the layer that this data comes from
An example (and the most common layer encountered in memory forensics) would be an Intel layer, which models the intel
An example (and the most common layer encountered in memory forensics) would be an Intel layer, which models the Intel
page mapping system. Based on a series of tables stored within the layer itself, an intel layer can convert a virtual
address to a physical address. It should be noted that intel layers allow multiple virtual addresses to map to the
same physical address (but a single virtual address cannot ever map to more than one physical address).
@@ -195,7 +195,7 @@ like `abcdr`, requesting `mapping(5, 4)` would return:
(7,2,0,2, 'physical_layer')
]
This mapping mechanism allows for great flexibility in that chunks making up a virtual layer can come from multiple
This mapping mechanism allows for great flexibility because chunks making up a virtual layer can come from multiple
different range layers, allowing for swap space to be used to construct the virtual layer, for example. Also, by
defining the mapping method, the read and write methods (which read and write into the domain layer) are defined for you
to write to the lower layers (which in turn can write to layers even lower than that) until eventually they arrive at a
@@ -264,7 +264,7 @@ so it therefore populates the `metadata` property. This is defined as a read-on
includes data from every underlying layer. As such, CrashDumpLayer would actually specify this value by setting it
in the protected dictionary by `self._direct_metadata['page_map_offset']`.
There is, unfortunately, no easy way to form consensus between a particular layer may want and what a particular layer
There is, unfortunately, no easy way to form consensus between what a particular layer may want and what a particular layer
may be able to provide. At the moment, the main information that layers may populate are:
* `os` with values of `Windows`, `Linux`, `Mac` or `unknown`
+3 -5
View File
@@ -198,7 +198,6 @@ that will be output as part of the :py:class:`~volatility3.framework.interfaces.
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None))
kernel = self.context.modules[self.config['kernel']]
return renderers.TreeGrid(
[
@@ -211,9 +210,8 @@ that will be output as part of the :py:class:`~volatility3.framework.interfaces.
],
self._generator(
pslist.PsList.list_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
context=self.context,
kernel_module_name=self.config['kernel'],
filter_func = filter_func
)
)
@@ -235,7 +233,7 @@ the :py:class:`~volatility3.plugins.windows.pslist.PsList` plugin. That plugin
so that other plugins can call it. As such, it takes all the necessary parameters rather than accessing them
from a configuration. Since it must be portable code, it takes a context, as well as the layer name,
symbol table and optionally a filter. In this instance we unconditionally
pass it the values from the configuration for the layer and symbol table from the kernel module object, constructed from
pass it the value from the configuration for the kernel module name, constructed from
the ``kernel`` configuration requirement. This will generate a list
of :py:class:`~volatility3.framework.symbols.windows.extensions.EPROCESS` objects, as provided by the :py:class:`~volatility.plugins.windows.pslist.PsList` plugin,
and is not covered here but is used as an example for how to share code across plugins
-1
View File
@@ -45,7 +45,6 @@ dev = [
test = [
"volatility3[dev]",
"pytest>=8.3.3,<9",
"capstone>=5.0.3,<6",
"yara-x>=0.10.0,<1",
]
+16
View File
@@ -0,0 +1,16 @@
from enum import Enum
class Sample:
def __init__(self, path: str):
self.path = path
class WindowsSamples(Enum):
WINDOWSXP_GENERIC = Sample("./test_images/win-xp-laptop-2005-06-25.img")
"""WindowsXP sample from early Volatility training."""
class LinuxSamples(Enum):
LINUX_GENERIC = Sample("./test_images/linux-sample-1.bin")
"""Linux Debian 3.2.0-4 sample from early Volatility training."""
View File
+617
View File
@@ -0,0 +1,617 @@
import contextlib
import tempfile
import os
import re
from test import test_volatility, LinuxSamples
class TestLinuxVolshell:
def test_linux_volshell(self, image, volatility, python):
out = test_volatility.basic_volshell_test(
image, volatility, python, globalargs=("-l",)
)
assert out.count(b"<task_struct") > 100
class TestLinuxPslist:
def test_linux_generic_pslist(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.pslist.PsList", image, volatility, python
)
assert rc == 0
out = out.lower()
assert (out.find(b"init") != -1) or (out.find(b"systemd") != -1)
assert out.find(b"watchdog") != -1
assert out.count(b"\n") > 10
class TestLinuxCheckIdt:
def test_linux_generic_check_idt(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.check_idt.Check_idt", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.count(b"__kernel__") >= 10
assert out.count(b"\n") > 10
class TestLinuxCheckSyscall:
def test_linux_generic_check_syscall(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.check_syscall.Check_syscall", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.find(b"sys_close") != -1
assert out.find(b"sys_open") != -1
assert out.count(b"\n") > 100
class TestLinuxLsmod:
def test_linux_generic_lsmod(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.lsmod.Lsmod", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.count(b"\n") > 10
class TestLinuxLsof:
def test_linux_generic_lsof(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.lsof.Lsof", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.count(b"socket:") >= 10
assert out.count(b"\n") > 35
class TestLinuxProcMaps:
def test_linux_generic_proc_maps(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.proc.Maps", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.count(b"anonymous mapping") >= 10
assert out.count(b"\n") > 100
class TestLinuxTtyCheck:
def test_linux_generic_tty_check(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.tty_check.tty_check", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.find(b"__kernel__") != -1
assert out.count(b"\n") >= 5
class TestLinuxSockstat:
def test_linux_generic_sockstat(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.sockstat.Sockstat", image, volatility, python
)
assert rc == 0
assert out.count(b"AF_UNIX") >= 354
assert out.count(b"AF_BLUETOOTH") >= 5
assert out.count(b"AF_INET") >= 32
assert out.count(b"AF_INET6") >= 20
assert out.count(b"AF_PACKET") >= 1
assert out.count(b"AF_NETLINK") >= 43
class TestLinuxLibraryList:
def test_linux_specific_library_list(self, volatility, python):
image = LinuxSamples.LINUX_GENERIC.value.path
rc, out, _err = test_volatility.runvol_plugin(
"linux.library_list.LibraryList",
image,
volatility,
python,
pluginargs=("--pids", "2363"),
)
assert rc == 0
assert re.search(
rb"NetworkManager\s2363\s0x7f52cdda0000\s/lib/x86_64-linux-gnu/libnss_files.so.2",
out,
)
assert out.count(b"\n") > 10
class TestLinuxPstree:
def test_linux_generic_pstree(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.pstree.PsTree", image, volatility, python
)
assert rc == 0
out = out.lower()
assert (out.find(b"init") != -1) or (out.find(b"systemd") != -1)
assert out.count(b"\n") > 10
class TestLinuxPidhashtable:
def test_linux_generic_pidhashtable(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.pidhashtable.PIDHashTable", image, volatility, python
)
assert rc == 0
out = out.lower()
assert (out.find(b"init") != -1) or (out.find(b"systemd") != -1)
assert out.count(b"\n") > 10
class TestLinuxBash:
def test_linux_bash(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.bash.Bash", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 10
class TestLinuxBoottime:
def test_linux_generic_boottime(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.boottime.Boottime", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.count(b"utc") >= 1
class TestLinuxCapabilities:
def test_linux_generic_capabilities(self, image, volatility, python):
rc, out, err = test_volatility.runvol_plugin(
"linux.capabilities.Capabilities",
image,
volatility,
python,
globalargs=("-vvv",),
)
if rc != 0 and err.count(b"Unsupported kernel capabilities implementation") > 0:
# The linux-sample-1.bin kernel implementation isn't supported.
# However, we can still check that the plugin requirements are met.
return None
assert rc == 0
assert out.count(b"\n") > 10
class TestLinuxCheckCreds:
def test_linux_generic_check_creds(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.check_creds.Check_creds", image, volatility, python
)
# linux-sample-1.bin has no processes sharing credentials.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxElfs:
def test_linux_generic_elfs(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.elfs.Elfs", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 10
class TestLinuxEnvars:
def test_linux_generic_envars(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.envars.Envars", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 10
class TestLinuxKthreads:
def test_linux_generic_kthreads(self, image, volatility, python):
rc, out, err = test_volatility.runvol_plugin(
"linux.kthreads.Kthreads",
image,
volatility,
python,
globalargs=("-vvv",),
)
if rc != 0 and err.count(b"Unsupported kthread implementation") > 0:
# The linux-sample-1.bin kernel implementation isn't supported.
# However, we can still check that the plugin requirements are met.
return None
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxMalfind:
def test_linux_generic_malfind(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.malfind.Malfind", image, volatility, python
)
# linux-sample-1.bin has no process memory ranges with potential injected code.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxMountinfo:
def test_linux_generic_mountinfo(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.mountinfo.MountInfo", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 10
class TestLinuxPsaux:
def test_linux_generic_psaux(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.psaux.PsAux", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 50
class TestLinuxPtrace:
def test_linux_generic_ptrace(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.ptrace.Ptrace", image, volatility, python
)
# linux-sample-1.bin has no processes being ptraced.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxVmaregexscan:
def test_linux_generic_vmaregexscan(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.vmaregexscan.VmaRegExScan",
image,
volatility,
python,
pluginargs=("--pid", "1", "--pattern", "\\x7fELF"),
)
assert rc == 0
assert out.count(b"\n") > 10
class TestLinuxVmayarascanYaraRule:
def test_linux_specific_vmayarascan_yara_rule(self, volatility, python):
image = LinuxSamples.LINUX_GENERIC.value.path
yara_rule_01 = r"""
rule fullvmayarascan
{
strings:
$s1 = "_nss_files_parse_grent"
$s2 = "/lib64/ld-linux-x86-64.so.2"
$s3 = "(bufferend - (char *) 0) % sizeof (char *) == 0"
condition:
all of them
}
"""
# FIXME: When the minimum Python version includes 3.12, replace the following with:
# with tempfile.NamedTemporaryFile(delete_on_close=False) as fd: ...
fd, filename = tempfile.mkstemp(suffix=".yar")
try:
with os.fdopen(fd, "w") as f:
f.write(yara_rule_01)
rc, out, _err = test_volatility.runvol_plugin(
"linux.vmayarascan.VmaYaraScan",
image,
volatility,
python,
pluginargs=("--pid", "8600", "--yara-file", filename),
)
finally:
with contextlib.suppress(FileNotFoundError):
os.remove(filename)
assert rc == 0
assert out.count(b"\n") > 4
class TestLinuxVmayarascanYaraString:
def test_linux_generic_vmayarascan_yara_string(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.vmayarascan.VmaYaraScan",
image,
volatility,
python,
pluginargs=("--pid", "1", "--yara-string", "ELF"),
)
assert rc == 0
assert out.count(b"\n") > 10
class TestLinuxPageCacheFiles:
def test_linux_specific_page_cache_files(self, volatility, python):
image = LinuxSamples.LINUX_GENERIC.value.path
rc, out, _err = test_volatility.runvol_plugin(
"linux.pagecache.Files",
image,
volatility,
python,
pluginargs=("--find", "/etc/passwd"),
)
assert rc == 0
assert out.count(b"\n") > 4
# inode_num inode_addr ... file_path
assert re.search(
rb"146829\s0x88001ab5c270.*?/etc/passwd",
out,
)
class TestLinuxPageCacheInodepages:
def test_linux_specific_page_cache_inodepages(self, volatility, python):
image = LinuxSamples.LINUX_GENERIC.value.path
inode_address = hex(0x88001AB5C270)
inode_dump_filename = f"inode_{inode_address}.dmp"
rc, out, _err = test_volatility.runvol_plugin(
"linux.pagecache.InodePages",
image,
volatility,
python,
pluginargs=("--inode", inode_address),
)
assert rc == 0
assert out.count(b"\n") > 4
# PageVAddr PagePAddr MappingAddr .. DumpSafe
assert re.search(
rb"0xea000054c5f8\s0x18389000\s0x88001ab5c3b0.*?True",
out,
)
try:
rc, out, _err = test_volatility.runvol_plugin(
"linux.pagecache.InodePages",
image,
volatility,
python,
pluginargs=("--inode", inode_address, "--dump"),
)
assert rc == 0
assert out.count(b"\n") >= 4
assert os.path.exists(inode_dump_filename)
with open(inode_dump_filename, "rb") as fp:
inode_contents = fp.read()
assert inode_contents.count(b"\n") > 30
assert inode_contents.count(b"root:x:0:0:root:/root:/bin/bash") > 0
finally:
with contextlib.suppress(FileNotFoundError):
os.remove(inode_dump_filename)
class TestLinuxCheckAfinfo:
def test_linux_generic_check_afinfo(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.check_afinfo.Check_afinfo", image, volatility, python
)
# linux-sample-1.bin has no suspicious results.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxCheckModules:
def test_linux_generic_check_modules(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.check_modules.Check_modules", image, volatility, python
)
# linux-sample-1.bin has no suspicious results.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxEbpf:
def test_linux_generic_ebpf_progs(self, image, volatility, python):
rc, out, err = test_volatility.runvol_plugin(
"linux.ebpf.EBPF",
image,
volatility,
python,
globalargs=("-vvv",),
)
if rc != 0 and err.count(b"Unsupported kernel") > 0:
# The linux-sample-1.bin kernel implementation isn't supported.
# However, we can still check that the plugin requirements are met.
return None
assert rc == 0
assert out.count(b"\n") > 4
class TestLinuxIomem:
def test_linux_generic_iomem(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.iomem.IOMem", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 100
class TestLinuxKeyboardNotifiers:
def test_linux_generic_keyboard_notifiers(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.keyboard_notifiers.Keyboard_notifiers", image, volatility, python
)
# linux-sample-1.bin has no suspicious results for this plugin.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxKmesg:
def test_linux_generic_kmesg(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.kmsg.Kmsg", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 100
class TestLinuxNetfilter:
def test_linux_generic_netfilter(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.netfilter.Netfilter", image, volatility, python
)
# linux-sample-1.bin has no suspicious results for this plugin.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxPsscan:
def test_linux_generic__psscan(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.psscan.PsScan", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 100
class TestLinuxHiddenModules:
def test_linux_specific_hidden_modules(self, volatility, python):
# TODO: this check should be specific, against a distinct infected sample
image = LinuxSamples.LINUX_GENERIC.value.path
rc, out, _err = test_volatility.runvol_plugin(
"linux.hidden_modules.Hidden_modules", image, volatility, python
)
# linux-sample-1.bin has no hidden modules.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxIpAddr:
def test_linux_specific_ip_addr(self, volatility, python):
image = LinuxSamples.LINUX_GENERIC.value.path
rc, out, err = test_volatility.runvol_plugin(
"linux.ip.Addr", image, volatility, python
)
assert re.search(
rb"2\s+eth0\s+00:0c:29:8f:ed:ca\s+False\s+192.168.201.161\s+24\s+global\s+UP",
out,
)
assert re.search(
rb"2\s+eth0\s+00:0c:29:8f:ed:ca\s+False\s+fe80::20c:29ff:fe8f:edca\s+64\s+link\s+UP",
out,
)
assert out.count(b"\n") >= 8
assert rc == 0
class TestLinuxIpLink:
def test_linux_specific_ip_link(self, volatility, python):
image = LinuxSamples.LINUX_GENERIC.value.path
rc, out, err = test_volatility.runvol_plugin(
"linux.ip.Link", image, volatility, python
)
assert re.search(
rb"-\s+lo\s+00:00:00:00:00:00\s+UNKNOWN\s+16436\s+noqueue\s+0\s+LOOPBACK,LOWER_UP,UP",
out,
)
assert re.search(
rb"-\s+eth0\s+00:0c:29:8f:ed:ca\s+UP\s+1500\s+pfifo_fast\s+1000\s+BROADCAST,LOWER_UP,MULTICAST,UP",
out,
)
assert out.count(b"\n") >= 6
assert rc == 0
class TestLinuxKallsyms:
def test_linux_specific_kallsyms(self, volatility, python):
image = LinuxSamples.LINUX_GENERIC.value.path
rc, out, _err = test_volatility.runvol_plugin(
"linux.kallsyms.Kallsyms",
image,
volatility,
python,
pluginargs=("--modules",),
)
# linux-sample-1.bin has no hidden modules.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") > 1000
# Addr Type Size Exported SubSystem ModuleName SymbolName Description
# 0xffffa009eba9 t 28 False module usbcore usb_mon_register Symbol is in the text (code) section
assert re.search(
rb"0xffffa009eba9\s+t\s+28\s+False\s+module\s+usbcore\s+usb_mon_register\s+Symbol is in the text \(code\) section",
out,
)
class TestLinuxPscallstack:
def test_linux_specific_pscallstack(self, volatility, python):
image = LinuxSamples.LINUX_GENERIC.value.path
rc, out, _err = test_volatility.runvol_plugin(
"linux.pscallstack.PsCallStack",
image,
volatility,
python,
pluginargs=("--pid", "1"),
)
assert rc == 0
assert out.count(b"\n") > 30
# TID Comm Position Address Value Name Type Module
# 1 init 39 0x88001f999a40 0xffff81109039 do_select T kernel
assert re.search(
rb"1\s+init\s+39\s+0x88001f999a40.*?0xffff81109039\s+do_select\s+T\s+kernel",
out,
)
+326
View File
@@ -0,0 +1,326 @@
import json
import hashlib
import shutil
import contextlib
import tempfile
import os
from test import test_volatility, WindowsSamples
class TestWindowsVolshell:
def test_windows_volshell(self, image, volatility, python):
out = test_volatility.basic_volshell_test(
image, volatility, python, globalargs=("-w",)
)
assert out.count(b"<EPROCESS") > 40
class TestWindowsPslist:
def test_windows_generic_pslist(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.pslist.PsList",
image,
volatility,
python,
# Notice that this is needed to hit lru_cache when "specific" will run
globalargs=("-r", "json"),
)
assert rc == 0
out = out.lower()
assert out.find(b"system") != -1
assert out.find(b"csrss.exe") != -1
assert out.find(b"svchost.exe") != -1
assert out.count(b"\n") > 10
def test_windows_specific_pslist(self, volatility, python):
image = WindowsSamples.WINDOWSXP_GENERIC.value.path
rc, out, _err = test_volatility.runvol_plugin(
"windows.pslist.PsList",
image,
volatility,
python,
globalargs=("-r", "json"),
)
assert rc == 0
expected_row = {
"CreateTime": None,
"ExitTime": None,
"File output": "Disabled",
"Handles": 1140,
"ImageFileName": "System",
"Offset(V)": 2185004992,
"PID": 4,
"PPID": 0,
"SessionId": None,
"Threads": 61,
"Wow64": False,
"__children": [],
}
assert test_volatility.match_output_row(expected_row, json.loads(out))
class TestWindowsPsscan:
def test_windows_generic_psscan(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.psscan.PsScan", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.find(b"system") != -1
assert out.find(b"csrss.exe") != -1
assert out.find(b"svchost.exe") != -1
assert out.count(b"\n") > 10
class TestWindowsDlllist:
def test_windows_generic_dlllist(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.dlllist.DllList", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.count(b"\n") > 10
class TestWindowsModules:
def test_windows_generic_modules(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.modules.Modules", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.count(b"\n") > 10
class TestWindowsHivelist:
def test_windows_generic_hivelist(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.registry.hivelist.HiveList", image, volatility, python
)
assert rc == 0
out = out.lower()
not_xp = out.find(b"\\systemroot\\system32\\config\\software")
if not_xp == -1:
assert (
out.find(
b"\\device\\harddiskvolume1\\windows\\system32\\config\\software"
)
!= -1
)
assert out.count(b"\n") > 10
class TestWindowsDumpfiles:
def test_windows_specific_dumpfiles(self, volatility, python):
image = WindowsSamples.WINDOWSXP_GENERIC.value.path
with open("./test/known_files.json") as json_file:
known_files = json.load(json_file)
failed_chksms = 0
file_name = os.path.basename(image)
try:
for addr in known_files["windows_dumpfiles"][file_name]:
path = tempfile.mkdtemp()
rc, _out, _err = test_volatility.runvol_plugin(
"windows.dumpfiles.DumpFiles",
image,
volatility,
python,
globalargs=("-o", path),
pluginargs=("--virtaddr", addr),
)
for file in os.listdir(path):
with open(os.path.join(path, file), "rb") as fp:
if (
hashlib.md5(fp.read()).hexdigest()
not in known_files["windows_dumpfiles"][file_name][addr]
):
failed_chksms += 1
shutil.rmtree(path)
json_file.close()
assert failed_chksms == 0
assert rc == 0
except Exception as e:
json_file.close()
print("Key Error raised on " + str(e))
assert False
class TestWindowsHandles:
def test_windows_generic_handles(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.handles.Handles",
image,
volatility,
python,
pluginargs=("--pid", "4"),
)
assert rc == 0
assert out.find(b"System Pid 4") != -1
assert (
out.find(
b"MACHINE\\SYSTEM\\CONTROLSET001\\CONTROL\\SESSION MANAGER\\MEMORY MANAGEMENT\\PREFETCHPARAMETERS"
)
!= -1
)
assert out.find(b"MACHINE\\SYSTEM\\SETUP") != -1
assert out.count(b"\n") > 500
class TestWindowsSvcscan:
def test_windows_generic_svcscan(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.svcscan.SvcScan", image, volatility, python
)
assert rc == 0
assert out.find(b"Microsoft ACPI Driver") != -1
assert out.count(b"\n") > 250
class TestWindowsThrdscan:
def test_windows_generic_thrdscan(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.thrdscan.ThrdScan", image, volatility, python
)
assert rc == 0
assert out.find(b"\t4\t8") != -1
assert out.find(b"\t4\t12") != -1
assert out.find(b"\t4\t16") != -1
class TestWindowsPrivileges:
def test_windows_generic_privileges(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.privileges.Privs",
image,
volatility,
python,
pluginargs=("--pid", "4"),
)
assert rc == 0
assert out.find(b"SeCreateTokenPrivilege") != -1
assert out.find(b"SeCreateGlobalPrivilege") != -1
assert out.find(b"SeAssignPrimaryTokenPrivilege") != -1
assert out.count(b"\n") > 20
class TestWindowsGetsids:
def test_windows_generic_getsids(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.getsids.GetSIDs",
image,
volatility,
python,
pluginargs=("--pid", "4"),
)
assert rc == 0
assert out.find(b"Local System") != -1
assert out.find(b"Administrators") != -1
assert out.find(b"Everyone") != -1
assert out.find(b"Authenticated Users") != -1
class TestWindowsEnvars:
def test_windows_generic_envars(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.envars.Envars", image, volatility, python
)
assert rc == 0
assert out.find(b"PATH") != -1
assert out.find(b"PROCESSOR_ARCHITECTURE") != -1
assert out.find(b"USERNAME") != -1
assert out.find(b"SystemRoot") != -1
assert out.find(b"CommonProgramFiles") != -1
assert out.count(b"\n") > 500
class TestWindowsCallbacks:
def test_windows_generic_callbacks(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.callbacks.Callbacks", image, volatility, python
)
assert rc == 0
assert out.find(b"PspCreateProcessNotifyRoutine") != -1
assert out.find(b"KeBugCheckCallbackListHead") != -1
assert out.find(b"KeBugCheckReasonCallbackListHead") != -1
assert out.count(b"KeBugCheckReasonCallbackListHead ") > 5
class TestWindowsVadwalk:
def test_windows_generic_vadwalk(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.vadwalk.VadWalk", image, volatility, python
)
assert rc == 0
assert out.find(b"Vad") != -1
assert out.find(b"VadS") != -1
assert out.find(b"Vadl") != -1
assert out.find(b"VadF") != -1
assert out.find(b"0x0") != -1
class TestWindowsDevicetree:
def test_windows_generic_devicetree(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.devicetree.DeviceTree", image, volatility, python
)
assert rc == 0
assert out.find(b"DEV") != -1
assert out.find(b"DRV") != -1
assert out.find(b"ATT") != -1
assert out.find(b"FILE_DEVICE_CONTROLLER") != -1
assert out.find(b"FILE_DEVICE_DISK") != -1
assert out.find(b"FILE_DEVICE_DISK_FILE_SYSTEM") != -1
class TestWindowsVadyarascan:
def test_windows_specific_vadyarascan_yara_rule(self, volatility, python):
image = WindowsSamples.WINDOWSXP_GENERIC.value.path
yara_rule_01 = r"""
rule fullvadyarascan
{
strings:
$s1 = "!This program cannot be run in DOS mode."
$s2 = "Qw))Pw"
$s3 = "W_wD)Pw"
$s4 = "1Xw+2Xw"
$s5 = "xd`wh``w"
$s6 = "0g`w0g`w8g`w8g`w@g`w@g`wHg`wHg`wPg`wPg`wXg`wXg`w`g`w`g`whg`whg`wpg`wpg`wxg`wxg`w"
condition:
all of them
}
"""
fd, filename = tempfile.mkstemp(suffix=".yar")
try:
with os.fdopen(fd, "w") as f:
f.write(yara_rule_01)
rc, out, _err = test_volatility.runvol_plugin(
"windows.vadyarascan.VadYaraScan",
image,
volatility,
python,
pluginargs=("--pid", "4012", "--yara-file", filename),
)
finally:
with contextlib.suppress(FileNotFoundError):
os.remove(filename)
assert rc == 0
assert out.count(b"\n") > 4
def test_windows_specific_vadyarascan_yara_string(self, volatility, python):
image = WindowsSamples.WINDOWSXP_GENERIC.value.path
rc, out, _err = test_volatility.runvol_plugin(
"windows.vadyarascan.VadYaraScan",
image,
volatility,
python,
pluginargs=("--pid", "4012", "--yara-string", "MZ"),
)
assert rc == 0
assert out.count(b"\n") > 10
+40 -812
View File
@@ -6,25 +6,24 @@
#
import os
import re
import subprocess
import sys
import shutil
import tempfile
import hashlib
import json
import contextlib
import functools
from typing import List, Tuple
#
# HELPER FUNCTIONS
#
@functools.lru_cache
def runvol(args, volatility, python):
volpy = volatility
python_cmd = python
cmd = [python_cmd, volpy] + args
cmd = (python_cmd, volpy) + args
print(" ".join(cmd))
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
@@ -38,17 +37,18 @@ def runvol(args, volatility, python):
return p.returncode, stdout, stderr
def runvol_plugin(plugin, img, volatility, python, pluginargs=None, globalargs=None):
pluginargs = pluginargs or []
globalargs = globalargs or []
@functools.lru_cache
def runvol_plugin(
plugin, img, volatility, python, pluginargs: Tuple = (), globalargs: Tuple = ()
):
args = (
globalargs
+ [
+ (
"--single-location",
img,
"-q",
plugin,
]
)
+ pluginargs
)
@@ -60,17 +60,43 @@ def runvolshell(img, volshell, python, volshellargs=None, globalargs=None):
globalargs = globalargs or []
args = (
globalargs
+ [
+ (
"--single-location",
img,
"-q",
]
)
+ volshellargs
)
return runvol(args, volshell, python)
def match_output_row(
expected_row: dict, plugin_json_out: List[dict], exact_match: bool = False
):
"""Search each row of a plugin's JSON output for an expected row. Each row is a dict.
Args:
expected_row: The expected row to be found in the output
plugin_json_out: The plugin's output in JSON format (typically obtained through -r json and json.loads)
exact_match: Whether to require exactly the expected row, no more no less, or to anticipate columns' addition by checking only
the expected row keys and values
"""
if not exact_match:
for row in plugin_json_out:
if all(
expected_item in row.items() for expected_item in expected_row.items()
):
return True
else:
for row in plugin_json_out:
if expected_row == row:
return True
return False
#
# TESTS
#
@@ -96,7 +122,7 @@ def basic_volshell_test(image, volatility, python, globalargs):
img=image,
volshell=volatility,
python=python,
volshellargs=["--script", filename],
volshellargs=("--script", filename),
globalargs=globalargs,
)
finally:
@@ -109,806 +135,8 @@ def basic_volshell_test(image, volatility, python, globalargs):
return out
# WINDOWS
def test_windows_volshell(image, volatility, python):
out = basic_volshell_test(image, volatility, python, globalargs=["-w"])
assert out.count(b"<EPROCESS") > 40
def test_windows_pslist(image, volatility, python):
rc, out, _err = runvol_plugin("windows.pslist.PsList", image, volatility, python)
out = out.lower()
assert out.find(b"system") != -1
assert out.find(b"csrss.exe") != -1
assert out.find(b"svchost.exe") != -1
assert out.count(b"\n") > 10
assert rc == 0
rc, out, _err = runvol_plugin(
"windows.pslist.PsList", image, volatility, python, pluginargs=["--pid", "4"]
)
out = out.lower()
assert out.find(b"system") != -1
assert out.count(b"\n") < 10
assert rc == 0
def test_windows_psscan(image, volatility, python):
rc, out, _err = runvol_plugin("windows.psscan.PsScan", image, volatility, python)
out = out.lower()
assert out.find(b"system") != -1
assert out.find(b"csrss.exe") != -1
assert out.find(b"svchost.exe") != -1
assert out.count(b"\n") > 10
assert rc == 0
def test_windows_dlllist(image, volatility, python):
rc, out, _err = runvol_plugin("windows.dlllist.DllList", image, volatility, python)
out = out.lower()
assert out.count(b"\n") > 10
assert rc == 0
def test_windows_modules(image, volatility, python):
rc, out, _err = runvol_plugin("windows.modules.Modules", image, volatility, python)
out = out.lower()
assert out.count(b"\n") > 10
assert rc == 0
def test_windows_hivelist(image, volatility, python):
rc, out, _err = runvol_plugin(
"windows.registry.hivelist.HiveList", image, volatility, python
)
out = out.lower()
not_xp = out.find(b"\\systemroot\\system32\\config\\software")
if not_xp == -1:
assert (
out.find(b"\\device\\harddiskvolume1\\windows\\system32\\config\\software")
!= -1
)
assert out.count(b"\n") > 10
assert rc == 0
def test_windows_dumpfiles(image, volatility, python):
with open("./test/known_files.json") as json_file:
known_files = json.load(json_file)
failed_chksms = 0
file_name = os.path.basename(image)
try:
for addr in known_files["windows_dumpfiles"][file_name]:
path = tempfile.mkdtemp()
rc, _out, _err = runvol_plugin(
"windows.dumpfiles.DumpFiles",
image,
volatility,
python,
globalargs=["-o", path],
pluginargs=["--virtaddr", addr],
)
for file in os.listdir(path):
with open(os.path.join(path, file), "rb") as fp:
if (
hashlib.md5(fp.read()).hexdigest()
not in known_files["windows_dumpfiles"][file_name][addr]
):
failed_chksms += 1
shutil.rmtree(path)
json_file.close()
assert failed_chksms == 0
assert rc == 0
except Exception as e:
json_file.close()
print("Key Error raised on " + str(e))
assert False
def test_windows_handles(image, volatility, python):
rc, out, _err = runvol_plugin(
"windows.handles.Handles", image, volatility, python, pluginargs=["--pid", "4"]
)
assert out.find(b"System Pid 4") != -1
assert (
out.find(
b"MACHINE\\SYSTEM\\CONTROLSET001\\CONTROL\\SESSION MANAGER\\MEMORY MANAGEMENT\\PREFETCHPARAMETERS"
)
!= -1
)
assert out.find(b"MACHINE\\SYSTEM\\SETUP") != -1
assert out.count(b"\n") > 500
assert rc == 0
def test_windows_svcscan(image, volatility, python):
rc, out, _err = runvol_plugin("windows.svcscan.SvcScan", image, volatility, python)
assert out.find(b"Microsoft ACPI Driver") != -1
assert out.count(b"\n") > 250
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"]
)
assert out.find(b"SeCreateTokenPrivilege") != -1
assert out.find(b"SeCreateGlobalPrivilege") != -1
assert out.find(b"SeAssignPrimaryTokenPrivilege") != -1
assert out.count(b"\n") > 20
assert rc == 0
def test_windows_getsids(image, volatility, python):
rc, out, _err = runvol_plugin(
"windows.getsids.GetSIDs", image, volatility, python, pluginargs=["--pid", "4"]
)
assert out.find(b"Local System") != -1
assert out.find(b"Administrators") != -1
assert out.find(b"Everyone") != -1
assert out.find(b"Authenticated Users") != -1
assert rc == 0
def test_windows_envars(image, volatility, python):
rc, out, _err = runvol_plugin("windows.envars.Envars", image, volatility, python)
assert out.find(b"PATH") != -1
assert out.find(b"PROCESSOR_ARCHITECTURE") != -1
assert out.find(b"USERNAME") != -1
assert out.find(b"SystemRoot") != -1
assert out.find(b"CommonProgramFiles") != -1
assert out.count(b"\n") > 500
assert rc == 0
def test_windows_callbacks(image, volatility, python):
rc, out, _err = runvol_plugin(
"windows.callbacks.Callbacks", image, volatility, python
)
assert out.find(b"PspCreateProcessNotifyRoutine") != -1
assert out.find(b"KeBugCheckCallbackListHead") != -1
assert out.find(b"KeBugCheckReasonCallbackListHead") != -1
assert out.count(b"KeBugCheckReasonCallbackListHead ") > 5
assert rc == 0
def test_windows_vadwalk(image, volatility, python):
rc, out, _err = runvol_plugin("windows.vadwalk.VadWalk", image, volatility, python)
assert out.find(b"Vad") != -1
assert out.find(b"VadS") != -1
assert out.find(b"Vadl") != -1
assert out.find(b"VadF") != -1
assert out.find(b"0x0") != -1
assert rc == 0
def test_windows_devicetree(image, volatility, python):
rc, out, _err = runvol_plugin(
"windows.devicetree.DeviceTree", image, volatility, python
)
assert out.find(b"DEV") != -1
assert out.find(b"DRV") != -1
assert out.find(b"ATT") != -1
assert out.find(b"FILE_DEVICE_CONTROLLER") != -1
assert out.find(b"FILE_DEVICE_DISK") != -1
assert out.find(b"FILE_DEVICE_DISK_FILE_SYSTEM") != -1
assert rc == 0
def test_windows_vadyarascan_yara_rule(image, volatility, python):
yara_rule_01 = r"""
rule fullvadyarascan
{
strings:
$s1 = "!This program cannot be run in DOS mode."
$s2 = "Qw))Pw"
$s3 = "W_wD)Pw"
$s4 = "1Xw+2Xw"
$s5 = "xd`wh``w"
$s6 = "0g`w0g`w8g`w8g`w@g`w@g`wHg`wHg`wPg`wPg`wXg`wXg`w`g`w`g`whg`whg`wpg`wpg`wxg`wxg`w"
condition:
all of them
}
"""
# FIXME: When the minimum Python version includes 3.12, replace the following with:
# with tempfile.NamedTemporaryFile(delete_on_close=False) as fd: ...
fd, filename = tempfile.mkstemp(suffix=".yar")
try:
with os.fdopen(fd, "w") as f:
f.write(yara_rule_01)
rc, out, _err = runvol_plugin(
"windows.vadyarascan.VadYaraScan",
image,
volatility,
python,
pluginargs=["--pid", "4012", "--yara-file", filename],
)
finally:
with contextlib.suppress(FileNotFoundError):
os.remove(filename)
out = out.lower()
assert out.count(b"\n") > 4
assert rc == 0
def test_windows_vadyarascan_yara_string(image, volatility, python):
rc, out, _err = runvol_plugin(
"windows.vadyarascan.VadYaraScan",
image,
volatility,
python,
pluginargs=["--pid", "4012", "--yara-string", "MZ"],
)
out = out.lower()
assert out.count(b"\n") > 10
assert rc == 0
# LINUX
def test_linux_volshell(image, volatility, python):
out = basic_volshell_test(image, volatility, python, globalargs=["-l"])
assert out.count(b"<task_struct") > 100
def test_linux_pslist(image, volatility, python):
rc, out, _err = runvol_plugin("linux.pslist.PsList", image, volatility, python)
assert rc == 0
out = out.lower()
assert (out.find(b"init") != -1) or (out.find(b"systemd") != -1)
assert out.find(b"watchdog") != -1
assert out.count(b"\n") > 10
def test_linux_check_idt(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.check_idt.Check_idt", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.count(b"__kernel__") >= 10
assert out.count(b"\n") > 10
def test_linux_check_syscall(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.check_syscall.Check_syscall", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.find(b"sys_close") != -1
assert out.find(b"sys_open") != -1
assert out.count(b"\n") > 100
def test_linux_lsmod(image, volatility, python):
rc, out, _err = runvol_plugin("linux.lsmod.Lsmod", image, volatility, python)
assert rc == 0
out = out.lower()
assert out.count(b"\n") > 10
def test_linux_lsof(image, volatility, python):
rc, out, _err = runvol_plugin("linux.lsof.Lsof", image, volatility, python)
assert rc == 0
out = out.lower()
assert out.count(b"socket:") >= 10
assert out.count(b"\n") > 35
def test_linux_proc_maps(image, volatility, python):
rc, out, _err = runvol_plugin("linux.proc.Maps", image, volatility, python)
assert rc == 0
out = out.lower()
assert out.count(b"anonymous mapping") >= 10
assert out.count(b"\n") > 100
def test_linux_tty_check(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.tty_check.tty_check", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.find(b"__kernel__") != -1
assert out.count(b"\n") >= 5
def test_linux_sockstat(image, volatility, python):
rc, out, _err = runvol_plugin("linux.sockstat.Sockstat", image, volatility, python)
assert rc == 0
assert out.count(b"AF_UNIX") >= 354
assert out.count(b"AF_BLUETOOTH") >= 5
assert out.count(b"AF_INET") >= 32
assert out.count(b"AF_INET6") >= 20
assert out.count(b"AF_PACKET") >= 1
assert out.count(b"AF_NETLINK") >= 43
def test_linux_library_list(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.library_list.LibraryList",
image,
volatility,
python,
pluginargs=["--pids", "2363"],
)
assert rc == 0
assert re.search(
rb"NetworkManager\s2363\s0x7f52cdda0000\s/lib/x86_64-linux-gnu/libnss_files.so.2",
out,
)
assert out.count(b"\n") > 10
def test_linux_pstree(image, volatility, python):
rc, out, _err = runvol_plugin("linux.pstree.PsTree", image, volatility, python)
assert rc == 0
out = out.lower()
assert (out.find(b"init") != -1) or (out.find(b"systemd") != -1)
assert out.count(b"\n") > 10
def test_linux_pidhashtable(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.pidhashtable.PIDHashTable", image, volatility, python
)
assert rc == 0
out = out.lower()
assert (out.find(b"init") != -1) or (out.find(b"systemd") != -1)
assert out.count(b"\n") > 10
def test_linux_bash(image, volatility, python):
rc, out, _err = runvol_plugin("linux.bash.Bash", image, volatility, python)
assert rc == 0
assert out.count(b"\n") > 10
def test_linux_boottime(image, volatility, python):
rc, out, _err = runvol_plugin("linux.boottime.Boottime", image, volatility, python)
assert rc == 0
out = out.lower()
assert out.count(b"utc") >= 1
def test_linux_capabilities(image, volatility, python):
rc, out, err = runvol_plugin(
"linux.capabilities.Capabilities",
image,
volatility,
python,
globalargs=["-vvv"],
)
if rc != 0 and err.count(b"Unsupported kernel capabilities implementation") > 0:
# The linux-sample-1.bin kernel implementation isn't supported.
# However, we can still check that the plugin requirements are met.
return None
assert rc == 0
assert out.count(b"\n") > 10
def test_linux_check_creds(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.check_creds.Check_creds", image, volatility, python
)
# linux-sample-1.bin has no processes sharing credentials.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
def test_linux_elfs(image, volatility, python):
rc, out, _err = runvol_plugin("linux.elfs.Elfs", image, volatility, python)
assert rc == 0
assert out.count(b"\n") > 10
def test_linux_envars(image, volatility, python):
rc, out, _err = runvol_plugin("linux.envars.Envars", image, volatility, python)
assert rc == 0
assert out.count(b"\n") > 10
def test_linux_kthreads(image, volatility, python):
rc, out, err = runvol_plugin(
"linux.kthreads.Kthreads",
image,
volatility,
python,
globalargs=["-vvv"],
)
if rc != 0 and err.count(b"Unsupported kthread implementation") > 0:
# The linux-sample-1.bin kernel implementation isn't supported.
# However, we can still check that the plugin requirements are met.
return None
assert rc == 0
assert out.count(b"\n") >= 4
def test_linux_malfind(image, volatility, python):
rc, out, _err = runvol_plugin("linux.malfind.Malfind", image, volatility, python)
# linux-sample-1.bin has no process memory ranges with potential injected code.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
def test_linux_mountinfo(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.mountinfo.MountInfo", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 10
def test_linux_psaux(image, volatility, python):
rc, out, _err = runvol_plugin("linux.psaux.PsAux", image, volatility, python)
assert rc == 0
assert out.count(b"\n") > 50
def test_linux_ptrace(image, volatility, python):
rc, out, _err = runvol_plugin("linux.ptrace.Ptrace", image, volatility, python)
# linux-sample-1.bin has no processes being ptraced.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
def test_linux_vmaregexscan(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.vmaregexscan.VmaRegExScan",
image,
volatility,
python,
pluginargs=["--pid", "1", "--pattern", "\\x7fELF"],
)
assert rc == 0
assert out.count(b"\n") > 10
def test_linux_vmayarascan_yara_rule(image, volatility, python):
yara_rule_01 = r"""
rule fullvmayarascan
{
strings:
$s1 = "_nss_files_parse_grent"
$s2 = "/lib64/ld-linux-x86-64.so.2"
$s3 = "(bufferend - (char *) 0) % sizeof (char *) == 0"
condition:
all of them
}
"""
# FIXME: When the minimum Python version includes 3.12, replace the following with:
# with tempfile.NamedTemporaryFile(delete_on_close=False) as fd: ...
fd, filename = tempfile.mkstemp(suffix=".yar")
try:
with os.fdopen(fd, "w") as f:
f.write(yara_rule_01)
rc, out, _err = runvol_plugin(
"linux.vmayarascan.VmaYaraScan",
image,
volatility,
python,
pluginargs=["--pid", "8600", "--yara-file", filename],
)
finally:
with contextlib.suppress(FileNotFoundError):
os.remove(filename)
assert rc == 0
assert out.count(b"\n") > 4
def test_linux_vmayarascan_yara_string(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.vmayarascan.VmaYaraScan",
image,
volatility,
python,
pluginargs=["--pid", "1", "--yara-string", "ELF"],
)
assert rc == 0
assert out.count(b"\n") > 10
def test_linux_page_cache_files(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.pagecache.Files",
image,
volatility,
python,
pluginargs=["--find", "/etc/passwd"],
)
assert rc == 0
assert out.count(b"\n") > 4
# inode_num inode_addr ... file_path
assert re.search(
rb"146829\s0x88001ab5c270.*?/etc/passwd",
out,
)
def test_linux_page_cache_inodepages(image, volatility, python):
inode_address = hex(0x88001AB5C270)
inode_dump_filename = f"inode_{inode_address}.dmp"
rc, out, _err = runvol_plugin(
"linux.pagecache.InodePages",
image,
volatility,
python,
pluginargs=["--inode", inode_address],
)
assert rc == 0
assert out.count(b"\n") > 4
# PageVAddr PagePAddr MappingAddr .. DumpSafe
assert re.search(
rb"0xea000054c5f8\s0x18389000\s0x88001ab5c3b0.*?True",
out,
)
try:
rc, out, _err = runvol_plugin(
"linux.pagecache.InodePages",
image,
volatility,
python,
pluginargs=["--inode", inode_address, "--dump"],
)
assert rc == 0
assert out.count(b"\n") >= 4
assert os.path.exists(inode_dump_filename)
with open(inode_dump_filename, "rb") as fp:
inode_contents = fp.read()
assert inode_contents.count(b"\n") > 30
assert inode_contents.count(b"root:x:0:0:root:/root:/bin/bash") > 0
finally:
with contextlib.suppress(FileNotFoundError):
os.remove(inode_dump_filename)
def test_linux_check_afinfo(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.check_afinfo.Check_afinfo", image, volatility, python
)
# linux-sample-1.bin has no suspicious results.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
def test_linux_check_modules(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.check_modules.Check_modules", image, volatility, python
)
# linux-sample-1.bin has no suspicious results.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
def test_linux_ebpf_progs(image, volatility, python):
rc, out, err = runvol_plugin(
"linux.ebpf.EBPF",
image,
volatility,
python,
globalargs=["-vvv"],
)
if rc != 0 and err.count(b"Unsupported kernel") > 0:
# The linux-sample-1.bin kernel implementation isn't supported.
# However, we can still check that the plugin requirements are met.
return None
assert rc == 0
assert out.count(b"\n") > 4
def test_linux_iomem(image, volatility, python):
rc, out, _err = runvol_plugin("linux.iomem.IOMem", image, volatility, python)
assert rc == 0
assert out.count(b"\n") > 100
def test_linux_keyboard_notifiers(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.keyboard_notifiers.Keyboard_notifiers", image, volatility, python
)
# linux-sample-1.bin has no suspicious results for this plugin.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
def test_linux_kmesg(image, volatility, python):
rc, out, _err = runvol_plugin("linux.kmsg.Kmsg", image, volatility, python)
assert rc == 0
assert out.count(b"\n") > 100
def test_linux_netfilter(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.netfilter.Netfilter", image, volatility, python
)
# linux-sample-1.bin has no suspicious results for this plugin.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
def test_linux_psscan(image, volatility, python):
rc, out, _err = runvol_plugin("linux.psscan.PsScan", image, volatility, python)
assert rc == 0
assert out.count(b"\n") > 100
def test_linux_hidden_modules(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.hidden_modules.Hidden_modules", image, volatility, python
)
# linux-sample-1.bin has no hidden modules.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
def test_linux_ip_addr(image, volatility, python):
rc, out, err = runvol_plugin("linux.ip.Addr", image, volatility, python)
assert re.search(
rb"2\s+eth0\s+00:0c:29:8f:ed:ca\s+False\s+192.168.201.161\s+24\s+global\s+UP",
out,
)
assert re.search(
rb"2\s+eth0\s+00:0c:29:8f:ed:ca\s+False\s+fe80::20c:29ff:fe8f:edca\s+64\s+link\s+UP",
out,
)
assert out.count(b"\n") >= 8
assert rc == 0
def test_linux_ip_link(image, volatility, python):
rc, out, err = runvol_plugin("linux.ip.Link", image, volatility, python)
assert re.search(
rb"-\s+lo\s+00:00:00:00:00:00\s+UNKNOWN\s+16436\s+noqueue\s+0\s+LOOPBACK,LOWER_UP,UP",
out,
)
assert re.search(
rb"-\s+eth0\s+00:0c:29:8f:ed:ca\s+UP\s+1500\s+pfifo_fast\s+1000\s+BROADCAST,LOWER_UP,MULTICAST,UP",
out,
)
assert out.count(b"\n") >= 6
assert rc == 0
def test_linux_kallsyms(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.kallsyms.Kallsyms",
image,
volatility,
python,
pluginargs=["--modules"],
)
# linux-sample-1.bin has no hidden modules.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") > 1000
# Addr Type Size Exported SubSystem ModuleName SymbolName Description
# 0xffffa009eba9 t 28 False module usbcore usb_mon_register Symbol is in the text (code) section
assert re.search(
rb"0xffffa009eba9\s+t\s+28\s+False\s+module\s+usbcore\s+usb_mon_register\s+Symbol is in the text \(code\) section",
out,
)
def test_linux_pscallstack(image, volatility, python):
rc, out, _err = runvol_plugin(
"linux.pscallstack.PsCallStack",
image,
volatility,
python,
pluginargs=["--pid", "1"],
)
assert rc == 0
assert out.count(b"\n") > 30
# TID Comm Position Address Value Name Type Module
# 1 init 39 0x88001f999a40 0xffff81109039 do_select T kernel
assert re.search(
rb"1\s+init\s+39\s+0x88001f999a40.*?0xffff81109039\s+do_select\s+T\s+kernel",
out,
)
# MAC
# TODO: Migrate and integrate in testing (once analysis is fixed ?)
def test_mac_volshell(image, volatility, python):
+2 -4
View File
@@ -18,7 +18,7 @@ class Volshell(generic.Volshell):
return [
requirements.ModuleRequirement(name="kernel", description="Windows kernel"),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.IntRequirement(
name="pid", description="Process ID", optional=True
@@ -39,9 +39,7 @@ class Volshell(generic.Volshell):
"""Returns a list of EPROCESS objects from the primary layer"""
# We always use the main kernel memory and associated symbols
return list(
pslist.PsList.list_processes(
self.context, self.current_layer, self.current_symbol_table
)
pslist.PsList.list_processes(self.context, self.current_kernel_name)
)
def get_process(self, pid=None, virtaddr=None, physaddr=None):
@@ -40,6 +40,8 @@ SYMBOL_BASEPATHS = [
ISF_EXTENSIONS = [".json", ".json.xz", ".json.gz", ".json.bz2"]
"""List of accepted extensions for ISF files"""
SYMBOL_SERVER_URL = "http://msdl.microsoft.com/download/symbols"
if hasattr(sys, "frozen") and sys.frozen:
# Ensure we include the executable's directory as the base for plugins and symbols
PLUGINS_PATH = [
+1 -1
View File
@@ -1,6 +1,6 @@
# We use the SemVer 2.0.0 versioning scheme
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
VERSION_MINOR = 22 # Number of changes that only add to the interface
VERSION_MINOR = 23 # Number of changes that only add to the interface
VERSION_PATCH = 0 # Number of changes that do not change the interface
VERSION_SUFFIX = ""
+1 -1
View File
@@ -133,7 +133,7 @@ class ObjectInterface(metaclass=abc.ABCMeta):
def __getattr__(self, attr: str) -> Any:
"""Method for ensuring volatility members can be returned."""
raise AttributeError()
raise AttributeError(f"Unable to find {attr} for type {type(self)}")
@property
def vol(self) -> ReadOnlyMapping:
+1 -1
View File
@@ -66,7 +66,7 @@ class RegistryHive(linear.LinearlyMappedLayer):
# Win10 17063 introduced the Registry process to map most hives. Check
# if it exists and update RegistryHive._base_layer
for proc in pslist.PsList.list_processes(
self.context, self.config["base_layer"], self.config["nt_symbols"]
context=self.context, kernel_module_name=self.config["kernel_module_name"]
):
proc_name = proc.ImageFileName.cast(
"string", max_length=proc.ImageFileName.vol.count, errors="replace"
+1 -1
View File
@@ -46,7 +46,7 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
def _generator(self, tasks):
vmlinux = self.context.modules[self.config["kernel"]]
is_32bit = not symbols.symbol_table_is_64bit(
self.context, vmlinux.symbol_table_name
context=self.context, symbol_table_name=vmlinux.symbol_table_name
)
if is_32bit:
pack_format = "I"
@@ -3,7 +3,7 @@
#
import logging
from typing import List
from typing import List, Optional
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
from volatility3.framework import interfaces, renderers, symbols
@@ -20,6 +20,9 @@ class Check_idt(interfaces.plugins.PluginInterface):
_required_framework_version = (2, 0, 0)
# 2.0.0 - Add versioning at all, add `get_idt_type`
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
@@ -41,7 +44,42 @@ class Check_idt(interfaces.plugins.PluginInterface):
),
]
@staticmethod
def get_idt_type(context, vmlinux_name) -> Optional[str]:
"""
Determines the IDT type for this symbol table or returns None
The original version ended clauses with an `else` leading to bad fall through
of returning a type that did not exist in the symbol table.
Future updates should not leave fall through cases to avoid this repeating.
"""
vmlinux = context.modules[vmlinux_name]
is_32bit = not symbols.symbol_table_is_64bit(context, vmlinux.symbol_table_name)
# These are in a specific order. Only append to the lists going forward
# or ask Andrew to run tests before merging.
if is_32bit:
idt_types = ["gate_struct", "desc_struct", "gate_struct32"]
else:
idt_types = ["gate_struct64", "gate_struct", "idt_desc"]
for idt_type in idt_types:
if vmlinux.has_type(idt_type):
return idt_type
return None
def _generator(self):
idt_type = self.get_idt_type(self.context, self.config["kernel"])
if not idt_type:
vollog.error(
"Unable to determine the data structure type for IDT entries. Please file a bug on the GitHub tracker with your kernel version."
)
return
vmlinux = self.context.modules[self.config["kernel"]]
modules = lsmod.Lsmod.list_modules(self.context, vmlinux.name)
@@ -50,30 +88,15 @@ class Check_idt(interfaces.plugins.PluginInterface):
self.context, vmlinux.name, modules
)
is_32bit = not symbols.symbol_table_is_64bit(
self.context, vmlinux.symbol_table_name
)
idt_table_size = 256
address_mask = self.context.layers[vmlinux.layer_name].address_mask
kernel_layer = self.context.layers[vmlinux.layer_name]
address_mask = kernel_layer.address_mask
# hw handlers + system call
check_idxs = list(range(20)) + [128]
if is_32bit:
if vmlinux.has_type("gate_struct"):
idt_type = "gate_struct"
else:
idt_type = "desc_struct"
else:
if vmlinux.has_type("gate_struct64"):
idt_type = "gate_struct64"
elif vmlinux.has_type("gate_struct"):
idt_type = "gate_struct"
else:
idt_type = "idt_desc"
addrs = vmlinux.object_from_symbol("idt_table")
table = vmlinux.object(
@@ -87,15 +110,16 @@ class Check_idt(interfaces.plugins.PluginInterface):
for i in check_idxs:
ent = table[i]
if not ent:
if not ent or not kernel_layer.is_valid(ent.vol.offset):
continue
if hasattr(ent, "Address"):
idt_addr = ent.Address
if hasattr(ent, "a"):
idt_addr = (ent.b & 0xFFFF0000) | (ent.a & 0x0000FFFF)
else:
low = ent.offset_low
middle = ent.offset_middle
# offset_high is for 64bit systems
if hasattr(ent, "offset_high"):
high = ent.offset_high
else:
@@ -105,11 +129,16 @@ class Check_idt(interfaces.plugins.PluginInterface):
idt_addr = idt_addr & address_mask
module_name, symbol_name = (
linux_utilities_modules.Modules.lookup_module_address(
self.context, vmlinux.name, handlers, idt_addr
# 0 means unintialized/unused, not a rootkit
if idt_addr == 0:
module_name = renderers.NotAvailableValue()
symbol_name = renderers.NotAvailableValue()
else:
module_name, symbol_name = (
linux_utilities_modules.Modules.lookup_module_address(
self.context, vmlinux.name, handlers, idt_addr
)
)
)
yield (
0,
@@ -65,7 +65,11 @@ class Check_modules(plugins.PluginInterface):
mod = mod_kobj.mod
name = utility.pointer_to_string(kobj.name, 32)
try:
name = utility.pointer_to_string(kobj.name, 32)
except exceptions.InvalidAddressException:
continue
if kobj.name and kobj.reference_count() > 2:
ret[name] = mod
@@ -95,7 +95,9 @@ class Envars(plugins.PluginInterface):
envar_data = envar_data.rstrip(b"\x00")
for envar_pair in envar_data.split(b"\x00"):
try:
env_key, env_value = envar_pair.decode().split("=", 1)
env_key, env_value = envar_pair.decode(
encoding="utf8", errors="replace"
).split("=", 1)
except ValueError:
# Some legitimate programs, like 'avahi-daemon', avoid reallocating the args
# and instead exploit the fact that the environment variables area is contiguous
@@ -61,6 +61,10 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface):
"This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt."
)
if not self.context.layers[vmlinux.layer_name].is_valid(knl_addr.vol.offset):
vollog.error("The head of the keyboard notifier list is paged out.")
return
knl = vmlinux.object(
object_type="atomic_notifier_head",
offset=knl_addr.vol.offset,
+59 -31
View File
@@ -5,7 +5,7 @@ import re
import logging
from abc import ABC, abstractmethod
from enum import Enum
from typing import Generator, Iterator, List, Tuple, Union
from typing import Generator, Iterator, List, Tuple, Optional
from volatility3.framework import (
class_subclasses,
@@ -73,7 +73,7 @@ class ABCKmsg(ABC):
cls,
context: interfaces.context.ContextInterface,
config: interfaces.configuration.HierarchicalDict,
) -> Iterator[Tuple[str, str, str, str, str]]:
) -> Iterator[Tuple[str, str, str, Optional[str], str]]:
"""It calls each subclass symtab_checks() to test the required
conditions to that specific kernel implementation.
@@ -108,10 +108,12 @@ class ABCKmsg(ABC):
break
if kmsg_inst is None:
vollog.error("Unsupported kernel ring buffer implementation")
vollog.error(
"Unsupported kernel ring buffer implementation. Please file a bug on our issue tracker with your specific kernel version."
)
@abstractmethod
def run(self) -> Iterator[Tuple[str, str, str, str, str]]:
def run(self) -> Iterator[Tuple[str, str, str, Optional[str], str]]:
"""Walks through the specific kernel implementation.
Returns:
@@ -135,7 +137,7 @@ class ABCKmsg(ABC):
bool: True if the kernel being analyzed fulfill the class requirements.
"""
def get_string(self, addr: int, length: int) -> Union[str, None]:
def get_string(self, addr: int, length: int) -> Optional[str]:
layer = self._context.layers[self.layer_name]
if not layer.is_valid(addr, length):
vollog.warning("Failed to read log record at address 0x%x", addr)
@@ -161,21 +163,21 @@ class ABCKmsg(ABC):
# obj could be log, printk_log or printk_info
return self.nsec_to_sec_str(obj.ts_nsec)
def get_caller(self, obj):
def get_caller(self, obj) -> Optional[str]:
# In some kernel versions, it's only available if CONFIG_PRINTK_CALLER is defined.
# caller_id is a member of printk_log struct from 5.1 to the latest 5.9
# From kernels 5.10 on, it's a member of printk_info struct
if obj.has_member("caller_id"):
return self.get_caller_text(obj.caller_id)
else:
return renderers.NotAvailableValue()
def get_caller_text(self, caller_id):
return None
def get_caller_text(self, caller_id) -> str:
caller_name = "CPU" if caller_id & 0x80000000 else "Task"
caller = f"{caller_name}({caller_id & ~0x80000000})"
return caller
def get_prefix(self, obj) -> Tuple[int, int, str, str]:
def get_prefix(self, obj) -> Tuple[int, int, str, Optional[str]]:
# obj could be log, printk_log or printk_info
return (
obj.facility,
@@ -213,6 +215,7 @@ class Kmsg_pre_3_5(ABCKmsg):
def symtab_checks(cls, vmlinux) -> bool:
return (
vmlinux.has_symbol("log_end")
and vmlinux.has_symbol("log_buf_len")
and not vmlinux.has_symbol("log_first_idx")
and not (
vmlinux.has_type("log")
@@ -220,7 +223,7 @@ class Kmsg_pre_3_5(ABCKmsg):
)
)
def run(self) -> Iterator[Tuple[str, str, str, str, str]]:
def run(self) -> Iterator[Tuple[str, str, str, Optional[str], str]]:
log_buf_ptr = self.vmlinux.object_from_symbol(symbol_name="log_buf")
log_buf_len = self.vmlinux.object_from_symbol(symbol_name="log_buf_len")
log_buf = utility.pointer_to_string(log_buf_ptr, count=log_buf_len)
@@ -249,7 +252,7 @@ class Kmsg_pre_3_5(ABCKmsg):
facility = level_facility >> 3
level_txt = self.get_level_text(level)
facility_txt = self.get_facility_text(facility)
caller = renderers.NotAvailableValue()
caller = None
yield facility_txt, level_txt, timestamp_str, caller, line
@@ -266,10 +269,10 @@ class Kmsg_3_5_to_3_11(ABCKmsg):
and vmlinux.has_symbol("log_first_idx")
)
def _get_log_struct_name(self):
def _get_log_struct_name(self) -> str:
return "log"
def get_text_from_log(self, msg) -> Union[str, None]:
def get_text_from_log(self, msg) -> Optional[str]:
log_struct_name = self._get_log_struct_name()
log_struct_size = self.vmlinux.get_type(log_struct_name).size
msg_offset = msg.vol.offset + log_struct_size
@@ -283,7 +286,7 @@ class Kmsg_3_5_to_3_11(ABCKmsg):
def get_dict_lines(self, msg) -> Generator[str, None, None]:
if msg.dict_len == 0:
return None
return
log_struct_name = self._get_log_struct_name()
log_struct_size = self.vmlinux.get_type(log_struct_name).size
@@ -293,12 +296,12 @@ class Kmsg_3_5_to_3_11(ABCKmsg):
dict_data = layer.read(dict_offset, msg.dict_len)
except exceptions.InvalidAddressException:
vollog.debug("Unable to read kmsg dict from 0x%x", dict_offset)
return None
return
for chunk in dict_data.split(b"\x00"):
yield " " + chunk.decode()
yield " " + chunk.decode(encoding="utf8", errors="replace")
def run(self) -> Iterator[Tuple[str, str, str, str, str]]:
def run(self) -> Iterator[Tuple[str, str, str, Optional[str], str]]:
# First, the ring buffer size is determined in the kernel configuration
# by CONFIG_LOG_BUF_SHIFT. This static buffer is held in the '__log_buf'
# global variable, with 'log_buf' serving as a pointer to it.
@@ -311,7 +314,13 @@ class Kmsg_3_5_to_3_11(ABCKmsg):
# remains unused. Therefore, it is crucial to read from 'log_buf' rather
# than '__log_buf'.
log_buf_ptr = self.vmlinux.object_from_symbol("log_buf")
# This can happen on kernels where log_buf is declared twice
try:
log_buf_ptr = self.vmlinux.object_from_symbol("log_buf")
except exceptions.InvalidAddressException:
vollog.debug("Unable to access `log_buf`. Bailing.")
return
log_buf_len = self.vmlinux.object_from_symbol("log_buf_len")
log_first_idx = int(self.vmlinux.object_from_symbol("log_first_idx"))
@@ -327,7 +336,10 @@ class Kmsg_3_5_to_3_11(ABCKmsg):
while cur_idx < end_idx:
msg_offset = log_buf_ptr + cur_idx # type: ignore
msg = self.vmlinux.object(object_type=log_struct_name, offset=msg_offset)
msg = self.vmlinux.object(
object_type=log_struct_name, offset=msg_offset, absolute=True
)
try:
if msg.len == 0:
# As per kernel/printk.c:
@@ -359,9 +371,14 @@ class Kmsg_3_11_to_5_10(Kmsg_3_5_to_3_11):
@classmethod
def symtab_checks(cls, vmlinux) -> bool:
return vmlinux.has_type("printk_log")
return (
not vmlinux.has_type("printk_ringbuffer")
and vmlinux.has_type("printk_log")
and vmlinux.get_type("printk_log").has_member("ts_nsec")
and vmlinux.has_symbol("log_first_idx")
)
def _get_log_struct_name(self):
def _get_log_struct_name(self) -> str:
return "printk_log"
@@ -412,9 +429,9 @@ class Kmsg_5_10_to_(ABCKmsg):
@classmethod
def symtab_checks(cls, vmlinux) -> bool:
return vmlinux.has_symbol("prb")
return vmlinux.has_symbol("prb") and vmlinux.has_type("printk_ringbuffer")
def get_text_from_data_ring(self, text_data_ring, desc, info) -> Union[str, None]:
def get_text_from_data_ring(self, text_data_ring, desc, info) -> Optional[str]:
text_data_sz = text_data_ring.size_bits
text_data_mask = 1 << text_data_sz
@@ -423,7 +440,7 @@ class Kmsg_5_10_to_(ABCKmsg):
# This record doesn't contain text
if begin & 1:
return ""
return None
# This means a wrap-around to the beginning of the buffer
if begin > end:
@@ -454,7 +471,7 @@ class Kmsg_5_10_to_(ABCKmsg):
if dict_text:
yield f" DEVICE={dict_text}"
def run(self) -> Iterator[Tuple[str, str, str, str, str]]:
def run(self) -> Iterator[Tuple[str, str, str, Optional[str], str]]:
# static struct printk_ringbuffer *prb = &printk_rb_static;
ringbuffers = self.vmlinux.object_from_symbol("prb").dereference()
@@ -516,7 +533,7 @@ class Kmsg(interfaces.plugins.PluginInterface):
_required_framework_version = (2, 6, 0)
_version = (1, 0, 2)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -528,17 +545,28 @@ class Kmsg(interfaces.plugins.PluginInterface):
),
]
def _generator(self) -> Iterator[Tuple[int, Tuple[str, str, str, str, str]]]:
for values in ABCKmsg.run_all(context=self.context, config=self.config):
yield (0, values)
def _generator(
self,
) -> Iterator[Tuple[int, Tuple[str, str, str, Optional[str], str]]]:
for facility, level, timestamp, caller, line in ABCKmsg.run_all(
context=self.context, config=self.config
):
yield 0, (
facility,
level,
timestamp,
caller or renderers.NotAvailableValue(),
line,
)
def run(self):
if not self.context.symbol_space.verify_table_versions(
"dwarf2json", lambda version, _: (not version) or version > (0, 4, 1)
):
raise exceptions.SymbolSpaceError(
vollog.info(
"Invalid symbol table, please ensure the ISF table produced by dwarf2json was produced using a version > 0.4.1"
)
return
return renderers.TreeGrid(
[
@@ -64,7 +64,7 @@ class Malfind(interfaces.plugins.PluginInterface):
# determine if we're on a 32 or 64 bit kernel
vmlinux = self.context.modules[self.config["kernel"]]
is_32bit_arch = not symbols.symbol_table_is_64bit(
self.context, vmlinux.symbol_table_name
context=self.context, symbol_table_name=vmlinux.symbol_table_name
)
for task in tasks:
@@ -6,7 +6,7 @@ from abc import ABC, abstractmethod
import logging
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
from typing import Iterator, List, Tuple
from typing import Iterator, List, Tuple, Optional
from volatility3 import framework
from volatility3.framework import (
constants,
@@ -245,7 +245,9 @@ class AbstractNetfilter(ABC):
for hook_idx, hook_name in enumerate(proto.hooks):
yield proto_idx, proto.name, hook_idx, hook_name
def build_nf_hook_ops_array(self, nf_hook_entries):
def build_nf_hook_ops_array(
self, nf_hook_entries
) -> Optional[interfaces.objects.ObjectInterface]:
"""Function helper to build the nf_hook_ops array when it is not part of the
struct 'nf_hook_entries' definition.
@@ -260,16 +262,27 @@ class AbstractNetfilter(ABC):
}
"""
nf_hook_entry_size = self.vmlinux.get_type("nf_hook_entry").size
try:
num_hook_entries = nf_hook_entries.num_hook_entries
except exceptions.InvalidAddressException:
return None
orig_ops_addr = (
nf_hook_entries.hooks.vol.offset
+ nf_hook_entry_size * nf_hook_entries.num_hook_entries
nf_hook_entries.hooks.vol.offset + nf_hook_entry_size * num_hook_entries
)
if not self.vmlinux._context.layers[self.vmlinux.layer_name].is_valid(
orig_ops_addr
):
return None
orig_ops = self._context.object(
object_type=self.get_symbol_fullname("array"),
offset=orig_ops_addr,
subtype=self.vmlinux.get_type("pointer"),
layer_name=self.layer_name,
count=nf_hook_entries.num_hook_entries,
count=num_hook_entries,
)
return orig_ops
@@ -515,6 +528,9 @@ class NetfilterImp_4_14_to_4_16(AbstractNetfilter):
nf_hook_ops_name = self.get_symbol_fullname("nf_hook_ops")
nf_hook_ops_ptr_arr = self.build_nf_hook_ops_array(nf_hook_entries)
if not nf_hook_ops_ptr_arr:
return
for nf_hook_ops_ptr in nf_hook_ops_ptr_arr:
nf_hook_ops = nf_hook_ops_ptr.dereference().cast(nf_hook_ops_name)
yield nf_hook_ops
@@ -695,6 +711,9 @@ class NetfilterNetDevImp_4_14_to_latest(AbstractNetfilterNetDev):
nf_hook_ops_name = self.get_symbol_fullname("nf_hook_ops")
nf_hook_ops_ptr_arr = self.build_nf_hook_ops_array(nf_hook_entries)
if not nf_hook_ops_ptr_arr:
return
for nf_hook_ops_ptr in nf_hook_ops_ptr_arr:
nf_hook_ops = nf_hook_ops_ptr.dereference().cast(nf_hook_ops_name)
yield nf_hook_ops
@@ -639,7 +639,7 @@ class RecoverFs(plugins.PluginInterface):
Troubleshooting: to fix extraction errors related to long paths, please consider using https://github.com/mxmlnkn/ratarmount.
"""
_version = (1, 0, 0)
_version = (1, 0, 1)
_required_framework_version = (2, 21, 0)
@classmethod
@@ -656,6 +656,12 @@ class RecoverFs(plugins.PluginInterface):
requirements.PluginRequirement(
name="inodepages", plugin=InodePages, version=(3, 0, 0)
),
requirements.BooleanRequirement(
name="tmpfs_only",
description="Extracts only files from tmpfs file systems",
default=False,
optional=True,
),
requirements.ChoiceRequirement(
name="compression_format",
description="Compression format (default: gz)",
@@ -805,6 +811,17 @@ class RecoverFs(plugins.PluginInterface):
)
continue
sb_type = inode_in.superblock.get_type()
if not sb_type:
vollog.debug(
f"Unable to read superblock type for inode at {inode_in.inode.vol.offset}"
)
continue
if self.config["tmpfs_only"] and sb_type != "tmpfs":
vollog.debug(f"Skipping non-tmpfs filesystem {sb_type}")
continue
# Construct the output path
if uuid_as_prefix:
prefix = f"/{inode_in.superblock.uuid}"
+1 -1
View File
@@ -78,7 +78,7 @@ class PsAux(plugins.PluginInterface):
return renderers.UnreadableValue()
# the arguments are null byte terminated, replace the nulls with spaces
s = argv.decode().split("\x00")
s = argv.decode(encoding="utf8", errors="replace").split("\x00")
args = " ".join(s)
else:
# kernel thread
@@ -85,7 +85,6 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
Returns:
Function which, when provided a process object, returns True if the process is to be filtered out of the list
"""
# FIXME: mypy #4973 or #2608
pid_list = pid_list or []
filter_list = [x for x in pid_list if x is not None]
if filter_list:
@@ -84,7 +84,9 @@ class PsScan(interfaces.plugins.PluginInterface):
vmlinux = context.modules[vmlinux_module_name]
# check if this image is 32bit or 64bit
is_32bit = not symbols.symbol_table_is_64bit(context, vmlinux.symbol_table_name)
is_32bit = not symbols.symbol_table_is_64bit(
context=context, symbol_table_name=vmlinux.symbol_table_name
)
if is_32bit:
pack_format = "I"
else:
+43 -3
View File
@@ -2,11 +2,15 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from volatility3.framework import interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.plugins.linux import pslist
vollog = logging.getLogger(__name__)
class PsTree(interfaces.plugins.PluginInterface):
"""Plugin for listing processes in a tree based on their parent process ID."""
@@ -52,19 +56,41 @@ class PsTree(interfaces.plugins.PluginInterface):
Args:
pid: PID to find the level in the hierarchy
"""
seen = set([pid])
seen_ppids = set()
seen_offsets = set()
level = 0
proc = self._tasks.get(pid)
while proc and proc.get_parent_pid() not in seen:
while proc:
# we don't want swapper in the tree
if proc.pid == 0:
break
if proc.is_thread_group_leader:
parent_pid = proc.get_parent_pid()
else:
parent_pid = proc.tgid
if parent_pid in seen_ppids or proc.vol.offset in seen_offsets:
break
# only pid 1 (init/systemd) or 2 (kthreadd) should have swapper as a parent
# any other process with a ppid of 0 is smeared or terminated
if parent_pid == 0 and proc.pid > 2:
vollog.debug(
"Smeared process with parent PID of 0 and PID greater than 2 ({proc.pid}) is being skipped."
)
break
seen_ppids.add(parent_pid)
seen_offsets.add(proc.vol.offset)
child_list = self._children.setdefault(parent_pid, set())
child_list.add(proc.pid)
proc = self._tasks.get(parent_pid)
level += 1
self._levels[pid] = level
@@ -110,12 +136,26 @@ class PsTree(interfaces.plugins.PluginInterface):
)
yield (self._levels[task_fields.user_tid] - 1, fields)
seen_children = set()
for child_pid in sorted(self._children.get(task_fields.user_tid, [])):
if child_pid in seen_children:
break
seen_children.add(child_pid)
yield from yield_processes(child_pid)
seen_processes = set()
for pid, level in self._levels.items():
if level == 1:
yield from yield_processes(pid)
for fields in yield_processes(pid):
pid = fields[1]
if pid in seen_processes:
break
seen_processes.add(pid)
yield fields
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
@@ -5,7 +5,7 @@
# Public researches: https://i.blackhat.com/USA21/Wednesday-Handouts/us-21-Fixing-A-Memory-Forensics-Blind-Spot-Linux-Kernel-Tracing-wp.pdf
import logging
from typing import Dict, List, Iterable, Optional
from typing import Dict, List, Generator
from enum import Enum
from dataclasses import dataclass
@@ -67,7 +67,7 @@ class CheckFtrace(interfaces.plugins.PluginInterface):
Investigate the ftrace infrastructure to uncover kernel attached callbacks, which can be leveraged
to hook kernel functions and modify their behaviour."""
_version = (1, 0, 0)
_version = (2, 0, 0)
_required_framework_version = (2, 19, 0)
@classmethod
@@ -103,32 +103,35 @@ class CheckFtrace(interfaces.plugins.PluginInterface):
def extract_hash_table_filters(
cls,
ftrace_ops: interfaces.objects.ObjectInterface,
) -> Optional[Iterable[interfaces.objects.ObjectInterface]]:
) -> Generator[interfaces.objects.ObjectInterface, None, None]:
"""Wrap the process of walking to every ftrace_func_entry of an ftrace_ops.
Those are stored in a hash table of filters that indicates the addresses hooked.
Args:
ftrace_ops: The ftrace_ops struct to walk through
Returns:
Return, None, None:
An iterable of ftrace_func_entry structs
"""
if hasattr(ftrace_ops, "func_hash"):
ftrace_hash = ftrace_ops.func_hash.filter_hash
else:
ftrace_hash = ftrace_ops.filter_hash
try:
current_bucket_ptr = ftrace_ops.func_hash.filter_hash.buckets.first
current_bucket_ptr = ftrace_hash.buckets.first
except exceptions.InvalidAddressException:
vollog.log(
constants.LOGLEVEL_VV,
f"ftrace_func_entry list of ftrace_ops@{ftrace_ops.vol.offset:#x} is empty/invalid. Skipping it...",
)
return []
return
while current_bucket_ptr.is_readable():
yield current_bucket_ptr.dereference().cast("ftrace_func_entry")
current_bucket_ptr = current_bucket_ptr.next
return None
@classmethod
def parse_ftrace_ops(
cls,
@@ -137,7 +140,7 @@ class CheckFtrace(interfaces.plugins.PluginInterface):
known_modules: Dict[str, List[extensions.module]],
ftrace_ops: interfaces.objects.ObjectInterface,
run_hidden_modules: bool = True,
) -> Optional[Iterable[ParsedFtraceOps]]:
) -> Generator[ParsedFtraceOps, None, None]:
"""Parse an ftrace_ops struct to highlight ftrace kernel hooking.
Iterates over embedded ftrace_func_entry entries, which point to hooked memory areas.
@@ -234,12 +237,10 @@ if the "hidden_modules" key is present in known_modules.
formatted_ftrace_flags,
)
return None
@classmethod
def iterate_ftrace_ops_list(
cls, context: interfaces.context.ContextInterface, kernel_name: str
) -> Optional[Iterable[interfaces.objects.ObjectInterface]]:
) -> Generator[interfaces.objects.ObjectInterface, None, None]:
"""Iterate over (ftrace_ops *)ftrace_ops_list.
Returns:
@@ -116,18 +116,25 @@ class CheckTracepoints(interfaces.plugins.PluginInterface):
known_modules: A dict of known modules, used to locate callbacks origin. Typically obtained through modxview.run_modules_scanners().
tracepoint: The tracepoint struct to parse
run_hidden_modules: Whether to run the hidden_modules plugin or not. Note: it won't be run, even if specified, \
if the "hidden_modules" key is present in known_modules.
if the "hidden_modules" key is present in known_modules.
Yields:
An iterable of ParsedTracepointFunc dataclasses, containing a selection of useful fields related to a tracepoint struct
"""
kernel = context.modules[kernel_name]
kernel_layer = context.layers[kernel.layer_name]
for tracepoint_func in cls.iterate_tracepoint_funcs(
context, kernel_layer.name, tracepoint
):
try:
tracepoint_name = utility.pointer_to_string(tracepoint.name, count=512)
except exceptions.InvalidAddressException:
vollog.debug(
f"Tracepoint function at {tracepoint.vol.offset:#x} is smeared."
)
continue
probe_handler_address = tracepoint_func.func
probe_handler_symbol = module_address = module_name = None
@@ -183,16 +190,21 @@ if the "hidden_modules" key is present in known_modules.
probe_handler_address
)
else:
vollog.warning(
vollog.debug(
f"Could not determine tracepoint@{tracepoint.vol.offset:#x} probe handler {probe_handler_address:#x} module origin.",
)
if hasattr(tracepoint_func, "prio"):
prio = tracepoint_func.prio
else:
prio = None
yield ParsedTracepointFunc(
utility.pointer_to_string(tracepoint.name, count=512),
tracepoint_name,
tracepoint.vol.offset,
probe_handler_symbol,
probe_handler_address,
tracepoint_func.prio,
prio,
module_name,
module_address,
)
@@ -258,11 +270,11 @@ if the "hidden_modules" key is present in known_modules.
kernel_layer = self.context.layers[kernel.layer_name]
if not kernel.has_symbol("__start___tracepoints_ptrs"):
raise exceptions.SymbolError(
"__start___tracepoints_ptrs",
self.vmlinux.symbol_table_name,
'The provided symbol table does not include the "__start___tracepoints_ptrs" symbol. This means you are either analyzing an unsupported kernel version or that your symbol table is corrupted.',
vollog.error(
'The provided symbol table does not include the "__start___tracepoints_ptrs" symbol.'
"This means you are either analyzing an unsupported kernel version or that your symbol table is corrupted."
)
return
known_modules = modxview.Modxview.run_modules_scanners(
self.context, kernel_name, run_hidden_modules=False
@@ -281,7 +293,7 @@ if the "hidden_modules" key is present in known_modules.
format_hints.Hex(tracepoint_parsed.tracepoint_address),
tracepoint_parsed.probe_name or NotAvailableValue(),
format_hints.Hex(tracepoint_parsed.probe_address),
tracepoint_parsed.probe_priority,
tracepoint_parsed.probe_priority or NotAvailableValue(),
tracepoint_parsed.module_name or NotAvailableValue(),
(
format_hints.Hex(tracepoint_parsed.module_address)
@@ -81,9 +81,11 @@ class tty_check(plugins.PluginInterface):
if tty_dev == 0:
continue
name = utility.array_to_string(tty_dev.name)
recv_buf = tty_dev.ldisc.ops.receive_buf
try:
name = utility.array_to_string(tty_dev.name)
recv_buf = tty_dev.ldisc.ops.receive_buf
except exceptions.InvalidAddressException:
continue
module_name, symbol_name = (
linux_utilities_modules.Modules.lookup_module_address(
+1 -1
View File
@@ -44,7 +44,7 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
def _generator(self, tasks):
darwin = self.context.modules[self.config["kernel"]]
is_32bit = not symbols.symbol_table_is_64bit(
self.context, darwin.symbol_table_name
context=self.context, symbol_table_name=darwin.symbol_table_name
)
if is_32bit:
pack_format = "I"
@@ -218,7 +218,9 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Extract information on executed applications from the AmCache."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
# 2.0.0 - changed the signature of get_amcache_hive
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -230,7 +232,7 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
]
@@ -252,7 +254,7 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel: interfaces.context.ModuleInterface,
kernel_module_name: str,
) -> Optional[registry.RegistryHive]:
"""Retrieves the `Amcache.hve` registry hive from the kernel module, if it can be located."""
return next(
@@ -261,8 +263,7 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
base_config_path=interfaces.configuration.path_join(
config_path, "hivelist"
),
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=kernel_module_name,
filter_string="amcache",
),
None,
@@ -523,8 +524,6 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
)
def _generator(self) -> Iterator[Tuple[int, _AmcacheEntry]]:
kernel = self.context.modules[self.config["kernel"]]
def indented(
entry_gen: Iterable[_AmcacheEntry], indent: int = 0
) -> Iterator[Tuple[int, _AmcacheEntry]]:
@@ -533,7 +532,9 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# Building the dictionary ahead of time is much better for performance
# vs looking up each service's DLL individually.
amcache = self.get_amcache_hive(self.context, self.config_path, kernel)
amcache = self.get_amcache_hive(
self.context, self.config_path, self.config["kernel"]
)
if amcache is None:
return
@@ -21,7 +21,7 @@ class BigPools(interfaces.plugins.PluginInterface):
"""List big page pools."""
_required_framework_version = (2, 0, 0)
_version = (1, 1, 1)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -50,8 +50,7 @@ class BigPools(interfaces.plugins.PluginInterface):
def list_big_pools(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
tags: Optional[list] = None,
show_free: bool = False,
):
@@ -59,19 +58,13 @@ class BigPools(interfaces.plugins.PluginInterface):
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
kernel_module_name: The name of the module for the kernel
tags: An optional list of pool tags to filter big page pool tags by
Yields:
A big page pool object
"""
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ntkrnlmp = context.modules[kernel_module_name]
big_page_table_offset = ntkrnlmp.get_symbol("PoolBigPageTable").address
big_page_table = ntkrnlmp.object(
@@ -87,8 +80,10 @@ class BigPools(interfaces.plugins.PluginInterface):
big_page_table_type = ntkrnlmp.get_type("_POOL_TRACKER_BIG_PAGES")
except exceptions.SymbolError:
# We have to manually load a symbol table
is_vista_or_later = versions.is_vista_or_later(context, symbol_table)
is_win10 = versions.is_win10(context, symbol_table)
is_vista_or_later = versions.is_vista_or_later(
context, ntkrnlmp.symbol_table_name
)
is_win10 = versions.is_win10(context, ntkrnlmp.symbol_table_name)
if is_win10:
big_pools_json_filename = "bigpools-win10"
elif is_vista_or_later:
@@ -96,7 +91,7 @@ class BigPools(interfaces.plugins.PluginInterface):
else:
big_pools_json_filename = "bigpools"
if symbols.symbol_table_is_64bit(context, symbol_table):
if symbols.symbol_table_is_64bit(context, ntkrnlmp.symbol_table_name):
big_pools_json_filename += "-x64"
else:
big_pools_json_filename += "-x86"
@@ -104,16 +99,17 @@ class BigPools(interfaces.plugins.PluginInterface):
new_table_name = intermed.IntermediateSymbolTable.create(
context=context,
config_path=configuration.path_join(
context.symbol_space[symbol_table].config_path, "bigpools"
context.symbol_space[ntkrnlmp.symbol_table_name].config_path,
"bigpools",
),
sub_path=os.path.join("windows", "bigpools"),
filename=big_pools_json_filename,
table_mapping={"nt_symbols": symbol_table},
table_mapping={"nt_symbols": ntkrnlmp.symbol_table_name},
class_types={
"_POOL_TRACKER_BIG_PAGES": extensions.pool.POOL_TRACKER_BIG_PAGES
},
)
module = context.module(new_table_name, layer_name, offset=0)
module = context.module(new_table_name, ntkrnlmp.layer_name, offset=0)
big_page_table_type = module.get_type("_POOL_TRACKER_BIG_PAGES")
big_pools = ntkrnlmp.object(
@@ -136,12 +132,10 @@ class BigPools(interfaces.plugins.PluginInterface):
tags = [tag for tag in self.config["tags"].split(",")]
else:
tags = None
kernel = self.context.modules[self.config["kernel"]]
for big_pool in self.list_big_pools(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
tags=tags,
show_free=self.config.get("show-free"),
):
@@ -22,7 +22,7 @@ class Cachedump(interfaces.plugins.PluginInterface):
"""Dumps lsa secrets from memory"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
_version = (1, 0, 2)
@classmethod
def get_requirements(cls):
@@ -33,7 +33,7 @@ class Cachedump(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="lsadump", plugin=lsadump.Lsadump, version=(1, 0, 0)
@@ -169,13 +169,11 @@ class Cachedump(interfaces.plugins.PluginInterface):
offset = self.config.get("offset", None)
syshive = sechive = None
kernel = self.context.modules[self.config["kernel"]]
for hive in hivelist.HiveList.list_hives(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
context=self.context,
base_config_path=self.config_path,
kernel_module_name=self.config["kernel"],
hive_offsets=None if offset is None else [offset],
):
if hive.get_name().split("\\")[-1].upper() == "SYSTEM":
@@ -28,7 +28,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
"""Lists kernel callbacks and notification routines."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 1)
_version = (3, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -39,16 +39,16 @@ class Callbacks(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0)
),
requirements.PluginRequirement(
name="driverirp", plugin=driverirp.DriverIrp, version=(1, 0, 0)
),
requirements.PluginRequirement(
name="handles", plugin=handles.Handles, version=(2, 0, 0)
name="handles", plugin=handles.Handles, version=(3, 0, 0)
),
]
@@ -187,7 +187,9 @@ class Callbacks(interfaces.plugins.PluginInterface):
The name of the constructed symbol table
"""
native_types = context.symbol_space[nt_symbol_table].natives
is_64bit = symbols.symbol_table_is_64bit(context, nt_symbol_table)
is_64bit = symbols.symbol_table_is_64bit(
context=context, symbol_table_name=nt_symbol_table
)
table_mapping = {"nt_symbols": nt_symbol_table}
if is_64bit:
@@ -209,8 +211,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
def scan(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
nt_symbol_table: str,
kernel_module_name: str,
callback_symbol_table: str,
) -> Iterable[
Tuple[
@@ -223,18 +224,21 @@ class Callbacks(interfaces.plugins.PluginInterface):
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
nt_symbol_table: The name of the table containing the kernel symbols
kernel_module_name: Name of the module for the kernel
callback_symbol_table: The name of the table containing the callback object symbols (_SHUTDOWN_PACKET etc.)
Returns:
A list of callback objects found by scanning the `layer_name` layer for callback pool signatures
"""
kernel = context.modules[kernel_module_name]
is_vista_or_later = versions.is_vista_or_later(
context=context, symbol_table=nt_symbol_table
context=context, symbol_table=kernel.symbol_table_name
)
type_map = handles.Handles.get_type_map(context, layer_name, nt_symbol_table)
type_map = handles.Handles.get_type_map(
context=context, kernel_module_name=kernel_module_name
)
constraints = cls.create_callback_scan_constraints(
context, callback_symbol_table, is_vista_or_later
@@ -245,7 +249,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
mem_object,
_header,
) in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, nt_symbol_table, constraints
context, kernel_module_name, constraints
):
try:
if isinstance(mem_object, callbacks._SHUTDOWN_PACKET):
@@ -345,31 +349,24 @@ class Callbacks(interfaces.plugins.PluginInterface):
def list_notify_routines(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
callback_table_name: str,
) -> Iterable[Tuple[str, int, Optional[str]]]:
"""Lists all kernel notification routines.
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
kernel_module_name: The name of the module of the kernel
callback_table_name: The name of the table containing the callback symbols
Yields:
A name, location and optional detail string
"""
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ntkrnlmp = context.modules[kernel_module_name]
is_vista_or_later = versions.is_vista_or_later(
context=context, symbol_table=symbol_table
context=context, symbol_table=ntkrnlmp.symbol_table_name
)
full_type_name = callback_table_name + constants.BANG + "_GENERIC_CALLBACK"
@@ -414,20 +411,14 @@ class Callbacks(interfaces.plugins.PluginInterface):
def _list_registry_callbacks_legacy(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
callback_table_name: str,
) -> Iterable[Tuple[str, int, None]]:
"""
Lists all registry callbacks from the old format via the CmpCallBackVector.
"""
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ntkrnlmp = context.modules[kernel_module_name]
full_type_name = (
callback_table_name + constants.BANG + "_EX_CALLBACK_ROUTINE_BLOCK"
)
@@ -465,20 +456,13 @@ class Callbacks(interfaces.plugins.PluginInterface):
def _list_registry_callbacks_new(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
callback_table_name: str,
) -> Iterable[Tuple[str, int, Optional[str]]]:
"""
Lists all registry callbacks via the CallbackListHead.
"""
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ntkrnlmp = context.modules[kernel_module_name]
full_type_name = callback_table_name + constants.BANG + "_CM_CALLBACK_ENTRY"
symbol_offset = ntkrnlmp.get_symbol("CallbackListHead").address
@@ -502,40 +486,33 @@ class Callbacks(interfaces.plugins.PluginInterface):
def list_registry_callbacks(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
callback_table_name: str,
) -> Iterable[Tuple[str, int, Optional[str]]]:
"""Lists all registry callbacks.
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
kernel_module_name: The name of the module of the kernel
callback_table_name: The name of the table containing the callback symbols
Yields:
A name, location and optional detail string
"""
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ntkrnlmp = context.modules[kernel_module_name]
if ntkrnlmp.has_symbol("CmpCallBackVector") and ntkrnlmp.has_symbol(
"CmpCallBackCount"
):
yield from cls._list_registry_callbacks_legacy(
context, layer_name, symbol_table, callback_table_name
context, kernel_module_name, callback_table_name
)
elif ntkrnlmp.has_symbol("CallbackListHead") and ntkrnlmp.has_symbol(
"CmpCallBackCount"
):
yield from cls._list_registry_callbacks_new(
context, layer_name, symbol_table, callback_table_name
context, kernel_module_name, callback_table_name
)
else:
symbols_to_check = [
@@ -550,14 +527,11 @@ class Callbacks(interfaces.plugins.PluginInterface):
symbol_status = "exists"
vollog.debug(f"symbol {symbol_name} {symbol_status}.")
return None
@classmethod
def list_bugcheck_reason_callbacks(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
callback_table_name: str,
) -> Iterable[
Tuple[
@@ -570,20 +544,14 @@ class Callbacks(interfaces.plugins.PluginInterface):
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
kernel_module_name: The name of the module of the kernel
callback_table_name: The name of the table containing the callback symbols
Yields:
A name, location and optional detail string
"""
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ntkrnlmp = context.modules[kernel_module_name]
try:
list_offset = ntkrnlmp.get_symbol(
@@ -597,11 +565,15 @@ class Callbacks(interfaces.plugins.PluginInterface):
callback_table_name + constants.BANG + "_KBUGCHECK_REASON_CALLBACK_RECORD"
)
callback_record = context.object(
object_type=full_type_name, offset=kvo + list_offset, layer_name=layer_name
object_type=full_type_name,
offset=ntkrnlmp.offset + list_offset,
layer_name=ntkrnlmp.layer_name,
)
for callback in callback_record.Entry:
if not context.layers[layer_name].is_valid(callback.CallbackRoutine, 64):
if not context.layers[ntkrnlmp.layer_name].is_valid(
callback.CallbackRoutine, 64
):
continue
try:
@@ -624,8 +596,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
def list_bugcheck_callbacks(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
callback_table_name: str,
) -> Iterable[
Tuple[
@@ -638,20 +609,13 @@ class Callbacks(interfaces.plugins.PluginInterface):
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
kernel_module_name: The name of the module of the kernel
callback_table_name: The name of the table containing the callback symbols
Yields:
A name, location and optional detail string
"""
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ntkrnlmp = context.modules[kernel_module_name]
try:
list_offset = ntkrnlmp.get_symbol("KeBugCheckCallbackListHead").address
@@ -663,17 +627,20 @@ class Callbacks(interfaces.plugins.PluginInterface):
callback_table_name + constants.BANG + "_KBUGCHECK_CALLBACK_RECORD"
)
callback_record = context.object(
full_type_name, offset=kvo + list_offset, layer_name=layer_name
full_type_name,
offset=ntkrnlmp.offset + list_offset,
layer_name=ntkrnlmp.layer_name,
)
for callback in callback_record.Entry:
if not context.layers[layer_name].is_valid(callback.CallbackRoutine, 64):
if not context.layers[ntkrnlmp.layer_name].is_valid(
callback.CallbackRoutine, 64
):
continue
try:
component = context.object(
symbol_table + constants.BANG + "string",
layer_name=layer_name,
component = ntkrnlmp.object(
"string",
offset=callback.Component,
max_length=64,
errors="replace",
@@ -691,7 +658,8 @@ class Callbacks(interfaces.plugins.PluginInterface):
)
collection = ssdt.SSDT.build_module_collection(
self.context, kernel.layer_name, kernel.symbol_table_name
context=self.context,
kernel_module_name=self.config["kernel"],
)
callback_methods = (
@@ -705,8 +673,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
for callback_method in callback_methods:
for callback_type, callback_address, callback_detail in callback_method(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
callback_symbol_table,
):
if callback_detail is None:
@@ -2,7 +2,7 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import List
from typing import List, Optional
from volatility3.framework import constants, exceptions, renderers, interfaces
from volatility3.framework.configuration import requirements
@@ -28,7 +28,7 @@ class CmdLine(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -41,7 +41,7 @@ class CmdLine(interfaces.plugins.PluginInterface):
@classmethod
def get_cmdline(
cls, context: interfaces.context.ContextInterface, kernel_table_name: str, proc
):
) -> Optional[str]:
"""Extracts the cmdline from PEB
Args:
@@ -54,15 +54,16 @@ class CmdLine(interfaces.plugins.PluginInterface):
"""
proc_layer_name = proc.add_process_layer()
if not proc_layer_name:
return None
peb = context.object(
kernel_table_name + constants.BANG + "_PEB",
layer_name=proc_layer_name,
offset=proc.Peb,
)
result_text = peb.ProcessParameters.CommandLine.get_string()
return result_text
return peb.ProcessParameters.CommandLine.get_string()
def _generator(self, procs):
kernel = self.context.modules[self.config["kernel"]]
@@ -99,7 +100,6 @@ class CmdLine(interfaces.plugins.PluginInterface):
yield (0, (proc.UniqueProcessId, process_name, result_text))
def run(self):
kernel = self.context.modules[self.config["kernel"]]
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
return renderers.TreeGrid(
@@ -107,8 +107,7 @@ class CmdLine(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
@@ -24,7 +24,7 @@ class CmdScan(interfaces.plugins.PluginInterface):
"""Looks for Windows Command History lists"""
_required_framework_version = (2, 4, 0)
_version = (1, 0, 0)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
@@ -36,10 +36,10 @@ class CmdScan(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.PluginRequirement(
name="consoles", plugin=consoles.Consoles, version=(1, 0, 0)
name="consoles", plugin=consoles.Consoles, version=(3, 0, 0)
),
requirements.BooleanRequirement(
name="no_registry",
@@ -83,9 +83,8 @@ class CmdScan(interfaces.plugins.PluginInterface):
def get_command_history(
cls,
context: interfaces.context.ContextInterface,
kernel_layer_name: str,
kernel_symbol_table_name: str,
config_path: str,
kernel_module_name: str,
procs: Generator[interfaces.objects.ObjectInterface, None, None],
max_history: Set[int],
) -> Tuple[
@@ -97,8 +96,6 @@ class CmdScan(interfaces.plugins.PluginInterface):
Args:
context: The context to retrieve required elements (layers, symbol tables) from
kernel_layer_name: The name of the layer on which to operate
kernel_symbol_table_name: The name of the table containing the kernel symbols
config_path: The config path where to find symbol files
procs: List of process objects
max_history: An initial set of CommandHistorySize values
@@ -135,9 +132,8 @@ class CmdScan(interfaces.plugins.PluginInterface):
if conhost_symbol_table is None:
conhost_symbol_table = consoles.Consoles.create_conhost_symbol_table(
context,
kernel_layer_name,
kernel_symbol_table_name,
config_path,
kernel_module_name,
proc_layer_name,
conhostexe_base,
)
@@ -279,19 +275,16 @@ class CmdScan(interfaces.plugins.PluginInterface):
procs: the process list filtered to conhost.exe instances
"""
kernel = self.context.modules[self.config["kernel"]]
max_history = set(self.config.get("max_history", [50]))
no_registry = self.config.get("no_registry")
if no_registry is False:
max_history, _ = consoles.Consoles.get_console_settings_from_registry(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
max_history,
[],
context=self.context,
config_path=self.config_path,
kernel_module_name=self.config["kernel"],
max_history=max_history,
max_buffers=[],
)
vollog.debug(f"Possible CommandHistorySize values: {max_history}")
@@ -303,9 +296,8 @@ class CmdScan(interfaces.plugins.PluginInterface):
command_history_properties,
) in self.get_command_history(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config_path,
self.config["kernel"],
procs,
max_history,
):
@@ -360,8 +352,6 @@ class CmdScan(interfaces.plugins.PluginInterface):
return process_name != "conhost.exe"
def run(self):
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
("PID", int),
@@ -374,8 +364,7 @@ class CmdScan(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=self._conhost_proc_filter,
)
),
@@ -29,7 +29,9 @@ class Consoles(interfaces.plugins.PluginInterface):
"""Looks for Windows console buffers"""
_required_framework_version = (2, 4, 0)
_version = (1, 0, 0)
# 2.0.0 - change the signature of `get_console_settings_from_registry`
_version = (3, 0, 0)
@classmethod
def get_requirements(cls):
@@ -41,13 +43,13 @@ class Consoles(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="verinfo", component=verinfo.VerInfo, version=(1, 0, 0)
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
requirements.BooleanRequirement(
name="no_registry",
@@ -126,9 +128,8 @@ class Consoles(interfaces.plugins.PluginInterface):
def determine_conhost_version(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
nt_symbol_table: str,
config_path: str,
kernel_module_name: str,
conhost_layer_name: str,
conhost_base: int,
) -> Tuple[Optional[str], Dict[str, Type]]:
@@ -137,9 +138,8 @@ class Consoles(interfaces.plugins.PluginInterface):
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
nt_symbol_table: The name of the table containing the kernel symbols
config_path: The config path where to find symbol files
kernel_module_name: The name of the module for the kernel
conhost_layer_name: The name of the conhot process memory layer
conhost_base: the base address of conhost.exe
@@ -147,16 +147,20 @@ class Consoles(interfaces.plugins.PluginInterface):
The filename of the symbol table to use and the associated class types.
"""
is_64bit = symbols.symbol_table_is_64bit(context, nt_symbol_table)
kernel = context.modules[kernel_module_name]
is_64bit = symbols.symbol_table_is_64bit(
context=context, symbol_table_name=kernel.symbol_table_name
)
if is_64bit:
arch = "x64"
else:
arch = "x86"
vers = info.Info.get_version_structure(context, layer_name, nt_symbol_table)
vers = info.Info.get_version_structure(context, kernel_module_name)
kuser = info.Info.get_kuser_structure(context, layer_name, nt_symbol_table)
kuser = info.Info.get_kuser_structure(context, kernel_module_name)
try:
vers_minor_version = int(vers.MinorVersion)
@@ -243,7 +247,7 @@ class Consoles(interfaces.plugins.PluginInterface):
)
except (exceptions.InvalidAddressException, TypeError, AttributeError):
# the following is IntelLayer specific and might need to be adapted to other architectures.
physical_layer_name = context.layers[layer_name].config.get(
physical_layer_name = context.layers[kernel.layer_name].config.get(
"memory_layer", None
)
if physical_layer_name:
@@ -314,9 +318,8 @@ class Consoles(interfaces.plugins.PluginInterface):
def create_conhost_symbol_table(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
nt_symbol_table: str,
config_path: str,
kernel_module_name: str,
conhost_layer_name: str,
conhost_base: int,
) -> str:
@@ -324,20 +327,20 @@ class Consoles(interfaces.plugins.PluginInterface):
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
nt_symbol_table: The name of the table containing the kernel symbols
config_path: The config path where to find symbol files
kernel_module_name: The name of the module of the kernel
Returns:
The name of the constructed symbol table
"""
table_mapping = {"nt_symbols": nt_symbol_table}
kernel = context.modules[kernel_module_name]
table_mapping = {"nt_symbols": kernel.symbol_table_name}
symbol_filename, class_types = cls.determine_conhost_version(
context,
layer_name,
nt_symbol_table,
config_path,
kernel_module_name,
conhost_layer_name,
conhost_base,
)
@@ -362,9 +365,8 @@ class Consoles(interfaces.plugins.PluginInterface):
def get_console_info(
cls,
context: interfaces.context.ContextInterface,
kernel_layer_name: str,
kernel_table_name: str,
config_path: str,
kernel_module_name: str,
procs: Generator[interfaces.objects.ObjectInterface, None, None],
max_history: Set[int],
max_buffers: Set[int],
@@ -381,9 +383,8 @@ class Consoles(interfaces.plugins.PluginInterface):
Args:
context: The context to retrieve required elements (layers, symbol tables) from
kernel_layer_name: The name of the layer on which to operate
kernel_table_name: The name of the table containing the kernel symbols
config_path: The config path where to find symbol files
kernel_module_name: The name of the module for the kernel
procs: list of process objects
max_history: an initial set of CommandHistorySize values
max_buffers: an initial list of HistoryBufferMax values
@@ -423,9 +424,8 @@ class Consoles(interfaces.plugins.PluginInterface):
if conhost_symbol_table is None:
conhost_symbol_table = cls.create_conhost_symbol_table(
context,
kernel_layer_name,
kernel_table_name,
config_path,
kernel_module_name,
proc_layer_name,
conhostexe_base,
)
@@ -795,8 +795,7 @@ class Consoles(interfaces.plugins.PluginInterface):
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel_layer_name: str,
kernel_symbol_table_name: str,
kernel_module_name: str,
max_history: Set[int],
max_buffers: Set[int],
) -> Tuple[Set[int], Set[int]]:
@@ -807,8 +806,7 @@ class Consoles(interfaces.plugins.PluginInterface):
Args:
context: The context to retrieve required elements (layers, symbol tables) from
config_path: The config path where to find symbol files
kernel_layer_name: The name of the layer on which to operate
kernel_symbol_table_name: The name of the table containing the kernel symbols
kernel_module_name: The name of the module for the kernel
max_history: an initial set of CommandHistorySize values
max_buffers: an initial list of HistoryBufferMax values
@@ -825,8 +823,7 @@ class Consoles(interfaces.plugins.PluginInterface):
for hive in hivelist.HiveList.list_hives(
context=context,
base_config_path=config_path,
layer_name=kernel_layer_name,
symbol_table=kernel_symbol_table_name,
kernel_module_name=kernel_module_name,
hive_offsets=None,
):
try:
@@ -851,8 +848,6 @@ class Consoles(interfaces.plugins.PluginInterface):
procs: the process list filtered to conhost.exe instances
"""
kernel = self.context.modules[self.config["kernel"]]
max_history = set(self.config.get("max_history", [50]))
max_buffers = set(self.config.get("max_buffers", [4]))
no_registry = self.config.get("no_registry")
@@ -861,8 +856,7 @@ class Consoles(interfaces.plugins.PluginInterface):
max_history, max_buffers = self.get_console_settings_from_registry(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
max_history,
max_buffers,
)
@@ -873,9 +867,8 @@ class Consoles(interfaces.plugins.PluginInterface):
proc = None
for proc, console_info, console_properties in self.get_console_info(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config_path,
self.config["kernel"],
procs,
max_history,
max_buffers,
@@ -933,8 +926,6 @@ class Consoles(interfaces.plugins.PluginInterface):
return process_name.lower() != "conhost.exe"
def run(self):
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
("PID", int),
@@ -947,8 +938,7 @@ class Consoles(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=self._conhost_proc_filter,
)
),
@@ -35,13 +35,13 @@ class DebugRegisters(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="threads", component=threads.Threads, version=(1, 0, 0)
name="threads", component=threads.Threads, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="pe_symbols", component=pe_symbols.PESymbols, version=(1, 0, 0)
name="pe_symbols", component=pe_symbols.PESymbols, version=(3, 0, 0)
),
]
@@ -111,20 +111,18 @@ class DebugRegisters(interfaces.plugins.PluginInterface):
None,
None,
]:
kernel = self.context.modules[self.config["kernel"]]
vads_cache: Dict[int, pe_symbols.ranges_type] = {}
proc_modules = None
procs = pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
context=self.context, kernel_module_name=self.config["kernel"]
)
for proc in procs:
for thread in threads.Threads.list_threads(kernel, proc):
for thread in threads.Threads.list_threads(
self.context, self.config["kernel"], proc
):
debug_info = self._get_debug_info(thread)
if not debug_info:
continue
@@ -140,7 +138,7 @@ class DebugRegisters(interfaces.plugins.PluginInterface):
# this lookup takes a while, so only perform if we need to
if not proc_modules:
proc_modules = pe_symbols.PESymbols.get_process_modules(
self.context, kernel.layer_name, kernel.symbol_table_name, None
self.context, self.config["kernel"], None
)
path_and_symbol = partial(
pe_symbols.PESymbols.path_and_symbol_for_address,
@@ -0,0 +1,82 @@
# This file is Copyright 2025 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, Iterable, Tuple
from volatility3.framework import interfaces
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.plugins.windows import desktops, windowstations
vollog = logging.getLogger(__name__)
class DeskScan(desktops.Desktops):
"""Scans for the Desktop instances of each Window Station"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.implementation = self.scan_desktops
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="desktops", plugin=desktops.Desktops, version=(1, 0, 0)
),
requirements.PluginRequirement(
name="windowstations",
plugin=windowstations.WindowStations,
version=(1, 0, 0),
),
]
@classmethod
def scan_desktops(
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel_module_name: str,
) -> Iterable[Tuple[int, str, int, str, str, int]]:
"""
Yields the information about each desktop and desktop thread needed for analysis
The tuple yielded includes the:
Virtual address of the desktop
The window station name
The session id
Desktop name
Process name
Process ID (PID)
"""
kernel = context.modules[kernel_module_name]
for desktop in windowstations.WindowStations.scan_gui_object(
context, config_path, kernel_module_name, b"Desk", "tagDESKTOP"
):
desktop_name = desktop.get_name(kernel.symbol_table_name)
if not desktop_name:
continue
winsta = desktop.get_window_station()
if not winsta:
continue
winsta_name, session_id = winsta.get_info(kernel.symbol_table_name)
if not winsta_name or session_id is None:
continue
for _thread, process_name, process_pid in desktop.get_threads():
yield format_hints.Hex(
desktop.vol.offset
), winsta_name, session_id, desktop_name, process_name, process_pid
@@ -0,0 +1,91 @@
# This file is Copyright 2025 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, Iterable
from volatility3.framework import interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.plugins.windows import windowstations
vollog = logging.getLogger(__name__)
class Desktops(interfaces.plugins.PluginInterface):
"""Enumerates the Desktop instances of each Window Station"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.implementation = self.list_desktops
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="windowstations",
plugin=windowstations.WindowStations,
version=(1, 0, 0),
),
]
@classmethod
def list_desktops(
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel_module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""
Uses `scan_window_stations` to find each window station
For each found, enumerates its desktops followed by the
threads of each desktop.
"""
kernel = context.modules[kernel_module_name]
for (
winsta,
station_name,
session_id,
) in windowstations.WindowStations.scan_window_stations(
context, config_path, kernel_module_name
):
# for each window station, walk its list of desktops
for desktop, desktop_name in winsta.desktops(kernel.symbol_table_name):
# for each desktop, walk its threads
for _thread, process_name, process_pid in desktop.get_threads():
yield format_hints.Hex(
desktop.vol.offset
), station_name, session_id, desktop_name, process_name, process_pid
def _generator(self):
kernel_name = self.config["kernel"]
# call the implementation for finding desktops
# yield the information, which will include the owning window station and process
for desktop_info in self.implementation(
self.context, self.config_path, kernel_name
):
yield 0, desktop_info
def run(self):
return renderers.TreeGrid(
[
("Offset", format_hints.Hex),
("Window Station", str),
("Session", int),
("Desktop", str),
("Process", str),
("PID", int),
],
self._generator(),
)
@@ -53,7 +53,9 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
"""Detects the Direct System Call technique used to bypass EDRs"""
_required_framework_version = (2, 4, 0)
_version = (1, 0, 1)
# 2.0.0 - changes signature of `get_tasks_to_scan`
_version = (2, 0, 0)
# DLLs that are expected to host system call invocations
valid_syscall_handlers = ("ntdll.dll", "win32u.dll")
@@ -90,7 +92,7 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="yarascanner", component=yarascan.YaraScanner, version=(2, 1, 0)
@@ -334,8 +336,7 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
def get_tasks_to_scan(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table_name: str,
kernel_module_name: str,
) -> Generator[
Tuple[interfaces.objects.ObjectInterface, str, str, str], None, None
]:
@@ -350,12 +351,15 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
# gather active processes
filter_func = pslist.PsList.create_active_process_filter()
is_32bit_arch = not symbols.symbol_table_is_64bit(context, symbol_table_name)
kernel = context.modules[kernel_module_name]
is_32bit_arch = not symbols.symbol_table_is_64bit(
context=context, symbol_table_name=kernel.symbol_table_name
)
for proc in pslist.PsList.list_processes(
context=context,
layer_name=layer_name,
symbol_table=symbol_table_name,
kernel_module_name=kernel_module_name,
filter_func=filter_func,
):
proc_name = utility.array_to_string(proc.ImageFileName)
@@ -426,10 +430,8 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
)
return
kernel = self.context.modules[self.config["kernel"]]
for proc, proc_name, proc_layer_name, architecture in self.get_tasks_to_scan(
self.context, kernel.layer_name, kernel.symbol_table_name
self.context, self.config["kernel"]
):
proc_layer = self.context.layers[proc_layer_name]
@@ -13,7 +13,7 @@ from volatility3.framework.renderers import conversion, format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows.extensions import pe
from volatility3.plugins import timeliner
from volatility3.plugins.windows import info, pslist, psscan, pedump
from volatility3.plugins.windows import info, pedump, pslist, psscan
vollog = logging.getLogger(__name__)
@@ -34,16 +34,16 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="psscan", component=psscan.PsScan, version=(1, 1, 0)
name="psscan", component=psscan.PsScan, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="pedump", component=pedump.PEDump, version=(1, 0, 0)
name="pedump", component=pedump.PEDump, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="info", component=info.Info, version=(1, 0, 0)
name="info", component=info.Info, version=(2, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -85,11 +85,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
self.context, self.config_path, "windows", "pe", class_types=pe.class_types
)
kernel = self.context.modules[self.config["kernel"]]
kuser = info.Info.get_kuser_structure(
self.context, kernel.layer_name, kernel.symbol_table_name
)
kuser = info.Info.get_kuser_structure(self.context, self.config["kernel"])
nt_major_version = int(kuser.NtMajorVersion)
nt_minor_version = int(kuser.NtMinorVersion)
@@ -191,12 +187,9 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
)
def generate_timeline(self):
kernel = self.context.modules[self.config["kernel"]]
for row in self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
context=self.context, kernel_module_name=self.config["kernel"]
)
):
_depth, row_data = row
@@ -212,8 +205,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
if self.config["offset"]:
procs = psscan.PsScan.scan_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
filter_func=psscan.PsScan.create_offset_filter(
self.context,
kernel.layer_name,
@@ -223,8 +215,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
else:
procs = pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
@@ -59,25 +59,25 @@ class DriverIrp(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="driverscan", plugin=driverscan.DriverScan, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="modules", plugin=modules.Modules, version=(2, 1, 0)
name="modules", plugin=modules.Modules, version=(3, 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
context=self.context,
kernel_module_name=self.config["kernel"],
)
kernel_space_start = modules.Modules.get_kernel_space_start(
self.context, self.config["kernel"]
context=self.context,
module_name=self.config["kernel"],
)
for driver in driverscan.DriverScan.scan_drivers(
@@ -26,13 +26,13 @@ class DriverModule(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="driverscan", plugin=driverscan.DriverScan, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="modules", plugin=modules.Modules, version=(2, 1, 0)
name="modules", plugin=modules.Modules, version=(3, 0, 0)
),
]
@@ -42,10 +42,9 @@ class DriverModule(interfaces.plugins.PluginInterface):
A common rootkit technique is to register drivers from modules that are hidden,
which allows us to detect the disconnect between a malicious driver and its hidden module.
"""
kernel = self.context.modules[self.config["kernel"]]
collection = ssdt.SSDT.build_module_collection(
self.context, kernel.layer_name, kernel.symbol_table_name
context=self.context,
kernel_module_name=self.config["kernel"],
)
kernel_space_start = modules.Modules.get_kernel_space_start(
@@ -25,7 +25,7 @@ class DriverScan(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0)
),
]
@@ -48,15 +48,11 @@ class DriverScan(interfaces.plugins.PluginInterface):
kernel = context.modules[kernel_module_name]
symbol_table_name = kernel.symbol_table_name
layer_name = kernel.layer_name
constraints = poolscanner.PoolScanner.builtin_constraints(
symbol_table_name, [b"Dri\xf6", b"Driv"]
kernel.symbol_table_name, [b"Dri\xf6", b"Driv"]
)
module = context.module(symbol_table_name, layer_name, 0)
driver_start_offset = module.get_type("_DRIVER_OBJECT").relative_child_offset(
driver_start_offset = kernel.get_type("_DRIVER_OBJECT").relative_child_offset(
"DriverStart"
)
@@ -65,7 +61,7 @@ class DriverScan(interfaces.plugins.PluginInterface):
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, symbol_table_name, constraints
context, kernel_module_name, constraints
):
_constraint, mem_object, _header = result
@@ -68,10 +68,10 @@ class DumpFiles(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="handles", component=handles.Handles, version=(2, 0, 0)
name="handles", component=handles.Handles, version=(3, 0, 0)
),
]
@@ -231,13 +231,11 @@ class DumpFiles(interfaces.plugins.PluginInterface):
)
type_map = handles_plugin.get_type_map(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
)
cookie = handles_plugin.find_cookie(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
)
dumped_files = set()
@@ -352,7 +350,6 @@ class DumpFiles(interfaces.plugins.PluginInterface):
offsets = list()
# a list of processes matching the pid filter. all files for these process(es) will be dumped.
procs = list()
kernel = self.context.modules[self.config["kernel"]]
if self.config["filter"] and (
self.config["virtaddr"] or self.config["physaddr"]
@@ -372,9 +369,8 @@ class DumpFiles(interfaces.plugins.PluginInterface):
[self.config.get("pid", None)]
)
procs = pslist.PsList.list_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
context=self.context,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
@@ -40,10 +40,10 @@ class Envars(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
]
@@ -58,13 +58,11 @@ class Envars(interfaces.plugins.PluginInterface):
"""
values = []
kernel = self.context.modules[self.config["kernel"]]
for hive 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,
kernel_module_name=self.config["kernel"],
hive_offsets=None,
):
## The global variables
@@ -219,7 +217,6 @@ class Envars(interfaces.plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
@@ -232,8 +229,7 @@ class Envars(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
@@ -14,7 +14,7 @@ class FileScan(interfaces.plugins.PluginInterface):
"""Scans for file objects present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
@@ -25,7 +25,7 @@ class FileScan(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0)
),
]
@@ -33,36 +33,32 @@ class FileScan(interfaces.plugins.PluginInterface):
def scan_files(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Scans for file objects 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
kernel_module_name: The name of the module for the kernel
Returns:
A list of File objects as found from the `layer_name` layer based on File pool signatures
"""
kernel = context.modules[kernel_module_name]
constraints = poolscanner.PoolScanner.builtin_constraints(
symbol_table, [b"Fil\xe5", b"File"]
kernel.symbol_table_name, [b"Fil\xe5", b"File"]
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, symbol_table, constraints
context, kernel_module_name, constraints
):
_constraint, mem_object, _header = result
yield mem_object
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
for fileobj in self.scan_files(
self.context, kernel.layer_name, kernel.symbol_table_name
):
for fileobj in self.scan_files(self.context, self.config["kernel"]):
try:
file_name = fileobj.FileName.String
except exceptions.InvalidAddressException:
@@ -69,18 +69,16 @@ class GetServiceSIDs(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
]
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
# Get the system hive
for hive 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,
kernel_module_name=self.config["kernel"],
filter_string="machine\\system",
hive_offsets=None,
):
@@ -84,10 +84,10 @@ class GetSIDs(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
]
@@ -101,14 +101,12 @@ class GetSIDs(interfaces.plugins.PluginInterface):
key = "Microsoft\\Windows NT\\CurrentVersion\\ProfileList"
val = "ProfileImagePath"
kernel = self.context.modules[self.config["kernel"]]
sids = {}
for hive 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,
kernel_module_name=self.config["kernel"],
filter_string="config\\software",
hive_offsets=None,
):
@@ -187,12 +185,14 @@ class GetSIDs(interfaces.plugins.PluginInterface):
except exceptions.InvalidAddressException:
token = False
task_name = objects.utility.array_to_string(task.ImageFileName)
if not token or not isinstance(token, interfaces.objects.ObjectInterface):
yield (
0,
[
int(task.UniqueProcessId),
str(task.ImageFileName),
task_name,
"Token unreadable",
"",
],
@@ -218,7 +218,7 @@ class GetSIDs(interfaces.plugins.PluginInterface):
0,
(
task.UniqueProcessId,
objects.utility.array_to_string(task.ImageFileName),
task_name,
sid_string,
sid_name,
),
@@ -226,15 +226,13 @@ class GetSIDs(interfaces.plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[("PID", int), ("Process", str), ("SID", str), ("Name", str)],
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
@@ -18,7 +18,7 @@ class Handles(interfaces.plugins.PluginInterface):
"""Lists process open handles."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 1)
_version = (3, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -36,10 +36,10 @@ class Handles(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="psscan", component=psscan.PsScan, version=(1, 1, 0)
name="psscan", component=psscan.PsScan, version=(2, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -78,7 +78,7 @@ class Handles(interfaces.plugins.PluginInterface):
except AttributeError:
# starting with windows 8
is_64bit = symbols.symbol_table_is_64bit(
self.context, kernel.symbol_table_name
context=self.context, symbol_table_name=kernel.symbol_table_name
)
if is_64bit:
@@ -121,8 +121,7 @@ class Handles(interfaces.plugins.PluginInterface):
def get_type_map(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> Dict[int, str]:
"""List the executive object types (_OBJECT_TYPE) using the
ObTypeIndexTable or ObpObjectTypes symbol (differs per OS). This method
@@ -144,21 +143,16 @@ class Handles(interfaces.plugins.PluginInterface):
type_map: Dict[int, str] = {}
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ntkrnlmp = context.modules[kernel_module_name]
try:
table_addr = ntkrnlmp.get_symbol("ObTypeIndexTable").address
except exceptions.SymbolError:
table_addr = ntkrnlmp.get_symbol("ObpObjectTypes").address
trans_layer = context.layers[layer_name]
trans_layer = context.layers[ntkrnlmp.layer_name]
if not trans_layer.is_valid(kvo + table_addr):
if not trans_layer.is_valid(ntkrnlmp.offset + table_addr):
return type_map
ptrs = ntkrnlmp.object(
@@ -176,7 +170,7 @@ class Handles(interfaces.plugins.PluginInterface):
try:
objt = ptr.dereference().cast(
symbol_table + constants.BANG + "_OBJECT_TYPE"
ntkrnlmp.symbol_table_name + constants.BANG + "_OBJECT_TYPE"
)
type_name = objt.Name.String
except exceptions.InvalidAddressException:
@@ -194,27 +188,21 @@ class Handles(interfaces.plugins.PluginInterface):
def find_cookie(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> Optional[interfaces.objects.ObjectInterface]:
"""Find the ObHeaderCookie value (if it exists)"""
kernel = context.modules[kernel_module_name]
try:
offset = context.symbol_space.get_symbol(
symbol_table + constants.BANG + "ObHeaderCookie"
).address
symbol_offset = kernel.get_symbol("ObHeaderCookie").address
except exceptions.SymbolError:
vollog.debug('Unable to get symbol information for "ObHeaderCookie"')
return None
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
return context.object(
symbol_table + constants.BANG + "unsigned int",
layer_name,
offset=kvo + offset,
return kernel.object(
"unsigned int",
offset=symbol_offset,
)
def _make_handle_array(self, offset, level, depth=0):
@@ -223,24 +211,17 @@ class Handles(interfaces.plugins.PluginInterface):
kernel = self.context.modules[self.config["kernel"]]
virtual = kernel.layer_name
kvo = kernel.offset
ntkrnlmp = self.context.module(
kernel.symbol_table_name, layer_name=virtual, offset=kvo
)
if level > 0:
subtype = ntkrnlmp.get_type("pointer")
subtype = kernel.get_type("pointer")
count = 0x1000 / subtype.size
else:
subtype = ntkrnlmp.get_type("_HANDLE_TABLE_ENTRY")
subtype = kernel.get_type("_HANDLE_TABLE_ENTRY")
count = 0x1000 / subtype.size
if not self.context.layers[virtual].is_valid(offset):
if not self.context.layers[kernel.layer_name].is_valid(offset):
return None
table = ntkrnlmp.object(
table = kernel.object(
object_type="array",
offset=offset,
subtype=subtype,
@@ -248,7 +229,7 @@ class Handles(interfaces.plugins.PluginInterface):
absolute=True,
)
layer_object = self.context.layers[virtual]
layer_object = self.context.layers[kernel.layer_name]
masked_offset = offset & layer_object.maximum_address
for i in range(len(table)):
@@ -262,7 +243,7 @@ class Handles(interfaces.plugins.PluginInterface):
# The code above this calls `is_valid` on the `offset`
# It is sent but then does not validate `entry` before
# sending it to `_get_item`
if not self.context.layers[virtual].is_valid(entry.vol.offset):
if not self.context.layers[kernel.layer_name].is_valid(entry.vol.offset):
continue
if level > 0:
@@ -305,18 +286,12 @@ class Handles(interfaces.plugins.PluginInterface):
yield from self._make_handle_array(TableCode, table_levels)
def _generator(self, procs):
kernel = self.context.modules[self.config["kernel"]]
type_map = self.get_type_map(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
context=self.context, kernel_module_name=self.config["kernel"]
)
cookie = self.find_cookie(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
context=self.context, kernel_module_name=self.config["kernel"]
)
for proc in procs:
@@ -383,8 +358,7 @@ class Handles(interfaces.plugins.PluginInterface):
if self.config["offset"]:
procs = psscan.PsScan.scan_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
filter_func=psscan.PsScan.create_offset_filter(
self.context,
kernel.layer_name,
@@ -394,8 +368,7 @@ class Handles(interfaces.plugins.PluginInterface):
else:
procs = pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
@@ -9,7 +9,7 @@ from typing import List, Optional, Tuple
from Crypto.Cipher import AES, ARC4, DES
from volatility3.framework import interfaces, renderers, constants
from volatility3.framework import interfaces, renderers, exceptions, constants
from volatility3.framework.configuration import requirements
from volatility3.framework.exceptions import InvalidAddressException
from volatility3.framework.symbols.windows.extensions import registry
@@ -22,7 +22,7 @@ class Hashdump(interfaces.plugins.PluginInterface):
"""Dumps user hashes from memory"""
_required_framework_version = (2, 0, 0)
_version = (1, 1, 0)
_version = (1, 1, 1)
@classmethod
def get_requirements(cls):
@@ -33,7 +33,7 @@ class Hashdump(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
]
@@ -327,7 +327,9 @@ class Hashdump(interfaces.plugins.PluginInterface):
empty_nt = b"\x31\xd6\xcf\xe0\xd1\x6a\xe9\x31\xb7\x3c\x59\xd7\xe0\xc0\x89\xc0"
@classmethod
def get_hive_key(cls, hive: registry.RegistryHive, key: str):
def get_hive_key(
cls, hive: registry.RegistryHive, key: str
) -> Optional["registry.CM_KEY_NODE"]:
result = None
try:
if hive:
@@ -352,6 +354,9 @@ class Hashdump(interfaces.plugins.PluginInterface):
@classmethod
def get_bootkey(cls, syshive: registry.RegistryHive) -> Optional[bytes]:
"""
Returns the scrambled bootkey necesary to decrypt hashes
"""
cs = 1
lsa_base = f"ControlSet{cs:03}" + "\\Control\\Lsa"
lsa_keys = ["JD", "Skew1", "GBG", "Data"]
@@ -367,7 +372,10 @@ class Hashdump(interfaces.plugins.PluginInterface):
key = cls.get_hive_key(syshive, lsa_base + "\\" + lk)
class_data = None
if key:
class_data = syshive.read(key.Class + 4, key.ClassLength)
try:
class_data = syshive.read(key.Class + 4, key.ClassLength)
except exceptions.InvalidAddressException:
return None
if class_data is None:
return None
@@ -401,7 +409,11 @@ class Hashdump(interfaces.plugins.PluginInterface):
sam_data = None
for v in sam_account_key.get_values():
if v.get_name() == "F":
sam_data = samhive.read(v.Data + 4, v.DataLength)
try:
sam_data = samhive.read(v.Data + 4, v.DataLength)
except exceptions.InvalidAddressException:
return None
if not sam_data:
return None
@@ -450,11 +462,12 @@ class Hashdump(interfaces.plugins.PluginInterface):
return None
sam_data = None
for v in user.get_values():
try:
if v.get_name() == "V":
if v.get_name() == "V":
try:
sam_data = samhive.read(v.Data + 4, v.DataLength)
except (InvalidAddressException, registry.RegistryInvalidIndex):
continue
except (exceptions.InvalidAddressException, registry.RegistryHive):
return None
if not sam_data:
return None
@@ -556,7 +569,11 @@ class Hashdump(interfaces.plugins.PluginInterface):
value = None
for v in user.get_values():
if v.get_name() == "V":
value = samhive.read(v.Data + 4, v.DataLength)
try:
value = samhive.read(v.Data + 4, v.DataLength)
except exceptions.InvalidAddressException:
return None
if not value:
return None
@@ -603,12 +620,10 @@ class Hashdump(interfaces.plugins.PluginInterface):
offset = self.config.get("offset", None)
syshive = None
samhive = None
kernel = self.context.modules[self.config["kernel"]]
for hive in hivelist.HiveList.list_hives(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
context=self.context,
base_config_path=self.config_path,
kernel_module_name=self.config["kernel"],
hive_offsets=None if offset is None else [offset],
):
if hive.get_name().split("\\")[-1].upper() == "SYSTEM":
@@ -48,7 +48,7 @@ class HollowProcesses(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0)
@@ -205,7 +205,6 @@ class HollowProcesses(interfaces.plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
@@ -216,8 +215,7 @@ class HollowProcesses(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
+18 -9
View File
@@ -28,7 +28,7 @@ class IAT(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -69,11 +69,23 @@ class IAT(interfaces.plugins.PluginInterface):
layer_name=proc_layer_name,
)
for offset, data in dos_header.reconstruct():
pe_data.seek(offset)
pe_data.write(data)
try:
for offset, data in dos_header.reconstruct():
pe_data.seek(offset)
pe_data.write(data)
except (exceptions.InvalidAddressException, ValueError) as excp:
vollog.warning(
f"Exception triggered when reconstructing PE file for process {proc.UniqueProcessId} at address {peb.ImageBaseAddress:#x} due to {excp}. Output file may be corrupt and/or truncated."
)
try:
pe_obj = pefile.PE(data=pe_data.getvalue(), fast_load=True)
except pefile.PEFormatError as excp:
vollog.debug(
f"Exception triggered when creating PE file object for process {proc.UniqueProcessId} at address {peb.ImageBaseAddress:#x} due to {excp}. Unable to extract file."
)
continue
pe_obj = pefile.PE(data=pe_data.getvalue(), fast_load=True)
pe_obj.parse_data_directories(
[pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_IMPORT"]]
)
@@ -126,8 +138,6 @@ class IAT(interfaces.plugins.PluginInterface):
continue
def run(self):
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
("PID", int),
@@ -140,8 +150,7 @@ class IAT(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=pslist.PsList.create_pid_filter(
self.config.get("pid", None)
),
@@ -9,7 +9,7 @@ from typing import List, Optional
from volatility3.framework import interfaces, exceptions
from volatility3.framework.configuration import requirements
from volatility3.plugins import yarascan
from volatility3.plugins.windows import pslist, direct_system_calls
from volatility3.plugins.windows import direct_system_calls
vollog = logging.getLogger(__name__)
@@ -43,9 +43,6 @@ class IndirectSystemCalls(direct_system_calls.DirectSystemCalls):
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="yarascanner", component=yarascan.YaraScanner, version=(2, 1, 0)
),
@@ -55,7 +52,7 @@ class IndirectSystemCalls(direct_system_calls.DirectSystemCalls):
requirements.PluginRequirement(
name="direct_system_calls",
plugin=direct_system_calls.DirectSystemCalls,
version=(1, 0, 0),
version=(2, 0, 0),
),
]
+42 -35
View File
@@ -17,7 +17,7 @@ class Info(plugins.PluginInterface):
"""Show OS & kernel details of the memory sample being analyzed."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -56,6 +56,9 @@ class Info(plugins.PluginInterface):
# FileLayer won't have dependencies
pass
# FIXME - this needs to be deprecated. This is exactly the same
# as getting it from context.modules
# Deprecation warning will go once the API is overhauled
@classmethod
def get_kernel_module(
cls,
@@ -80,13 +83,12 @@ class Info(plugins.PluginInterface):
cls,
context: interfaces.context.ContextInterface,
config_path: str,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> interfaces.objects.ObjectInterface:
"""Returns the KDDEBUGGER_DATA64 structure for a kernel"""
ntkrnlmp = cls.get_kernel_module(context, layer_name, symbol_table)
ntkrnlmp = context.modules[kernel_module_name]
native_types = context.symbol_space[symbol_table].natives
native_types = context.symbol_space[ntkrnlmp.symbol_table_name].natives
kdbg_offset = ntkrnlmp.get_symbol("KdDebuggerDataBlock").address
@@ -102,7 +104,7 @@ class Info(plugins.PluginInterface):
kdbg_obj = context.object(
kdbg_table_name + constants.BANG + "_KDDEBUGGER_DATA64",
offset=ntkrnlmp.offset + kdbg_offset,
layer_name=layer_name,
layer_name=ntkrnlmp.layer_name,
)
return kdbg_obj
@@ -111,16 +113,15 @@ class Info(plugins.PluginInterface):
def get_kuser_structure(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> interfaces.objects.ObjectInterface:
"""Returns the _KUSER_SHARED_DATA structure for a kernel"""
virtual_layer = context.layers[layer_name]
ntkrnlmp = context.modules[kernel_module_name]
virtual_layer = context.layers[ntkrnlmp.layer_name]
if not isinstance(virtual_layer, layers.intel.Intel):
raise TypeError("Virtual Layer is not an intel layer")
ntkrnlmp = cls.get_kernel_module(context, layer_name, symbol_table)
# this is a hard-coded address in the Windows OS
if virtual_layer.bits_per_register == 32:
kuser_addr = 0xFFDF0000
@@ -129,7 +130,6 @@ class Info(plugins.PluginInterface):
kuser = ntkrnlmp.object(
object_type="_KUSER_SHARED_DATA",
layer_name=layer_name,
offset=kuser_addr,
absolute=True,
)
@@ -140,17 +140,15 @@ class Info(plugins.PluginInterface):
def get_version_structure(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> interfaces.objects.ObjectInterface:
"""Returns the KdVersionBlock information from a kernel"""
ntkrnlmp = cls.get_kernel_module(context, layer_name, symbol_table)
ntkrnlmp = context.modules[kernel_module_name]
vers_offset = ntkrnlmp.get_symbol("KdVersionBlock").address
vers = ntkrnlmp.object(
object_type="_DBGKD_GET_VERSION64",
layer_name=layer_name,
offset=vers_offset,
)
@@ -193,28 +191,38 @@ class Info(plugins.PluginInterface):
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
layer_name = kernel.layer_name
symbol_table = kernel.symbol_table_name
layer = self.context.layers[layer_name]
table = self.context.symbol_space[symbol_table]
kernel_layer = self.context.layers[kernel.layer_name]
symbol_table = self.context.symbol_space[kernel.symbol_table_name]
kdbg = self.get_kdbg_structure(
self.context, self.config_path, layer_name, symbol_table
self.context,
self.config_path,
self.config["kernel"],
)
yield (0, ("Kernel Base", hex(layer.config["kernel_virtual_offset"])))
yield (0, ("DTB", hex(layer.config["page_map_offset"])))
yield (0, ("Symbols", table.config["isf_url"]))
yield (0, ("Kernel Base", hex(kernel_layer.config["kernel_virtual_offset"])))
yield (0, ("DTB", hex(kernel_layer.config["page_map_offset"])))
yield (0, ("Symbols", symbol_table.config["isf_url"]))
yield (
0,
("Is64Bit", str(symbols.symbol_table_is_64bit(self.context, symbol_table))),
(
"Is64Bit",
str(
symbols.symbol_table_is_64bit(
context=self.context, symbol_table_name=kernel.symbol_table_name
)
),
),
)
yield (
0,
("IsPAE", str(self.context.layers[layer_name].metadata.get("pae", False))),
(
"IsPAE",
str(self.context.layers[kernel.layer_name].metadata.get("pae", False)),
),
)
for i, layer in self.get_depends(self.context, layer_name):
for i, layer in self.get_depends(self.context, kernel.layer_name):
yield (0, (layer.name, f"{i} {layer.__class__.__name__}"))
if kdbg.Header.OwnerTag == 0x4742444B:
@@ -222,23 +230,22 @@ class Info(plugins.PluginInterface):
yield (0, ("NTBuildLab", kdbg.get_build_lab()))
yield (0, ("CSDVersion", str(kdbg.get_csdversion())))
vers = self.get_version_structure(self.context, layer_name, symbol_table)
vers = self.get_version_structure(self.context, self.config["kernel"])
yield (0, ("KdVersionBlock", hex(vers.vol.offset)))
yield (0, ("Major/Minor", f"{vers.MajorVersion}.{vers.MinorVersion}"))
yield (0, ("MachineType", str(vers.MachineType)))
ntkrnlmp = self.get_kernel_module(self.context, layer_name, symbol_table)
cpu_count_offset = kernel.get_symbol("KeNumberProcessors").address
cpu_count_offset = ntkrnlmp.get_symbol("KeNumberProcessors").address
cpu_count = ntkrnlmp.object(
object_type="unsigned int", layer_name=layer_name, offset=cpu_count_offset
cpu_count = kernel.object(
object_type="unsigned int",
offset=cpu_count_offset,
)
yield (0, ("KeNumberProcessors", str(cpu_count)))
kuser = self.get_kuser_structure(self.context, layer_name, symbol_table)
kuser = self.get_kuser_structure(self.context, self.config["kernel"])
yield (0, ("SystemTime", str(kuser.SystemTime.get_time())))
yield (
@@ -259,7 +266,7 @@ class Info(plugins.PluginInterface):
# yield (0, ("SafeBootMode", "True" if kuser.SafeBootMode else "False"))
nt_header = self.get_ntheader_structure(
self.context, self.config_path, layer_name
self.context, self.config_path, kernel.layer_name
)
yield (
@@ -36,16 +36,18 @@ class JobLinks(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
]
def _generator(self) -> Iterator[Tuple]:
kernel = self.context.modules[self.config["kernel"]]
memory = self.context.layers[kernel.layer_name]
for proc in pslist.PsList.list_processes(
self.context, kernel.layer_name, kernel.symbol_table_name
context=self.context,
kernel_module_name=self.config["kernel"],
):
try:
if not self.config["physical"]:
+34 -24
View File
@@ -4,7 +4,7 @@
import logging
from typing import Iterator, List, Tuple
from typing import Iterator, Generator, List, Tuple
from volatility3.framework import (
renderers,
@@ -22,7 +22,7 @@ class KPCRs(interfaces.plugins.PluginInterface):
"""Print KPCR structure for each processor"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -39,60 +39,70 @@ class KPCRs(interfaces.plugins.PluginInterface):
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
layer_name: str,
symbol_table: str,
) -> interfaces.objects.ObjectInterface:
) -> Generator[Tuple[interfaces.objects.ObjectInterface, int], None, None]:
"""Returns the KPCR structure for each processor
Args:
context: The context to retrieve required elements (layers, symbol tables) from
kernel_module_name: The name of the kernel module on which to operate
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
Returns:
The _KPCR structure for each processor
"""
kernel = context.modules[kernel_module_name]
kernel_layer = context.layers[kernel.layer_name]
kpcr_type = kernel.get_type("_KPCR")
reloff = kpcr_type.relative_child_offset("Prcb")
if kpcr_type.has_member("CurrentPrcb"):
kpcr_member = "CurrentPrcb"
else:
kpcr_member = "Prcb"
cpu_count_offset = kernel.get_symbol("KeNumberProcessors").address
cpu_count = kernel.object(
object_type="unsigned int", layer_name=layer_name, offset=cpu_count_offset
object_type="unsigned int",
layer_name=kernel_layer.name,
offset=cpu_count_offset,
)
processor_block = kernel.object(
object_type="pointer",
layer_name=layer_name,
layer_name=kernel_layer.name,
offset=kernel.get_symbol("KiProcessorBlock").address,
)
processor_pointers = utility.array_of_pointers(
context=context,
array=processor_block,
count=cpu_count,
subtype=symbol_table + constants.BANG + "_KPRCB",
subtype=kernel.symbol_table_name + constants.BANG + "_KPRCB",
)
for pointer in processor_pointers:
kprcb = pointer.dereference()
reloff = kernel.get_type("_KPCR").relative_child_offset("Prcb")
kpcr = context.object(
symbol_table + constants.BANG + "_KPCR",
offset=kprcb.vol.offset - reloff,
layer_name=layer_name,
)
yield kpcr
object_address = kprcb.vol.offset - reloff
if not kernel_layer.is_valid(kprcb.vol.offset):
continue
kpcr = kernel.object("_KPCR", offset=object_address, absolute=True)
yield kpcr, kpcr.member(kpcr_member)
def _generator(self) -> Iterator[Tuple]:
kernel = self.context.modules[self.config["kernel"]]
layer_name = kernel.layer_name
symbol_table = kernel.symbol_table_name
for kpcr in self.list_kpcrs(
self.context, self.config["kernel"], layer_name, symbol_table
):
for kpcr, current_prcb in self.list_kpcrs(self.context, self.config["kernel"]):
yield (
0,
(
format_hints.Hex(kpcr.vol.offset),
format_hints.Hex(kpcr.CurrentPrcb),
format_hints.Hex(current_prcb),
),
)
@@ -29,7 +29,7 @@ class LdrModules(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0)
@@ -107,7 +107,6 @@ class LdrModules(interfaces.plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
@@ -122,8 +121,7 @@ class LdrModules(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
@@ -16,6 +16,7 @@ from volatility3.framework.layers import registry
from volatility3.framework.symbols.windows import versions
from volatility3.plugins.windows import hashdump
from volatility3.plugins.windows.registry import hivelist
from volatility3.framework.renderers import format_hints
vollog = logging.getLogger(__name__)
@@ -24,7 +25,7 @@ class Lsadump(interfaces.plugins.PluginInterface):
"""Dumps lsa secrets from memory"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
_version = (1, 0, 1)
@classmethod
def get_requirements(cls):
@@ -38,7 +39,7 @@ class Lsadump(interfaces.plugins.PluginInterface):
name="hashdump", component=hashdump.Hashdump, version=(1, 1, 0)
),
requirements.VersionRequirement(
name="hivelist", component=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", component=hivelist.HiveList, version=(2, 0, 0)
),
]
@@ -78,8 +79,7 @@ class Lsadump(interfaces.plugins.PluginInterface):
enc_reg_key = hashdump.Hashdump.get_hive_key(sechive, "Policy\\" + policy_key)
if not enc_reg_key:
return None
enc_reg_value = next(enc_reg_key.get_values())
enc_reg_value = next(enc_reg_key.get_values(), None)
if not enc_reg_value:
return None
@@ -114,7 +114,7 @@ class Lsadump(interfaces.plugins.PluginInterface):
name: str,
lsakey: bytes,
is_vista_or_later: bool,
):
) -> Optional[bytes]:
enc_secret_key = hashdump.Hashdump.get_hive_key(
sechive, "Policy\\Secrets\\" + name + "\\CurrVal"
)
@@ -122,14 +122,18 @@ class Lsadump(interfaces.plugins.PluginInterface):
secret = None
if enc_secret_key:
try:
enc_secret_value = next(enc_secret_key.get_values())
enc_secret_value = next(enc_secret_key.get_values(), None)
except (InvalidAddressException, registry.RegistryFormatException, registry.RegistryInvalidIndex):
enc_secret_value = None
if enc_secret_value:
enc_secret = sechive.read(
enc_secret_value.Data + 4, enc_secret_value.DataLength
)
try:
enc_secret = sechive.read(
enc_secret_value.Data + 4, enc_secret_value.DataLength
)
except exceptions.InvalidAddressExceptions:
return None
if enc_secret:
if not is_vista_or_later:
secret = cls.decrypt_secret(enc_secret[0xC:], lsakey)
@@ -139,7 +143,7 @@ class Lsadump(interfaces.plugins.PluginInterface):
return secret
@classmethod
def decrypt_secret(cls, secret: bytes, key: bytes):
def decrypt_secret(cls, secret: bytes, key: bytes) -> bytes:
"""Python implementation of SystemFunction005.
Decrypts a block of data with DES using given key.
@@ -197,18 +201,20 @@ class Lsadump(interfaces.plugins.PluginInterface):
continue
try:
enc_secret_value = next(sec_val_key.get_values())
enc_secret_value = next(sec_val_key.get_values(), None)
except (StopIteration, InvalidAddressException, registry.RegistryFormatException, registry.RegistryInvalidIndex):
enc_secret_value = None
if not enc_secret_value:
continue
enc_secret = sechive.read(
enc_secret_value.Data + 4, enc_secret_value.DataLength
)
if not enc_secret:
try:
enc_secret = sechive.read(
enc_secret_value.Data + 4, enc_secret_value.DataLength
)
except exceptions.InvalidAddressExceptions:
continue
if not vista_or_later:
secret = self.decrypt_secret(enc_secret[0xC:], lsakey)
else:
@@ -219,18 +225,17 @@ class Lsadump(interfaces.plugins.PluginInterface):
except (InvalidAddressException, registry.RegistryFormatException, registry.RegistryInvalidIndex):
key_name = renderers.UnreadableValue()
yield (0, (key_name, secret.decode("latin1"), secret))
yield (0, (key_name, format_hints.HexBytes(secret), secret))
def run(self):
offset = self.config.get("offset", None)
syshive = sechive = None
kernel = self.context.modules[self.config["kernel"]]
for hive in hivelist.HiveList.list_hives(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
context=self.context,
base_config_path=self.config_path,
kernel_module_name=self.config["kernel"],
hive_offsets=None if offset is None else [offset],
):
if hive.get_name().split("\\")[-1].upper() == "SYSTEM":
@@ -239,6 +244,6 @@ class Lsadump(interfaces.plugins.PluginInterface):
sechive = hive
return renderers.TreeGrid(
[("Key", str), ("Secret", str), ("Hex", bytes)],
[("Key", str), ("Secret", format_hints.HexBytes), ("Hex", bytes)],
self._generator(syshive, sechive),
)
@@ -41,7 +41,7 @@ class Malfind(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0)
@@ -172,7 +172,7 @@ class Malfind(interfaces.plugins.PluginInterface):
}
is_32bit_arch = not symbols.symbol_table_is_64bit(
self.context, kernel.symbol_table_name
context=self.context, symbol_table_name=kernel.symbol_table_name
)
for proc in procs:
@@ -238,7 +238,6 @@ class Malfind(interfaces.plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
@@ -258,8 +257,7 @@ class Malfind(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
@@ -53,7 +53,9 @@ class MBRScan(interfaces.plugins.PluginInterface):
layer = self.context.layers[physical_layer_name]
architecture = (
"intel"
if not symbols.symbol_table_is_64bit(self.context, kernel.symbol_table_name)
if not symbols.symbol_table_is_64bit(
context=self.context, symbol_table_name=kernel.symbol_table_name
)
else "intel64"
)
@@ -28,7 +28,7 @@ class Memmap(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.IntRequirement(
name="pid",
@@ -97,7 +97,6 @@ class Memmap(interfaces.plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_pid_filter([self.config.get("pid", None)])
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
@@ -110,8 +109,7 @@ class Memmap(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
@@ -15,7 +15,9 @@ class ModScan(modules.Modules):
"""Scans for modules present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 0)
# 3.0.0 changed the signature of enumeration methods (scan_modules)
_version = (3, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -30,10 +32,10 @@ class ModScan(modules.Modules):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="poolscanner", component=poolscanner.PoolScanner, version=(1, 0, 0)
name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(2, 0, 0)
name="modules", component=modules.Modules, version=(3, 0, 0)
),
requirements.BooleanRequirement(
name="dump",
@@ -53,7 +55,7 @@ class ModScan(modules.Modules):
default=None,
),
requirements.VersionRequirement(
name="pedump", component=pedump.PEDump, version=(1, 0, 0)
name="pedump", component=pedump.PEDump, version=(2, 0, 0)
),
]
@@ -61,26 +63,25 @@ class ModScan(modules.Modules):
def scan_modules(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Scans for modules 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
kernel_module_name: Name of the module for the kernel
Returns:
A list of Driver objects as found from the `layer_name` layer based on Driver pool signatures
A list of kernel module objects as found from the primary (kernel) layer based on module pool signatures
"""
kernel = context.modules[kernel_module_name]
constraints = poolscanner.PoolScanner.builtin_constraints(
symbol_table, [b"MmLd"]
kernel.symbol_table_name, [b"MmLd"]
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, symbol_table, constraints
context, kernel_module_name, constraints
):
_constraint, mem_object, _header = result
yield mem_object
@@ -2,7 +2,7 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import Generator, Iterable, List, Optional
from typing import Generator, Iterable, List, Optional, Dict, Tuple
from volatility3.framework import symbols, constants, exceptions, interfaces, renderers
from volatility3.framework.configuration import requirements
@@ -18,7 +18,9 @@ class Modules(interfaces.plugins.PluginInterface):
"""Lists the loaded kernel modules."""
_required_framework_version = (2, 0, 0)
_version = (2, 1, 0)
# 3.0.0 - changed signature of get_session_layers, added get_session_layers_map
_version = (3, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -33,7 +35,10 @@ class Modules(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="pedump", component=pedump.PEDump, version=(2, 0, 0)
),
requirements.BooleanRequirement(
name="dump",
@@ -52,9 +57,6 @@ class Modules(interfaces.plugins.PluginInterface):
optional=True,
default=None,
),
requirements.VersionRequirement(
name="pedump", component=pedump.PEDump, version=(1, 0, 0)
),
]
def dump_module(self, session_layers, pe_table_name, mod):
@@ -76,8 +78,6 @@ class Modules(interfaces.plugins.PluginInterface):
return file_output
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
pe_table_name = None
session_layers = None
@@ -92,12 +92,13 @@ class Modules(interfaces.plugins.PluginInterface):
session_layers = list(
self.get_session_layers(
self.context, kernel.layer_name, kernel.symbol_table_name
context=self.context,
kernel_module_name=self.config["kernel"],
)
)
for mod in self._enumeration_method(
self.context, kernel.layer_name, kernel.symbol_table_name
self.context, kernel_module_name=self.config["kernel"]
):
if self.config["base"] and self.config["base"] != mod.DllBase:
continue
@@ -139,7 +140,9 @@ class Modules(interfaces.plugins.PluginInterface):
module = context.modules[module_name]
# default is used if/when MmSystemRangeStart is paged out
if symbols.symbol_table_is_64bit(context, module.symbol_table_name):
if symbols.symbol_table_is_64bit(
context=context, symbol_table_name=module.symbol_table_name
):
object_type = "unsigned long long"
default_start = 0xFFFF800000000000
else:
@@ -163,33 +166,32 @@ class Modules(interfaces.plugins.PluginInterface):
return kernel_space_start & layer.address_mask
@classmethod
def get_session_layers(
def _do_get_session_layers(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
pids: Optional[List[int]] = None,
) -> Generator[str, None, None]:
) -> Generator[Tuple[int, str], None, None]:
"""Build a cache of possible virtual layers, in priority starting with
the primary/kernel layer. Then keep one layer per session by cycling
through the process list.
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
kernel_module_name: The name of the module for the kernel
pids: A list of process identifiers to include exclusively or None for no filter
Returns:
A list of session layer names
A generator of session layer names
"""
seen_ids: List[interfaces.objects.ObjectInterface] = []
filter_func = pslist.PsList.create_pid_filter(pids or [])
kernel = context.modules[kernel_module_name]
for proc in pslist.PsList.list_processes(
context=context,
layer_name=layer_name,
symbol_table=symbol_table,
kernel_module_name=kernel_module_name,
filter_func=filter_func,
):
proc_id = "Unknown"
@@ -201,8 +203,8 @@ class Modules(interfaces.plugins.PluginInterface):
# not all processes have a valid session pointer.
try:
session_space = context.object(
symbol_table + constants.BANG + "_MM_SESSION_SPACE",
layer_name=layer_name,
kernel.symbol_table_name + constants.BANG + "_MM_SESSION_SPACE",
layer_name=kernel.layer_name,
offset=proc.Session,
)
session_id = session_space.SessionId
@@ -218,8 +220,10 @@ class Modules(interfaces.plugins.PluginInterface):
# create an unsigned long at that offset and use that
# instead.
session_id = context.object(
layer_name=layer_name,
object_type=symbol_table + constants.BANG + "unsigned long",
layer_name=kernel.layer_name,
object_type=kernel.symbol_table_name
+ constants.BANG
+ "unsigned long",
offset=proc.Session + 8,
)
@@ -235,8 +239,46 @@ class Modules(interfaces.plugins.PluginInterface):
# save the layer if we haven't seen the session yet
seen_ids.append(session_id)
yield session_id, proc_layer_name
@classmethod
def get_session_layers(
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
pids: Optional[List[int]] = None,
) -> Generator[str, None, None]:
"""
Args:
context: The context to retrieve required elements (layers, symbol tables) from
kernel_module_name: The name of the module for the kernel
pids: A list of process identifiers to include exclusively or None for no filter
Yields the names of the unique memory layers that map sessions
"""
for _session_id, proc_layer_name in cls._do_get_session_layers(
context, kernel_module_name, pids
):
yield proc_layer_name
@classmethod
def get_session_layers_map(
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
pids: Optional[List[int]] = None,
) -> Dict[int, str]:
"""
Args:
context: The context to retrieve required elements (layers, symbol tables) from
kernel_module_name: The name of the module for the kernel
pids: A list of process identifiers to include exclusively or None for no filter
Wraps `_do_get_session_layers` to produce a dictionary where each key is a session_id
and the value is the name of the layer for that session
"""
return dict(cls._do_get_session_layers(context, kernel_module_name, pids))
@classmethod
def find_session_layer(
cls,
@@ -268,39 +310,35 @@ class Modules(interfaces.plugins.PluginInterface):
def list_modules(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Lists all the modules in the primary layer.
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
kernel_module_name: The name of the module for the kernel
Returns:
A list of Modules as retrieved from PsLoadedModuleList
"""
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
kernel = context.modules[kernel_module_name]
if not kernel.offset:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
try:
# use this type if its available (starting with windows 10)
ldr_entry_type = ntkrnlmp.get_type("_KLDR_DATA_TABLE_ENTRY")
ldr_entry_type = kernel.get_type("_KLDR_DATA_TABLE_ENTRY")
except exceptions.SymbolError:
ldr_entry_type = ntkrnlmp.get_type("_LDR_DATA_TABLE_ENTRY")
ldr_entry_type = kernel.get_type("_LDR_DATA_TABLE_ENTRY")
type_name = ldr_entry_type.type_name.split(constants.BANG)[1]
list_head = ntkrnlmp.get_symbol("PsLoadedModuleList").address
list_entry = ntkrnlmp.object(object_type="_LIST_ENTRY", offset=list_head)
list_head = kernel.get_symbol("PsLoadedModuleList").address
list_entry = kernel.object(object_type="_LIST_ENTRY", offset=list_head)
reloff = ldr_entry_type.relative_child_offset("InLoadOrderLinks")
module = ntkrnlmp.object(
module = kernel.object(
object_type=type_name, offset=list_entry.vol.offset - reloff, absolute=True
)
@@ -14,6 +14,7 @@ class MutantScan(interfaces.plugins.PluginInterface):
"""Scans for mutexes present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
@@ -24,7 +25,7 @@ class MutantScan(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0)
),
]
@@ -32,36 +33,32 @@ class MutantScan(interfaces.plugins.PluginInterface):
def scan_mutants(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Scans for mutants 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
kernel_module_name: The name of the module for the kernel
Returns:
A list of Mutant objects found by scanning memory for the Mutant pool signatures
"""
kernel = context.modules[kernel_module_name]
constraints = poolscanner.PoolScanner.builtin_constraints(
symbol_table, [b"Mut\xe1", b"Muta"]
kernel.symbol_table_name, [b"Mut\xe1", b"Muta"]
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, symbol_table, constraints
context, kernel_module_name, constraints
):
_constraint, mem_object, _header = result
yield mem_object
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
for mutant in self.scan_mutants(
self.context, kernel.layer_name, kernel.symbol_table_name
):
for mutant in self.scan_mutants(self.context, self.config["kernel"]):
try:
name = mutant.get_name()
except (ValueError, exceptions.InvalidAddressException):
@@ -23,7 +23,7 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Scans for network objects present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
@@ -34,10 +34,10 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="poolscanner", component=poolscanner.PoolScanner, version=(1, 0, 0)
name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="info", component=info.Info, version=(1, 0, 0)
name="info", component=info.Info, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="verinfo", component=verinfo.VerInfo, version=(1, 0, 0)
@@ -117,15 +117,13 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def determine_tcpip_version(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
nt_symbol_table: str,
kernel_module_name: str,
) -> Tuple[str, Type]:
"""Tries to determine which symbol filename to use for the image's tcpip driver. The logic is partially taken from the info plugin.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
nt_symbol_table: The name of the table containing the kernel symbols
kernel_module_name: Name of the module for the kernel
Returns:
The filename of the symbol table to use.
@@ -137,10 +135,14 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# therefore we determine the version based on the kernel version as testing
# with several windows versions has showed this to work out correctly.
is_64bit = symbols.symbol_table_is_64bit(context, nt_symbol_table)
kernel = context.modules[kernel_module_name]
is_64bit = symbols.symbol_table_is_64bit(
context=context, symbol_table_name=kernel.symbol_table_name
)
is_18363_or_later = versions.is_win10_18363_or_later(
context=context, symbol_table=nt_symbol_table
context=context, symbol_table=kernel.symbol_table_name
)
if is_64bit:
@@ -148,9 +150,9 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
else:
arch = "x86"
vers = info.Info.get_version_structure(context, layer_name, nt_symbol_table)
vers = info.Info.get_version_structure(context, kernel_module_name)
kuser = info.Info.get_kuser_structure(context, layer_name, nt_symbol_table)
kuser = info.Info.get_kuser_structure(context, kernel_module_name)
try:
vers_minor_version = int(vers.MinorVersion)
@@ -257,7 +259,7 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"Requiring further version inspection due to OS version by checking tcpip.sys's FileVersion header"
)
# the following is IntelLayer specific and might need to be adapted to other architectures.
physical_layer_name = context.layers[layer_name].config.get(
physical_layer_name = context.layers[kernel.layer_name].config.get(
"memory_layer", None
)
if physical_layer_name:
@@ -320,27 +322,26 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def create_netscan_symbol_table(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
nt_symbol_table: str,
kernel_module_name: str,
config_path: str,
) -> str:
"""Creates a symbol table for TCP Listeners and TCP/UDP Endpoints.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
nt_symbol_table: The name of the table containing the kernel symbols
kernel_module_name: Name of the module for the kernel
config_path: The config path where to find symbol files
Returns:
The name of the constructed symbol table
"""
table_mapping = {"nt_symbols": nt_symbol_table}
kernel = context.modules[kernel_module_name]
table_mapping = {"nt_symbols": kernel.symbol_table_name}
symbol_filename, class_types = cls.determine_tcpip_version(
context,
layer_name,
nt_symbol_table,
kernel_module_name,
)
return intermed.IntermediateSymbolTable.create(
@@ -356,16 +357,14 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def scan(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
nt_symbol_table: str,
kernel_module_name: str,
netscan_symbol_table: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Scans for network objects 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
nt_symbol_table: The name of the table containing the kernel symbols
kernel_module_name: The name of the module for the kernel
netscan_symbol_table: The name of the table containing the network object symbols (_TCP_LISTENER etc.)
Returns:
@@ -375,7 +374,7 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
constraints = cls.create_netscan_constraints(context, netscan_symbol_table)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, nt_symbol_table, constraints
context, kernel_module_name, constraints
):
_constraint, mem_object, _header = result
yield mem_object
@@ -383,16 +382,13 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def _generator(self, show_corrupt_results: Optional[bool] = None):
"""Generates the network objects for use in rendering."""
kernel = self.context.modules[self.config["kernel"]]
netscan_symbol_table = self.create_netscan_symbol_table(
self.context, kernel.layer_name, kernel.symbol_table_name, self.config_path
self.context, self.config["kernel"], self.config_path
)
for netw_obj in self.scan(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
netscan_symbol_table,
):
vollog.debug(
@@ -21,7 +21,9 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Traverses network tracking structures present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
# 2.0.0 changed the signature of `get_tcpip_module`
_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
@@ -32,16 +34,16 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="netscan", component=netscan.NetScan, version=(1, 0, 0)
name="netscan", component=netscan.NetScan, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(2, 0, 0)
name="modules", component=modules.Modules, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="pdbutil", component=pdbutil.PDBUtility, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="info", component=info.Info, version=(1, 0, 0)
name="info", component=info.Info, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="verinfo", component=verinfo.VerInfo, version=(1, 0, 0)
@@ -234,20 +236,18 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def get_tcpip_module(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
nt_symbols: str,
kernel_module_name: str,
) -> Optional[interfaces.objects.ObjectInterface]:
"""Uses `windows.modules` to find tcpip.sys in memory.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
nt_symbols: The name of the table containing the kernel symbols
kernel_module_name: The name of the module for the kernel
Returns:
The constructed tcpip.sys module object.
"""
for mod in modules.Modules.list_modules(context, layer_name, nt_symbols):
for mod in modules.Modules.list_modules(context, kernel_module_name):
if mod.BaseDllName.get_string() == "tcpip.sys":
vollog.debug(f"Found tcpip.sys image base @ 0x{mod.DllBase:x}")
return mod
@@ -319,7 +319,9 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
Returns:
The list of TCP endpoint objects from the `layer_name` layer's `PartitionTable`
"""
if symbols.symbol_table_is_64bit(context, net_symbol_table):
if symbols.symbol_table_is_64bit(
context=context, symbol_table_name=net_symbol_table
):
alignment = 0x10
else:
alignment = 8
@@ -627,12 +629,10 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
kernel = self.context.modules[self.config["kernel"]]
netscan_symbol_table = netscan.NetScan.create_netscan_symbol_table(
self.context, kernel.layer_name, kernel.symbol_table_name, self.config_path
self.context, self.config["kernel"], self.config_path
)
tcpip_module = self.get_tcpip_module(
self.context, kernel.layer_name, kernel.symbol_table_name
)
tcpip_module = self.get_tcpip_module(self.context, self.config["kernel"])
if not tcpip_module:
vollog.error("Unable to locate symbols for the memory image's tcpip module")
@@ -647,6 +647,11 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
)
except exceptions.VolatilityException:
vollog.error("Unable to locate symbols for the memory image's tcpip module")
return
if not tcpip_symbol_table:
vollog.error("Unable to reconstruct symbol table for tcpip.sys")
return
for netw_obj in self.list_sockets(
self.context,
@@ -16,7 +16,9 @@ class Threads(thrdscan.ThrdScan):
"""Lists process threads"""
_required_framework_version = (2, 4, 0)
_version = (1, 0, 0)
# 2.0.0 - changed the signature of `list_orphan_kernel_threads`
_version = (2, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -35,10 +37,10 @@ class Threads(thrdscan.ThrdScan):
name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 1, 0)
),
requirements.PluginRequirement(
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
name="ssdt", plugin=ssdt.SSDT, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="modules", plugin=modules.Modules, version=(2, 1, 0)
name="modules", plugin=modules.Modules, version=(3, 0, 0)
),
]
@@ -46,7 +48,7 @@ class Threads(thrdscan.ThrdScan):
def list_orphan_kernel_threads(
cls,
context: interfaces.context.ContextInterface,
module_name: str,
kernel_module_name: str,
) -> Generator[interfaces.objects.ObjectInterface, None, None]:
"""Yields thread objects of kernel threads that do not map to a module
@@ -57,19 +59,16 @@ class Threads(thrdscan.ThrdScan):
Returns:
A generator of thread objects of orphaned threads
"""
module = context.modules[module_name]
layer_name = module.layer_name
symbol_table_name = module.symbol_table_name
collection = ssdt.SSDT.build_module_collection(
context, layer_name, symbol_table_name
context=context,
kernel_module_name=kernel_module_name,
)
kernel_space_start = modules.Modules.get_kernel_space_start(
context, module_name
context, kernel_module_name
)
for thread in thrdscan.ThrdScan.scan_threads(context, module_name):
for thread in thrdscan.ThrdScan.scan_threads(context, kernel_module_name):
# We don't want smeared or terminated threads
# So we access the owning process (which could also be terminated or smeared)
# Plus check the start address holding page
@@ -1,7 +1,6 @@
# 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 copy
import io
import logging
import ntpath
@@ -11,7 +10,7 @@ from typing import Dict, Tuple, Optional, List, Generator, Union, Callable
import pefile
from volatility3.framework import interfaces, exceptions
from volatility3.framework import renderers, constants
from volatility3.framework import renderers, constants, objects
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import intermed
@@ -244,7 +243,9 @@ class PESymbols(interfaces.plugins.PluginInterface):
_required_framework_version = (2, 7, 0)
_version = (1, 1, 0)
# 2.0.0 - changed signature of get_kernel_modules, get_all_vads_with_file_paths, addresses_for_process_symbols, get_process_modules
# 3.0.0 - find_symbols wil now throw a ValueError if the provided wanted symbol information does not follow the spec
_version = (3, 0, 0)
# used for special handling of the kernel PDB file. See later notes
os_module_name = "ntoskrnl.exe"
@@ -259,10 +260,10 @@ class PESymbols(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(2, 0, 0)
name="modules", component=modules.Modules, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="pdbutil", component=pdbutil.PDBUtility, version=(1, 0, 0)
@@ -297,7 +298,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
cls,
context: interfaces.context.ContextInterface,
pe_table_name: str,
layer_name: str,
process_layer_name: str,
base_address: int,
) -> Optional[pefile.PE]:
"""
@@ -305,7 +306,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
Args:
pe_table_name: name of the pe types table
layer_name: name of the process layer
process_layer_name: name of the process layer
base_address: base address of the module
Returns:
@@ -317,7 +318,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
dos_header = context.object(
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
offset=base_address,
layer_name=layer_name,
layer_name=process_layer_name,
)
for offset, data in dos_header.reconstruct():
@@ -326,7 +327,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
pe_ret = pefile.PE(data=pe_data.getvalue(), fast_load=True)
except exceptions.InvalidAddressException:
except (exceptions.InvalidAddressException, ValueError):
pe_ret = None
return pe_ret
@@ -388,8 +389,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
cls,
context: interfaces.context.ContextInterface,
config_path: str,
layer_name: str,
symbol_table_name: str,
kernel_module_name: str,
symbols: filter_modules_type,
) -> found_symbols_type:
"""
@@ -405,7 +405,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
found_symbols_type: The dictionary of symbols that were resolved
"""
collected_modules = PESymbols.get_process_modules(
context, layer_name, symbol_table_name, symbols
context, kernel_module_name, symbols
)
found_symbols, missing_symbols = PESymbols.find_symbols(
@@ -483,12 +483,12 @@ class PESymbols(interfaces.plugins.PluginInterface):
instance for it
"""
layer_name = module_info[0]
process_layer_name = module_info[0]
module_start = module_info[1]
# we need a valid PE with an export table
pe_module = PESymbols.get_pefile_obj(
context, pe_table_name, layer_name, module_start
context, pe_table_name, process_layer_name, module_start
)
if not pe_module:
return None
@@ -500,7 +500,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
return None
return ExportSymbolFinder(
layer_name,
process_layer_name,
mod_name.lower(),
module_start,
pe_module.DIRECTORY_ENTRY_EXPORT.symbols,
@@ -671,6 +671,53 @@ class PESymbols(interfaces.plugins.PluginInterface):
else:
yield symbol_key, value_index, symbol_value, wanted_value # type: ignore
@classmethod
def _validate_wanted_modules(
cls,
wanted: PESymbolFinder.cached_module_lists,
) -> Optional[PESymbolFinder.cached_module_lists]:
"""
Validates and makes a copy of the address(es) and/or name(s) wanted from a particular module
Throws ValueError if invalid values found
"""
remaining: PESymbolFinder.cached_module_lists = {}
valid_name_types = [str]
valid_address_types = [int, objects.Pointer]
for wanted_type, wanted_symbols in wanted.items():
if wanted_type not in [
wanted_names_identifier,
wanted_addresses_identifier,
]:
raise ValueError(
f"The symbol type specified ({wanted_type}) is not valid. Values choices: {wanted_names_identifier}, {wanted_addresses_identifier}"
)
remaining[wanted_type] = []
# symbol_info will be a symbol name or address requested
for symbol_info in wanted_symbols:
if (
wanted_type == wanted_names_identifier
and type(symbol_info) not in valid_name_types
):
raise ValueError(
f"The requested symbol name has a type of {type(symbol_info)} which is not in the allowed set of {valid_name_types}"
)
elif (
wanted_type == wanted_addresses_identifier
and type(symbol_info) not in valid_address_types
):
raise ValueError(
f"The requested address has a type of {type(symbol_info)} which is not in the allowed set of {valid_address_types}"
)
remaining[wanted_type].append(symbol_info)
return remaining
@staticmethod
def _resolve_symbols_through_methods(
context: interfaces.context.ContextInterface,
@@ -700,8 +747,8 @@ class PESymbols(interfaces.plugins.PluginInterface):
# the symbols wanted from this module by the caller
wanted = wanted_modules[mod_name]
# make a copy to remove from inside this function for returning to the caller
remaining = copy.deepcopy(wanted)
# The ValueError will pass through to the caller
remaining = PESymbols._validate_wanted_modules(wanted)
done_processing = False
@@ -748,6 +795,8 @@ class PESymbols(interfaces.plugins.PluginInterface):
Loops through each method of symbol analysis until each wanted symbol is found
Returns the resolved symbols as a dictionary that includes the name and runtime address
`wanted_modules` must be correctly formatted or a ValueError will be thrown
Args:
wanted_modules: the dictionary of modules and symbols to resolve. Modified to remove symbols as they are resolved.
collected_modules: return value from `get_kernel_modules` or `get_process_modules`
@@ -763,6 +812,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
module_instances = collected_modules[mod_name]
# The ValueError from an invalid wanted_modules will pass through to the caller
# try to resolve the symbols for `mod_name` through each method (PDB and export table currently)
(
found_in_module,
@@ -783,8 +833,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
def get_kernel_modules(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
filter_modules: Optional[filter_modules_type],
) -> collected_modules_type:
"""
@@ -804,7 +853,9 @@ class PESymbols(interfaces.plugins.PluginInterface):
filter_modules_check = None
session_layers = list(
modules.Modules.get_session_layers(context, layer_name, symbol_table)
modules.Modules.get_session_layers(
context=context, kernel_module_name=kernel_module_name
)
)
# special handling for the kernel
@@ -813,7 +864,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
)
for index, mod in enumerate(
modules.Modules.list_modules(context, layer_name, symbol_table)
modules.Modules.list_modules(context, kernel_module_name)
):
try:
mod_name = str(mod.BaseDllName.get_string().lower())
@@ -906,8 +957,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
def get_all_vads_with_file_paths(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table_name: str,
kernel_module_name: str,
) -> Generator[
Tuple[interfaces.objects.ObjectInterface, str, ranges_type],
None,
@@ -920,9 +970,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
Generator[Tuple[interfaces.objects.ObjectInterface, str, ranges_type]]: Yields tuple of process objects, layers, and VADs mapping files
"""
procs = pslist.PsList.list_processes(
context=context,
layer_name=layer_name,
symbol_table=symbol_table_name,
context=context, kernel_module_name=kernel_module_name
)
for proc in procs:
@@ -939,8 +987,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
def get_process_modules(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
filter_modules: Optional[filter_modules_type],
) -> collected_modules_type:
"""
@@ -960,7 +1007,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
filter_modules_check = None
for _proc, proc_layer_name, vads in PESymbols.get_all_vads_with_file_paths(
context, layer_name, symbol_table
context, kernel_module_name
):
for vad_start, vad_size, filepath in vads:
filename = PESymbols.filename_for_path(filepath)
@@ -977,8 +1024,6 @@ class PESymbols(interfaces.plugins.PluginInterface):
return proc_modules
def _generator(self) -> Generator[Tuple[int, Tuple[str, str, int]], None, None]:
kernel = self.context.modules[self.config["kernel"]]
if self.config["symbols"]:
filter_module = {
self.config["module"].lower(): {
@@ -1003,7 +1048,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
module_resolver = self.get_process_modules
collected_modules = module_resolver(
self.context, kernel.layer_name, kernel.symbol_table_name, filter_module
self.context, self.config["kernel"], filter_module
)
found_symbols, _missing_symbols = PESymbols.find_symbols(
@@ -3,7 +3,7 @@
#
import logging
import ntpath
from typing import List, Type, Optional
from typing import List, Type, Optional, Iterator, Tuple
from volatility3.framework import constants, exceptions, interfaces, renderers
from volatility3.framework.configuration import requirements
@@ -18,7 +18,9 @@ class PEDump(interfaces.plugins.PluginInterface):
"""Allows extracting PE Files from a specific address in a specific address space"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
# 2.0.0 - changed the signature of `dump_kernel_pe_at_base`
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -30,7 +32,10 @@ class PEDump(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(3, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -145,9 +150,19 @@ class PEDump(interfaces.plugins.PluginInterface):
)
@classmethod
def dump_kernel_pe_at_base(cls, context, kernel, pe_table_name, open_method, base):
def dump_kernel_pe_at_base(
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
pe_table_name: str,
open_method: Type[interfaces.plugins.FileHandlerInterface],
base: int,
) -> Iterator[Tuple[int, str, str]]:
"""
Extracts a PE file from kernel memory at the given base address
"""
session_layers = modules.Modules.get_session_layers(
context, kernel.layer_name, kernel.symbol_table_name
context=context, kernel_module_name=kernel_module_name
)
session_layer_name = modules.Modules.find_session_layer(
@@ -182,8 +197,7 @@ class PEDump(interfaces.plugins.PluginInterface):
for proc in pslist.PsList.list_processes(
context=context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=kernel.name,
filter_func=filter_func,
):
pid = proc.UniqueProcessId
@@ -224,7 +238,11 @@ class PEDump(interfaces.plugins.PluginInterface):
if self.config["kernel_module"]:
pe_files = self.dump_kernel_pe_at_base(
self.context, kernel, pe_table_name, self.open, self.config["base"]
context=self.context,
kernel_module_name=self.config["kernel"],
pe_table_name=pe_table_name,
open_method=self.open,
base=self.config["base"],
)
else:
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
@@ -79,6 +79,7 @@ class PoolHeaderScanner(interfaces.layers.ScannerInterface):
offset=offset - self._header_offset,
absolute=True,
)
constraint = self._constraint_lookup[pattern]
try:
# Size check
@@ -128,7 +129,7 @@ class PoolScanner(plugins.PluginInterface):
"""A generic pool scanner plugin."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
_version = (3, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -139,7 +140,7 @@ class PoolScanner(plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="handles", plugin=handles.Handles, version=(2, 0, 0)
name="handles", plugin=handles.Handles, version=(3, 0, 0)
),
]
@@ -150,7 +151,7 @@ class PoolScanner(plugins.PluginInterface):
constraints = self.builtin_constraints(symbol_table)
for constraint, mem_object, header in self.generate_pool_scan(
self.context, kernel.layer_name, symbol_table, constraints
self.context, self.config["kernel"], constraints
):
# generate some type-specific info for sanity checking
if constraint.object_type == "Process":
@@ -181,6 +182,36 @@ class PoolScanner(plugins.PluginInterface):
),
)
@staticmethod
def gui_poolscanner_constraints(
gui_table: str, tags_filter: Optional[List[bytes]] = None
) -> List[PoolConstraint]:
"""
Constraints for objects managed by the GUI subsystem (win32k*.sys)
"""
builtins = [
PoolConstraint(
b"Wind",
type_name=gui_table + constants.BANG + "tagWINDOWSTATION",
size=(0x90, None),
page_type=PoolType.PAGED,
object_type="WindowStation",
skip_type_test=True,
),
PoolConstraint(
b"Desk",
type_name=gui_table + constants.BANG + "tagDESKTOP",
page_type=PoolType.PAGED,
object_type="Desktop",
skip_type_test=True,
),
]
if not tags_filter:
return builtins
return [constraint for constraint in builtins if constraint.tag in tags_filter]
@classmethod
def builtin_constraints(
cls, symbol_table: str, tags_filter: Optional[List[bytes]] = None
@@ -331,11 +362,11 @@ class PoolScanner(plugins.PluginInterface):
return [constraint for constraint in builtins if constraint.tag in tags_filter]
@classmethod
def generate_pool_scan(
def generate_pool_scan_extended(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
object_symbol_table_name: str,
constraints: List[PoolConstraint],
) -> Generator[
Tuple[
@@ -347,49 +378,63 @@ class PoolScanner(plugins.PluginInterface):
None,
]:
"""
The extended version of `generate_pool_scan` to support pool scanning for objects outside of the kernel (ntoskrnl).
This requires the symbol table of the object being scanned for.
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
kernel_module_name: The name of the module for the kernel
object_symbol_table_name: The name of the symbol table for the object being scanned for
constraints: List of pool constraints used to limit the scan results
Returns:
Iterable of tuples, containing the constraint that matched, the object from memory, the object header used to determine the object
"""
kernel = context.modules[kernel_module_name]
# get the object type map
type_map = handles.Handles.get_type_map(
context=context, layer_name=layer_name, symbol_table=symbol_table
context=context, kernel_module_name=kernel_module_name
)
cookie = handles.Handles.find_cookie(
context=context, layer_name=layer_name, symbol_table=symbol_table
context=context, kernel_module_name=kernel_module_name
)
is_windows_10 = versions.is_windows_10(context, symbol_table)
is_windows_8_or_later = versions.is_windows_8_or_later(context, symbol_table)
is_windows_10 = versions.is_windows_10(context, kernel.symbol_table_name)
is_windows_8_or_later = versions.is_windows_8_or_later(
context, kernel.symbol_table_name
)
# start off with the primary virtual layer
scan_layer = layer_name
scan_layer = kernel.layer_name
# switch to a non-virtual layer if necessary
if not is_windows_10:
scan_layer = context.layers[scan_layer].config["memory_layer"]
if symbols.symbol_table_is_64bit(context, symbol_table):
if symbols.symbol_table_is_64bit(
context=context, symbol_table_name=kernel.symbol_table_name
):
alignment = 0x10
else:
alignment = 8
# scan in the main kernel layer for the object(s)
for constraint, header in cls.pool_scan(
context, scan_layer, symbol_table, constraints, alignment=alignment
context,
kernel_module_name,
scan_layer,
object_symbol_table_name,
constraints,
alignment=alignment,
):
mem_objects = header.get_object(
constraint=constraint,
use_top_down=is_windows_8_or_later,
native_layer_name=layer_name,
kernel_symbol_table=symbol_table,
native_layer_name=kernel.layer_name,
kernel_symbol_table=kernel.symbol_table_name,
)
for mem_object in mem_objects:
@@ -398,6 +443,7 @@ class PoolScanner(plugins.PluginInterface):
constants.LOGLEVEL_VVV,
f"Cannot create an instance of {constraint.type_name}",
)
continue
if constraint.object_type is not None and not constraint.skip_type_test:
@@ -418,10 +464,45 @@ class PoolScanner(plugins.PluginInterface):
yield constraint, mem_object, header
@classmethod
def generate_pool_scan(
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
constraints: List[PoolConstraint],
) -> Generator[
Tuple[
PoolConstraint,
interfaces.objects.ObjectInterface,
interfaces.objects.ObjectInterface,
],
None,
None,
]:
"""
The original version of `generate_pool_scan` which is sufficient for objects in the kernel (ntoskrnl),
Args:
context: The context to retrieve required elements (layers, symbol tables) from
kernel_module_name: The name of the module for the kernel
constraints: List of pool constraints used to limit the scan results
Returns:
Iterable of tuples, containing the constraint that matched, the object from memory, the object header used to determine the object
"""
kernel = context.modules[kernel_module_name]
# repeat the symbol table to match the original `generate_pool_scan` behaviour
yield from cls.generate_pool_scan_extended(
context, kernel_module_name, kernel.symbol_table_name, constraints
)
@classmethod
def pool_scan(
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
layer_name: str,
symbol_table: str,
pool_constraints: List[PoolConstraint],
@@ -455,8 +536,16 @@ class PoolScanner(plugins.PluginInterface):
)
constraint_lookup[constraint.tag] = constraint
pool_header_table_name = cls.get_pool_header_table(context, symbol_table)
module = context.module(pool_header_table_name, layer_name, offset=0)
kernel = context.modules[kernel_module_name]
if kernel.has_type("_POOL_HEADER"):
pool_header_table_name = kernel.symbol_table_name
else:
pool_header_table_name = cls.get_pool_header_table(context, symbol_table)
module = context.module(
pool_header_table_name, layer_name, offset=kernel.offset
)
# Run the scan locating the offsets of a particular tag
layer = context.layers[layer_name]
@@ -474,41 +563,37 @@ class PoolScanner(plugins.PluginInterface):
context: The context that the symbol tables does (or will) reside in
symbol_table: The expected symbol_table to contain the _POOL_HEADER type
"""
# Setup the pool header and offset differential
try:
context.symbol_space.get_type(
symbol_table + constants.BANG + "_POOL_HEADER"
)
table_name = symbol_table
except exceptions.SymbolError:
# We have to manually load a symbol table
# We have to manually load a symbol table
if symbols.symbol_table_is_64bit(context, symbol_table):
is_win_7 = versions.is_windows_7(context, symbol_table)
if is_win_7:
pool_header_json_filename = "poolheader-x64-win7"
else:
pool_header_json_filename = "poolheader-x64"
if symbols.symbol_table_is_64bit(
context=context, symbol_table_name=symbol_table
):
is_win_7 = versions.is_windows_7(context, symbol_table)
if is_win_7:
pool_header_json_filename = "poolheader-x64-win7"
else:
pool_header_json_filename = "poolheader-x86"
pool_header_json_filename = "poolheader-x64"
else:
pool_header_json_filename = "poolheader-x86"
# set the class_type to match the normal WindowsKernelIntermedSymbols
is_vista_or_later = versions.is_vista_or_later(context, symbol_table)
if is_vista_or_later:
class_type = extensions.pool.POOL_HEADER_VISTA
else:
class_type = extensions.pool.POOL_HEADER
# set the class_type to match the normal WindowsKernelIntermedSymbols
is_vista_or_later = versions.is_vista_or_later(context, symbol_table)
if is_vista_or_later:
class_type = extensions.pool.POOL_HEADER_VISTA
else:
class_type = extensions.pool.POOL_HEADER
table_name = intermed.IntermediateSymbolTable.create(
context=context,
config_path=configuration.path_join(
context.symbol_space[symbol_table].config_path, "poolheader"
),
sub_path="windows",
filename=pool_header_json_filename,
table_mapping={"nt_symbols": symbol_table},
class_types={"_POOL_HEADER": class_type},
)
table_name = intermed.IntermediateSymbolTable.create(
context=context,
config_path=configuration.path_join(
context.symbol_space[symbol_table].config_path, "poolheader"
),
sub_path="windows",
filename=pool_header_json_filename,
table_mapping={"nt_symbols": symbol_table},
class_types={"_POOL_HEADER": class_type},
)
return table_name
def run(self) -> renderers.TreeGrid:
@@ -61,7 +61,7 @@ class Privs(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
]
@@ -107,7 +107,6 @@ class Privs(interfaces.plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
@@ -121,8 +120,7 @@ class Privs(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
@@ -29,7 +29,7 @@ class ProcessGhosting(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
]
@@ -83,7 +83,6 @@ class ProcessGhosting(interfaces.plugins.PluginInterface):
def run(self):
filter_func = pslist.PsList.create_active_process_filter()
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
@@ -96,8 +95,7 @@ class ProcessGhosting(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
)
),
+15 -16
View File
@@ -22,7 +22,9 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Lists the processes present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 1)
# 3.0.0 - changed signature for `list_processes`
_version = (3, 0, 0)
PHYSICAL_DEFAULT = False
@classmethod
@@ -206,35 +208,33 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def list_processes(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
filter_func: Callable[
[interfaces.objects.ObjectInterface], bool
] = lambda _: False,
) -> Iterator["extensions.EPROCESS"]:
"""Lists all the processes in the primary layer that are in the pid
"""Lists all the processes in the given layer that are in the pid
config option.
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
layer_iname: The name of the layer on which to operate
symbol_table_name: The name of the table containing the kernel symbols
filter_func: A function which takes an EPROCESS object and returns True if the process should be ignored/filtered
Returns:
The list of EPROCESS objects from the `layer_name` layer's PsActiveProcessHead list after filtering
"""
# We only use the object factory to demonstrate how to use one
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
kernel = context.modules[kernel_module_name]
if not kernel.offset:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
ps_aph_offset = ntkrnlmp.get_symbol("PsActiveProcessHead").address
list_entry = ntkrnlmp.object(object_type="_LIST_ENTRY", offset=ps_aph_offset)
ps_aph_offset = kernel.get_symbol("PsActiveProcessHead").address
list_entry = kernel.object(object_type="_LIST_ENTRY", offset=ps_aph_offset)
# This is example code to demonstrate how to use symbol_space directly, rather than through a module:
#
@@ -247,10 +247,10 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# Note: "nt_symbols!_EPROCESS" could have been used, but would rely on the "nt_symbols" symbol table not already
# having been present. Strictly, the value of the requirement should be joined with the BANG character
# defined in the constants file
reloff = ntkrnlmp.get_type("_EPROCESS").relative_child_offset(
reloff = kernel.get_type("_EPROCESS").relative_child_offset(
"ActiveProcessLinks"
)
eproc = ntkrnlmp.object(
eproc = kernel.object(
object_type="_EPROCESS",
offset=list_entry.vol.offset - reloff,
absolute=True,
@@ -273,8 +273,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
for proc in self.list_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
filter_func=self.create_pid_filter(self.config.get("pid", None)),
):
if not self.config.get("physical", self.PHYSICAL_DEFAULT):
+25 -35
View File
@@ -23,7 +23,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Scans for processes present in a particular windows memory image."""
_required_framework_version = (2, 3, 1)
_version = (1, 1, 1)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
@@ -34,10 +34,13 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="info", component=info.Info, version=(1, 0, 0)
name="info", component=info.Info, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -141,8 +144,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def scan_processes(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
filter_func: Callable[
[interfaces.objects.ObjectInterface], bool
] = lambda _: False,
@@ -151,19 +153,20 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
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
kernel_module_name: The name of the module for the kernel
Returns:
A list of processes found by scanning the `layer_name` layer for process pool signatures
"""
kernel = context.modules[kernel_module_name]
constraints = poolscanner.PoolScanner.builtin_constraints(
symbol_table, [b"Pro\xe3", b"Proc"]
kernel.symbol_table_name, [b"Pro\xe3", b"Proc"]
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, symbol_table, constraints
context, kernel_module_name, constraints
):
_constraint, mem_object, _header = result
if not filter_func(mem_object):
@@ -173,16 +176,14 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def virtual_process_from_physical(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
proc: interfaces.objects.ObjectInterface,
) -> Optional[interfaces.objects.ObjectInterface]:
"""Returns a virtual process from a physical addressed one
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
kernel_module_name: The name of the module inside the kernel
proc: the process object with physical address
Returns:
@@ -190,16 +191,10 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""
version = cls.get_osversion(context, layer_name, symbol_table)
ntkrnlmp = context.modules[kernel_module_name]
version = cls.get_osversion(context, kernel_module_name)
# If it's WinXP->8.1 we have now a physical process address.
# We'll use the first thread to bounce back to the virtual process
kvo = context.layers[layer_name].config.get("kernel_virtual_offset", None)
if not kvo:
raise ValueError(
"Intel layer does not have an associated kernel virtual offset, failing"
)
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
tleoffset = ntkrnlmp.get_type("_ETHREAD").relative_child_offset(
"ThreadListEntry"
)
@@ -208,7 +203,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# If (and only if) we're dealing with 64-bit Windows 7 SP1
# then add the other commonly seen member offset to the list
bits = context.layers[layer_name].bits_per_register
bits = context.layers[ntkrnlmp.layer_name].bits_per_register
if version == (6, 1, 7601) and bits == 64:
offsets.append(tleoffset + 8)
@@ -225,7 +220,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# Sanity check the bounce.
# This compares the original offset with the new one (translated from virtual layer)
(_, _, ph_offset, _, _) = list(
context.layers[layer_name].mapping(
context.layers[ntkrnlmp.layer_name].mapping(
offset=virtual_process.vol.offset, length=0
)
)[0]
@@ -237,23 +232,20 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
def get_osversion(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> Tuple[int, int, int]:
"""Returns the complete OS version (MAJ,MIN,BUILD)
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
kernel_module_name: The name of the module for the kernel
Returns:
A tuple with (MAJ,MIN,BUILD)
"""
kuser = info.Info.get_kuser_structure(context, layer_name, symbol_table)
kuser = info.Info.get_kuser_structure(context, kernel_module_name)
nt_major_version = int(kuser.NtMajorVersion)
nt_minor_version = int(kuser.NtMinorVersion)
vers = info.Info.get_version_structure(context, layer_name, symbol_table)
vers = info.Info.get_version_structure(context, kernel_module_name)
build = vers.MinorVersion
return (nt_major_version, nt_minor_version, build)
@@ -268,8 +260,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
for proc in self.scan_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
filter_func=pslist.PsList.create_pid_filter(self.config.get("pid", None)),
):
file_output = "Disabled"
@@ -281,8 +272,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
try:
vproc = self.virtual_process_from_physical(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
proc,
)
except exceptions.PagedInvalidAddressException:
@@ -40,7 +40,7 @@ class PsTree(interfaces.plugins.PluginInterface):
optional=True,
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -85,7 +85,7 @@ class PsTree(interfaces.plugins.PluginInterface):
kernel = self.context.modules[self.config["kernel"]]
for proc in pslist.PsList.list_processes(
self.context, kernel.layer_name, kernel.symbol_table_name
context=self.context, kernel_module_name=self.config["kernel"]
):
if not self.config.get("physical", pslist.PsList.PHYSICAL_DEFAULT):
offset = proc.vol.offset
@@ -11,7 +11,6 @@ from volatility3.framework.renderers import TreeGrid, format_hints
from volatility3.framework.symbols.windows import extensions
from volatility3.plugins.windows import (
handles,
info,
pslist,
psscan,
thrdscan,
@@ -50,19 +49,16 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="info", component=info.Info, version=(1, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="psscan", component=psscan.PsScan, version=(1, 0, 0)
name="psscan", component=psscan.PsScan, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="thrdscan", component=thrdscan.ThrdScan, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="handles", component=handles.Handles, version=(2, 0, 0)
name="handles", component=handles.Handles, version=(3, 0, 0)
),
requirements.BooleanRequirement(
name="physical-offsets",
@@ -114,10 +110,10 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
return self._proc_list_to_dict(tasks)
def _check_psscan(
self, layer_name: str, symbol_table: str
self,
) -> Dict[int, extensions.EPROCESS]:
res = psscan.PsScan.scan_processes(
context=self.context, layer_name=layer_name, symbol_table=symbol_table
context=self.context, kernel_module_name=self.config["kernel"]
)
return self._proc_list_to_dict(res)
@@ -144,7 +140,7 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
return self._proc_list_to_dict(ret)
def _check_csrss_handles(
self, tasks: Iterable[extensions.EPROCESS], layer_name: str, symbol_table: str
self, tasks: Iterable[extensions.EPROCESS]
) -> Dict[int, extensions.EPROCESS]:
ret: List[extensions.EPROCESS] = []
@@ -152,12 +148,12 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
context=self.context, config_path=self.config_path
)
type_map = handles_plugin.get_type_map(self.context, layer_name, symbol_table)
type_map = handles_plugin.get_type_map(
context=self.context, kernel_module_name=self.config["kernel"]
)
cookie = handles_plugin.find_cookie(
context=self.context,
layer_name=layer_name,
symbol_table=symbol_table,
context=self.context, kernel_module_name=self.config["kernel"]
)
for p in tasks:
@@ -179,14 +175,9 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
return self._proc_list_to_dict(ret)
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
layer_name = kernel.layer_name
symbol_table = kernel.symbol_table_name
kdbg_list_processes = list(
pslist.PsList.list_processes(
context=self.context, layer_name=layer_name, symbol_table=symbol_table
context=self.context, kernel_module_name=self.config["kernel"]
)
)
@@ -194,11 +185,9 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
processes: Dict[str, Dict[int, extensions.EPROCESS]] = {}
processes["pslist"] = self._check_pslist(kdbg_list_processes)
processes["psscan"] = self._check_psscan(layer_name, symbol_table)
processes["psscan"] = self._check_psscan()
processes["thrdscan"] = self._check_thrdscan()
processes["csrss"] = self._check_csrss_handles(
kdbg_list_processes, layer_name, symbol_table
)
processes["csrss"] = self._check_csrss_handles(kdbg_list_processes)
# Unique set of all offsets from all sources
offsets = set(chain(*(mapping.keys() for mapping in processes.values())))
@@ -28,18 +28,16 @@ class GetCellRoutine(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
name="ssdt", plugin=ssdt.SSDT, version=(2, 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
context=self.context, kernel_module_name=self.config["kernel"]
)
# walk each hive and validate that the GetCellRoutine handler
@@ -47,8 +45,7 @@ class GetCellRoutine(interfaces.plugins.PluginInterface):
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,
kernel_module_name=self.config["kernel"],
):
hive = hive_object.hive
@@ -41,9 +41,11 @@ class HiveGenerator:
class HiveList(interfaces.plugins.PluginInterface):
"""Lists the registry hives present in a particular memory image."""
_version = (1, 0, 1)
_required_framework_version = (2, 0, 0)
# 2.0.0 - changed the signature of list_hives
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
@@ -93,10 +95,9 @@ class HiveList(interfaces.plugins.PluginInterface):
# Construct the hive
hive = next(
self.list_hives(
self.context,
self.config_path,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
context=self.context,
base_config_path=self.config_path,
kernel_module_name=self.config["kernel"],
hive_offsets=[hive_object.vol.offset],
)
)
@@ -137,8 +138,7 @@ class HiveList(interfaces.plugins.PluginInterface):
cls,
context: interfaces.context.ContextInterface,
base_config_path: str,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
filter_string: Optional[str] = None,
hive_offsets: Optional[List[int]] = None,
) -> Iterator[registry.RegistryHive]:
@@ -148,20 +148,24 @@ class HiveList(interfaces.plugins.PluginInterface):
Args:
context: The context to retrieve required elements (layers, symbol tables) from
base_config_path: The configuration path for any settings required by the new table
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
kernel_module_name: The name of the module for the kernel
filter_string: An optional string which must be present in the hive name if specified
offset: An optional offset to specify a specific hive to iterate over (takes precedence over filter_string)
Yields:
A registry hive layer name
"""
kernel = context.modules[kernel_module_name]
if hive_offsets is None:
try:
hive_offsets = [
hive.vol.offset
for hive in cls.list_hive_objects(
context, layer_name, symbol_table, filter_string
context=context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
filter_string=filter_string,
)
]
except ImportError:
@@ -178,8 +182,9 @@ class HiveList(interfaces.plugins.PluginInterface):
context=context,
base_config_path=base_config_path,
hive_offset=hive_offset,
base_layer=layer_name,
nt_symbols=symbol_table,
base_layer=kernel.layer_name,
nt_symbols=kernel.symbol_table_name,
kernel_module_name=kernel_module_name,
)
try:
@@ -26,10 +26,10 @@ class HiveScan(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
name="poolscanner", plugin=poolscanner.PoolScanner, version=(3, 0, 0)
),
requirements.PluginRequirement(
name="bigpools", plugin=bigpools.BigPools, version=(1, 0, 0)
name="bigpools", plugin=bigpools.BigPools, version=(2, 0, 0)
),
]
@@ -50,7 +50,9 @@ class HiveScan(interfaces.plugins.PluginInterface):
kernel = context.modules[kernel_name]
is_64bit = symbols.symbol_table_is_64bit(context, kernel.symbol_table_name)
is_64bit = symbols.symbol_table_is_64bit(
context=context, symbol_table_name=kernel.symbol_table_name
)
is_windows_8_1_or_later = versions.is_windows_8_1_or_later(
context=context, symbol_table=kernel.symbol_table_name
)
@@ -60,8 +62,7 @@ class HiveScan(interfaces.plugins.PluginInterface):
for pool in bigpools.BigPools.list_big_pools(
context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=kernel_name,
tags=["CM10"],
):
cmhive = ntkrnlmp.object(
@@ -75,7 +76,7 @@ class HiveScan(interfaces.plugins.PluginInterface):
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, kernel.layer_name, kernel.symbol_table_name, constraints
context, kernel_name, constraints
):
_constraint, mem_object, _header = result
yield mem_object
@@ -32,7 +32,7 @@ class PrintKey(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
requirements.IntRequirement(
name="offset", description="Hive Offset", default=None, optional=True
@@ -250,17 +250,14 @@ class PrintKey(interfaces.plugins.PluginInterface):
def _registry_walker(
self,
layer_name: str,
symbol_table: str,
hive_offsets: Optional[List[int]] = None,
key: Optional[str] = None,
recurse: bool = False,
):
for hive in hivelist.HiveList.list_hives(
self.context,
self.config_path,
layer_name=layer_name,
symbol_table=symbol_table,
context=self.context,
base_config_path=self.config_path,
kernel_module_name=self.config["kernel"],
hive_offsets=hive_offsets,
):
try:
@@ -302,7 +299,6 @@ class PrintKey(interfaces.plugins.PluginInterface):
def run(self):
offset = self.config.get("offset", None)
kernel = self.context.modules[self.config["kernel"]]
return TreeGrid(
columns=[
@@ -315,8 +311,6 @@ class PrintKey(interfaces.plugins.PluginInterface):
("Volatile", bool),
],
generator=self._registry_walker(
kernel.layer_name,
kernel.symbol_table_name,
hive_offsets=None if offset is None else [offset],
key=self.config.get("key", None),
recurse=self.config.get("recurse", None),
@@ -54,7 +54,7 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac
name="offset", description="Hive Offset", default=None, optional=True
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
]
@@ -303,7 +303,6 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac
hive_offsets = None
if self.config.get("offset", None) is not None:
hive_offsets = [self.config.get("offset", None)]
kernel = self.context.modules[self.config["kernel"]]
self._reg_table_name = intermed.IntermediateSymbolTable.create(
self.context, self._config_path, "windows", "registry"
@@ -313,8 +312,7 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac
for hive 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,
kernel_module_name=self.config["kernel"],
filter_string="ntuser.dat",
hive_offsets=hive_offsets,
):
@@ -1106,7 +1106,7 @@ class ScheduledTasks(interfaces.plugins.PluginInterface, timeliner.TimeLinerInte
information about triggers, actions, run times, and creation times."""
_required_framework_version = (2, 11, 0)
_version = (1, 0, 0)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -1118,7 +1118,7 @@ information about triggers, actions, run times, and creation times."""
architectures=["Intel33", "Intel64"],
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
]
@@ -1138,7 +1138,7 @@ information about triggers, actions, run times, and creation times."""
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel: interfaces.context.ModuleInterface,
kernel_module_name: str,
) -> Optional[registry.RegistryHive]:
"""Retrieves the `Amcache.hve` registry hive from the kernel module, if it can be located."""
return next(
@@ -1147,8 +1147,7 @@ information about triggers, actions, run times, and creation times."""
base_config_path=interfaces.configuration.path_join(
config_path, "hivelist"
),
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=kernel_module_name,
filter_string="SOFTWARE",
),
None,
@@ -1360,7 +1359,7 @@ information about triggers, actions, run times, and creation times."""
args,
(
action_set.context
if action_set is not None and action_set.context is not None
if (action_set is not None and action_set.context is not None)
else renderers.NotAvailableValue()
),
working_directory,
@@ -1368,11 +1367,11 @@ information about triggers, actions, run times, and creation times."""
)
def _generator(self) -> Iterator[Tuple[int, _ScheduledTaskEntry]]:
kernel = self.context.modules[self.config["kernel"]]
# Building the dictionary ahead of time is much better for performance
# vs looking up each service's DLL individually.
software_hive = self.get_software_hive(self.context, self.config_path, kernel)
software_hive = self.get_software_hive(
self.context, self.config_path, self.config["kernel"]
)
if software_hive is None:
vollog.warning("Failed to get SOFTWARE hive")
return
@@ -28,7 +28,7 @@ class Sessions(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -39,16 +39,14 @@ class Sessions(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
]
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
# Collect all the values as we will want to group them later
sessions = {}
for proc in pslist.PsList.list_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
context=self.context,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
):
session_id = proc.get_session_id()
@@ -65,13 +65,13 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(2, 0, 0)
name="modules", component=modules.Modules, version=(3, 0, 0)
),
]
@@ -79,7 +79,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
def create_shimcache_table(
cls,
context: interfaces.context.ContextInterface,
symbol_table: str,
symbol_table_name: str,
config_path: str,
) -> str:
"""Creates a shimcache symbol table
@@ -92,16 +92,18 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
Returns:
The name of the constructed shimcache table
"""
native_types = context.symbol_space[symbol_table].natives
is_64bit = symbols.symbol_table_is_64bit(context, symbol_table)
table_mapping = {"nt_symbols": symbol_table}
native_types = context.symbol_space[symbol_table_name].natives
is_64bit = symbols.symbol_table_is_64bit(
context=context, symbol_table_name=symbol_table_name
)
table_mapping = {"nt_symbols": symbol_table_name}
try:
symbol_filename = next(
filename
for version_check, for_64bit, filename in ShimcacheMem._win_version_file_map
if is_64bit == for_64bit
and version_check(context=context, symbol_table=symbol_table)
and version_check(context=context, symbol_table=symbol_table_name)
)
except StopIteration:
raise NotImplementedError("This version of Windows is not supported!")
@@ -122,8 +124,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
def find_shimcache_win_xp(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
kernel_symbol_table: str,
kernel_module_name: str,
shimcache_symbol_table: str,
) -> Iterator[shimcache.SHIM_CACHE_ENTRY]:
"""Attempts to find the shimcache in a Windows XP memory image
@@ -142,9 +143,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
seen = set()
for process in pslist.PsList.list_processes(
context, layer_name, kernel_symbol_table
):
for process in pslist.PsList.list_processes(context, kernel_module_name):
pid = process.UniqueProcessId
vollog.debug("checking process %d", pid)
for vad in vadinfo.VadInfo.list_vads(
@@ -219,8 +218,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel_layer_name: str,
nt_symbol_table: str,
kernel_module_name: str,
shimcache_symbol_table: str,
) -> Iterator[shimcache.SHIM_CACHE_ENTRY]:
"""Implements the algorithm to search for the shim cache on Windows 2000
@@ -239,31 +237,37 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
:param shimcache_symbol_table: The name of a symbol table containing the hand-crafted shimcache symbols
"""
kernel = context.modules[kernel_module_name]
data_sec = cls.get_module_section_range(
context,
config_path,
kernel_layer_name,
nt_symbol_table,
kernel_module_name,
cls.NT_KRNL_MODS,
".data",
)
mod_page = cls.get_module_section_range(
context,
config_path,
kernel_layer_name,
nt_symbol_table,
kernel_module_name,
cls.NT_KRNL_MODS,
"PAGE",
)
# We require both in order to accurately handle AVL table
if not (data_sec and mod_page):
return None
return
data_sec_offset, data_sec_size = data_sec
mod_page_offset, mod_page_size = mod_page
addr_size = 8 if symbols.symbol_table_is_64bit(context, nt_symbol_table) else 4
addr_size = (
8
if symbols.symbol_table_is_64bit(
context=context, symbol_table_name=kernel.symbol_table_name
)
else 4
)
shim_head = None
for offset in range(
@@ -272,8 +276,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
shim_head = cls.try_get_shim_head_at_offset(
context,
shimcache_symbol_table,
nt_symbol_table,
kernel_layer_name,
kernel_module_name,
mod_page_offset,
mod_page_offset + mod_page_size,
offset,
@@ -293,9 +296,8 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
def try_get_shim_head_at_offset(
cls,
context: interfaces.context.ContextInterface,
symbol_table: str,
kernel_symbol_table: str,
layer_name: str,
shimcache_symbol_table: str,
kernel_module_name: str,
mod_page_start: int,
mod_page_end: int,
offset: int,
@@ -307,9 +309,14 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
If a number of validity checks are passed, this method will return the `SHIM_CACHE_HEAD`
object. Otherwise, `None` is returned.
"""
kernel = context.modules[kernel_module_name]
# Check RTL_AVL_TABLE at offset
rtl_avl_table = context.object(
symbol_table + constants.BANG + "_RTL_AVL_TABLE", layer_name, offset
shimcache_symbol_table + constants.BANG + "_RTL_AVL_TABLE",
kernel.layer_name,
offset,
)
if not rtl_avl_table.is_valid(mod_page_start, mod_page_end):
return None
@@ -317,11 +324,13 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
vollog.debug(f"Candidate RTL_AVL_TABLE found at offset {offset:#x}")
ersrc_size = context.symbol_space.get_type(
kernel_symbol_table + constants.BANG + "_ERESOURCE"
kernel.symbol_table_name + constants.BANG + "_ERESOURCE"
).size
ersrc_alignment = (
0x20
if symbols.symbol_table_is_64bit(context, kernel_symbol_table)
if symbols.symbol_table_is_64bit(
context=context, symbol_table_name=kernel.symbol_table_name
)
else 0x10
# 0x20 if context.symbol_space.get_type("pointer").size == 8 else 0x10
)
@@ -334,8 +343,8 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
vollog.debug(f"Constructing ERESOURCE at {eresource_offset:#x}")
eresource = context.object(
kernel_symbol_table + constants.BANG + "_ERESOURCE",
layer_name,
kernel.symbol_table_name + constants.BANG + "_ERESOURCE",
kernel.layer_name,
eresource_offset,
)
if not eresource.is_valid():
@@ -344,12 +353,12 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
shim_head_offset = offset + rtl_avl_table.vol.size
if not context.layers[layer_name].is_valid(shim_head_offset):
if not context.layers[kernel.layer_name].is_valid(shim_head_offset):
return None
shim_head = context.object(
symbol_table + constants.BANG + "SHIM_CACHE_ENTRY",
layer_name,
shimcache_symbol_table + constants.BANG + "SHIM_CACHE_ENTRY",
kernel.layer_name,
shim_head_offset,
)
@@ -365,8 +374,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel_layer_name: str,
nt_symbol_table: str,
kernel_module_name: str,
shimcache_symbol_table: str,
) -> Iterator[shimcache.SHIM_CACHE_ENTRY]:
"""Attempts to locate and yield shimcache entries from a Windows 8 or later memory image.
@@ -376,10 +384,11 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
:param kernel_symbol_table: The name of an existing symbol table containing the kernel symbols
:param shimcache_symbol_table: The name of a symbol table containing the hand-crafted shimcache symbols
"""
kernel = context.modules[kernel_module_name]
is_8_1_or_later = versions.is_windows_8_1_or_later(
context, nt_symbol_table
) or versions.is_win10(context, nt_symbol_table)
context, kernel.symbol_table_name
) or versions.is_win10(context, kernel.symbol_table_name)
module_names = ["ahcache.sys"] if is_8_1_or_later else cls.NT_KRNL_MODS
vollog.debug(f"Searching for modules {module_names}")
@@ -387,16 +396,14 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
data_sec = cls.get_module_section_range(
context,
config_path,
kernel_layer_name,
nt_symbol_table,
kernel_module_name,
module_names,
".data",
)
mod_page = cls.get_module_section_range(
context,
config_path,
kernel_layer_name,
nt_symbol_table,
kernel_module_name,
module_names,
"PAGE",
)
@@ -419,12 +426,18 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
for offset in range(
data_sec_offset,
data_sec_offset + data_sec_size,
8 if symbols.symbol_table_is_64bit(context, nt_symbol_table) else 4,
(
8
if symbols.symbol_table_is_64bit(
context=context, symbol_table_name=kernel.symbol_table_name
)
else 4
),
):
vollog.debug(f"Building shim handle pointer at {offset:#x}")
shim_handle = context.object(
object_type=shimcache_symbol_table + constants.BANG + "pointer",
layer_name=kernel_layer_name,
layer_name=kernel.layer_name,
subtype=handle_type,
offset=offset,
)
@@ -445,7 +458,9 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
# On Windows 8 x64, the first cache contains the shim cache.
# On Windows 8 x86, 8.1 x86/x64, and 10, the second cache contains the shim cache.
if (
not symbols.symbol_table_is_64bit(context, nt_symbol_table)
not symbols.symbol_table_is_64bit(
context=context, symbol_table_name=kernel.symbol_table_name
)
and not is_8_1_or_later
):
valid_head = shim_heads[1]
@@ -474,8 +489,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
entries = self.find_shimcache_win_8_or_later(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
shimcache_table_name,
)
@@ -488,8 +502,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
entries = self.find_shimcache_win_2k3_to_7(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
shimcache_table_name,
)
@@ -499,8 +512,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
vollog.info("Finding shimcache entries for WinXP")
entries = self.find_shimcache_win_xp(
self._context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
shimcache_table_name,
)
else:
@@ -547,8 +559,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
cls,
context: interfaces.context.ContextInterface,
config_path: str,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
module_list: List[str],
section_name: str,
) -> Optional[Tuple[int, int]]:
@@ -566,14 +577,14 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
try:
krnl_mod = next(
module
for module in modules.Modules.list_modules(
context, layer_name, symbol_table
)
for module in modules.Modules.list_modules(context, kernel_module_name)
if module.BaseDllName.String in module_list
)
except StopIteration:
return None
kernel = context.modules[kernel_module_name]
pe_table_name = intermed.IntermediateSymbolTable.create(
context,
interfaces.configuration.path_join(config_path, "pe"),
@@ -585,7 +596,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
# code taken from Win32KBase._section_chunks (win32_core.py)
dos_header = context.object(
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
layer_name,
kernel.layer_name,
offset=krnl_mod.DllBase,
)
@@ -52,7 +52,7 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0)
@@ -61,7 +61,7 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface):
name="pdbutil", component=pdbutil.PDBUtility, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="pe_symbols", component=pe_symbols.PESymbols, version=(1, 1, 0)
name="pe_symbols", component=pe_symbols.PESymbols, version=(3, 0, 0)
),
]
@@ -568,7 +568,9 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface):
"""
kernel = self.context.modules[self.config["kernel"]]
if not symbols.symbol_table_is_64bit(self.context, kernel.symbol_table_name):
if not symbols.symbol_table_is_64bit(
context=self.context, symbol_table_name=kernel.symbol_table_name
):
vollog.info("This plugin only supports 64bit Windows memory samples")
return None
@@ -660,8 +662,6 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface):
return process_name != "lsass.exe"
def run(self):
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
("PID", int),
@@ -673,8 +673,7 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface):
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=self._lsass_proc_filter,
)
),
+14 -16
View File
@@ -19,7 +19,9 @@ class SSDT(plugins.PluginInterface):
"""Lists the system call table."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
# 2.0.0 - changed the signature of `build_module_collection`
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -30,7 +32,7 @@ class SSDT(plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="modules", plugin=modules.Modules, version=(2, 0, 0)
name="modules", plugin=modules.Modules, version=(3, 0, 0)
),
]
@@ -38,23 +40,23 @@ class SSDT(plugins.PluginInterface):
def build_module_collection(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> contexts.ModuleCollection:
"""Builds a collection of modules.
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
kernel_module_name: Name of the module for the kernel
Returns:
A Module collection of available modules based on `Modules.list_modules`
"""
mods = modules.Modules.list_modules(context, layer_name, symbol_table)
mods = modules.Modules.list_modules(context, kernel_module_name)
context_modules = []
kernel = context.modules[kernel_module_name]
for mod in mods:
try:
module_name_with_ext = mod.BaseDllName.get_string()
@@ -64,17 +66,13 @@ class SSDT(plugins.PluginInterface):
module_name = os.path.splitext(module_name_with_ext)[0]
symbol_table_name = None
if module_name in constants.windows.KERNEL_MODULE_NAMES:
symbol_table_name = symbol_table
context_module = contexts.SizedModule.create(
context=context,
module_name=module_name,
layer_name=layer_name,
layer_name=kernel.layer_name,
offset=mod.DllBase,
size=mod.SizeOfImage,
symbol_table_name=symbol_table_name,
symbol_table_name=kernel.symbol_table_name,
)
context_modules.append(context_module)
@@ -84,9 +82,9 @@ class SSDT(plugins.PluginInterface):
def _generator(self) -> Iterator[Tuple[int, Tuple[int, int, Any, Any]]]:
kernel = self.context.modules[self.config["kernel"]]
layer_name = kernel.layer_name
collection = self.build_module_collection(
self.context, layer_name, kernel.symbol_table_name
context=self.context,
kernel_module_name=self.config["kernel"],
)
ntkrnlmp = kernel
@@ -103,7 +101,7 @@ class SSDT(plugins.PluginInterface):
# on 64-bit systems the indexes are also 32-bits but they're offsets from the
# base address of the table and can be negative, so we need a signed data type
is_kernel_64 = symbols.symbol_table_is_64bit(
self.context, kernel.symbol_table_name
context=self.context, symbol_table_name=kernel.symbol_table_name
)
if is_kernel_64:
array_subtype = "long"
@@ -18,8 +18,11 @@ vollog = logging.getLogger(__name__)
class Strings(interfaces.plugins.PluginInterface):
"""Reads output from the strings command and indicates which process(es) each string belongs to."""
_version = (1, 2, 0)
_required_framework_version = (2, 0, 0)
# 2.0.0 - change signature of `generate_mapping`
_version = (2, 0, 0)
strings_pattern = re.compile(rb"^(?:\W*)([0-9]+)(?:\W*)(\w[\w\W]+)\n?")
@classmethod
@@ -31,7 +34,7 @@ class Strings(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -68,12 +71,10 @@ class Strings(interfaces.plugins.PluginInterface):
except ValueError:
vollog.error(f"Line in unrecognized format: line {count}")
line = strings_fp.readline()
kernel = self.context.modules[self.config["kernel"]]
revmap = self.generate_mapping(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
context=self.context,
kernel_module_name=self.config["kernel"],
progress_callback=self._progress_callback,
pid_list=self.config["pid"],
)
@@ -122,8 +123,7 @@ class Strings(interfaces.plugins.PluginInterface):
def generate_mapping(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
progress_callback: constants.ProgressCallback = None,
pid_list: Optional[List[int]] = None,
) -> Dict[int, Set[Tuple[str, int]]]:
@@ -132,8 +132,7 @@ class Strings(interfaces.plugins.PluginInterface):
Args:
context: the context for the method to run against
layer_name: the layer to map against the string lines
symbol_table: the name of the symbol table for the provided layer
kernel_module_name: the name of the module forthe kernel
progress_callback: an optional callable to display progress
pid_list: a lit of process IDs to consider when generating the reverse map
@@ -142,7 +141,9 @@ class Strings(interfaces.plugins.PluginInterface):
"""
filter = pslist.PsList.create_pid_filter(pid_list)
layer = context.layers[layer_name]
kernel = context.modules[kernel_module_name]
layer = context.layers[kernel.layer_name]
reverse_map: Dict[int, Set[Tuple[str, int]]] = dict()
if isinstance(layer, intel.Intel):
# We don't care about errors, we just wanted chunks that map correctly
@@ -161,7 +162,7 @@ class Strings(interfaces.plugins.PluginInterface):
# TODO: Include kernel modules
for process in pslist.PsList.list_processes(
context, layer_name, symbol_table
context=context, kernel_module_name=kernel_module_name
):
if not filter(process):
proc_id = "Unknown"
@@ -179,7 +180,7 @@ class Strings(interfaces.plugins.PluginInterface):
for mapval in proc_layer.mapping(
0x0, proc_layer.maximum_address, ignore_errors=True
):
mapped_offset, _, offset, mapped_size, maplayer = mapval
mapped_offset, _, offset, mapped_size, _maplayer = mapval
for val in range(
mapped_offset, mapped_offset + mapped_size, 0x1000
):
@@ -30,13 +30,13 @@ class SuspendedThreads(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="pe_symbols", component=pe_symbols.PESymbols, version=(1, 0, 0)
name="pe_symbols", component=pe_symbols.PESymbols, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="threads", component=threads.Threads, version=(1, 0, 0)
name="threads", component=threads.Threads, version=(3, 0, 0)
),
]
@@ -54,19 +54,17 @@ class SuspendedThreads(interfaces.plugins.PluginInterface):
https://www.volexity.com/wp-content/uploads/2024/08/Defcon24_EDR_Evasion_Detection_White-Paper_Andrew-Case.pdf
"""
kernel = self.context.modules[self.config["kernel"]]
vads_cache: Dict[int, pe_symbols.PESymbols.ranges_type] = {}
proc_modules = None
# walk the threads of each process checking for suspended threads
for proc in pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
context=self.context, kernel_module_name=self.config["kernel"]
):
for thread in threads.Threads.list_threads(kernel, proc):
for thread in threads.Threads.list_threads(
self.context, self.config["kernel"], proc
):
try:
# we only care if the thread is suspended
if thread.Tcb.SuspendCount == 0:
@@ -96,7 +94,9 @@ class SuspendedThreads(interfaces.plugins.PluginInterface):
# will not have suspended threads
if not proc_modules:
proc_modules = pe_symbols.PESymbols.get_process_modules(
self.context, kernel.layer_name, kernel.symbol_table_name, None
context=self.context,
kernel_module_name=self.config["kernel"],
filter_modules=None,
)
path_and_symbol = functools.partial(
@@ -34,8 +34,14 @@ class SuspiciousThreads(interfaces.plugins.PluginInterface):
element_type=int,
optional=True,
),
requirements.PluginRequirement(
name="threads", plugin=threads.Threads, version=(1, 0, 0)
requirements.VersionRequirement(
name="thrdscan", component=thrdscan.ThrdScan, version=(1, 1, 0)
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="threads", component=threads.Threads, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0)
@@ -133,8 +139,7 @@ class SuspiciousThreads(interfaces.plugins.PluginInterface):
for proc in pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
):
ranges = self._get_ranges(kernel, all_ranges, proc)
@@ -164,7 +169,9 @@ class SuspiciousThreads(interfaces.plugins.PluginInterface):
# there is no benefit to checking the same address more than once per process
checked = set()
for thread in threads.Threads.list_threads(kernel, proc):
for thread in threads.Threads.list_threads(
self.context, self.config["kernel"], proc
):
# do not process if a thread is exited or terminated (4 = Terminated)
if thread.ExitTime.QuadPart > 0 or thread.Tcb.State == 4:
continue
@@ -26,6 +26,8 @@ class SvcDiff(svcscan.SvcScan):
_required_framework_version = (2, 4, 0)
_version = (2, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._enumeration_method = self.service_diff
@@ -40,10 +42,10 @@ class SvcDiff(svcscan.SvcScan):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="svclist", component=svclist.SvcList, version=(1, 0, 0)
name="svclist", component=svclist.SvcList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="svcscan", component=svcscan.SvcScan, version=(3, 0, 0)
name="svcscan", component=svcscan.SvcScan, version=(4, 0, 0)
),
]
@@ -51,8 +53,7 @@ class SvcDiff(svcscan.SvcScan):
def service_diff(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
service_table_name: str,
service_binary_dll_map,
filter_func,
@@ -61,10 +62,12 @@ class SvcDiff(svcscan.SvcScan):
On Windows 10 version 15063+ 64bit Windows memory samples, walk the services list
and scan for services then report differences
"""
kernel = context.modules[kernel_module_name]
if not symbols.symbol_table_is_64bit(
context, symbol_table
context=context, symbol_table_name=kernel.symbol_table_name
) or not versions.is_win10_15063_or_later(
context=context, symbol_table=symbol_table
context=context, symbol_table=kernel.symbol_table_name
):
vollog.warning(
"This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples"
@@ -78,8 +81,7 @@ class SvcDiff(svcscan.SvcScan):
# collect unique service names from scanning
for service in svcscan.SvcScan.service_scan(
context,
layer_name,
symbol_table,
kernel_module_name,
service_table_name,
service_binary_dll_map,
filter_func,
@@ -90,8 +92,7 @@ class SvcDiff(svcscan.SvcScan):
# collect services from listing walking
for service in svclist.SvcList.service_list(
context,
layer_name,
symbol_table,
kernel_module_name,
service_table_name,
service_binary_dll_map,
filter_func,
@@ -19,7 +19,9 @@ class SvcList(svcscan.SvcScan):
"""Lists services contained with the services.exe doubly linked list of services"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
# 2.0.0 - service_list signature changed
_version = (2, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -30,7 +32,7 @@ class SvcList(svcscan.SvcScan):
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.PluginRequirement(
name="svcscan", plugin=svcscan.SvcScan, version=(3, 0, 0)
name="svcscan", plugin=svcscan.SvcScan, version=(4, 0, 0)
),
requirements.ModuleRequirement(
name="kernel",
@@ -60,16 +62,17 @@ class SvcList(svcscan.SvcScan):
def service_list(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
service_table_name: str,
service_binary_dll_map,
filter_func,
):
kernel = context.modules[kernel_module_name]
if not symbols.symbol_table_is_64bit(
context, symbol_table
context=context, symbol_table_name=kernel.symbol_table_name
) or not versions.is_win10_15063_or_later(
context=context, symbol_table=symbol_table
context=context, symbol_table=kernel.symbol_table_name
):
vollog.warning(
"This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples"
@@ -78,19 +81,18 @@ class SvcList(svcscan.SvcScan):
for proc in pslist.PsList.list_processes(
context=context,
layer_name=layer_name,
symbol_table=symbol_table,
kernel_module_name=kernel_module_name,
filter_func=filter_func,
):
try:
layer_name = proc.add_process_layer()
proc_layer_name = proc.add_process_layer()
except exceptions.InvalidAddressException:
vollog.warning(
f"Unable to access memory of services.exe running with PID: {proc.UniqueProcessId}"
)
continue
layer = context.layers[layer_name]
proc_layer = context.layers[proc_layer_name]
exe_range = cls._get_exe_range(proc)
if not exe_range:
@@ -99,7 +101,7 @@ class SvcList(svcscan.SvcScan):
)
continue
for offset in layer.scan(
for offset in proc_layer.scan(
context=context,
scanner=scanners.BytesScanner(needle=b"Sc27"),
sections=exe_range,
@@ -108,6 +110,6 @@ class SvcList(svcscan.SvcScan):
context,
service_table_name,
service_binary_dll_map,
layer_name,
proc_layer_name,
offset,
)
@@ -4,7 +4,7 @@
import logging
import os
from typing import Dict, List, NamedTuple, Optional, Tuple, Union, cast
from typing import Dict, List, NamedTuple, Optional, Tuple, Union, cast, Callable
from volatility3.framework import (
constants,
@@ -20,7 +20,7 @@ from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows import versions
from volatility3.framework.symbols.windows.extensions import services as services_types
from volatility3.plugins.windows import poolscanner, pslist
from volatility3.plugins.windows import pslist
from volatility3.plugins.windows.registry import hivelist
vollog = logging.getLogger(__name__)
@@ -35,7 +35,7 @@ class SvcScan(interfaces.plugins.PluginInterface):
"""Scans for windows services."""
_required_framework_version = (2, 0, 0)
_version = (3, 0, 2)
_version = (4, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -51,13 +51,10 @@ class SvcScan(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
),
requirements.PluginRequirement(
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
name="hivelist", plugin=hivelist.HiveList, version=(2, 0, 0)
),
]
@@ -106,7 +103,7 @@ class SvcScan(interfaces.plugins.PluginInterface):
@staticmethod
def _create_service_table(
context: interfaces.context.ContextInterface,
symbol_table: str,
symbol_table_name: str,
config_path: str,
) -> str:
"""Constructs a symbol table containing the symbols for services
@@ -120,15 +117,17 @@ class SvcScan(interfaces.plugins.PluginInterface):
Returns:
A symbol table containing the symbols necessary for services
"""
native_types = context.symbol_space[symbol_table].natives
is_64bit = symbols.symbol_table_is_64bit(context, symbol_table)
native_types = context.symbol_space[symbol_table_name].natives
is_64bit = symbols.symbol_table_is_64bit(
context=context, symbol_table_name=symbol_table_name
)
try:
symbol_filename = next(
filename
for version_check, for_64bit, filename in SvcScan._win_version_file_map
if is_64bit == for_64bit
and version_check(context=context, symbol_table=symbol_table)
and version_check(context=context, symbol_table=symbol_table_name)
)
except StopIteration:
raise NotImplementedError("This version of Windows is not supported!")
@@ -144,15 +143,15 @@ class SvcScan(interfaces.plugins.PluginInterface):
@staticmethod
def _get_service_key(
context, config_path: str, layer_name: str, symbol_table: str
context, config_path: str, kernel_module_name: str
) -> Optional[objects.StructType]:
for hive in hivelist.HiveList.list_hives(
context=context,
base_config_path=interfaces.configuration.path_join(
config_path, "hivelist"
),
layer_name=layer_name,
symbol_table=symbol_table,
kernel_module_name=kernel_module_name,
filter_string="machine\\system",
):
# Get ControlSet\Services.
@@ -278,18 +277,19 @@ class SvcScan(interfaces.plugins.PluginInterface):
def service_scan(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
service_table_name: str,
service_binary_dll_map,
filter_func,
):
kernel = context.modules[kernel_module_name]
relative_tag_offset = context.symbol_space.get_type(
service_table_name + constants.BANG + "_SERVICE_RECORD"
).relative_child_offset("Tag")
is_vista_or_later = versions.is_vista_or_later(
context=context, symbol_table=symbol_table
context=context, symbol_table=kernel.symbol_table_name
)
if is_vista_or_later:
@@ -300,9 +300,8 @@ class SvcScan(interfaces.plugins.PluginInterface):
seen = []
for task in pslist.PsList.list_processes(
context=context,
layer_name=layer_name,
symbol_table=symbol_table,
context,
kernel_module_name=kernel_module_name,
filter_func=filter_func,
):
proc_id = "Unknown"
@@ -315,7 +314,7 @@ class SvcScan(interfaces.plugins.PluginInterface):
)
continue
layer = context.layers[proc_layer_name]
process_layer = context.layers[proc_layer_name]
# get process sections for scanning
sections = []
@@ -324,7 +323,7 @@ class SvcScan(interfaces.plugins.PluginInterface):
if vad.get_size():
sections.append((base, vad.get_size()))
for offset in layer.scan(
for offset in process_layer.scan(
context=context,
scanner=scanners.BytesScanner(needle=service_tag),
sections=sections,
@@ -360,18 +359,20 @@ class SvcScan(interfaces.plugins.PluginInterface):
yield service_record
@classmethod
def get_prereq_info(cls, context, config_path, layer_name: str, symbol_table: str):
def get_prereq_info(
cls, context, config_path: str, kernel_module_name: str
) -> Tuple[str, Dict, Callable]:
"""
Data structures and information needed to analyze service information
"""
kernel = context.modules[kernel_module_name]
service_table_name = cls._create_service_table(
context, symbol_table, config_path
context, kernel.symbol_table_name, config_path
)
services_key = cls._get_service_key(
context, config_path, layer_name, symbol_table
)
services_key = cls._get_service_key(context, config_path, kernel_module_name)
service_binary_dll_map = (
cls._get_service_binary_map(services_key)
@@ -384,16 +385,13 @@ class SvcScan(interfaces.plugins.PluginInterface):
return service_table_name, service_binary_dll_map, filter_func
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info(
self.context, self.config_path, kernel.layer_name, kernel.symbol_table_name
self.context, self.config_path, self.config["kernel"]
)
for record in self._enumeration_method(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
self.config["kernel"],
service_table_name,
service_binary_dll_map,
filter_func,
@@ -17,6 +17,8 @@ class SymlinkScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfa
_required_framework_version = (2, 0, 0)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
return [
@@ -25,14 +27,16 @@ class SymlinkScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfa
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="poolscanner", component=poolscanner.PoolScanner, version=(3, 0, 0)
),
]
@classmethod
def scan_symlinks(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
kernel_module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Scans for links using the poolscanner module and constraints.
@@ -45,22 +49,20 @@ class SymlinkScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfa
A list of symlink objects found by scanning memory for the Symlink pool signatures
"""
kernel = context.modules[kernel_module_name]
constraints = poolscanner.PoolScanner.builtin_constraints(
symbol_table, [b"Sym\xe2", b"Symb"]
kernel.symbol_table_name, [b"Sym\xe2", b"Symb"]
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, symbol_table, constraints
context, kernel_module_name, constraints
):
_constraint, mem_object, _header = result
yield mem_object
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
for link in self.scan_symlinks(
self.context, kernel.layer_name, kernel.symbol_table_name
):
for link in self.scan_symlinks(self.context, self.config["kernel"]):
try:
from_name = link.get_link_name()
except (ValueError, exceptions.InvalidAddressException):

Some files were not shown because too many files have changed in this diff Show More