mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-08-24 15:12:23 +02:00
#1476 - merge develop
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -15,7 +15,7 @@ on:
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.8"]
|
||||
|
||||
@@ -23,7 +23,7 @@ on:
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -45,7 +45,6 @@ dev = [
|
||||
test = [
|
||||
"volatility3[dev]",
|
||||
"pytest>=8.3.3,<9",
|
||||
"capstone>=5.0.3,<6",
|
||||
"yara-x>=0.10.0,<1",
|
||||
]
|
||||
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -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,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 = ""
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
),
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -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"]:
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
),
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
),
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user