mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-08 02:37:39 +02:00
Merge branch 'develop' into linux_lsof_refactoring_fixes_and_improvements
This commit is contained in:
@@ -134,4 +134,5 @@ def __getattr__(name):
|
||||
]:
|
||||
warnings.warn(f"{name} is deprecated", FutureWarning)
|
||||
return globals()[f"{deprecated_tag}{name}"]
|
||||
return None
|
||||
|
||||
return getattr(__import__(__name__), name)
|
||||
|
||||
@@ -494,8 +494,7 @@ class SimpleTypeRequirement(RequirementInterface):
|
||||
"""Validates the instance requirement based upon its
|
||||
`instance_type`."""
|
||||
config_path = path_join(config_path, self.name)
|
||||
|
||||
value = self.config_value(context, config_path, None)
|
||||
value = self.config_value(context, config_path, self.default)
|
||||
if not isinstance(value, self.instance_type):
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_V,
|
||||
@@ -536,7 +535,7 @@ class ClassRequirement(RequirementInterface):
|
||||
"""Checks to see if a class can be recovered."""
|
||||
config_path = path_join(config_path, self.name)
|
||||
|
||||
value = self.config_value(context, config_path, None)
|
||||
value = self.config_value(context, config_path, self.default)
|
||||
self._cls = None
|
||||
if value is not None and isinstance(value, str):
|
||||
if "." in value:
|
||||
|
||||
@@ -2,20 +2,19 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from volatility3.framework import interfaces, renderers
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.plugins.linux import pslist
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Check_creds(interfaces.plugins.PluginInterface):
|
||||
"""Checks if any processes are sharing credential structures"""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
_version = (2, 0, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
return [
|
||||
@@ -46,20 +45,28 @@ class Check_creds(interfaces.plugins.PluginInterface):
|
||||
tasks = pslist.PsList.list_tasks(self.context, vmlinux.name)
|
||||
|
||||
for task in tasks:
|
||||
cred_addr = task.cred.dereference().vol.offset
|
||||
task_cred_ptr = task.cred
|
||||
if not (task_cred_ptr and task_cred_ptr.is_readable()):
|
||||
continue
|
||||
|
||||
if cred_addr not in creds:
|
||||
creds[cred_addr] = []
|
||||
cred_addr = task_cred_ptr.dereference().vol.offset
|
||||
|
||||
creds.setdefault(cred_addr, [])
|
||||
creds[cred_addr].append(task.pid)
|
||||
|
||||
for _, pids in creds.items():
|
||||
for cred_addr, pids in creds.items():
|
||||
if len(pids) > 1:
|
||||
pid_str = ""
|
||||
for pid in pids:
|
||||
pid_str = pid_str + f"{pid:d}, "
|
||||
pid_str = pid_str[:-2]
|
||||
yield (0, [str(pid_str)])
|
||||
pid_str = ", ".join([str(pid) for pid in pids])
|
||||
|
||||
fields = [
|
||||
format_hints.Hex(cred_addr),
|
||||
pid_str,
|
||||
]
|
||||
yield (0, fields)
|
||||
|
||||
def run(self):
|
||||
return renderers.TreeGrid([("PIDs", str)], self._generator())
|
||||
headers = [
|
||||
("CredVAddr", format_hints.Hex),
|
||||
("PIDs", str),
|
||||
]
|
||||
return renderers.TreeGrid(headers, self._generator())
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
from typing import List
|
||||
import logging
|
||||
from volatility3.framework import constants, interfaces
|
||||
from volatility3.framework import renderers
|
||||
from volatility3.framework import renderers, symbols
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.framework.renderers import format_hints
|
||||
@@ -63,15 +63,9 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
def _generator(self, tasks):
|
||||
# determine if we're on a 32 or 64 bit kernel
|
||||
vmlinux = self.context.modules[self.config["kernel"]]
|
||||
if (
|
||||
self.context.symbol_space.get_type(
|
||||
vmlinux.symbol_table_name + constants.BANG + "pointer"
|
||||
).size
|
||||
== 4
|
||||
):
|
||||
is_32bit_arch = True
|
||||
else:
|
||||
is_32bit_arch = False
|
||||
is_32bit_arch = not symbols.symbol_table_is_64bit(
|
||||
self.context, vmlinux.symbol_table_name
|
||||
)
|
||||
|
||||
for task in tasks:
|
||||
process_name = utility.array_to_string(task.comm)
|
||||
|
||||
@@ -20,7 +20,7 @@ class PIDHashTable(plugins.PluginInterface):
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
_version = (1, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -45,9 +45,7 @@ class PIDHashTable(plugins.PluginInterface):
|
||||
]
|
||||
|
||||
def _is_valid_task(self, task) -> bool:
|
||||
vmlinux = self.context.modules[self.config["kernel"]]
|
||||
vmlinux_layer = self.context.layers[vmlinux.layer_name]
|
||||
return bool(task and task.pid > 0 and vmlinux_layer.is_valid(task.parent))
|
||||
return bool(task and task.pid > 0 and task.parent.is_readable())
|
||||
|
||||
def _get_pidtype_pid(self):
|
||||
vmlinux = self.context.modules[self.config["kernel"]]
|
||||
@@ -96,7 +94,7 @@ class PIDHashTable(plugins.PluginInterface):
|
||||
seen_upids.add(upid.vol.offset)
|
||||
|
||||
pid_chain = upid.pid_chain
|
||||
if not (pid_chain and vmlinux_layer.is_valid(pid_chain.vol.offset)):
|
||||
if not (pid_chain.next and pid_chain.next.is_readable()):
|
||||
break
|
||||
|
||||
upid = linux.LinuxUtilities.container_of(
|
||||
@@ -105,7 +103,6 @@ class PIDHashTable(plugins.PluginInterface):
|
||||
|
||||
def _get_upids(self):
|
||||
vmlinux = self.context.modules[self.config["kernel"]]
|
||||
vmlinux_layer = self.context.layers[vmlinux.layer_name]
|
||||
|
||||
# 2.6.24 <= kernels < 4.15
|
||||
pidhash = self._get_pidhash_array()
|
||||
@@ -115,7 +112,7 @@ class PIDHashTable(plugins.PluginInterface):
|
||||
# each entry in the hlist is a upid which is wrapped in a pid
|
||||
ent = hlist.first
|
||||
|
||||
while ent and vmlinux_layer.is_valid(ent.vol.offset):
|
||||
while ent and ent.is_readable():
|
||||
# upid->pid_chain exists 2.6.24 <= kernel < 4.15
|
||||
upid = linux.LinuxUtilities.container_of(
|
||||
ent.vol.offset, "upid", "pid_chain", vmlinux
|
||||
@@ -143,7 +140,7 @@ class PIDHashTable(plugins.PluginInterface):
|
||||
continue
|
||||
|
||||
pid_tasks_0 = pid.tasks[pidtype_pid].first
|
||||
if not pid_tasks_0:
|
||||
if not (pid_tasks_0 and pid_tasks_0.is_readable()):
|
||||
continue
|
||||
|
||||
task = vmlinux.object(
|
||||
@@ -160,7 +157,7 @@ class PIDHashTable(plugins.PluginInterface):
|
||||
pidtype_pid = self._get_pidtype_pid()
|
||||
|
||||
pid_tasks_0 = pid.tasks[pidtype_pid].first
|
||||
if not pid_tasks_0:
|
||||
if not (pid_tasks_0 and pid_tasks_0.is_readable()):
|
||||
return None
|
||||
|
||||
task_struct_type = vmlinux.get_type("task_struct")
|
||||
|
||||
@@ -22,7 +22,7 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
_version = (1, 0, 1)
|
||||
_version = (2, 0, 0)
|
||||
|
||||
def __init__(self, vmlinux, task, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -450,7 +450,7 @@ class Sockstat(plugins.PluginInterface):
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="SockHandlers", component=SockHandlers, version=(1, 0, 0)
|
||||
name="SockHandlers", component=SockHandlers, version=(2, 0, 0)
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="lsof", plugin=lsof.Lsof, version=(2, 0, 0)
|
||||
@@ -550,7 +550,7 @@ class Sockstat(plugins.PluginInterface):
|
||||
except AttributeError:
|
||||
netns_id = NotAvailableValue()
|
||||
|
||||
yield task, netns_id, fd_num, family, sock_type, protocol, sock_fields
|
||||
yield task_comm, task, netns_id, fd_num, family, sock_type, protocol, sock_fields
|
||||
|
||||
def _format_fields(self, sock_stat, protocol):
|
||||
"""Prepare the socket fields to be rendered
|
||||
@@ -597,6 +597,7 @@ class Sockstat(plugins.PluginInterface):
|
||||
)
|
||||
|
||||
for (
|
||||
task_comm,
|
||||
task,
|
||||
netns_id,
|
||||
fd_num,
|
||||
@@ -619,6 +620,7 @@ class Sockstat(plugins.PluginInterface):
|
||||
|
||||
fields = (
|
||||
netns_id,
|
||||
task_comm,
|
||||
task.tgid,
|
||||
task.pid,
|
||||
fd_num,
|
||||
@@ -639,6 +641,7 @@ class Sockstat(plugins.PluginInterface):
|
||||
|
||||
tree_grid_args = [
|
||||
("NetNS", int),
|
||||
("Process Name", str),
|
||||
("PID", int),
|
||||
("TID", int),
|
||||
("FD", int),
|
||||
|
||||
@@ -45,6 +45,7 @@ class Timeliner(interfaces.plugins.PluginInterface):
|
||||
orders the results by time."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 1, 0)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -198,9 +199,10 @@ class Timeliner(interfaces.plugins.PluginInterface):
|
||||
),
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
vollog.log(
|
||||
logging.INFO, f"Exception occurred running plugin: {plugin_name}"
|
||||
logging.INFO,
|
||||
f"Exception occurred running plugin: {plugin_name}: {e}",
|
||||
)
|
||||
vollog.log(logging.DEBUG, traceback.format_exc())
|
||||
|
||||
@@ -245,6 +247,18 @@ class Timeliner(interfaces.plugins.PluginInterface):
|
||||
filter_list = self.config["plugin-filter"]
|
||||
# Identify plugins that we can run which output datetimes
|
||||
for plugin_class in self.usable_plugins:
|
||||
if not issubclass(plugin_class, TimeLinerInterface):
|
||||
# get_usable_plugins() should filter this, but adding a safeguard just in case
|
||||
continue
|
||||
|
||||
if filter_list and not any(
|
||||
[
|
||||
filter in plugin_class.__module__ + "." + plugin_class.__name__
|
||||
for filter in filter_list
|
||||
]
|
||||
):
|
||||
continue
|
||||
|
||||
try:
|
||||
automagics = automagic.choose_automagic(self.automagics, plugin_class)
|
||||
|
||||
@@ -276,15 +290,8 @@ class Timeliner(interfaces.plugins.PluginInterface):
|
||||
config_value,
|
||||
)
|
||||
|
||||
if isinstance(plugin, TimeLinerInterface):
|
||||
if not len(filter_list) or any(
|
||||
[
|
||||
filter
|
||||
in plugin.__module__ + "." + plugin.__class__.__name__
|
||||
for filter in filter_list
|
||||
]
|
||||
):
|
||||
plugins_to_run.append(plugin)
|
||||
plugins_to_run.append(plugin)
|
||||
|
||||
except exceptions.UnsatisfiedException as excp:
|
||||
# Remove the failed plugin from the list and continue
|
||||
vollog.debug(
|
||||
|
||||
@@ -248,8 +248,12 @@ class Callbacks(interfaces.plugins.PluginInterface):
|
||||
context, layer_name, nt_symbol_table, constraints
|
||||
):
|
||||
try:
|
||||
if hasattr(mem_object, "is_valid") and not mem_object.is_valid():
|
||||
continue
|
||||
if isinstance(mem_object, callbacks._SHUTDOWN_PACKET):
|
||||
if not mem_object.is_parseable(type_map):
|
||||
continue
|
||||
elif hasattr(mem_object, "is_valid"):
|
||||
if not mem_object.is_valid():
|
||||
continue
|
||||
|
||||
yield cls._process_scanned_callback(mem_object, type_map)
|
||||
except exceptions.InvalidAddressException:
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
# 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
|
||||
|
||||
# Full details on the techniques used in these plugins to detect EDR-evading malware
|
||||
# can be found in our 20 page whitepaper submitted to DEFCON along with the presentation
|
||||
# https://www.volexity.com/wp-content/uploads/2024/08/Defcon24_EDR_Evasion_Detection_White-Paper_Andrew-Case.pdf
|
||||
|
||||
import logging
|
||||
|
||||
from typing import Tuple, Optional, Generator, List, Dict
|
||||
|
||||
from functools import partial
|
||||
|
||||
from volatility3.framework import renderers, interfaces, exceptions
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import format_hints
|
||||
import volatility3.plugins.windows.pslist as pslist
|
||||
import volatility3.plugins.windows.threads as threads
|
||||
import volatility3.plugins.windows.pe_symbols as pe_symbols
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DebugRegisters(interfaces.plugins.PluginInterface):
|
||||
# version 2.6.0 adds support for scanning for 'Ethread' structures by pool tags
|
||||
_required_framework_version = (2, 6, 0)
|
||||
_version = (1, 0, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List:
|
||||
return [
|
||||
requirements.ModuleRequirement(
|
||||
name="kernel",
|
||||
description="Windows kernel",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="pslist", component=pslist.PsList, version=(2, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="pe_symbols", component=pe_symbols.PESymbols, version=(1, 0, 0)
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def _get_debug_info(
|
||||
ethread: interfaces.objects.ObjectInterface,
|
||||
) -> Optional[Tuple[interfaces.objects.ObjectInterface, int, int, int, int, int]]:
|
||||
"""
|
||||
Gathers information related to the debug registers for the given thread
|
||||
Args:
|
||||
ethread: the thread (_ETHREAD) to examine
|
||||
Returns:
|
||||
Tuple[interfaces.objects.ObjectInterface, int, int, int, int, int]: The owner process of the thread and the values for dr7, dr0, dr1, dr2, dr3
|
||||
"""
|
||||
try:
|
||||
dr7 = ethread.Tcb.TrapFrame.Dr7
|
||||
state = ethread.Tcb.State
|
||||
except exceptions.InvalidAddressException:
|
||||
return None
|
||||
|
||||
# 0 = debug registers not active
|
||||
# 4 = terminated
|
||||
if dr7 == 0 or state == 4:
|
||||
return None
|
||||
|
||||
try:
|
||||
owner_proc = ethread.owning_process()
|
||||
except (AttributeError, exceptions.InvalidAddressException):
|
||||
return None
|
||||
|
||||
dr0 = ethread.Tcb.TrapFrame.Dr0
|
||||
dr1 = ethread.Tcb.TrapFrame.Dr1
|
||||
dr2 = ethread.Tcb.TrapFrame.Dr2
|
||||
dr3 = ethread.Tcb.TrapFrame.Dr3
|
||||
|
||||
# bail if all are 0
|
||||
if not (dr0 or dr1 or dr2 or dr3):
|
||||
return None
|
||||
|
||||
return owner_proc, dr7, dr0, dr1, dr2, dr3
|
||||
|
||||
def _generator(
|
||||
self,
|
||||
) -> Generator[
|
||||
Tuple[
|
||||
int,
|
||||
Tuple[
|
||||
str,
|
||||
int,
|
||||
int,
|
||||
int,
|
||||
int,
|
||||
format_hints.Hex,
|
||||
str,
|
||||
str,
|
||||
format_hints.Hex,
|
||||
str,
|
||||
str,
|
||||
format_hints.Hex,
|
||||
str,
|
||||
str,
|
||||
format_hints.Hex,
|
||||
str,
|
||||
str,
|
||||
],
|
||||
],
|
||||
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,
|
||||
)
|
||||
|
||||
for proc in procs:
|
||||
for thread in threads.Threads.list_threads(kernel, proc):
|
||||
debug_info = self._get_debug_info(thread)
|
||||
if not debug_info:
|
||||
continue
|
||||
|
||||
owner_proc, dr7, dr0, dr1, dr2, dr3 = debug_info
|
||||
|
||||
vads = pe_symbols.PESymbols.get_vads_for_process_cache(
|
||||
vads_cache, owner_proc
|
||||
)
|
||||
if not vads:
|
||||
continue
|
||||
|
||||
# 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
|
||||
)
|
||||
path_and_symbol = partial(
|
||||
pe_symbols.PESymbols.path_and_symbol_for_address,
|
||||
self.context,
|
||||
self.config_path,
|
||||
proc_modules,
|
||||
)
|
||||
|
||||
file0, sym0 = path_and_symbol(vads, dr0)
|
||||
file1, sym1 = path_and_symbol(vads, dr1)
|
||||
file2, sym2 = path_and_symbol(vads, dr2)
|
||||
file3, sym3 = path_and_symbol(vads, dr3)
|
||||
|
||||
# if none map to an actual file VAD then bail
|
||||
if not (file0 or file1 or file2 or file3):
|
||||
continue
|
||||
|
||||
process_name = owner_proc.ImageFileName.cast(
|
||||
"string",
|
||||
max_length=owner_proc.ImageFileName.vol.count,
|
||||
errors="replace",
|
||||
)
|
||||
|
||||
thread_tid = thread.Cid.UniqueThread
|
||||
|
||||
yield (
|
||||
0,
|
||||
(
|
||||
process_name,
|
||||
owner_proc.UniqueProcessId,
|
||||
thread_tid,
|
||||
thread.Tcb.State,
|
||||
dr7,
|
||||
format_hints.Hex(dr0),
|
||||
file0 or renderers.NotApplicableValue(),
|
||||
sym0 or renderers.NotApplicableValue(),
|
||||
format_hints.Hex(dr1),
|
||||
file1 or renderers.NotApplicableValue(),
|
||||
sym1 or renderers.NotApplicableValue(),
|
||||
format_hints.Hex(dr2),
|
||||
file2 or renderers.NotApplicableValue(),
|
||||
sym2 or renderers.NotApplicableValue(),
|
||||
format_hints.Hex(dr3),
|
||||
file3 or renderers.NotApplicableValue(),
|
||||
sym3 or renderers.NotApplicableValue(),
|
||||
),
|
||||
)
|
||||
|
||||
def run(self) -> renderers.TreeGrid:
|
||||
return renderers.TreeGrid(
|
||||
[
|
||||
("Process", str),
|
||||
("PID", int),
|
||||
("TID", int),
|
||||
("State", int),
|
||||
("Dr7", int),
|
||||
("Dr0", format_hints.Hex),
|
||||
("Range0", str),
|
||||
("Symbol0", str),
|
||||
("Dr1", format_hints.Hex),
|
||||
("Range1", str),
|
||||
("Symbol1", str),
|
||||
("Dr2", format_hints.Hex),
|
||||
("Range2", str),
|
||||
("Symbol2", str),
|
||||
("Dr3", format_hints.Hex),
|
||||
("Range3", str),
|
||||
("Symbol3", str),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
@@ -76,7 +76,7 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
|
||||
# ~ vollog.debug("Using pool size constraints: TcpL {}, TcpE {}, UdpA {}".format(tcpl_size, tcpe_size, udpa_size))
|
||||
|
||||
return [
|
||||
constraints = [
|
||||
# TCP listener
|
||||
poolscanner.PoolConstraint(
|
||||
b"TcpL",
|
||||
@@ -100,6 +100,19 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
),
|
||||
]
|
||||
|
||||
if symbol_table.startswith("netscan-win10-20348"):
|
||||
vollog.debug("Adding additional pool constraint for `TTcb` tags")
|
||||
constraints.append(
|
||||
poolscanner.PoolConstraint(
|
||||
b"TTcb",
|
||||
type_name=symbol_table + constants.BANG + "_TCP_ENDPOINT",
|
||||
size=(tcpe_size, None),
|
||||
page_type=poolscanner.PoolType.NONPAGED | poolscanner.PoolType.FREE,
|
||||
)
|
||||
)
|
||||
|
||||
return constraints
|
||||
|
||||
@classmethod
|
||||
def determine_tcpip_version(
|
||||
cls,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
from typing import Callable, Iterable, List, Type
|
||||
from typing import Callable, Iterator, List, Type
|
||||
|
||||
from volatility3.framework import renderers, interfaces, layers, exceptions, constants
|
||||
from volatility3.framework.configuration import requirements
|
||||
@@ -12,6 +12,7 @@ from volatility3.framework.objects import utility
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.framework.symbols import intermed
|
||||
from volatility3.framework.symbols.windows.extensions import pe
|
||||
from volatility3.framework.symbols.windows import extensions
|
||||
from volatility3.plugins import timeliner
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
@@ -197,7 +198,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
filter_func: Callable[
|
||||
[interfaces.objects.ObjectInterface], bool
|
||||
] = lambda _: False,
|
||||
) -> Iterable[interfaces.objects.ObjectInterface]:
|
||||
) -> Iterator["extensions.EPROCESS"]:
|
||||
"""Lists all the processes in the primary layer that are in the pid
|
||||
config option.
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import datetime, logging, string
|
||||
import datetime
|
||||
import logging
|
||||
import string
|
||||
from itertools import chain
|
||||
from typing import Dict, Iterable, List
|
||||
|
||||
from volatility3.framework import constants, exceptions
|
||||
from volatility3.framework.interfaces import plugins
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import format_hints, TreeGrid
|
||||
from volatility3.framework.interfaces import plugins
|
||||
from volatility3.framework.renderers import TreeGrid, format_hints
|
||||
from volatility3.framework.symbols.windows import extensions
|
||||
from volatility3.plugins.windows import (
|
||||
handles,
|
||||
info,
|
||||
@@ -71,20 +76,19 @@ class PsXView(plugins.PluginInterface):
|
||||
"string", max_length=proc.ImageFileName.vol.count, errors="replace"
|
||||
)
|
||||
|
||||
def _is_valid_proc_name(self, str):
|
||||
for c in str:
|
||||
if not c in self.valid_proc_name_chars:
|
||||
return False
|
||||
return True
|
||||
def _is_valid_proc_name(self, string: str) -> bool:
|
||||
return all(c in self.valid_proc_name_chars for c in string)
|
||||
|
||||
def _filter_garbage_procs(self, proc_list):
|
||||
def _filter_garbage_procs(
|
||||
self, proc_list: Iterable[extensions.EPROCESS]
|
||||
) -> List[extensions.EPROCESS]:
|
||||
return [
|
||||
p
|
||||
for p in proc_list
|
||||
if p.is_valid() and self._is_valid_proc_name(self._proc_name_to_string(p))
|
||||
]
|
||||
|
||||
def _translate_offset(self, offset):
|
||||
def _translate_offset(self, offset: int) -> int:
|
||||
if not self.config["physical-offsets"]:
|
||||
return offset
|
||||
|
||||
@@ -100,21 +104,25 @@ class PsXView(plugins.PluginInterface):
|
||||
|
||||
return offset
|
||||
|
||||
def _proc_list_to_dict(self, tasks):
|
||||
def _proc_list_to_dict(
|
||||
self, tasks: Iterable[extensions.EPROCESS]
|
||||
) -> Dict[int, extensions.EPROCESS]:
|
||||
tasks = self._filter_garbage_procs(tasks)
|
||||
return {self._translate_offset(proc.vol.offset): proc for proc in tasks}
|
||||
|
||||
def _check_pslist(self, tasks):
|
||||
return self._proc_list_to_dict(tasks)
|
||||
|
||||
def _check_psscan(self, layer_name, symbol_table):
|
||||
def _check_psscan(
|
||||
self, layer_name: str, symbol_table: str
|
||||
) -> Dict[int, extensions.EPROCESS]:
|
||||
res = psscan.PsScan.scan_processes(
|
||||
context=self.context, layer_name=layer_name, symbol_table=symbol_table
|
||||
)
|
||||
|
||||
return self._proc_list_to_dict(res)
|
||||
|
||||
def _check_thrdscan(self):
|
||||
def _check_thrdscan(self) -> Dict[int, extensions.EPROCESS]:
|
||||
ret = []
|
||||
|
||||
for ethread in thrdscan.ThrdScan.scan_threads(
|
||||
@@ -135,33 +143,38 @@ class PsXView(plugins.PluginInterface):
|
||||
|
||||
return self._proc_list_to_dict(ret)
|
||||
|
||||
def _check_csrss_handles(self, tasks, layer_name, symbol_table):
|
||||
ret = []
|
||||
def _check_csrss_handles(
|
||||
self, tasks: Iterable[extensions.EPROCESS], layer_name: str, symbol_table: str
|
||||
) -> Dict[int, extensions.EPROCESS]:
|
||||
ret: List[extensions.EPROCESS] = []
|
||||
|
||||
handles_plugin = handles.Handles(
|
||||
context=self.context, config_path=self.config_path
|
||||
)
|
||||
|
||||
type_map = handles_plugin.get_type_map(self.context, layer_name, symbol_table)
|
||||
|
||||
cookie = handles_plugin.find_cookie(
|
||||
context=self.context,
|
||||
layer_name=layer_name,
|
||||
symbol_table=symbol_table,
|
||||
)
|
||||
|
||||
for p in tasks:
|
||||
name = self._proc_name_to_string(p)
|
||||
if name == "csrss.exe":
|
||||
try:
|
||||
if p.has_member("ObjectTable"):
|
||||
handles_plugin = handles.Handles(
|
||||
context=self.context, config_path=self.config_path
|
||||
)
|
||||
hndls = list(handles_plugin.handles(p.ObjectTable))
|
||||
for h in hndls:
|
||||
if (
|
||||
h.get_object_type(
|
||||
handles_plugin.get_type_map(
|
||||
self.context, layer_name, symbol_table
|
||||
)
|
||||
)
|
||||
== "Process"
|
||||
):
|
||||
ret.append(h.Body.cast("_EPROCESS"))
|
||||
if name != "csrss.exe":
|
||||
continue
|
||||
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVV, "Cannot access eprocess object table"
|
||||
)
|
||||
try:
|
||||
ret += [
|
||||
handle.Body.cast("_EPROCESS")
|
||||
for handle in handles_plugin.handles(p.ObjectTable)
|
||||
if handle.get_object_type(type_map, cookie) == "Process"
|
||||
]
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVV, "Cannot access eprocess object table"
|
||||
)
|
||||
|
||||
return self._proc_list_to_dict(ret)
|
||||
|
||||
@@ -178,7 +191,7 @@ class PsXView(plugins.PluginInterface):
|
||||
)
|
||||
|
||||
# get processes from each source
|
||||
processes = {}
|
||||
processes: Dict[str, Dict[int, extensions.EPROCESS]] = {}
|
||||
|
||||
processes["pslist"] = self._check_pslist(kdbg_list_processes)
|
||||
processes["psscan"] = self._check_psscan(layer_name, symbol_table)
|
||||
@@ -187,27 +200,20 @@ class PsXView(plugins.PluginInterface):
|
||||
kdbg_list_processes, layer_name, symbol_table
|
||||
)
|
||||
|
||||
# print results
|
||||
|
||||
# list of lists of offsets
|
||||
offsets = [list(processes[source].keys()) for source in processes]
|
||||
|
||||
# flatten to one list
|
||||
offsets = sum(offsets, [])
|
||||
|
||||
# remove duplicates
|
||||
offsets = set(offsets)
|
||||
# Unique set of all offsets from all sources
|
||||
offsets = set(chain(*(mapping.keys() for mapping in processes.values())))
|
||||
|
||||
for offset in offsets:
|
||||
proc = None
|
||||
# We know there will be at least one process mapped to each offset
|
||||
proc: extensions.EPROCESS = next(
|
||||
mapping[offset] for mapping in processes.values() if offset in mapping
|
||||
)
|
||||
|
||||
in_sources = {src: False for src in processes}
|
||||
|
||||
for source in processes:
|
||||
if offset in processes[source]:
|
||||
for source, process_mapping in processes.items():
|
||||
if offset in process_mapping:
|
||||
in_sources[source] = True
|
||||
if not proc:
|
||||
proc = processes[source][offset]
|
||||
|
||||
pid = proc.UniqueProcessId
|
||||
name = self._proc_name_to_string(proc)
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
# 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
|
||||
|
||||
# Full details on the techniques used in these plugins to detect EDR-evading malware
|
||||
# can be found in our 20 page whitepaper submitted to DEFCON along with the presentation
|
||||
# https://www.volexity.com/wp-content/uploads/2024/08/Defcon24_EDR_Evasion_Detection_White-Paper_Andrew-Case.pdf
|
||||
|
||||
import logging
|
||||
|
||||
from typing import Dict, Tuple, List, Generator
|
||||
|
||||
from volatility3.framework import interfaces, exceptions
|
||||
from volatility3.framework import renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.plugins.windows import pslist, pe_symbols
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class unhooked_system_calls(interfaces.plugins.PluginInterface):
|
||||
"""Looks for signs of Skeleton Key malware"""
|
||||
|
||||
_required_framework_version = (2, 4, 0)
|
||||
|
||||
system_calls = {
|
||||
"ntdll.dll": {
|
||||
pe_symbols.wanted_names_identifier: [
|
||||
"NtCreateThread",
|
||||
"NtProtectVirtualMemory",
|
||||
"NtReadVirtualMemory",
|
||||
"NtOpenProcess",
|
||||
"NtWriteFile",
|
||||
"NtQueryVirtualMemory",
|
||||
"NtAllocateVirtualMemory",
|
||||
"NtWorkerFactoryWorkerReady",
|
||||
"NtAcceptConnectPort",
|
||||
"NtAddDriverEntry",
|
||||
"NtAdjustPrivilegesToken",
|
||||
"NtAlpcCreatePort",
|
||||
"NtClose",
|
||||
"NtCreateFile",
|
||||
"NtCreateMutant",
|
||||
"NtOpenFile",
|
||||
"NtOpenIoCompletion",
|
||||
"NtOpenJobObject",
|
||||
"NtOpenKey",
|
||||
"NtOpenKeyEx",
|
||||
"NtOpenThread",
|
||||
"NtOpenThreadToken",
|
||||
"NtOpenThreadTokenEx",
|
||||
"NtWriteVirtualMemory",
|
||||
"NtTraceEvent",
|
||||
"NtTranslateFilePath",
|
||||
"NtUmsThreadYield",
|
||||
"NtUnloadDriver",
|
||||
"NtUnloadKey",
|
||||
"NtUnloadKey2",
|
||||
"NtUnloadKeyEx",
|
||||
"NtCreateKey",
|
||||
"NtCreateSection",
|
||||
"NtDeleteKey",
|
||||
"NtDeleteValueKey",
|
||||
"NtDuplicateObject",
|
||||
"NtQueryValueKey",
|
||||
"NtReplaceKey",
|
||||
"NtRequestWaitReplyPort",
|
||||
"NtRestoreKey",
|
||||
"NtSetContextThread",
|
||||
"NtSetSecurityObject",
|
||||
"NtSetValueKey",
|
||||
"NtSystemDebugControl",
|
||||
"NtTerminateProcess",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
# This data structure is used to track unique implementations of functions across processes
|
||||
# The outer dictionary holds the module name (e.g., ntdll.dll)
|
||||
# The next dictionary holds the function names (NtTerminateProcess, NtSetValueKey, etc.) inside a module
|
||||
# The innermost dictionary holds the unique implementation (bytes) of a function across processes
|
||||
# Each implementation is tracked along with the process(es) that host it
|
||||
# For systems without malware, all functions should have the same implementation
|
||||
# When API hooking/module unhooking is done, the victim (infected) processes will have unique implementations
|
||||
_code_bytes_type = Dict[str, Dict[str, Dict[bytes, List[Tuple[int, str]]]]]
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List:
|
||||
# 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.VersionRequirement(
|
||||
name="pslist", component=pslist.PsList, version=(2, 0, 0)
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pe_symbols", plugin=pe_symbols.PESymbols, version=(1, 0, 0)
|
||||
),
|
||||
]
|
||||
|
||||
def _gather_code_bytes(
|
||||
self,
|
||||
kernel: interfaces.context.ModuleInterface,
|
||||
found_symbols: pe_symbols.found_symbols_type,
|
||||
) -> _code_bytes_type:
|
||||
"""
|
||||
Enumerates the desired DLLs and function implementations in each process
|
||||
Groups based on unique implementations of each DLLs' functions
|
||||
The purpose is to detect when a function has different implementations (code)
|
||||
in different processes.
|
||||
This very effectively detects code injection.
|
||||
"""
|
||||
code_bytes: unhooked_system_calls._code_bytes_type = {}
|
||||
|
||||
procs = pslist.PsList.list_processes(
|
||||
context=self.context,
|
||||
layer_name=kernel.layer_name,
|
||||
symbol_table=kernel.symbol_table_name,
|
||||
)
|
||||
|
||||
for proc in procs:
|
||||
try:
|
||||
proc_id = proc.UniqueProcessId
|
||||
proc_name = utility.array_to_string(proc.ImageFileName)
|
||||
proc_layer_name = proc.add_process_layer()
|
||||
except exceptions.InvalidAddressException:
|
||||
continue
|
||||
|
||||
for dll_name, functions in found_symbols.items():
|
||||
for func_name, func_addr in functions:
|
||||
try:
|
||||
fbytes = self.context.layers[proc_layer_name].read(
|
||||
func_addr, 0x20
|
||||
)
|
||||
except exceptions.InvalidAddressException:
|
||||
continue
|
||||
|
||||
# see the definition of _code_bytes_type for details of this data structure
|
||||
if dll_name not in code_bytes:
|
||||
code_bytes[dll_name] = {}
|
||||
|
||||
if func_name not in code_bytes[dll_name]:
|
||||
code_bytes[dll_name][func_name] = {}
|
||||
|
||||
if fbytes not in code_bytes[dll_name][func_name]:
|
||||
code_bytes[dll_name][func_name][fbytes] = []
|
||||
|
||||
code_bytes[dll_name][func_name][fbytes].append((proc_id, proc_name))
|
||||
|
||||
return code_bytes
|
||||
|
||||
def _generator(self) -> Generator[Tuple[int, Tuple[str, str, int]], None, None]:
|
||||
kernel = self.context.modules[self.config["kernel"]]
|
||||
|
||||
found_symbols = pe_symbols.PESymbols.addresses_for_process_symbols(
|
||||
self.context,
|
||||
self.config_path,
|
||||
kernel.layer_name,
|
||||
kernel.symbol_table_name,
|
||||
unhooked_system_calls.system_calls,
|
||||
)
|
||||
|
||||
# code_bytes[dll_name][func_name][func_bytes]
|
||||
code_bytes = self._gather_code_bytes(kernel, found_symbols)
|
||||
|
||||
# walk the functions that were evaluated
|
||||
for functions in code_bytes.values():
|
||||
# cbb is the distinct groups of bytes (instructions)
|
||||
# for this function across processes
|
||||
for func_name, cbb in functions.items():
|
||||
# the dict key here is the raw instructions, which is not helpful to look at
|
||||
# the values are the list of tuples for the (proc_id, proc_name) pairs for this set of bytes (instructions)
|
||||
cb = list(cbb.values())
|
||||
|
||||
# if all processes map to the same implementation, then no malware is present
|
||||
if len(cb) == 1:
|
||||
yield 0, (func_name, "", len(cb[0]))
|
||||
else:
|
||||
# if there are differing implementations then it means
|
||||
# that malware has overwritten system call(s) in infected processes
|
||||
# max_idx and small_idx find which implementation of a system call has the least processes
|
||||
# as all observed malware and open source projects only infected a few targets, leaving the
|
||||
# rest with the original EDR hooks in place
|
||||
max_idx = 0 if len(cb[0]) > len(cb[1]) else 1
|
||||
small_idx = (~max_idx) & 1
|
||||
|
||||
ps = []
|
||||
|
||||
# gather processes on small_idx since these are the malware infected ones
|
||||
for pid, pname in cb[small_idx]:
|
||||
ps.append("{:d}:{}".format(pid, pname))
|
||||
|
||||
proc_names = ", ".join(ps)
|
||||
|
||||
yield 0, (func_name, proc_names, len(cb[max_idx]))
|
||||
|
||||
def run(self) -> renderers.TreeGrid:
|
||||
return renderers.TreeGrid(
|
||||
[
|
||||
("Function", str),
|
||||
("Distinct Implementations", str),
|
||||
("Total Implementations", int),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from typing import Callable, List, Generator, Iterable, Type, Optional
|
||||
from typing import Callable, List, Generator, Iterable, Type, Optional, Tuple
|
||||
|
||||
from volatility3.framework import renderers, interfaces, exceptions
|
||||
from volatility3.framework.configuration import requirements
|
||||
@@ -196,11 +196,31 @@ class VadInfo(interfaces.plugins.PluginInterface):
|
||||
|
||||
return file_handle
|
||||
|
||||
def _generator(self, procs):
|
||||
def _generator(self, procs: List[interfaces.objects.ObjectInterface]) -> Generator[
|
||||
Tuple[
|
||||
int,
|
||||
Tuple[
|
||||
int,
|
||||
str,
|
||||
format_hints.Hex,
|
||||
format_hints.Hex,
|
||||
format_hints.Hex,
|
||||
str,
|
||||
str,
|
||||
int,
|
||||
int,
|
||||
format_hints.Hex,
|
||||
str,
|
||||
str,
|
||||
],
|
||||
],
|
||||
None,
|
||||
None,
|
||||
]:
|
||||
kernel = self.context.modules[self.config["kernel"]]
|
||||
kernel_layer = self.context.layers[kernel.layer_name]
|
||||
|
||||
def passthrough(_: interfaces.objects.ObjectInterface) -> bool:
|
||||
def passthrough(x: interfaces.objects.ObjectInterface) -> bool:
|
||||
return False
|
||||
|
||||
filter_func = passthrough
|
||||
@@ -250,7 +270,7 @@ class VadInfo(interfaces.plugins.PluginInterface):
|
||||
),
|
||||
)
|
||||
|
||||
def run(self):
|
||||
def run(self) -> renderers.TreeGrid:
|
||||
kernel = self.context.modules[self.config["kernel"]]
|
||||
|
||||
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
|
||||
|
||||
@@ -18,7 +18,7 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
|
||||
"""Scans all the Virtual Address Descriptor memory maps using yara."""
|
||||
|
||||
_required_framework_version = (2, 4, 0)
|
||||
_version = (1, 1, 0)
|
||||
_version = (1, 1, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -68,7 +68,7 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
|
||||
layer = self.context.layers[layer_name]
|
||||
for start, size in self.get_vad_maps(task):
|
||||
if size > sanity_check:
|
||||
vollog.warn(
|
||||
vollog.debug(
|
||||
f"VAD at 0x{start:x} over sanity-check size, not scanning"
|
||||
)
|
||||
continue
|
||||
|
||||
@@ -48,9 +48,6 @@ class VerInfo(interfaces.plugins.PluginInterface):
|
||||
requirements.PluginRequirement(
|
||||
name="modules", plugin=modules.Modules, version=(2, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="dlllist", component=dlllist.DllList, version=(2, 0, 0)
|
||||
),
|
||||
requirements.BooleanRequirement(
|
||||
name="extensive",
|
||||
description="Search physical layer for version information",
|
||||
|
||||
@@ -169,13 +169,30 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
|
||||
Returns:
|
||||
str: Sock pipe pathname relative to the task's root directory.
|
||||
"""
|
||||
# FIXME: This function must be moved to the 'dentry' object extension
|
||||
# Also, the scope of this function went beyond the sock pipe path, so we need to rename this.
|
||||
# Once https://github.com/volatilityfoundation/volatility3/pull/1263 is merged, replace the
|
||||
# dentry inode getters
|
||||
|
||||
if not (filp and filp.is_readable()):
|
||||
return f"<invalid file pointer> {filp:x}"
|
||||
|
||||
dentry = filp.get_dentry()
|
||||
if not (dentry and dentry.is_readable()):
|
||||
return f"<invalid dentry pointer> {dentry:x}"
|
||||
|
||||
kernel_module = cls.get_module_from_volobj_type(context, dentry)
|
||||
|
||||
sym_addr = dentry.d_op.d_dname
|
||||
if not (sym_addr and sym_addr.is_readable()):
|
||||
return f"<invalid d_dname pointer> {sym_addr:x}"
|
||||
|
||||
symbs = list(kernel_module.get_symbols_by_absolute_location(sym_addr))
|
||||
|
||||
inode = dentry.d_inode
|
||||
if not (inode and inode.is_readable() and inode.is_valid()):
|
||||
return f"<invalid dentry inode> {inode:x}"
|
||||
|
||||
if len(symbs) == 1:
|
||||
sym = symbs[0].split(constants.BANG)[1]
|
||||
|
||||
@@ -191,15 +208,41 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
|
||||
elif sym == "simple_dname":
|
||||
pre_name = cls._get_path_file(task, filp)
|
||||
|
||||
elif sym == "ns_dname":
|
||||
# From Kernels 3.19
|
||||
|
||||
# In Kernels >= 6.9, see Linux kernel commit 1fa08aece42512be072351f482096d5796edf7ca
|
||||
# ns_common->stashed change from 'atomic64_t' to 'dentry*'
|
||||
try:
|
||||
ns_common_type = kernel_module.get_type("ns_common")
|
||||
stashed_template = ns_common_type.child_template("stashed")
|
||||
stashed_type_full_name = stashed_template.vol.type_name
|
||||
stashed_type_name = stashed_type_full_name.split(constants.BANG)[1]
|
||||
if stashed_type_name == "atomic64_t":
|
||||
# 3.19 <= Kernels < 6.9
|
||||
fsdata_ptr = dentry.d_fsdata
|
||||
if not (fsdata_ptr and fsdata_ptr.is_readable()):
|
||||
raise IndexError
|
||||
|
||||
ns_ops = fsdata_ptr.dereference().cast("proc_ns_operations")
|
||||
else:
|
||||
# Kernels >= 6.9
|
||||
private_ptr = inode.i_private
|
||||
if not (private_ptr and private_ptr.is_readable()):
|
||||
raise IndexError
|
||||
|
||||
ns_common = private_ptr.dereference().cast("ns_common")
|
||||
ns_ops = ns_common.ops
|
||||
|
||||
pre_name = utility.pointer_to_string(ns_ops.name, 255)
|
||||
except IndexError:
|
||||
pre_name = "<unsupported ns_dname implementation>"
|
||||
else:
|
||||
pre_name = f"<unsupported d_op symbol: {sym}>"
|
||||
|
||||
ret = f"{pre_name}:[{dentry.d_inode.i_ino:d}]"
|
||||
|
||||
pre_name = f"<unsupported d_op symbol> {sym}"
|
||||
else:
|
||||
ret = f"<invalid d_dname pointer> {sym_addr:x}"
|
||||
pre_name = f"<unknown d_dname pointer> {sym_addr:x}"
|
||||
|
||||
return ret
|
||||
return f"{pre_name}:[{inode.i_ino:d}]"
|
||||
|
||||
@classmethod
|
||||
def path_for_file(cls, context, task, filp) -> str:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
from typing import Dict
|
||||
|
||||
from volatility3.framework import exceptions, objects
|
||||
from volatility3.framework.symbols.windows.extensions import pool
|
||||
@@ -24,12 +25,8 @@ class _SHUTDOWN_PACKET(objects.StructType, pool.ExecutiveObject):
|
||||
and self.Entry.Blink.is_readable()
|
||||
and self.DeviceObject.is_readable()
|
||||
):
|
||||
return False
|
||||
|
||||
device = self.DeviceObject
|
||||
if not device or not (device.DriverObject.DriverStart % 0x1000 == 0):
|
||||
vollog.debug(
|
||||
f"callback obj 0x{self.vol.offset:x} invalid due to invalid device object"
|
||||
f"Callback obj 0x{self.vol.offset:x} invalid due to unreadable structure members"
|
||||
)
|
||||
return False
|
||||
|
||||
@@ -39,12 +36,43 @@ class _SHUTDOWN_PACKET(objects.StructType, pool.ExecutiveObject):
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def is_parseable(self, type_map: Dict[int, str]) -> bool:
|
||||
"""
|
||||
Determines whether or not this `_SHUTDOWN_PACKET` callback can be reliably parsed.
|
||||
Requires a `type_map` that maps NT executive object type indices to string representations.
|
||||
This type map can be acquired via the `handles.Handles.get_type_map` classmethod.
|
||||
"""
|
||||
if not self.is_valid():
|
||||
return False
|
||||
|
||||
try:
|
||||
|
||||
device = self.DeviceObject
|
||||
if not device or not (device.DriverObject.DriverStart % 0x1000 == 0):
|
||||
vollog.debug(
|
||||
f"callback obj 0x{self.vol.offset:x} invalid due to invalid device object"
|
||||
)
|
||||
return False
|
||||
|
||||
header = device.get_object_header()
|
||||
valid = header.NameInfo.Name == "Device"
|
||||
return valid
|
||||
object_type = header.get_object_type(type_map)
|
||||
is_valid = object_type == "Device"
|
||||
if not is_valid:
|
||||
vollog.debug(
|
||||
f"Callback obj 0x{self.vol.offset:x} invalid due to invalid device type: wanted 'Device', found '{object_type}'"
|
||||
)
|
||||
return is_valid
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug(
|
||||
f"callback obj 0x{self.vol.offset:x} invalid due to invalid address access"
|
||||
)
|
||||
return False
|
||||
except ValueError:
|
||||
vollog.debug(f"Could not get NameInfo for object at 0x{self.vol.offset:x}")
|
||||
vollog.debug(
|
||||
f"Could not get object type for object at 0x{self.vol.offset:x}"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
{
|
||||
"base_types": {
|
||||
"pointer": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 8
|
||||
},
|
||||
"unsigned char": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"unsigned long": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 4
|
||||
},
|
||||
"unsigned long long": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 8
|
||||
},
|
||||
"unsigned short": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 2
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"metadata": {
|
||||
"format": "6.1.0",
|
||||
"producer": {
|
||||
"datetime": "2021-07-31T17:37:28.302702",
|
||||
"name": "vmextract-by-hand",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
},
|
||||
"symbols": {
|
||||
"revision_id": {
|
||||
"address": 0,
|
||||
"constant_data": "MTQ="
|
||||
}
|
||||
},
|
||||
"user_types": {
|
||||
"_VMCS": {
|
||||
"fields": {
|
||||
"ept": {
|
||||
"offset": 232,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"executive_vmcs_ptr": {
|
||||
"offset": 208,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"guest_cr3": {
|
||||
"offset": 736,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"guest_cr4": {
|
||||
"offset": 744,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"guest_pdpte": {
|
||||
"offset": 928,
|
||||
"type": {
|
||||
"count": 4,
|
||||
"kind": "array",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"guest_physical_addr": {
|
||||
"offset": 240,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"host_cr3": {
|
||||
"offset": 832,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"host_cr4": {
|
||||
"offset": 840,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"vmcs_link_ptr": {
|
||||
"offset": 248,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"vpid": {
|
||||
"offset": 752,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned short"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 4096
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
{
|
||||
"base_types": {
|
||||
"pointer": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 8
|
||||
},
|
||||
"unsigned char": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"unsigned long": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 4
|
||||
},
|
||||
"unsigned long long": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 8
|
||||
},
|
||||
"unsigned short": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 2
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"metadata": {
|
||||
"format": "6.1.0",
|
||||
"producer": {
|
||||
"datetime": "2021-07-31T17:37:28.311608",
|
||||
"name": "vmextract-by-hand",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
},
|
||||
"symbols": {
|
||||
"revision_id": {
|
||||
"address": 0,
|
||||
"constant_data": "MTY="
|
||||
}
|
||||
},
|
||||
"user_types": {
|
||||
"_VMCS": {
|
||||
"fields": {
|
||||
"ept": {
|
||||
"offset": 232,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"executive_vmcs_ptr": {
|
||||
"offset": 208,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"guest_cr3": {
|
||||
"offset": 736,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"guest_cr4": {
|
||||
"offset": 744,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"guest_pdpte": {
|
||||
"offset": 928,
|
||||
"type": {
|
||||
"count": 4,
|
||||
"kind": "array",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"guest_physical_addr": {
|
||||
"offset": 240,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"host_cr3": {
|
||||
"offset": 832,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"host_cr4": {
|
||||
"offset": 840,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"vmcs_link_ptr": {
|
||||
"offset": 248,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"vpid": {
|
||||
"offset": 752,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned short"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 4096
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
{
|
||||
"base_types": {
|
||||
"pointer": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 8
|
||||
},
|
||||
"unsigned char": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 1
|
||||
},
|
||||
"unsigned long": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 4
|
||||
},
|
||||
"unsigned long long": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 8
|
||||
},
|
||||
"unsigned short": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": false,
|
||||
"size": 2
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"metadata": {
|
||||
"format": "6.1.0",
|
||||
"producer": {
|
||||
"datetime": "2021-07-31T17:37:28.314801",
|
||||
"name": "vmextract-by-hand",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
},
|
||||
"symbols": {
|
||||
"revision_id": {
|
||||
"address": 0,
|
||||
"constant_data": "MTU="
|
||||
}
|
||||
},
|
||||
"user_types": {
|
||||
"_VMCS": {
|
||||
"fields": {
|
||||
"ept": {
|
||||
"offset": 320,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"executive_vmcs_ptr": {
|
||||
"offset": 208,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"guest_cr3": {
|
||||
"offset": 736,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"guest_cr4": {
|
||||
"offset": 744,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"guest_pdpte": {
|
||||
"offset": 928,
|
||||
"type": {
|
||||
"count": 4,
|
||||
"kind": "array",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"guest_physical_addr": {
|
||||
"offset": 328,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"host_cr3": {
|
||||
"offset": 832,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"host_cr4": {
|
||||
"offset": 840,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"vmcs_link_ptr": {
|
||||
"offset": 248,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned long long"
|
||||
}
|
||||
},
|
||||
"vpid": {
|
||||
"offset": 220,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "unsigned short"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 4096
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user