From abc448009eddd8563835f233e8e95095a7b32a15 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Wed, 10 Jan 2024 15:56:00 -0300 Subject: [PATCH 01/85] Linux: Add netfilter hooks enumeration plugin. --- .../framework/plugins/linux/netfilter.py | 715 ++++++++++++++++++ 1 file changed, 715 insertions(+) create mode 100644 volatility3/framework/plugins/linux/netfilter.py diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py new file mode 100644 index 000000000..cc4786b4e --- /dev/null +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -0,0 +1,715 @@ +# 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 +# +from dataclasses import dataclass, field +from abc import ABC, abstractmethod +import logging + +from typing import Iterator, List, Tuple +from volatility3.framework import ( + class_subclasses, + constants, + interfaces, + renderers, +) +from volatility3.framework.renderers import format_hints +from volatility3.framework.configuration import requirements +from volatility3.framework.symbols import linux +from volatility3.plugins.linux import lsmod + +vollog = logging.getLogger(__name__) + + +@dataclass +class Proto: + name: str + hooks: Tuple[str] = field(default_factory=tuple) + + +PROTO_NOT_IMPLEMENTED = Proto(name="UNSPEC") + +NF_INET_HOOKS = ("PRE_ROUTING", "LOCAL_IN", "FORWARD", "LOCAL_OUT", "POST_ROUTING") +NF_DEC_HOOKS = ( + "PRE_ROUTING", + "LOCAL_IN", + "FORWARD", + "LOCAL_OUT", + "POST_ROUTING", + "HELLO", + "ROUTE", +) +NF_ARP_HOOKS = ("IN", "OUT", "FORWARD") +NF_NETDEV_HOOKS = ("INGRESS", "EGRESS") +LARGEST_HOOK_NUMBER = max( + len(NF_INET_HOOKS), len(NF_DEC_HOOKS), len(NF_ARP_HOOKS), len(NF_NETDEV_HOOKS) +) + + +class AbstractNetfilter(ABC): + """Netfilter Abstract Base Classes handling details across various + Netfilter implementations, including constants, helpers, and common + routines. + """ + + PROTO_HOOKS = ( + PROTO_NOT_IMPLEMENTED, # NFPROTO_UNSPEC + Proto(name="INET", hooks=NF_INET_HOOKS), # From kernels 3.14 + Proto(name="IPV4", hooks=NF_INET_HOOKS), + Proto(name="ARP", hooks=("IN", "OUT", "FORWARD")), + PROTO_NOT_IMPLEMENTED, + Proto(name="NETDEV", hooks=("INGRESS", "EGRESS")), + PROTO_NOT_IMPLEMENTED, + Proto(name="BRIDGE", hooks=NF_INET_HOOKS), + PROTO_NOT_IMPLEMENTED, + PROTO_NOT_IMPLEMENTED, + Proto(name="IPV6", hooks=NF_INET_HOOKS), + PROTO_NOT_IMPLEMENTED, + Proto(name="DECNET", hooks=NF_INET_HOOKS), # Removed in kernel 6.1 + ) + NF_MAX_HOOKS = LARGEST_HOOK_NUMBER + 1 + + def __init__( + self, + context: interfaces.context.ContextInterface, + config: interfaces.configuration.HierarchicalDict, + ): + self._context = context + self._config = config + symbol_table = self._config["kernel"] + self.vmlinux = context.modules[symbol_table] + self.layer_name = self.vmlinux.layer_name + + modules = lsmod.Lsmod.list_modules(context, symbol_table) + self.handlers = linux.LinuxUtilities.generate_kernel_handler_info( + context, symbol_table, modules + ) + + self._set_data_sizes() + + def _set_data_sizes(self): + self.ptr_size = self.vmlinux.get_type("pointer").size + self.list_head_size = self.vmlinux.get_type("list_head").size + + @classmethod + def run_all( + cls, + context: interfaces.context.ContextInterface, + config: interfaces.configuration.HierarchicalDict, + ) -> Iterator[Tuple[int, str, str, int, int, str, bool]]: + """It calls each subclass symtab_checks() to test the required + conditions to that specific kernel implementation. + + Args: + context: The volatility3 context on which to operate + config: Core configuration + + Yields: + The kmsg records. Same as _run() + """ + vmlinux = context.modules[config["kernel"]] + + implementation_inst = None # type: ignore + for subclass in class_subclasses(cls): + if not subclass.symtab_checks(vmlinux=vmlinux): + vollog.log( + constants.LOGLEVEL_VVVV, + "Netfilter implementation '%s' doesn't match this memory dump", + subclass.__name__, + ) + continue + + vollog.log( + constants.LOGLEVEL_VVVV, + "Netfilter implementation '%s' matches!", + subclass.__name__, + ) + implementation_inst = subclass(context=context, config=config) + # More than one class could be executed for an specific kernel version + # For instance: Netfilter Ingress hooks + yield from implementation_inst._run() + + if implementation_inst is None: + vollog.error("Unsupported Netfilter kernel implementation") + + def _run(self) -> Iterator[Tuple[int, str, str, int, int, str, bool]]: + """Iterates over namespaces and protocols, executing various callbacks that + allow customization of the code to the specific data structure used in a + particular kernel implementation + + get_hooks_container_by_protocol(net, proto_name) + It returns the data structure used in a specific kernel implementation + to store the hooks for a respective namespace and protocol, basically: + For Ingress hooks: + network_namespace[] -> net_device[] -> nf_hooks_ingress[] + For all the other Netfilter hooks: + <= 4.2.8 + nf_hooks[] + >= 4.3 + network_namespace[] -> nf.hooks[] + + get_hook_ops(hook_container, proto_idx, hook_idx) + Give the 'hook_container' got in get_hooks_container_by_protocol(), it + returns an iterable of 'nf_hook_ops' elements for a respective protocol + and hook type. + + Returns: + netns [int]: Network namespace id + proto_name [str]: Protocol name + hook_name [str]: Hook name + priority [int]: Priority + hook_ops_hook [int]: Hook address + module_name [str]: Linux kernel module name + hooked [bool]: hooked? + """ + for netns, net in self.get_net_namespaces(): + for proto_idx, proto_name, hook_idx, hook_name in self._proto_hook_loop(): + hooks_container = self.get_hooks_container_by_protocol(net, proto_name) + + for hook_container in hooks_container: + for hook_ops in self.get_hook_ops( + hook_container, proto_idx, hook_idx + ): + if not hook_ops: + continue + + priority = int(hook_ops.priority) + hook_ops_hook = hook_ops.hook + module_name = self.get_module_name_for_address(hook_ops_hook) + hooked = module_name is not None + + yield netns, proto_name, hook_name, priority, hook_ops_hook, module_name, hooked + + @classmethod + @abstractmethod + def symtab_checks(cls, vmlinux: interfaces.context.ModuleInterface) -> bool: + """This method on each sublasss will be called to evaluate if the kernel + being analyzed fulfill the type & symbols requirements for the implementation. + The first class returning True will be instantiated and called via the + run() method. + + Returns: + bool: True if the kernel being analyzed fulfill the class requirements. + """ + + def _proto_hook_loop(self) -> Iterator[Tuple[int, str, int, str]]: + """Flattens the protocol families and hooks""" + for proto_idx, proto in enumerate(AbstractNetfilter.PROTO_HOOKS): + if proto == PROTO_NOT_IMPLEMENTED: + continue + if proto.name not in self.subscribed_protocols(): + # This protocol is not managed in this object + continue + 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): + """Function helper to build the nf_hook_ops array when it is not part of the + struct 'nf_hook_entries' definition. + + nf_hook_ops was stored adjacent in memory to the nf_hook_entry array, in the + new struct 'nf_hook_entries'. However, this 'nf_hooks_ops' array 'orig_ops' is + not part of the 'nf_hook_entries' struct. So, we need to calculate the offset. + + struct nf_hook_entries { + u16 num_hook_entries; /* plus padding */ + struct nf_hook_entry hooks[]; + //const struct nf_hook_ops *orig_ops[]; + } + """ + nf_hook_entry_size = self.vmlinux.get_type("nf_hook_entry").size + orig_ops_addr = ( + nf_hook_entries.hooks.vol.offset + + nf_hook_entry_size * nf_hook_entries.num_hook_entries + ) + 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, + ) + + return orig_ops + + def subscribed_protocols(self) -> Tuple[str]: + """Allows to select which PROTO_HOOKS protocols will be processed by the + Netfiler subclass. + """ + + # Most implementation handlers respond to these protocols, except for + # the ingress hook, which specifically handles the 'NETDEV' protocol. + # However, there is no corresponding Netfilter hook implementation for + # the INET protocol in the kernel. AFAIU, this is used as + # 'NFPROTO_INET = NFPROTO_IPV4 || NFPROTO_IPV6' + # in other parts of the kernel source code. + return ("IPV4", "ARP", "BRIDGE", "IPV6", "DECNET") + + def get_module_name_for_address(self, addr) -> str: + """Helper to obtain the module and symbol name in the format needed for the + output of this plugin. + """ + module_name, symbol_name = linux.LinuxUtilities.lookup_module_address( + self.vmlinux, self.handlers, addr + ) + + if module_name == "UNKNOWN": + module_name = None + + if symbol_name != "N/A": + module_name = f"[{symbol_name}]" + + return module_name + + def get_net_namespaces(self): + """Common function to retrieve the different namespaces. + From 4.3 on, all the implementations use network namespaces. + """ + nethead = self.vmlinux.object_from_symbol("net_namespace_list") + symbol_net_name = self.get_symbol_fullname("net") + for net in nethead.to_list(symbol_net_name, "list"): + net_ns_id = net.ns.inum + yield net_ns_id, net + + def get_hooks_container_by_protocol(self, net, proto_name): + """Returns the data structure used in a specific kernel implementation to store + the hooks for a respective namespace and protocol. + + Except for kernels < 4.3, all the implementations use network namespaces. + Also the data structure which contains the hooks, even though it changes its + implementation and/or data type, it is always in this location. + """ + yield net.nf.hooks + + def get_hook_ops(self, hook_container, proto_idx, hook_idx): + """Given the hook_container obtained from get_hooks_container_by_protocol(), it + returns an iterable of 'nf_hook_ops' elements for a corresponding protocol + and hook type. + + This is the most variable/unstable part of all Netfilter hook designs, it + changes almost in every single implementation. + """ + raise NotImplementedError("You must implement this method") + + def get_symbol_fullname(self, symbol_basename: str) -> str: + """Given a short symbol or type name, it returns its full name""" + return self.vmlinux.symbol_table_name + constants.BANG + symbol_basename + + @staticmethod + def get_member_type( + vol_type: interfaces.objects.Template, member_name: str + ) -> List[str]: + """Returns a list of types/subtypes belonging to the given type member. + + Args: + vol_type (interfaces.objects.Template): A vol3 type object + member_name (str): The member name + + Returns: + list: A list of types/subtypes + """ + _size, vol_obj = vol_type.vol.members[member_name] + type_name = vol_obj.type_name + type_basename = type_name.split(constants.BANG)[1] + member_type = [type_basename] + cur_type = vol_obj + while hasattr(cur_type, "subtype"): + subtype_name = cur_type.subtype.type_name + subtype_basename = subtype_name.split(constants.BANG)[1] + member_type.append(subtype_basename) + cur_type = cur_type.subtype + + return member_type + + +class NetfilterImp_to_4_3(AbstractNetfilter): + """At this point, Netfilter hooks were implemented as a linked list of struct + 'nf_hook_ops' type. One linked list per protocol per hook type. + It was like that until 4.2.8. + + struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; + """ + + @classmethod + def symtab_checks(cls, vmlinux) -> bool: + return vmlinux.has_symbol("nf_hooks") + + def get_net_namespaces(self): + # In kernels <= 4.2.8 netfilter hooks are not implemented per namespaces + netns, net = renderers.NotAvailableValue(), renderers.NotAvailableValue() + yield netns, net + + def get_hooks_container_by_protocol(self, net, proto_name): + nf_hooks = self.vmlinux.object_from_symbol("nf_hooks") + if not nf_hooks: + return + + yield nf_hooks + + def get_hook_ops(self, hook_container, proto_idx, hook_idx): + list_head = hook_container[proto_idx][hook_idx] + nf_hooks_ops_name = self.get_symbol_fullname("nf_hook_ops") + return list_head.to_list(nf_hooks_ops_name, "list") + + +class NetfilterImp_4_3_to_4_9(AbstractNetfilter): + """Netfilter hooks were added to network namepaces in 4.3. + It is still implemented as a linked list of 'struct nf_hook_ops' type but inside a + network namespace. One linked list per protocol per hook type. + + struct net { ... struct netns_nf nf; ... } + struct netns_nf { ... + struct list_head hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; ... } + """ + + @classmethod + def symtab_checks(cls, vmlinux) -> bool: + return ( + vmlinux.has_symbol("net_namespace_list") + and vmlinux.has_type("netns_nf") + and vmlinux.get_type("netns_nf").has_member("hooks") + and cls.get_member_type(vmlinux.get_type("netns_nf"), "hooks") + == ["array", "array", "list_head"] + ) + + def get_hook_ops(self, hook_container, proto_idx, hook_idx): + list_head = hook_container[proto_idx][hook_idx] + nf_hooks_ops_name = self.get_symbol_fullname("nf_hook_ops") + return list_head.to_list(nf_hooks_ops_name, "list") + + +class NetfilterImp_4_9_to_4_14(AbstractNetfilter): + """In this range of kernel versions, the doubly-linked lists of netfilter hooks were + replaced by an array of arrays of 'nf_hook_entry' pointers in a singly-linked lists. + struct net { ... struct netns_nf nf; ... } + struct netns_nf { .. + struct nf_hook_entry __rcu *hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; ... } + + Also in v4.10 the struct nf_hook_entry changed, a hook function pointer was added to + it. However, for simplicity of this design, we will still take the hook address from + the 'nf_hook_ops'. As per v5.0-rc2, the hook address is duplicated in both sides. + - v4.9: + struct nf_hook_entry { + struct nf_hook_entry *next; + struct nf_hook_ops ops; + const struct nf_hook_ops *orig_ops; }; + - v4.10: + struct nf_hook_entry { + struct nf_hook_entry *next; + nf_hookfn *hook; + void *priv; + const struct nf_hook_ops *orig_ops; }; + (*) Even though the hook address is in the struct 'nf_hook_entry', we use the + original 'nf_hook_ops' hook address value, the one which was filled by the user, to + make it uniform to all the implementations. + """ + + @classmethod + def symtab_checks(cls, vmlinux) -> bool: + hooks_type = ["array", "array", "pointer", "nf_hook_entry"] + return ( + vmlinux.has_symbol("net_namespace_list") + and vmlinux.has_type("netns_nf") + and vmlinux.get_type("netns_nf").has_member("hooks") + and cls.get_member_type(vmlinux.get_type("netns_nf"), "hooks") == hooks_type + ) + + def _get_hook_ops(self, hook_container, proto_idx, hook_idx): + list_head = hook_container[proto_idx][hook_idx] + nf_hooks_ops_name = self.get_symbol_fullname("nf_hook_ops") + return list_head.to_list(nf_hooks_ops_name, "list") + + def get_hook_ops(self, hook_container, proto_idx, hook_idx): + nf_hook_entry_list = hook_container[proto_idx][hook_idx] + while nf_hook_entry_list: + yield nf_hook_entry_list.orig_ops + nf_hook_entry_list = nf_hook_entry_list.next + + +class NetfilterImp_4_14_to_4_16(AbstractNetfilter): + """'nf_hook_ops' was removed from struct 'nf_hook_entry'. Instead, it was stored + adjacent in memory to the 'nf_hook_entry' array, in the new struct 'nf_hook_entries' + However, 'orig_ops' is not part of the 'nf_hook_entries' struct definition. So, we + have to craft it by hand. + + struct net { ... struct netns_nf nf; ... } + struct netns_nf { + struct nf_hook_entries *hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; ... } + struct nf_hook_entries { + u16 num_hook_entries; /* plus padding */ + struct nf_hook_entry hooks[]; + //const struct nf_hook_ops *orig_ops[]; } + struct nf_hook_entry { + nf_hookfn *hook; + void *priv; } + + (*) Even though the hook address is in the struct 'nf_hook_entry', we use the + original 'nf_hook_ops' hook address value, the one which was filled by the user, to + make it uniform to all the implementations. + """ + + @classmethod + def symtab_checks(cls, vmlinux) -> bool: + hooks_type = ["array", "array", "pointer", "nf_hook_entries"] + return ( + vmlinux.has_symbol("net_namespace_list") + and vmlinux.has_type("netns_nf") + and vmlinux.get_type("netns_nf").has_member("hooks") + and cls.get_member_type(vmlinux.get_type("netns_nf"), "hooks") == hooks_type + ) + + def get_nf_hook_entries(self, nf_hooks_addr, proto_idx, hook_idx): + """This allows to support different hook array implementations from this version + on. For instance, in kernels >= 4.16 this multi-dimensional array is split in + one-dimensional array of pointers to 'nf_hooks_entries' per each protocol.""" + return nf_hooks_addr[proto_idx][hook_idx] + + def get_hook_ops(self, hook_container, proto_idx, hook_idx): + nf_hook_entries = self.get_nf_hook_entries(hook_container, proto_idx, hook_idx) + if not nf_hook_entries: + return + + 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) + 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 + + +class NetfilterImp_4_16_to_latest(NetfilterImp_4_14_to_4_16): + """The multidimensional array of nf_hook_entries was split in a one-dimensional + array per each protocol. + + struct net { + struct netns_nf nf; ... } + struct netns_nf { + struct nf_hook_entries * hooks_ipv4[NF_INET_NUMHOOKS]; + struct nf_hook_entries * hooks_ipv6[NF_INET_NUMHOOKS]; + struct nf_hook_entries * hooks_arp[NF_ARP_NUMHOOKS]; + struct nf_hook_entries * hooks_bridge[NF_INET_NUMHOOKS]; + struct nf_hook_entries * hooks_decnet[NF_DN_NUMHOOKS]; ... } + struct nf_hook_entries { + u16 num_hook_entries; /* plus padding */ + struct nf_hook_entry hooks[]; + //const struct nf_hook_ops *orig_ops[]; } + struct nf_hook_entry { + nf_hookfn *hook; + void *priv; } + + (*) Even though the hook address is in the struct nf_hook_entry, we use the original + nf_hook_ops hook address value, the one which was filled by the user, to make it + uniform to all the implementations. + """ + + @classmethod + def symtab_checks(cls, vmlinux) -> bool: + return ( + vmlinux.has_symbol("net_namespace_list") + and vmlinux.has_type("netns_nf") + and vmlinux.get_type("netns_nf").has_member("hooks_ipv4") + ) + + def get_hooks_container_by_protocol(self, net, proto_name): + try: + if proto_name == "IPV4": + net_nf_hooks = net.nf.hooks_ipv4 + elif proto_name == "ARP": + net_nf_hooks = net.nf.hooks_arp + elif proto_name == "BRIDGE": + net_nf_hooks = net.nf.hooks_bridge + elif proto_name == "IPV6": + net_nf_hooks = net.nf.hooks_ipv6 + elif proto_name == "DECNET": + net_nf_hooks = net.nf.hooks_decnet + else: + return + + yield net_nf_hooks + + except AttributeError: + # Protocol family disabled at kernel compilation + # CONFIG_NETFILTER_FAMILY_ARP=n || + # CONFIG_NETFILTER_FAMILY_BRIDGE=n || + # CONFIG_DECNET=n + pass + + def _get_nf_hook_entries_ptr(self, nf_hooks_addr, proto_idx, hook_idx): + nf_hook_entries_ptr = nf_hooks_addr[hook_idx] + return nf_hook_entries_ptr + + def get_nf_hook_entries(self, nf_hooks_addr, proto_idx, hook_idx): + return nf_hooks_addr[hook_idx] + + +class AbstractNetfilterNetDev(AbstractNetfilter): + """Base class to handle the Netfilter NetDev hooks. + It won't be executed. It has some common functions to all Netfilter NetDev hook + implementions. + + Netfilter NetDev hooks are set per network device which belongs to a network + namespace. + """ + + @classmethod + def symtab_checks(cls, vmlinux) -> bool: + return False + + def subscribed_protocols(self): + return ("NETDEV",) + + def get_hooks_container_by_protocol(self, net, proto_name): + if proto_name != "NETDEV": + return + + net_device_type = self.vmlinux.get_type("net_device") + net_device_name = self.get_symbol_fullname("net_device") + for net_device in net.dev_base_head.to_list(net_device_name, "dev_list"): + if net_device_type.has_member("nf_hooks_ingress"): + # CONFIG_NETFILTER_INGRESS=y + yield net_device.nf_hooks_ingress + + if net_device_type.has_member("nf_hooks_egress"): + # CONFIG_NETFILTER_EGRESS=y + yield net_device.nf_hooks_egress + + +class NetfilterIngressImp_4_2_to_4_9(AbstractNetfilterNetDev): + """This is the first version of Netfilter Ingress hooks which was implemented using + a doubly-linked list of 'nf_hook_ops'. + struct list_head nf_hooks_ingress; + """ + + @classmethod + def symtab_checks(cls, vmlinux) -> bool: + hooks_type = ["list_head"] + return ( + vmlinux.has_symbol("net_namespace_list") + and vmlinux.has_type("net_device") + and vmlinux.get_type("net_device").has_member("nf_hooks_ingress") + and cls.get_member_type(vmlinux.get_type("net_device"), "nf_hooks_ingress") + == hooks_type + ) + + def get_hook_ops(self, hook_container, proto_idx, hook_idx): + nf_hooks_ingress = hook_container + nf_hook_ops_name = self.get_symbol_fullname("nf_hook_ops") + return nf_hooks_ingress.to_list(nf_hook_ops_name, "list") + + +class NetfilterIngressImp_4_9_to_4_14(AbstractNetfilterNetDev): + """In 4.9 it was changed to a simple singly-linked list. + struct nf_hook_entry * nf_hooks_ingress; + """ + + @classmethod + def symtab_checks(cls, vmlinux) -> bool: + hooks_type = ["pointer", "nf_hook_entry"] + return ( + vmlinux.has_symbol("net_namespace_list") + and vmlinux.has_type("net_device") + and vmlinux.get_type("net_device").has_member("nf_hooks_ingress") + and cls.get_member_type(vmlinux.get_type("net_device"), "nf_hooks_ingress") + == hooks_type + ) + + def get_hook_ops(self, hook_container, proto_idx, hook_idx): + nf_hooks_ingress_ptr = hook_container + if not nf_hooks_ingress_ptr: + return + + while nf_hooks_ingress_ptr: + nf_hook_entry = nf_hooks_ingress_ptr.dereference() + orig_ops = nf_hook_entry.orig_ops.dereference() + yield orig_ops + nf_hooks_ingress_ptr = nf_hooks_ingress_ptr.next + + +class NetfilterIngressImp_4_14_to_latest(AbstractNetfilterNetDev): + """In 4.14 the hook list was converted to an array of pointers inside the struct + 'nf_hook_entries': + struct nf_hook_entries * nf_hooks_ingress; + struct nf_hook_entries { + u16 num_hook_entries; + struct nf_hook_entry hooks[]; + //const struct nf_hook_ops *orig_ops[]; } + """ + + @classmethod + def symtab_checks(cls, vmlinux) -> bool: + hooks_type = ["pointer", "nf_hook_entries"] + return ( + vmlinux.has_symbol("net_namespace_list") + and vmlinux.has_type("net_device") + and vmlinux.get_type("net_device").has_member("nf_hooks_ingress") + and cls.get_member_type(vmlinux.get_type("net_device"), "nf_hooks_ingress") + == hooks_type + ) + + def get_hook_ops(self, hook_container, proto_idx, hook_idx): + nf_hook_entries = hook_container + if not nf_hook_entries: + return + + 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) + 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 + + +class Netfilter(interfaces.plugins.PluginInterface): + """Lists Netfilter hooks.""" + + _required_framework_version = (2, 0, 0) + + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.PluginRequirement( + name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + ), + ] + + def _format_fields(self, fields): + ( + netns, + proto_name, + hook_name, + priority, + hook_func, + module_name, + hooked, + ) = fields + return ( + netns, + proto_name, + hook_name, + priority, + format_hints.Hex(hook_func), + module_name, + str(hooked), + ) + + def _generator(self): + for fields in AbstractNetfilter.run_all( + context=self.context, config=self.config + ): + yield (0, self._format_fields(fields)) + + def run(self): + headers = [ + ("Net NS", int), + ("Proto", str), + ("Hook", str), + ("Priority", int), + ("Handler", format_hints.Hex), + ("Module", str), + ("Is Hooked", str), + ] + return renderers.TreeGrid(headers, self._generator()) From 5ad7dd3bbcbad273e21610462e7e2517e87653ed Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Wed, 10 Jan 2024 16:06:55 -0300 Subject: [PATCH 02/85] Update netdev class names as it now supports both netdev hooks; ingress and egress --- volatility3/framework/plugins/linux/netfilter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index cc4786b4e..a9dbc742a 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -572,7 +572,7 @@ class AbstractNetfilterNetDev(AbstractNetfilter): yield net_device.nf_hooks_egress -class NetfilterIngressImp_4_2_to_4_9(AbstractNetfilterNetDev): +class NetfilterNetDevImp_4_2_to_4_9(AbstractNetfilterNetDev): """This is the first version of Netfilter Ingress hooks which was implemented using a doubly-linked list of 'nf_hook_ops'. struct list_head nf_hooks_ingress; @@ -595,7 +595,7 @@ class NetfilterIngressImp_4_2_to_4_9(AbstractNetfilterNetDev): return nf_hooks_ingress.to_list(nf_hook_ops_name, "list") -class NetfilterIngressImp_4_9_to_4_14(AbstractNetfilterNetDev): +class NetfilterNetDevImp_4_9_to_4_14(AbstractNetfilterNetDev): """In 4.9 it was changed to a simple singly-linked list. struct nf_hook_entry * nf_hooks_ingress; """ @@ -623,7 +623,7 @@ class NetfilterIngressImp_4_9_to_4_14(AbstractNetfilterNetDev): nf_hooks_ingress_ptr = nf_hooks_ingress_ptr.next -class NetfilterIngressImp_4_14_to_latest(AbstractNetfilterNetDev): +class NetfilterNetDevImp_4_14_to_latest(AbstractNetfilterNetDev): """In 4.14 the hook list was converted to an array of pointers inside the struct 'nf_hook_entries': struct nf_hook_entries * nf_hooks_ingress; From 5b5012d08ad7aff2dd48c5786e1bad2c62274989 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Wed, 10 Jan 2024 16:42:34 -0300 Subject: [PATCH 03/85] Fix. Use the global lists. DEC hooks were wrong --- volatility3/framework/plugins/linux/netfilter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index a9dbc742a..2b0a16a96 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -55,16 +55,16 @@ class AbstractNetfilter(ABC): PROTO_NOT_IMPLEMENTED, # NFPROTO_UNSPEC Proto(name="INET", hooks=NF_INET_HOOKS), # From kernels 3.14 Proto(name="IPV4", hooks=NF_INET_HOOKS), - Proto(name="ARP", hooks=("IN", "OUT", "FORWARD")), + Proto(name="ARP", hooks=NF_ARP_HOOKS), PROTO_NOT_IMPLEMENTED, - Proto(name="NETDEV", hooks=("INGRESS", "EGRESS")), + Proto(name="NETDEV", hooks=NF_NETDEV_HOOKS), PROTO_NOT_IMPLEMENTED, Proto(name="BRIDGE", hooks=NF_INET_HOOKS), PROTO_NOT_IMPLEMENTED, PROTO_NOT_IMPLEMENTED, Proto(name="IPV6", hooks=NF_INET_HOOKS), PROTO_NOT_IMPLEMENTED, - Proto(name="DECNET", hooks=NF_INET_HOOKS), # Removed in kernel 6.1 + Proto(name="DECNET", hooks=NF_DEC_HOOKS), # Removed in kernel 6.1 ) NF_MAX_HOOKS = LARGEST_HOOK_NUMBER + 1 From da6c2d863d17bdcef950cc8a8f9b7aa7c9e11b2a Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Wed, 10 Jan 2024 16:43:36 -0300 Subject: [PATCH 04/85] Fix netdev egress hooks --- .../framework/plugins/linux/netfilter.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index 2b0a16a96..33bb5c456 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -136,11 +136,13 @@ class AbstractNetfilter(ABC): allow customization of the code to the specific data structure used in a particular kernel implementation - get_hooks_container_by_protocol(net, proto_name) + get_hooks_container(net, proto_name, hook_name) It returns the data structure used in a specific kernel implementation to store the hooks for a respective namespace and protocol, basically: For Ingress hooks: network_namespace[] -> net_device[] -> nf_hooks_ingress[] + For egress hooks: + network_namespace[] -> net_device[] -> nf_hooks_egress[] For all the other Netfilter hooks: <= 4.2.8 nf_hooks[] @@ -148,7 +150,7 @@ class AbstractNetfilter(ABC): network_namespace[] -> nf.hooks[] get_hook_ops(hook_container, proto_idx, hook_idx) - Give the 'hook_container' got in get_hooks_container_by_protocol(), it + Give the 'hook_container' got in get_hooks_container(), it returns an iterable of 'nf_hook_ops' elements for a respective protocol and hook type. @@ -163,7 +165,7 @@ class AbstractNetfilter(ABC): """ for netns, net in self.get_net_namespaces(): for proto_idx, proto_name, hook_idx, hook_name in self._proto_hook_loop(): - hooks_container = self.get_hooks_container_by_protocol(net, proto_name) + hooks_container = self.get_hooks_container(net, proto_name, hook_name) for hook_container in hooks_container: for hook_ops in self.get_hook_ops( @@ -270,7 +272,7 @@ class AbstractNetfilter(ABC): net_ns_id = net.ns.inum yield net_ns_id, net - def get_hooks_container_by_protocol(self, net, proto_name): + def get_hooks_container(self, net, proto_name, hook_name): """Returns the data structure used in a specific kernel implementation to store the hooks for a respective namespace and protocol. @@ -281,7 +283,7 @@ class AbstractNetfilter(ABC): yield net.nf.hooks def get_hook_ops(self, hook_container, proto_idx, hook_idx): - """Given the hook_container obtained from get_hooks_container_by_protocol(), it + """Given the hook_container obtained from get_hooks_container(), it returns an iterable of 'nf_hook_ops' elements for a corresponding protocol and hook type. @@ -338,7 +340,7 @@ class NetfilterImp_to_4_3(AbstractNetfilter): netns, net = renderers.NotAvailableValue(), renderers.NotAvailableValue() yield netns, net - def get_hooks_container_by_protocol(self, net, proto_name): + def get_hooks_container(self, net, proto_name, hook_name): nf_hooks = self.vmlinux.object_from_symbol("nf_hooks") if not nf_hooks: return @@ -508,7 +510,7 @@ class NetfilterImp_4_16_to_latest(NetfilterImp_4_14_to_4_16): and vmlinux.get_type("netns_nf").has_member("hooks_ipv4") ) - def get_hooks_container_by_protocol(self, net, proto_name): + def get_hooks_container(self, net, proto_name, hook_name): try: if proto_name == "IPV4": net_nf_hooks = net.nf.hooks_ipv4 @@ -556,20 +558,19 @@ class AbstractNetfilterNetDev(AbstractNetfilter): def subscribed_protocols(self): return ("NETDEV",) - def get_hooks_container_by_protocol(self, net, proto_name): - if proto_name != "NETDEV": - return - + def get_hooks_container(self, net, proto_name, hook_name): net_device_type = self.vmlinux.get_type("net_device") net_device_name = self.get_symbol_fullname("net_device") for net_device in net.dev_base_head.to_list(net_device_name, "dev_list"): - if net_device_type.has_member("nf_hooks_ingress"): - # CONFIG_NETFILTER_INGRESS=y - yield net_device.nf_hooks_ingress + if hook_name == "INGRESS": + if net_device_type.has_member("nf_hooks_ingress"): + # CONFIG_NETFILTER_INGRESS=y + yield net_device.nf_hooks_ingress - if net_device_type.has_member("nf_hooks_egress"): - # CONFIG_NETFILTER_EGRESS=y - yield net_device.nf_hooks_egress + elif hook_name == "EGRESS": + if net_device_type.has_member("nf_hooks_egress"): + # CONFIG_NETFILTER_EGRESS=y + yield net_device.nf_hooks_egress class NetfilterNetDevImp_4_2_to_4_9(AbstractNetfilterNetDev): From 0defe0f07e3801e4a833c05e37148aceb93fe305 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sun, 9 Jun 2024 21:08:22 +1000 Subject: [PATCH 05/85] Rename symbol_table to kernel_module_name --- volatility3/framework/plugins/linux/netfilter.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index 33bb5c456..2b6b67268 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -75,13 +75,13 @@ class AbstractNetfilter(ABC): ): self._context = context self._config = config - symbol_table = self._config["kernel"] - self.vmlinux = context.modules[symbol_table] + kernel_module_name = self._config["kernel"] + self.vmlinux = context.modules[kernel_module_name] self.layer_name = self.vmlinux.layer_name - modules = lsmod.Lsmod.list_modules(context, symbol_table) + modules = lsmod.Lsmod.list_modules(context, kernel_module_name) self.handlers = linux.LinuxUtilities.generate_kernel_handler_info( - context, symbol_table, modules + context, kernel_module_name, modules ) self._set_data_sizes() From fefc6c4a4be2e1f12c81d04103d1e783303473b6 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sun, 9 Jun 2024 21:27:27 +1000 Subject: [PATCH 06/85] Pass just the kernel module name instead of the whole config --- volatility3/framework/plugins/linux/netfilter.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index 2b6b67268..c2af660b1 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -69,13 +69,9 @@ class AbstractNetfilter(ABC): NF_MAX_HOOKS = LARGEST_HOOK_NUMBER + 1 def __init__( - self, - context: interfaces.context.ContextInterface, - config: interfaces.configuration.HierarchicalDict, + self, context: interfaces.context.ContextInterface, kernel_module_name: str ): self._context = context - self._config = config - kernel_module_name = self._config["kernel"] self.vmlinux = context.modules[kernel_module_name] self.layer_name = self.vmlinux.layer_name @@ -106,7 +102,8 @@ class AbstractNetfilter(ABC): Yields: The kmsg records. Same as _run() """ - vmlinux = context.modules[config["kernel"]] + kernel_module_name = config["kernel"] + vmlinux = context.modules[kernel_module_name] implementation_inst = None # type: ignore for subclass in class_subclasses(cls): @@ -123,7 +120,9 @@ class AbstractNetfilter(ABC): "Netfilter implementation '%s' matches!", subclass.__name__, ) - implementation_inst = subclass(context=context, config=config) + implementation_inst = subclass( + context=context, kernel_module_name=kernel_module_name + ) # More than one class could be executed for an specific kernel version # For instance: Netfilter Ingress hooks yield from implementation_inst._run() From 0962f2638243517b2f5ff0a76423dfaeb7796071 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sun, 9 Jun 2024 21:33:59 +1000 Subject: [PATCH 07/85] class_subclasses is a function, soit's better import the volatility.framework to avoid people accidentally thinking it's defined here and then importing it from here. --- volatility3/framework/plugins/linux/netfilter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index c2af660b1..ad446123f 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -6,8 +6,8 @@ from abc import ABC, abstractmethod import logging from typing import Iterator, List, Tuple +from volatility3 import framework from volatility3.framework import ( - class_subclasses, constants, interfaces, renderers, @@ -106,7 +106,7 @@ class AbstractNetfilter(ABC): vmlinux = context.modules[kernel_module_name] implementation_inst = None # type: ignore - for subclass in class_subclasses(cls): + for subclass in framework.class_subclasses(cls): if not subclass.symtab_checks(vmlinux=vmlinux): vollog.log( constants.LOGLEVEL_VVVV, From 3793510f59adda53f25395bddd358a97e8397054 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sun, 9 Jun 2024 21:36:20 +1000 Subject: [PATCH 08/85] Add VersionRequirement for LinuxUtilities --- volatility3/framework/plugins/linux/netfilter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index ad446123f..b01ec25a7 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -674,6 +674,9 @@ class Netfilter(interfaces.plugins.PluginInterface): requirements.PluginRequirement( name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) ), + requirements.VersionRequirement( + name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0) + ), ] def _format_fields(self, fields): From 9d5636b03a7cbee926f88954e58284de30d78f1e Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sun, 9 Jun 2024 21:43:11 +1000 Subject: [PATCH 09/85] Move the data size setter to the __init__() --- volatility3/framework/plugins/linux/netfilter.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index b01ec25a7..d27106118 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -75,17 +75,15 @@ class AbstractNetfilter(ABC): self.vmlinux = context.modules[kernel_module_name] self.layer_name = self.vmlinux.layer_name + # Set data sizes + self.ptr_size = self.vmlinux.get_type("pointer").size + self.list_head_size = self.vmlinux.get_type("list_head").size + modules = lsmod.Lsmod.list_modules(context, kernel_module_name) self.handlers = linux.LinuxUtilities.generate_kernel_handler_info( context, kernel_module_name, modules ) - self._set_data_sizes() - - def _set_data_sizes(self): - self.ptr_size = self.vmlinux.get_type("pointer").size - self.list_head_size = self.vmlinux.get_type("list_head").size - @classmethod def run_all( cls, From 23b9ed8c0ca6241db3b5cad6213337ee620c4b7a Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Sun, 9 Jun 2024 22:02:41 +1000 Subject: [PATCH 10/85] Extend the changes in fefc6c4a to the run_all() class method --- volatility3/framework/plugins/linux/netfilter.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index d27106118..60a71f798 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -86,21 +86,18 @@ class AbstractNetfilter(ABC): @classmethod def run_all( - cls, - context: interfaces.context.ContextInterface, - config: interfaces.configuration.HierarchicalDict, + cls, context: interfaces.context.ContextInterface, kernel_module_name: str ) -> Iterator[Tuple[int, str, str, int, int, str, bool]]: """It calls each subclass symtab_checks() to test the required conditions to that specific kernel implementation. Args: context: The volatility3 context on which to operate - config: Core configuration + kernel_module_name: The name of the table containing the kernel symbols Yields: The kmsg records. Same as _run() """ - kernel_module_name = config["kernel"] vmlinux = context.modules[kernel_module_name] implementation_inst = None # type: ignore @@ -698,8 +695,9 @@ class Netfilter(interfaces.plugins.PluginInterface): ) def _generator(self): + kernel_module_name = self.config["kernel"] for fields in AbstractNetfilter.run_all( - context=self.context, config=self.config + context=self.context, kernel_module_name=kernel_module_name ): yield (0, self._format_fields(fields)) From aee81276a2d921e44e58db28174f7e4782fe7423 Mon Sep 17 00:00:00 2001 From: atcuno Date: Wed, 19 Jun 2024 11:27:32 -0500 Subject: [PATCH 11/85] Add processghosting plugin to detect process ghosting and related techniques. Add process filter that gathers only active, non-smeared userland processes --- .../plugins/windows/processghosting.py | 99 +++++++++++++++++++ .../framework/plugins/windows/pslist.py | 21 ++++ 2 files changed, 120 insertions(+) create mode 100644 volatility3/framework/plugins/windows/processghosting.py diff --git a/volatility3/framework/plugins/windows/processghosting.py b/volatility3/framework/plugins/windows/processghosting.py new file mode 100644 index 000000000..6c311f555 --- /dev/null +++ b/volatility3/framework/plugins/windows/processghosting.py @@ -0,0 +1,99 @@ +# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging + +from volatility3.framework import interfaces, exceptions +from volatility3.framework import renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.objects import utility +from volatility3.framework.renderers import format_hints +from volatility3.plugins.windows import pslist + +vollog = logging.getLogger(__name__) + + +class ProcessGhosting(interfaces.plugins.PluginInterface): + """Lists processes whose DeletePending bit is set or whose FILE_OBJECT is set to 0""" + + _required_framework_version = (2, 4, 0) + + @classmethod + def get_requirements(cls): + # 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) + ), + ] + + def _generator(self, procs): + # determine if we're on a 32 or 64 bit kernel + kernel = self.context.modules[self.config["kernel"]] + + if not kernel.get_type("_EPROCESS").has_member("ImageFilePointer"): + vollog.warning("This plugin only supports Windows 10 builds when the ImageFilePointer member of _EPROCESS is present") + return + + for proc in procs: + delete_pending = renderers.UnreadableValue() + process_name = utility.array_to_string(proc.ImageFileName) + + # if it is 0 then its a side effect of process ghosting + if proc.ImageFilePointer.vol.offset != 0: + try: + file_object = proc.ImageFilePointer + delete_pending = file_object.DeletePending + except exceptions.InvalidAddressException: + file_object = 0 + + # ImageFilePointer equal to 0 means process ghosting or similar techniques were used + else: + file_object = 0 + + # delete_pending besides 0 or 1 = smear + if file_object == 0 or delete_pending == 1: + path = renderers.UnreadableValue() + if file_object: + try: + path = file_object.FileName.String + except exceptions.InvalidAddressException: + path = renderers.UnreadableValue() + + yield ( + 0, + ( + proc.UniqueProcessId, + process_name, + format_hints.Hex(file_object), + delete_pending, + path + ), + ) + + def run(self): + filter_func = pslist.PsList.create_active_process_filter() + kernel = self.context.modules[self.config["kernel"]] + + return renderers.TreeGrid( + [ + ("PID", int), + ("Process", str), + ("FILE_OBJECT", format_hints.Hex), + ("DeletePending", str), + ("Path", str), + ], + self._generator( + pslist.PsList.list_processes( + context=self.context, + layer_name=kernel.layer_name, + symbol_table=kernel.symbol_table_name, + filter_func=filter_func, + ) + ), + ) diff --git a/volatility3/framework/plugins/windows/pslist.py b/volatility3/framework/plugins/windows/pslist.py index e7a0d5dd4..8ad0f9a05 100644 --- a/volatility3/framework/plugins/windows/pslist.py +++ b/volatility3/framework/plugins/windows/pslist.py @@ -136,6 +136,27 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): filter_func = lambda x: x.UniqueProcessId not in filter_list return filter_func + @classmethod + def create_active_process_filter( + cls + ) -> Callable[[interfaces.objects.ObjectInterface], bool]: + """A factory for producing a filter function that only returns + active, userland processes. This prevents plugins from operating on terminated + processes that are still in the process list due to smear or handle leaks as well + as kernel processes (System, Registry, etc.). Use of this filter for plugins searching + for system state anomalies significantly reduces false positive in smeared and terminated + processes. + Returns: + Filter function for passing to the `list_processes` method + """ + + return lambda x: not (x.is_valid() and \ + x.ActiveThreads > 0 and \ + x.UniqueProcessId != 4 and \ + x.InheritedFromUniqueProcessId != 4 and \ + x.ExitTime.QuadPart == 0 and \ + x.get_handle_count() != renderers.UnreadableValue()) + @classmethod def create_name_filter( cls, name_list: List[str] = None, exclude: bool = False From 6c42c51a8a6201255f120321885309cd92611c77 Mon Sep 17 00:00:00 2001 From: atcuno Date: Wed, 19 Jun 2024 11:31:43 -0500 Subject: [PATCH 12/85] Fixes for black --- .../framework/plugins/windows/processghosting.py | 7 ++++--- volatility3/framework/plugins/windows/pslist.py | 16 +++++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/volatility3/framework/plugins/windows/processghosting.py b/volatility3/framework/plugins/windows/processghosting.py index 6c311f555..b29ff04f0 100644 --- a/volatility3/framework/plugins/windows/processghosting.py +++ b/volatility3/framework/plugins/windows/processghosting.py @@ -33,11 +33,12 @@ class ProcessGhosting(interfaces.plugins.PluginInterface): ] def _generator(self, procs): - # determine if we're on a 32 or 64 bit kernel kernel = self.context.modules[self.config["kernel"]] if not kernel.get_type("_EPROCESS").has_member("ImageFilePointer"): - vollog.warning("This plugin only supports Windows 10 builds when the ImageFilePointer member of _EPROCESS is present") + vollog.warning( + "This plugin only supports Windows 10 builds when the ImageFilePointer member of _EPROCESS is present" + ) return for proc in procs: @@ -72,7 +73,7 @@ class ProcessGhosting(interfaces.plugins.PluginInterface): process_name, format_hints.Hex(file_object), delete_pending, - path + path, ), ) diff --git a/volatility3/framework/plugins/windows/pslist.py b/volatility3/framework/plugins/windows/pslist.py index 8ad0f9a05..55a3fc280 100644 --- a/volatility3/framework/plugins/windows/pslist.py +++ b/volatility3/framework/plugins/windows/pslist.py @@ -138,7 +138,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): @classmethod def create_active_process_filter( - cls + cls, ) -> Callable[[interfaces.objects.ObjectInterface], bool]: """A factory for producing a filter function that only returns active, userland processes. This prevents plugins from operating on terminated @@ -150,12 +150,14 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): Filter function for passing to the `list_processes` method """ - return lambda x: not (x.is_valid() and \ - x.ActiveThreads > 0 and \ - x.UniqueProcessId != 4 and \ - x.InheritedFromUniqueProcessId != 4 and \ - x.ExitTime.QuadPart == 0 and \ - x.get_handle_count() != renderers.UnreadableValue()) + return lambda x: not ( + x.is_valid() and + x.ActiveThreads > 0 and + x.UniqueProcessId != 4 and + x.InheritedFromUniqueProcessId != 4 and + x.ExitTime.QuadPart == 0 and + x.get_handle_count() != renderers.UnreadableValue() + ) @classmethod def create_name_filter( From 4e15019c46ae24899ea0527dd4855ade5fd7af00 Mon Sep 17 00:00:00 2001 From: atcuno Date: Wed, 19 Jun 2024 11:34:06 -0500 Subject: [PATCH 13/85] Fixes for black --- volatility3/framework/plugins/windows/pslist.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/plugins/windows/pslist.py b/volatility3/framework/plugins/windows/pslist.py index 55a3fc280..8234b210f 100644 --- a/volatility3/framework/plugins/windows/pslist.py +++ b/volatility3/framework/plugins/windows/pslist.py @@ -151,12 +151,12 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): """ return lambda x: not ( - x.is_valid() and - x.ActiveThreads > 0 and - x.UniqueProcessId != 4 and - x.InheritedFromUniqueProcessId != 4 and - x.ExitTime.QuadPart == 0 and - x.get_handle_count() != renderers.UnreadableValue() + x.is_valid() + and x.ActiveThreads > 0 + and x.UniqueProcessId != 4 + and x.InheritedFromUniqueProcessId != 4 + and x.ExitTime.QuadPart == 0 + and x.get_handle_count() != renderers.UnreadableValue() ) @classmethod From c4e7e50180a45dfb80b453b61e9b94860d5a01d3 Mon Sep 17 00:00:00 2001 From: atcuno Date: Wed, 19 Jun 2024 14:42:23 -0500 Subject: [PATCH 14/85] Add svclist and svcdiff plugins. Make svcscan more modular to support inheritance and cleaner code --- .../framework/plugins/windows/svcdiff.py | 74 ++++++++++++ .../framework/plugins/windows/svclist.py | 86 ++++++++++++++ .../framework/plugins/windows/svcscan.py | 111 +++++++++++------- .../framework/symbols/windows/versions.py | 9 ++ 4 files changed, 235 insertions(+), 45 deletions(-) create mode 100644 volatility3/framework/plugins/windows/svcdiff.py create mode 100644 volatility3/framework/plugins/windows/svclist.py diff --git a/volatility3/framework/plugins/windows/svcdiff.py b/volatility3/framework/plugins/windows/svcdiff.py new file mode 100644 index 000000000..03f0afcd3 --- /dev/null +++ b/volatility3/framework/plugins/windows/svcdiff.py @@ -0,0 +1,74 @@ +# 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 +# + +# This module attempts to locate skeleton-key like function hooks. +# It does this by locating the CSystems array through a variety of methods, +# and then validating the entry for RC4 HMAC (0x17 / 23) +# +# For a thorough walkthrough on how the R&D was performed to develop this plugin, +# please see our blogpost here: +# +# https://volatility-labs.blogspot.com/2021/10/memory-forensics-r-illustrated.html + +import logging + +from volatility3.framework import symbols +from volatility3.framework.configuration import requirements +from volatility3.plugins.windows import svclist, svcscan +from volatility3.framework.symbols.windows import versions + +vollog = logging.getLogger(__name__) + +class SvcDiff(svclist.SvcList, svcscan.SvcScan): + """Compares services found through list walking versus scanning to find rootkits""" + + _required_framework_version = (2, 4, 0) + + @classmethod + def get_requirements(cls): + # 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="svclist", component=svclist.SvcList, version=(1, 0, 0) + ), + requirements.VersionRequirement( + name="svcscan", component=svcscan.SvcScan, version=(2, 0, 0) + ), + ] + + def _generator(self): + """ + Finds services by walking the services.exe list on supported Windows 10 versions + """ + kernel = self.context.modules[self.config["kernel"]] + + if not symbols.symbol_table_is_64bit(self.context, kernel.symbol_table_name) or \ + not versions.is_win10_15063_or_later(context=self.context, symbol_table=kernel.symbol_table_name): + vollog.info("This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples") + return + + from_scan = set() + from_list = set() + records = {} + + service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info() + + # collect unique service names from scanning + for service in self.service_scan(service_table_name, service_binary_dll_map, filter_func): + from_scan.add(service[6]) + records[service[6]] = service + + # collect services from listing walking + for service in self.service_list(service_table_name, service_binary_dll_map, filter_func): + from_list.add(service[6]) + + # report services found from scanning but not list walking + for hidden_service in from_scan-from_list: + yield (0, records[hidden_service]) + diff --git a/volatility3/framework/plugins/windows/svclist.py b/volatility3/framework/plugins/windows/svclist.py new file mode 100644 index 000000000..b4541981d --- /dev/null +++ b/volatility3/framework/plugins/windows/svclist.py @@ -0,0 +1,86 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging + +from typing import List + +from volatility3.framework import interfaces, exceptions, symbols +from volatility3.framework.configuration import requirements +from volatility3.framework.symbols.windows import versions +from volatility3.plugins.windows import svcscan, pslist +from volatility3.framework.layers import scanners + +vollog = logging.getLogger(__name__) + + +class SvcList(svcscan.SvcScan): + """Lists services contained with the services.exe doubly linked list of services""" + + _version = (1, 0, 0) + + @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.PluginRequirement( + name="svcscan", plugin=svcscan.SvcScan, version=(2, 0, 0) + ), + ] + + def _get_exe_range(self, proc): + """ + Returns a tuple of starting,ending address for + the VAD containing services.exe + """ + + vad_root = proc.get_vad_root() + for vad in vad_root.traverse(): + filename = vad.get_file_name() + if isinstance(filename, str) and filename.lower().endswith("\\services.exe"): + return [(vad.get_start(), vad.get_size())] + + return None + + def service_list(self, service_table_name, service_binary_dll_map, filter_func): + kernel = self.context.modules[self.config["kernel"]] + + if not symbols.symbol_table_is_64bit(self.context, kernel.symbol_table_name) or \ + not versions.is_win10_15063_or_later(context=self.context, symbol_table=kernel.symbol_table_name): + vollog.info("This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples") + return + + for proc in pslist.PsList.list_processes( + context=self.context, + layer_name=kernel.layer_name, + symbol_table=kernel.symbol_table_name, + filter_func=filter_func, + ): + try: + layer_name = proc.add_process_layer() + except exceptions.InvalidAddressException: + vollog.warning("Unable to access memory of services.exe running with PID: {}".format(proc.UniqueProcessId)) + continue + + layer = self.context.layers[layer_name] + + exe_range = self._get_exe_range(proc) + if not exe_range: + vollog.warning("Could not find the application executable VAD for services.exe. Unable to proceed.") + continue + + for offset in layer.scan( + context=self.context, + scanner=scanners.BytesScanner(needle = b"Sc27"), + sections=exe_range, + ): + for record in self.enumerate_vista_or_later_header(service_table_name, service_binary_dll_map, layer_name, offset): + yield record + + def _generator(self): + service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info() + + for record in self.service_list(service_table_name, service_binary_dll_map, filter_func): + yield (0, record) + diff --git a/volatility3/framework/plugins/windows/svcscan.py b/volatility3/framework/plugins/windows/svcscan.py index 10de46e2a..08d11a946 100644 --- a/volatility3/framework/plugins/windows/svcscan.py +++ b/volatility3/framework/plugins/windows/svcscan.py @@ -19,7 +19,7 @@ from volatility3.framework.layers import scanners 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 +from volatility3.framework.symbols.windows.extensions import services as services_types from volatility3.plugins.windows import poolscanner, pslist, vadyarascan from volatility3.plugins.windows.registry import hivelist @@ -140,7 +140,7 @@ class SvcScan(interfaces.plugins.PluginInterface): config_path, os.path.join("windows", "services"), symbol_filename, - class_types=services.class_types, + class_types=services_types.class_types, native_types=native_types, ) @@ -232,28 +232,44 @@ class SvcScan(interfaces.plugins.PluginInterface): for service_key in services } - def _generator(self): + def enumerate_vista_or_later_header( + self, + service_table_name, + service_binary_dll_map, + proc_layer_name, + offset + ): + if offset % 8: + return + + service_header = self.context.object( + service_table_name + constants.BANG + "_SERVICE_HEADER", + offset=offset, + layer_name=proc_layer_name, + ) + + if not service_header.is_valid(): + return + + # since we walk the s-list backwards, if we've seen + # an object, then we've also seen all objects that + # exist before it, thus we can break at that time. + for service_record in service_header.ServiceRecord.traverse(): + service_info = service_binary_dll_map.get( + service_record.get_name(), + ServiceBinaryInfo( + renderers.UnreadableValue(), renderers.UnreadableValue() + ), + ) + yield self.get_record_tuple(service_record, service_info) + + def service_scan(self, service_table_name, service_binary_dll_map, filter_func): kernel = self.context.modules[self.config["kernel"]] - service_table_name = self.create_service_table( - self.context, kernel.symbol_table_name, self.config_path - ) - - # Building the dictionary ahead of time is much better for performance - # vs looking up each service's DLL individually. - services_key = self._get_service_key(kernel) - service_binary_dll_map = ( - self._get_service_binary_map(services_key) - if services_key is not None - else {} - ) - relative_tag_offset = self.context.symbol_space.get_type( service_table_name + constants.BANG + "_SERVICE_RECORD" ).relative_child_offset("Tag") - filter_func = pslist.PsList.create_name_filter(["services.exe"]) - is_vista_or_later = versions.is_vista_or_later( context=self.context, symbol_table=kernel.symbol_table_name ) @@ -306,37 +322,42 @@ class SvcScan(interfaces.plugins.PluginInterface): renderers.UnreadableValue(), renderers.UnreadableValue() ), ) - yield ( - 0, - self.get_record_tuple(service_record, service_info), - ) + yield self.get_record_tuple(service_record, service_info) else: - service_header = self.context.object( - service_table_name + constants.BANG + "_SERVICE_HEADER", - offset=offset, - layer_name=proc_layer_name, - ) - - if not service_header.is_valid(): - continue - - # since we walk the s-list backwards, if we've seen - # an object, then we've also seen all objects that - # exist before it, thus we can break at that time. - for service_record in service_header.ServiceRecord.traverse(): + for service_record in self.enumerate_vista_or_later_header(service_table_name, service_binary_dll_map, proc_layer_name, offset): if service_record in seen: break seen.append(service_record) - service_info = service_binary_dll_map.get( - service_record.get_name(), - ServiceBinaryInfo( - renderers.UnreadableValue(), renderers.UnreadableValue() - ), - ) - yield ( - 0, - self.get_record_tuple(service_record, service_info), - ) + yield service_record + + + def get_prereq_info(self): + """ + Data structures and information needed to analyze service information + """ + kernel = self.context.modules[self.config["kernel"]] + + service_table_name = self.create_service_table( + self.context, kernel.symbol_table_name, self.config_path + ) + + services_key = self._get_service_key(kernel) + + service_binary_dll_map = ( + self._get_service_binary_map(services_key) + if services_key is not None + else {} + ) + + filter_func = pslist.PsList.create_name_filter(["services.exe"]) + + return service_table_name, service_binary_dll_map, filter_func + + def _generator(self): + service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info() + + for record in self.service_scan(service_table_name, service_binary_dll_map, filter_func): + yield (0, record) def run(self): return renderers.TreeGrid( diff --git a/volatility3/framework/symbols/windows/versions.py b/volatility3/framework/symbols/windows/versions.py index e1e74afc0..d8964f575 100644 --- a/volatility3/framework/symbols/windows/versions.py +++ b/volatility3/framework/symbols/windows/versions.py @@ -141,6 +141,15 @@ is_win10_15063 = OsDistinguisher( ], ) +is_win10_15063_or_later = OsDistinguisher( + version_check=lambda x: x >= (10, 0, 15063), + fallback_checks=[ + ("ObHeaderCookie", None, True), + ("_HANDLE_TABLE", "HandleCount", False), + ("_EPROCESS", "KeepAliveCounter", False), + ], +) + is_win10_16299_or_later = OsDistinguisher( version_check=lambda x: x >= (10, 0, 16299), fallback_checks=[ From 91184c7d92f84cdd3371d3a2371cf1784a2eb1a2 Mon Sep 17 00:00:00 2001 From: atcuno Date: Wed, 19 Jun 2024 14:54:20 -0500 Subject: [PATCH 15/85] Format fixes --- .../framework/plugins/windows/svcdiff.py | 23 ++++++++---- .../framework/plugins/windows/svclist.py | 36 +++++++++++++------ .../framework/plugins/windows/svcscan.py | 18 +++++----- 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/volatility3/framework/plugins/windows/svcdiff.py b/volatility3/framework/plugins/windows/svcdiff.py index 03f0afcd3..71aa03636 100644 --- a/volatility3/framework/plugins/windows/svcdiff.py +++ b/volatility3/framework/plugins/windows/svcdiff.py @@ -48,27 +48,36 @@ class SvcDiff(svclist.SvcList, svcscan.SvcScan): """ kernel = self.context.modules[self.config["kernel"]] - if not symbols.symbol_table_is_64bit(self.context, kernel.symbol_table_name) or \ - not versions.is_win10_15063_or_later(context=self.context, symbol_table=kernel.symbol_table_name): - vollog.info("This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples") + if not symbols.symbol_table_is_64bit( + self.context, kernel.symbol_table_name + ) or not versions.is_win10_15063_or_later( + context=self.context, symbol_table=kernel.symbol_table_name + ): + vollog.info( + "This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples" + ) return from_scan = set() from_list = set() records = {} - + service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info() # collect unique service names from scanning - for service in self.service_scan(service_table_name, service_binary_dll_map, filter_func): + for service in self.service_scan( + service_table_name, service_binary_dll_map, filter_func + ): from_scan.add(service[6]) records[service[6]] = service # collect services from listing walking - for service in self.service_list(service_table_name, service_binary_dll_map, filter_func): + for service in self.service_list( + service_table_name, service_binary_dll_map, filter_func + ): from_list.add(service[6]) # report services found from scanning but not list walking - for hidden_service in from_scan-from_list: + for hidden_service in from_scan - from_list: yield (0, records[hidden_service]) diff --git a/volatility3/framework/plugins/windows/svclist.py b/volatility3/framework/plugins/windows/svclist.py index b4541981d..53ca68da7 100644 --- a/volatility3/framework/plugins/windows/svclist.py +++ b/volatility3/framework/plugins/windows/svclist.py @@ -6,7 +6,7 @@ import logging from typing import List -from volatility3.framework import interfaces, exceptions, symbols +from volatility3.framework import interfaces, exceptions, symbols from volatility3.framework.configuration import requirements from volatility3.framework.symbols.windows import versions from volatility3.plugins.windows import svcscan, pslist @@ -38,7 +38,9 @@ class SvcList(svcscan.SvcScan): vad_root = proc.get_vad_root() for vad in vad_root.traverse(): filename = vad.get_file_name() - if isinstance(filename, str) and filename.lower().endswith("\\services.exe"): + if isinstance(filename, str) and filename.lower().endswith( + "\\services.exe" + ): return [(vad.get_start(), vad.get_size())] return None @@ -46,9 +48,14 @@ class SvcList(svcscan.SvcScan): def service_list(self, service_table_name, service_binary_dll_map, filter_func): kernel = self.context.modules[self.config["kernel"]] - if not symbols.symbol_table_is_64bit(self.context, kernel.symbol_table_name) or \ - not versions.is_win10_15063_or_later(context=self.context, symbol_table=kernel.symbol_table_name): - vollog.info("This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples") + if not symbols.symbol_table_is_64bit( + self.context, kernel.symbol_table_name + ) or not versions.is_win10_15063_or_later( + context=self.context, symbol_table=kernel.symbol_table_name + ): + vollog.info( + "This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples" + ) return for proc in pslist.PsList.list_processes( @@ -60,27 +67,34 @@ class SvcList(svcscan.SvcScan): try: layer_name = proc.add_process_layer() except exceptions.InvalidAddressException: - vollog.warning("Unable to access memory of services.exe running with PID: {}".format(proc.UniqueProcessId)) + vollog.warning( + "Unable to access memory of services.exe running with PID: {}".format( + proc.UniqueProcessId + ) + ) continue layer = self.context.layers[layer_name] exe_range = self._get_exe_range(proc) if not exe_range: - vollog.warning("Could not find the application executable VAD for services.exe. Unable to proceed.") + vollog.warning( + "Could not find the application executable VAD for services.exe. Unable to proceed." + ) continue for offset in layer.scan( context=self.context, - scanner=scanners.BytesScanner(needle = b"Sc27"), + scanner=scanners.BytesScanner(needle=b"Sc27"), sections=exe_range, + ): + for record in self.enumerate_vista_or_later_header( + service_table_name, service_binary_dll_map, layer_name, offset ): - for record in self.enumerate_vista_or_later_header(service_table_name, service_binary_dll_map, layer_name, offset): yield record def _generator(self): service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info() - + for record in self.service_list(service_table_name, service_binary_dll_map, filter_func): yield (0, record) - diff --git a/volatility3/framework/plugins/windows/svcscan.py b/volatility3/framework/plugins/windows/svcscan.py index 08d11a946..c991ec943 100644 --- a/volatility3/framework/plugins/windows/svcscan.py +++ b/volatility3/framework/plugins/windows/svcscan.py @@ -233,11 +233,7 @@ class SvcScan(interfaces.plugins.PluginInterface): } def enumerate_vista_or_later_header( - self, - service_table_name, - service_binary_dll_map, - proc_layer_name, - offset + self, service_table_name, service_binary_dll_map, proc_layer_name, offset ): if offset % 8: return @@ -324,13 +320,17 @@ class SvcScan(interfaces.plugins.PluginInterface): ) yield self.get_record_tuple(service_record, service_info) else: - for service_record in self.enumerate_vista_or_later_header(service_table_name, service_binary_dll_map, proc_layer_name, offset): + for service_record in self.enumerate_vista_or_later_header( + service_table_name, + service_binary_dll_map, + proc_layer_name, + offset + ): if service_record in seen: break seen.append(service_record) yield service_record - def get_prereq_info(self): """ Data structures and information needed to analyze service information @@ -356,7 +356,9 @@ class SvcScan(interfaces.plugins.PluginInterface): def _generator(self): service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info() - for record in self.service_scan(service_table_name, service_binary_dll_map, filter_func): + for record in self.service_scan( + service_table_name, service_binary_dll_map, filter_func + ): yield (0, record) def run(self): From d42fb0a1451ca0a0cc00ee4a2a431be092c193b9 Mon Sep 17 00:00:00 2001 From: atcuno Date: Wed, 19 Jun 2024 14:56:57 -0500 Subject: [PATCH 16/85] Format fixes --- volatility3/framework/plugins/windows/svclist.py | 4 +++- volatility3/framework/plugins/windows/svcscan.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/windows/svclist.py b/volatility3/framework/plugins/windows/svclist.py index 53ca68da7..1938dd182 100644 --- a/volatility3/framework/plugins/windows/svclist.py +++ b/volatility3/framework/plugins/windows/svclist.py @@ -96,5 +96,7 @@ class SvcList(svcscan.SvcScan): def _generator(self): service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info() - for record in self.service_list(service_table_name, service_binary_dll_map, filter_func): + for record in self.service_list( + service_table_name, service_binary_dll_map, filter_func + ): yield (0, record) diff --git a/volatility3/framework/plugins/windows/svcscan.py b/volatility3/framework/plugins/windows/svcscan.py index c991ec943..07274f03d 100644 --- a/volatility3/framework/plugins/windows/svcscan.py +++ b/volatility3/framework/plugins/windows/svcscan.py @@ -324,7 +324,7 @@ class SvcScan(interfaces.plugins.PluginInterface): service_table_name, service_binary_dll_map, proc_layer_name, - offset + offset, ): if service_record in seen: break From f6a053d7b3db2421c9d4d8501eff1fcb78001f1f Mon Sep 17 00:00:00 2001 From: atcuno Date: Wed, 19 Jun 2024 14:58:05 -0500 Subject: [PATCH 17/85] Format fixes --- volatility3/framework/plugins/windows/svcdiff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/windows/svcdiff.py b/volatility3/framework/plugins/windows/svcdiff.py index 71aa03636..809064946 100644 --- a/volatility3/framework/plugins/windows/svcdiff.py +++ b/volatility3/framework/plugins/windows/svcdiff.py @@ -20,6 +20,7 @@ from volatility3.framework.symbols.windows import versions vollog = logging.getLogger(__name__) + class SvcDiff(svclist.SvcList, svcscan.SvcScan): """Compares services found through list walking versus scanning to find rootkits""" @@ -80,4 +81,3 @@ class SvcDiff(svclist.SvcList, svcscan.SvcScan): # report services found from scanning but not list walking for hidden_service in from_scan - from_list: yield (0, records[hidden_service]) - From b601250279fe206b2586d82820b38e1c2ea6fd83 Mon Sep 17 00:00:00 2001 From: Dave Lassalle Date: Fri, 21 Jun 2024 08:29:50 -0500 Subject: [PATCH 18/85] #1175 - initial unloadedmodules plugin --- .../plugins/windows/unloadedmodules.py | 159 ++++++++++++++++++ .../symbols/windows/unloadedmodules-x64.json | 109 ++++++++++++ .../symbols/windows/unloadedmodules-x86.json | 109 ++++++++++++ 3 files changed, 377 insertions(+) create mode 100644 volatility3/framework/plugins/windows/unloadedmodules.py create mode 100644 volatility3/framework/symbols/windows/unloadedmodules-x64.json create mode 100644 volatility3/framework/symbols/windows/unloadedmodules-x86.json diff --git a/volatility3/framework/plugins/windows/unloadedmodules.py b/volatility3/framework/plugins/windows/unloadedmodules.py new file mode 100644 index 000000000..1fd4914ac --- /dev/null +++ b/volatility3/framework/plugins/windows/unloadedmodules.py @@ -0,0 +1,159 @@ +# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +import datetime +from typing import List, Iterable + +from volatility3.framework import constants +from volatility3.framework import interfaces, symbols +from volatility3.framework import renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.renderers import format_hints, conversion +from volatility3.framework.symbols import intermed +from volatility3.plugins import timeliner + +vollog = logging.getLogger(__name__) + + +class UnloadedModules(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): + """Lists the unloaded kernel modules.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Windows kernel", + architectures=["Intel32", "Intel64"], + ), + ] + + @staticmethod + def create_unloadedmodules_table( + context: interfaces.context.ContextInterface, + symbol_table: str, + config_path: str, + ) -> str: + """Creates a symbol table for the unloaded modules. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + symbol_table: The name of an existing symbol table containing the kernel symbols + config_path: The configuration path within the context of the symbol table to create + + Returns: + The name of the constructed unloaded modules 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} + + if is_64bit: + symbol_filename = "unloadedmodules-x64" + else: + symbol_filename = "unloadedmodules-x86" + + return intermed.IntermediateSymbolTable.create( + context, + config_path, + "windows", + symbol_filename, + native_types=native_types, + table_mapping=table_mapping, + ) + + @classmethod + def list_unloadedmodules( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str, + unloadedmodule_table_name: str, + ) -> Iterable[interfaces.objects.ObjectInterface]: + """Lists all the unloaded 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 + + Returns: + A list of Unloaded Modules as retrieved from MmUnloadedDrivers + """ + + kvo = context.layers[layer_name].config["kernel_virtual_offset"] + ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) + unloadedmodules_offset = ntkrnlmp.get_symbol("MmUnloadedDrivers").address + unloadedmodules = ntkrnlmp.object( + object_type="pointer", + offset=unloadedmodules_offset, + subtype="array", + ) + is_64bit = symbols.symbol_table_is_64bit(context, symbol_table) + + if is_64bit: + unloaded_count_type = "unsigned long long" + else: + unloaded_count_type = "unsigned long" + + last_unloadedmodule_offset = ntkrnlmp.get_symbol("MmLastUnloadedDriver").address + unloaded_count = ntkrnlmp.object( + object_type=unloaded_count_type, offset=last_unloadedmodule_offset + ) + + unloadedmodules_array = context.object( + object_type=unloadedmodule_table_name + + constants.BANG + + "_UNLOADED_DRIVERS", + layer_name=layer_name, + offset=unloadedmodules, + ) + unloadedmodules_array.UnloadedDrivers.count = unloaded_count + + for mod in unloadedmodules_array.UnloadedDrivers: + yield mod + + def _generator(self): + kernel = self.context.modules[self.config["kernel"]] + + unloadedmodule_table_name = self.create_unloadedmodules_table( + self.context, kernel.symbol_table_name, self.config_path + ) + + for mod in self.list_unloadedmodules( + self.context, + kernel.layer_name, + kernel.symbol_table_name, + unloadedmodule_table_name, + ): + yield ( + 0, + ( + mod.Name.String, + format_hints.Hex(mod.StartAddress), + format_hints.Hex(mod.EndAddress), + conversion.wintime_to_datetime(mod.CurrentTime), + ), + ) + + def generate_timeline(self): + for row in self._generator(): + _depth, row_data = row + description = f"Unloaded Module: {row_data[0]}" + yield (description, timeliner.TimeLinerType.CHANGED, row_data[3]) + + def run(self): + return renderers.TreeGrid( + [ + ("Name", str), + ("StartAddress", format_hints.Hex), + ("EndAddress", format_hints.Hex), + ("Time", datetime.datetime), + ], + self._generator(), + ) diff --git a/volatility3/framework/symbols/windows/unloadedmodules-x64.json b/volatility3/framework/symbols/windows/unloadedmodules-x64.json new file mode 100644 index 000000000..75ab7c690 --- /dev/null +++ b/volatility3/framework/symbols/windows/unloadedmodules-x64.json @@ -0,0 +1,109 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_UNLOADED_DRIVER": { + "fields": { + "Name": { + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + }, + "offset": 0 + }, + "StartAddress": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 16 + }, + "EndAddress": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 24 + }, + "CurrentTime": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 32 + } + }, + "kind": "struct", + "size": 40 + }, + "_UNLOADED_DRIVERS": { + "fields": { + "UnloadedDrivers": { + "offset": 0, + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_UNLOADED_DRIVER" + } + } + } + }, + "kind": "struct", + "size": 8 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "Dave Lassalle by hand", + "datetime": "2024-06-19T17:57:16.394003" + }, + "format": "4.0.0" + } +} \ No newline at end of file diff --git a/volatility3/framework/symbols/windows/unloadedmodules-x86.json b/volatility3/framework/symbols/windows/unloadedmodules-x86.json new file mode 100644 index 000000000..ff9e78965 --- /dev/null +++ b/volatility3/framework/symbols/windows/unloadedmodules-x86.json @@ -0,0 +1,109 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_UNLOADED_DRIVER": { + "fields": { + "Name": { + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + }, + "offset": 0 + }, + "StartAddress": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 8 + }, + "EndAddress": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 12 + }, + "CurrentTime": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_UNLOADED_DRIVERS": { + "fields": { + "UnloadedDrivers": { + "offset": 0, + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_UNLOADED_DRIVER" + } + } + } + }, + "kind": "struct", + "size": 4 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "Dave Lassalle by hand", + "datetime": "2024-06-19T17:57:16.394003" + }, + "format": "4.0.0" + } +} \ No newline at end of file From e172676d3caaecd3592211cd561aa612d934bcbc Mon Sep 17 00:00:00 2001 From: Dave Lassalle Date: Fri, 21 Jun 2024 15:07:18 -0500 Subject: [PATCH 19/85] #118 - initial timers plugin --- volatility3/framework/objects/utility.py | 21 ++ .../framework/plugins/windows/timers.py | 264 ++++++++++++++++++ .../framework/symbols/windows/__init__.py | 1 + .../symbols/windows/extensions/__init__.py | 80 ++++++ 4 files changed, 366 insertions(+) create mode 100644 volatility3/framework/plugins/windows/timers.py diff --git a/volatility3/framework/objects/utility.py b/volatility3/framework/objects/utility.py index 0292608c1..8aa527cdb 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -7,6 +7,27 @@ from typing import Optional, Union from volatility3.framework import interfaces, objects, constants +def rol(value: int, count: int, max_bits: int = 64) -> int: + """A rotate-left instruction in Python""" + max_bits_mask = (1 << max_bits) - 1 + return (value << count % max_bits) & max_bits_mask | ( + (value & max_bits_mask) >> (max_bits - (count % max_bits)) + ) + + +def bswap_32(value: int) -> int: + value = ((value << 8) & 0xFF00FF00) | ((value >> 8) & 0x00FF00FF) + + return ((value << 16) | (value >> 16)) & 0xFFFFFFFF + + +def bswap_64(value: int) -> int: + low = bswap_32((value >> 32)) + high = bswap_32((value & 0xFFFFFFFF)) + + return ((high << 32) | low) & 0xFFFFFFFFFFFFFFFF + + def array_to_string( array: "objects.Array", count: Optional[int] = None, errors: str = "replace" ) -> interfaces.objects.ObjectInterface: diff --git a/volatility3/framework/plugins/windows/timers.py b/volatility3/framework/plugins/windows/timers.py new file mode 100644 index 000000000..19d52d9bf --- /dev/null +++ b/volatility3/framework/plugins/windows/timers.py @@ -0,0 +1,264 @@ +# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging + +from typing import Iterator, List, Tuple, Iterable + +from volatility3.framework import exceptions, layers, renderers, interfaces, constants, symbols +from volatility3.framework.configuration import requirements +from volatility3.framework.objects import utility +from volatility3.framework.renderers import format_hints +from volatility3.framework.symbols.windows import versions +from volatility3.plugins.windows import ssdt + +vollog = logging.getLogger(__name__) + + +class Timers(interfaces.plugins.PluginInterface): + """Print kernel timers and associated module DPCs""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Windows kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.PluginRequirement( + name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0) + ), + ] + + @classmethod + def get_kernel_module( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str, + ): + """Returns the kernel module based on the layer and symbol_table""" + virtual_layer = context.layers[layer_name] + if not isinstance(virtual_layer, layers.intel.Intel): + raise TypeError("Virtual Layer is not an intel layer") + + kvo = virtual_layer.config["kernel_virtual_offset"] + + ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) + return ntkrnlmp + + @classmethod + def get_kpcrs( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str, + ) -> interfaces.objects.ObjectInterface: + """Returns the KPCR structure for each processor + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + symbol_table: The name of an existing symbol table containing the kernel symbols + config_path: The configuration path within the context of the symbol table to create + + Returns: + The _KPCR structure for each processor + """ + + ntkrnlmp = cls.get_kernel_module(context, layer_name, symbol_table) + cpu_count_offset = ntkrnlmp.get_symbol("KeNumberProcessors").address + cpu_count = ntkrnlmp.object( + object_type="unsigned int", layer_name=layer_name, offset=cpu_count_offset + ) + processor_block = ntkrnlmp.object( + object_type="pointer", + layer_name=layer_name, + offset=ntkrnlmp.get_symbol("KiProcessorBlock").address, + ) + processor_pointers = utility.array_of_pointers( + context=context, + array=processor_block, + count=cpu_count, + subtype=symbol_table + constants.BANG + "_KPRCB", + ) + for pointer in processor_pointers: + kprcb = pointer.dereference() + reloff = ntkrnlmp.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 + + @classmethod + def list_timers( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str, + ) -> Iterable[Tuple[str, int, str]]: + """Lists all kernel timers. + + 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 + + Yields: + A _KTIMER entry + """ + ntkrnlmp = cls.get_kernel_module(context, layer_name, symbol_table) + + if versions.is_windows_7( + context=context, symbol_table=symbol_table + ) or versions.is_windows_8_or_later( + context=context, symbol_table=symbol_table + ): + # Starting with Windows 7, there is no more KiTimerTableListHead. The list is + # at _KPCR.PrcbData.TimerTable.TimerEntries + # See http://pastebin.com/FiRsGW3f + for kpcr in cls.get_kpcrs(context, layer_name, symbol_table): + if hasattr(kpcr.Prcb.TimerTable, "TableState"): + for timer_entries in kpcr.Prcb.TimerTable.TimerEntries: + for timer_entry in timer_entries: + for timer in timer_entry.Entry.to_list( + symbol_table + constants.BANG + "_KTIMER", + "TimerListEntry", + ): + yield timer + + else: + for timer_entries in kpcr.Prcb.TimerTable.TimerEntries: + for timer in timer_entries.Entry.to_list( + symbol_table + constants.BANG + "_KTIMER", + "TimerListEntry", + ): + yield timer + + elif versions.is_xp_or_2003( + context=context, symbol_table=symbol_table + ) or versions.is_vista_or_later( + context=context, symbol_table=symbol_table + ): + is_64bit = symbols.symbol_table_is_64bit(context, symbol_table) + if is_64bit or versions.is_vista_or_later(context=context, symbol_table=symbol_table): + # On XP x64, Windows 2003 SP1-SP2, and Vista SP0-SP2, KiTimerTableListHead + # is an array of 512 _KTIMER_TABLE_ENTRY structs. + array_size = 512 + else: + # On XP SP0-SP3 x86 and Windows 2003 SP0, KiTimerTableListHead + # is an array of 256 _LIST_ENTRY for _KTIMERs. + array_size = 256 + + timer_table_list_head = ntkrnlmp.object( + object_type="array", + offset=ntkrnlmp.get_symbol("KiTimerTableListHead").address, + subtype=ntkrnlmp.get_type("_LIST_ENTRY"), + count=array_size, + ) + for table in timer_table_list_head: + for timer in table.to_list( + symbol_table + constants.BANG + "_KTIMER", + "TimerListEntry", + ): + yield timer + + else: + raise NotImplementedError("This version of Windows is not supported!") + + + def _generator(self) -> Iterator[Tuple]: + kernel = self.context.modules[self.config["kernel"]] + layer_name = kernel.layer_name + symbol_table = kernel.symbol_table_name + + collection = ssdt.SSDT.build_module_collection( + self.context, kernel.layer_name, kernel.symbol_table_name + ) + + for timer in self.list_timers(self.context, layer_name, symbol_table): + if not timer.valid_type(): + continue + try: + dpc = timer.get_dpc() + if dpc == 0: + continue + if dpc.DeferredRoutine == 0: + continue + deferred_routine = dpc.DeferredRoutine + except Exception as e: + continue + + module_symbols = list( + collection.get_module_symbols_by_absolute_location(deferred_routine) + ) + + if module_symbols: + for module_name, symbol_generator in module_symbols: + symbols_found = False + + # we might have multiple symbols pointing to the same location + for symbol in symbol_generator: + symbols_found = True + yield ( + 0, + ( + format_hints.Hex(timer.vol.offset), + timer.get_due_time(), + timer.Period, + timer.get_signaled(), + format_hints.Hex(deferred_routine), + module_name, + symbol.split(constants.BANG)[1], + ), + ) + + # no symbols, but we at least can report the module name + if not symbols_found: + yield ( + 0, + ( + format_hints.Hex(timer.vol.offset), + timer.get_due_time(), + timer.Period, + timer.get_signaled(), + format_hints.Hex(deferred_routine), + module_name, + renderers.NotAvailableValue(), + ), + ) + else: + # no module was found at the absolute location + yield ( + 0, + ( + format_hints.Hex(timer.vol.offset), + timer.get_due_time(), + timer.Period, + timer.get_signaled(), + format_hints.Hex(deferred_routine), + renderers.NotAvailableValue(), + renderers.NotAvailableValue(), + ), + ) + + + def run(self): + return renderers.TreeGrid( + [ + ("Offset", format_hints.Hex), + ("DueTime", str), + ("Period(ms)", int), + ("Signaled", str), + ("Routine", format_hints.Hex), + ("Module", str), + ("Symbol", str), + ], + self._generator(), + ) diff --git a/volatility3/framework/symbols/windows/__init__.py b/volatility3/framework/symbols/windows/__init__.py index abf9f6da3..4aeb22dcf 100755 --- a/volatility3/framework/symbols/windows/__init__.py +++ b/volatility3/framework/symbols/windows/__init__.py @@ -39,6 +39,7 @@ class WindowsKernelIntermedSymbols(intermed.IntermediateSymbolTable): self.set_type_class("_VACB", extensions.VACB) self.set_type_class("_POOL_TRACKER_BIG_PAGES", pool.POOL_TRACKER_BIG_PAGES) self.set_type_class("_IMAGE_DOS_HEADER", pe.IMAGE_DOS_HEADER) + self.set_type_class("_KTIMER", extensions.KTIMER) # Might not necessarily defined in every version of windows self.optional_set_type_class("_IMAGE_NT_HEADERS", pe.IMAGE_NT_HEADERS) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index a8fa7b2ff..040c57bae 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -20,6 +20,7 @@ from volatility3.framework import ( ) from volatility3.framework.interfaces.objects import ObjectInterface from volatility3.framework.layers import intel +from volatility3.framework.objects import utility from volatility3.framework.renderers import conversion from volatility3.framework.symbols import generic from volatility3.framework.symbols.windows.extensions import kdbg, pe, pool @@ -994,6 +995,85 @@ class TOKEN(objects.StructType): vollog.log(constants.LOGLEVEL_VVVV, "Broken Token Privileges.") +class KTIMER(objects.StructType): + """A class for Kernel Timers""" + + VALID_TYPES = { + 8: "TimerNotificationObject", + 9: "TimerSynchronizationObject", + } + + def get_signaled(self): + if self.Header.SignalState: + return "Yes" + return "-" + + def get_raw_dpc(self): + """Returns the encoded DPC since it may not look like a pointer after encoding""" + symbol_table_name = self.get_symbol_table_name() + ulonglong_type = self._context.symbol_space.get_type( + symbol_table_name + constants.BANG + "unsigned long long" + ) + + return self._context.object( + object_type=ulonglong_type, + layer_name=self.vol.layer_name, + offset=self.Dpc.vol.offset, + ) + def valid_type(self): + return self.Header.Type in self.VALID_TYPES + + def get_due_time(self): + return "{0:#010x}:{1:#010x}".format(self.DueTime.HighPart, self.DueTime.LowPart) + + def get_dpc(self): + """Return Dpc, and if Windows 7 or later, decode it""" + symbol_table_name = self.get_symbol_table_name() + kvo = self._context.layers[self.vol.native_layer_name].config[ + "kernel_virtual_offset" + ] + ntkrnlmp = self._context.module( + symbol_table_name, + layer_name=self.vol.native_layer_name, + offset=kvo, + native_layer_name=self.vol.native_layer_name, + ) + + try: + wait_never = ntkrnlmp.object( + object_type="unsigned long long", + offset=ntkrnlmp.get_symbol("KiWaitNever").address, + ) + + wait_always = ntkrnlmp.object( + object_type="unsigned long long", + offset=ntkrnlmp.get_symbol("KiWaitAlways").address, + ) + except exceptions.SymbolError: + wait_never = None + wait_always = None + + if wait_never is None or wait_always is None: + return self.Dpc + else: + low_byte = (wait_never) & 0xFF + entry = utility.rol(self.get_raw_dpc() ^ wait_never, low_byte) + swap_xor = self.vol.offset | 0xFFFF000000000000 + entry = utility.bswap_64(entry ^ swap_xor) + dpc = entry ^ wait_always + + symbol_table_name = self.get_symbol_table_name() + kdpc_type = self._context.symbol_space.get_type( + symbol_table_name + constants.BANG + "_KDPC" + ) + + return self._context.object( + object_type=kdpc_type, + layer_name=self.vol.layer_name, + offset=dpc, + ) + + class KTHREAD(objects.StructType): """A class for thread control block objects.""" From c79dc52a854d2a5be41283a720081f63471c0df8 Mon Sep 17 00:00:00 2001 From: Dave Lassalle Date: Fri, 21 Jun 2024 15:09:21 -0500 Subject: [PATCH 20/85] #118 - black formatted --- .../framework/plugins/windows/timers.py | 23 +++++++++++-------- .../symbols/windows/extensions/__init__.py | 7 +++--- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/volatility3/framework/plugins/windows/timers.py b/volatility3/framework/plugins/windows/timers.py index 19d52d9bf..e54b2a13f 100644 --- a/volatility3/framework/plugins/windows/timers.py +++ b/volatility3/framework/plugins/windows/timers.py @@ -6,7 +6,14 @@ import logging from typing import Iterator, List, Tuple, Iterable -from volatility3.framework import exceptions, layers, renderers, interfaces, constants, symbols +from volatility3.framework import ( + exceptions, + layers, + renderers, + interfaces, + constants, + symbols, +) from volatility3.framework.configuration import requirements from volatility3.framework.objects import utility from volatility3.framework.renderers import format_hints @@ -117,9 +124,7 @@ class Timers(interfaces.plugins.PluginInterface): if versions.is_windows_7( context=context, symbol_table=symbol_table - ) or versions.is_windows_8_or_later( - context=context, symbol_table=symbol_table - ): + ) or versions.is_windows_8_or_later(context=context, symbol_table=symbol_table): # Starting with Windows 7, there is no more KiTimerTableListHead. The list is # at _KPCR.PrcbData.TimerTable.TimerEntries # See http://pastebin.com/FiRsGW3f @@ -143,11 +148,11 @@ class Timers(interfaces.plugins.PluginInterface): elif versions.is_xp_or_2003( context=context, symbol_table=symbol_table - ) or versions.is_vista_or_later( - context=context, symbol_table=symbol_table - ): + ) or versions.is_vista_or_later(context=context, symbol_table=symbol_table): is_64bit = symbols.symbol_table_is_64bit(context, symbol_table) - if is_64bit or versions.is_vista_or_later(context=context, symbol_table=symbol_table): + if is_64bit or versions.is_vista_or_later( + context=context, symbol_table=symbol_table + ): # On XP x64, Windows 2003 SP1-SP2, and Vista SP0-SP2, KiTimerTableListHead # is an array of 512 _KTIMER_TABLE_ENTRY structs. array_size = 512 @@ -172,7 +177,6 @@ class Timers(interfaces.plugins.PluginInterface): else: raise NotImplementedError("This version of Windows is not supported!") - def _generator(self) -> Iterator[Tuple]: kernel = self.context.modules[self.config["kernel"]] layer_name = kernel.layer_name @@ -248,7 +252,6 @@ class Timers(interfaces.plugins.PluginInterface): ), ) - def run(self): return renderers.TreeGrid( [ diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 040c57bae..b830a9d9f 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -453,9 +453,9 @@ class FILE_OBJECT(objects.StructType, pool.ExecutiveObject): ].is_valid(self.FileName.Buffer) def file_name_with_device(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: - name: Union[str, interfaces.renderers.BaseAbsentValue] = ( - renderers.UnreadableValue() - ) + name: Union[ + str, interfaces.renderers.BaseAbsentValue + ] = renderers.UnreadableValue() # this pointer needs to be checked against native_layer_name because the object may # be instantiated from a primary (virtual) layer or a memory (physical) layer. @@ -1020,6 +1020,7 @@ class KTIMER(objects.StructType): layer_name=self.vol.layer_name, offset=self.Dpc.vol.offset, ) + def valid_type(self): return self.Header.Type in self.VALID_TYPES From 7065446e8ab9efb02254e4415235072d30422596 Mon Sep 17 00:00:00 2001 From: Dave Lassalle Date: Fri, 21 Jun 2024 15:17:40 -0500 Subject: [PATCH 21/85] #118 - fix black issues --- volatility3/framework/plugins/windows/timers.py | 1 - volatility3/framework/symbols/windows/extensions/__init__.py | 1 - 2 files changed, 2 deletions(-) diff --git a/volatility3/framework/plugins/windows/timers.py b/volatility3/framework/plugins/windows/timers.py index e54b2a13f..ec8bc17e9 100644 --- a/volatility3/framework/plugins/windows/timers.py +++ b/volatility3/framework/plugins/windows/timers.py @@ -7,7 +7,6 @@ import logging from typing import Iterator, List, Tuple, Iterable from volatility3.framework import ( - exceptions, layers, renderers, interfaces, diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index b830a9d9f..895a8c094 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -1045,7 +1045,6 @@ class KTIMER(objects.StructType): object_type="unsigned long long", offset=ntkrnlmp.get_symbol("KiWaitNever").address, ) - wait_always = ntkrnlmp.object( object_type="unsigned long long", offset=ntkrnlmp.get_symbol("KiWaitAlways").address, From 2d7789f8509664f3930705bf83524d6b5ec8de33 Mon Sep 17 00:00:00 2001 From: Dave Lassalle Date: Fri, 21 Jun 2024 15:49:03 -0500 Subject: [PATCH 22/85] #118 - fix black issues --- .../framework/symbols/windows/extensions/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 895a8c094..4d4ffc055 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -453,9 +453,9 @@ class FILE_OBJECT(objects.StructType, pool.ExecutiveObject): ].is_valid(self.FileName.Buffer) def file_name_with_device(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: - name: Union[ - str, interfaces.renderers.BaseAbsentValue - ] = renderers.UnreadableValue() + name: Union[str, interfaces.renderers.BaseAbsentValue] = ( + renderers.UnreadableValue() + ) # this pointer needs to be checked against native_layer_name because the object may # be instantiated from a primary (virtual) layer or a memory (physical) layer. From 146baab14c4a17bdcbc35715b30a4d190ed82214 Mon Sep 17 00:00:00 2001 From: Dave Lassalle Date: Fri, 21 Jun 2024 15:54:16 -0500 Subject: [PATCH 23/85] #118 - refactor get_dpc --- .../framework/symbols/windows/extensions/__init__.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 4d4ffc055..b9eabcda6 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -1040,7 +1040,7 @@ class KTIMER(objects.StructType): native_layer_name=self.vol.native_layer_name, ) - try: + if ntkrnlmp.has_symbol("KiWaitNever") and ntkrnlmp.has_symbol("KiWaitAlways"): wait_never = ntkrnlmp.object( object_type="unsigned long long", offset=ntkrnlmp.get_symbol("KiWaitNever").address, @@ -1049,13 +1049,7 @@ class KTIMER(objects.StructType): object_type="unsigned long long", offset=ntkrnlmp.get_symbol("KiWaitAlways").address, ) - except exceptions.SymbolError: - wait_never = None - wait_always = None - if wait_never is None or wait_always is None: - return self.Dpc - else: low_byte = (wait_never) & 0xFF entry = utility.rol(self.get_raw_dpc() ^ wait_never, low_byte) swap_xor = self.vol.offset | 0xFFFF000000000000 @@ -1072,6 +1066,8 @@ class KTIMER(objects.StructType): layer_name=self.vol.layer_name, offset=dpc, ) + else: + return self.Dpc class KTHREAD(objects.StructType): From 8c1a5c46e32c8ffaacd6507ec036cbd07c9072b7 Mon Sep 17 00:00:00 2001 From: atcuno Date: Thu, 27 Jun 2024 17:13:37 -0500 Subject: [PATCH 24/85] Add timeliner support to userassist --- .../plugins/windows/registry/userassist.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/windows/registry/userassist.py b/volatility3/framework/plugins/windows/registry/userassist.py index 70c75b50b..54ec2fc74 100644 --- a/volatility3/framework/plugins/windows/registry/userassist.py +++ b/volatility3/framework/plugins/windows/registry/userassist.py @@ -17,11 +17,12 @@ from volatility3.framework.layers.registry import RegistryHive from volatility3.framework.renderers import conversion, format_hints from volatility3.framework.symbols import intermed from volatility3.plugins.windows.registry import hivelist +from volatility3.plugins import timeliner vollog = logging.getLogger(__name__) -class UserAssist(interfaces.plugins.PluginInterface): +class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): """Print userassist registry keys and information.""" _required_framework_version = (2, 0, 0) @@ -335,6 +336,19 @@ class UserAssist(interfaces.plugins.PluginInterface): ) yield result + + def generate_timeline(self): + self._reg_table_name = intermed.IntermediateSymbolTable.create( + self.context, self._config_path, "windows", "registry" + ) + + for row in self._generator(): + _depth, row_data = row + # check the name and the timestamp to not be empty + if isinstance(row_data[5], str) and not isinstance(row_data[10], renderers.NotApplicableValue): + description = f"UserAssist: {row_data[5]} {row_data[2]} ({row_data[7]})" + yield (description, timeliner.TimeLinerType.MODIFIED, row_data[10]) + def run(self): self._reg_table_name = intermed.IntermediateSymbolTable.create( self.context, self._config_path, "windows", "registry" From 8525edd3331c6ccd967c8048d61adf5d2c3e1015 Mon Sep 17 00:00:00 2001 From: atcuno Date: Thu, 27 Jun 2024 17:15:49 -0500 Subject: [PATCH 25/85] Formatting fixes --- volatility3/framework/plugins/windows/registry/userassist.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/windows/registry/userassist.py b/volatility3/framework/plugins/windows/registry/userassist.py index 54ec2fc74..cf345c901 100644 --- a/volatility3/framework/plugins/windows/registry/userassist.py +++ b/volatility3/framework/plugins/windows/registry/userassist.py @@ -336,7 +336,6 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac ) yield result - def generate_timeline(self): self._reg_table_name = intermed.IntermediateSymbolTable.create( self.context, self._config_path, "windows", "registry" @@ -345,7 +344,9 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac for row in self._generator(): _depth, row_data = row # check the name and the timestamp to not be empty - if isinstance(row_data[5], str) and not isinstance(row_data[10], renderers.NotApplicableValue): + if isinstance(row_data[5], str) and not isinstance( + row_data[10], renderers.NotApplicableValue + ): description = f"UserAssist: {row_data[5]} {row_data[2]} ({row_data[7]})" yield (description, timeliner.TimeLinerType.MODIFIED, row_data[10]) From c3cf319f7e44161ef6fd6537787de70daf71d126 Mon Sep 17 00:00:00 2001 From: Eve Date: Fri, 12 Jul 2024 09:13:08 +0100 Subject: [PATCH 26/85] Windows: remove size from filescan output as it is not the file size but the object size --- volatility3/framework/plugins/windows/filescan.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/windows/filescan.py b/volatility3/framework/plugins/windows/filescan.py index 0f68f39d4..e21b5f518 100644 --- a/volatility3/framework/plugins/windows/filescan.py +++ b/volatility3/framework/plugins/windows/filescan.py @@ -13,7 +13,7 @@ from volatility3.plugins.windows import poolscanner class FileScan(interfaces.plugins.PluginInterface): """Scans for file objects present in a particular windows memory image.""" - _required_framework_version = (2, 0, 0) + _required_framework_version = (2, 0, 1) @classmethod def get_requirements(cls): @@ -67,10 +67,10 @@ class FileScan(interfaces.plugins.PluginInterface): except exceptions.InvalidAddressException: continue - yield (0, (format_hints.Hex(fileobj.vol.offset), file_name, fileobj.Size)) + yield (0, (format_hints.Hex(fileobj.vol.offset), file_name)) def run(self): return renderers.TreeGrid( - [("Offset", format_hints.Hex), ("Name", str), ("Size", int)], + [("Offset", format_hints.Hex), ("Name", str)], self._generator(), ) From e5a530f08acc198939e3fa90616fb12062da34e7 Mon Sep 17 00:00:00 2001 From: Eve Date: Fri, 12 Jul 2024 10:20:44 +0100 Subject: [PATCH 27/85] Windows: revert required framework version for filescan plugin --- volatility3/framework/plugins/windows/filescan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/windows/filescan.py b/volatility3/framework/plugins/windows/filescan.py index e21b5f518..34c0c60d3 100644 --- a/volatility3/framework/plugins/windows/filescan.py +++ b/volatility3/framework/plugins/windows/filescan.py @@ -13,7 +13,7 @@ from volatility3.plugins.windows import poolscanner class FileScan(interfaces.plugins.PluginInterface): """Scans for file objects present in a particular windows memory image.""" - _required_framework_version = (2, 0, 1) + _required_framework_version = (2, 0, 0) @classmethod def get_requirements(cls): From ac9236cb436d40a156f94b7aeab63afcc545663d Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 15 Jul 2024 19:59:41 +1000 Subject: [PATCH 28/85] linux.netfilter.Netfilter: Add LinuxUtilities version requirement check on AbstractNetfilter --- .../framework/plugins/linux/netfilter.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index 60a71f798..e0fbcbd70 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -11,6 +11,7 @@ from volatility3.framework import ( constants, interfaces, renderers, + exceptions, ) from volatility3.framework.renderers import format_hints from volatility3.framework.configuration import requirements @@ -80,6 +81,16 @@ class AbstractNetfilter(ABC): self.list_head_size = self.vmlinux.get_type("list_head").size modules = lsmod.Lsmod.list_modules(context, kernel_module_name) + + linuxutils_required_version = Netfilter._required_linuxutils_version + linuxutils_current_version = linux.LinuxUtilities._version + if not requirements.VersionRequirement.matches_required( + linuxutils_required_version, linuxutils_current_version + ): + raise exceptions.PluginRequirementException( + f"linux.LinuxUtilities version not suitable: required {linuxutils_required_version} found {linuxutils_current_version}" + ) + self.handlers = linux.LinuxUtilities.generate_kernel_handler_info( context, kernel_module_name, modules ) @@ -658,6 +669,8 @@ class Netfilter(interfaces.plugins.PluginInterface): _version = (1, 0, 0) + _required_linuxutils_version = (2, 1, 0) + @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: return [ @@ -670,7 +683,9 @@ class Netfilter(interfaces.plugins.PluginInterface): name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) ), requirements.VersionRequirement( - name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0) + name="linuxutils", + component=linux.LinuxUtilities, + version=cls._required_linuxutils_version, ), ] From cb6929163b31726d811f7e825bcfad8690170b9c Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Mon, 15 Jul 2024 18:14:50 +0100 Subject: [PATCH 29/85] Windows: Fix vadyarascan sanity check and bad documentation --- volatility3/framework/plugins/windows/vadyarascan.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/plugins/windows/vadyarascan.py b/volatility3/framework/plugins/windows/vadyarascan.py index 4b00fee57..219faf028 100644 --- a/volatility3/framework/plugins/windows/vadyarascan.py +++ b/volatility3/framework/plugins/windows/vadyarascan.py @@ -56,7 +56,7 @@ class VadYaraScan(interfaces.plugins.PluginInterface): filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) - sanity_check = 0x1000 * 0x1000 * 0x1000 + sanity_check = 0x2000 * 0x1000 * 0x1000 for task in pslist.PsList.list_processes( context=self.context, @@ -66,15 +66,14 @@ class VadYaraScan(interfaces.plugins.PluginInterface): ): layer_name = task.add_process_layer() layer = self.context.layers[layer_name] - for start, end in self.get_vad_maps(task): - size = end - start + for start, size in self.get_vad_maps(task): if size > sanity_check: vollog.warn( f"VAD at 0x{start:x} over sanity-check size, not scanning" ) continue - for match in rules.match(data=layer.read(start, end - start, True)): + for match in rules.match(data=layer.read(start, size, True)): if yarascan.YaraScan.yara_returns_instances(): for match_string in match.strings: for instance in match_string.instances: @@ -106,7 +105,7 @@ class VadYaraScan(interfaces.plugins.PluginInterface): task: The EPROCESS object of which to traverse the vad tree Returns: - An iterable of tuples containing start and end addresses for each descriptor + An iterable of tuples containing start and size for each descriptor """ vad_root = task.get_vad_root() for vad in vad_root.traverse(): From 55fe4ba47aece0882a0b5c690710cba1fa438989 Mon Sep 17 00:00:00 2001 From: Dave Lassalle Date: Mon, 15 Jul 2024 15:20:10 -0500 Subject: [PATCH 30/85] #118 - MR feedback --- .../framework/plugins/windows/kpcrs.py | 106 ++++++++++++++++++ .../framework/plugins/windows/timers.py | 86 +++----------- .../symbols/windows/extensions/__init__.py | 6 +- 3 files changed, 125 insertions(+), 73 deletions(-) create mode 100644 volatility3/framework/plugins/windows/kpcrs.py diff --git a/volatility3/framework/plugins/windows/kpcrs.py b/volatility3/framework/plugins/windows/kpcrs.py new file mode 100644 index 000000000..558ea844c --- /dev/null +++ b/volatility3/framework/plugins/windows/kpcrs.py @@ -0,0 +1,106 @@ +# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging + +from typing import Iterator, List, Tuple + +from volatility3.framework import ( + renderers, + interfaces, + constants, +) +from volatility3.framework.configuration import requirements +from volatility3.framework.objects import utility +from volatility3.framework.renderers import format_hints + +vollog = logging.getLogger(__name__) + + +class KPCRs(interfaces.plugins.PluginInterface): + """Print KPCR structure for each processor""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Windows kernel", + architectures=["Intel32", "Intel64"], + ), + ] + + @classmethod + def list_kpcrs( + cls, + context: interfaces.context.ContextInterface, + kernel_module_name: str, + layer_name: str, + symbol_table: str, + ) -> interfaces.objects.ObjectInterface: + """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] + cpu_count_offset = kernel.get_symbol("KeNumberProcessors").address + cpu_count = kernel.object( + object_type="unsigned int", layer_name=layer_name, offset=cpu_count_offset + ) + processor_block = kernel.object( + object_type="pointer", + layer_name=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", + ) + 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 + + 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 + ): + yield ( + 0, + ( + format_hints.Hex(kpcr.vol.offset), + format_hints.Hex(kpcr.CurrentPrcb), + ), + ) + + def run(self): + return renderers.TreeGrid( + [ + ("Offset", format_hints.Hex), + ("PRCB Offset", format_hints.Hex), + ], + self._generator(), + ) diff --git a/volatility3/framework/plugins/windows/timers.py b/volatility3/framework/plugins/windows/timers.py index ec8bc17e9..d49c28784 100644 --- a/volatility3/framework/plugins/windows/timers.py +++ b/volatility3/framework/plugins/windows/timers.py @@ -7,17 +7,15 @@ import logging from typing import Iterator, List, Tuple, Iterable from volatility3.framework import ( - layers, renderers, interfaces, constants, symbols, ) from volatility3.framework.configuration import requirements -from volatility3.framework.objects import utility from volatility3.framework.renderers import format_hints from volatility3.framework.symbols.windows import versions -from volatility3.plugins.windows import ssdt +from volatility3.plugins.windows import ssdt, kpcrs vollog = logging.getLogger(__name__) @@ -39,73 +37,16 @@ class Timers(interfaces.plugins.PluginInterface): requirements.PluginRequirement( name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0) ), + requirements.PluginRequirement( + name="kpcrs", plugin=kpcrs.KPCRs, version=(1, 0, 0) + ), ] - @classmethod - def get_kernel_module( - cls, - context: interfaces.context.ContextInterface, - layer_name: str, - symbol_table: str, - ): - """Returns the kernel module based on the layer and symbol_table""" - virtual_layer = context.layers[layer_name] - if not isinstance(virtual_layer, layers.intel.Intel): - raise TypeError("Virtual Layer is not an intel layer") - - kvo = virtual_layer.config["kernel_virtual_offset"] - - ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) - return ntkrnlmp - - @classmethod - def get_kpcrs( - cls, - context: interfaces.context.ContextInterface, - layer_name: str, - symbol_table: str, - ) -> interfaces.objects.ObjectInterface: - """Returns the KPCR structure for each processor - - Args: - context: The context to retrieve required elements (layers, symbol tables) from - symbol_table: The name of an existing symbol table containing the kernel symbols - config_path: The configuration path within the context of the symbol table to create - - Returns: - The _KPCR structure for each processor - """ - - ntkrnlmp = cls.get_kernel_module(context, layer_name, symbol_table) - cpu_count_offset = ntkrnlmp.get_symbol("KeNumberProcessors").address - cpu_count = ntkrnlmp.object( - object_type="unsigned int", layer_name=layer_name, offset=cpu_count_offset - ) - processor_block = ntkrnlmp.object( - object_type="pointer", - layer_name=layer_name, - offset=ntkrnlmp.get_symbol("KiProcessorBlock").address, - ) - processor_pointers = utility.array_of_pointers( - context=context, - array=processor_block, - count=cpu_count, - subtype=symbol_table + constants.BANG + "_KPRCB", - ) - for pointer in processor_pointers: - kprcb = pointer.dereference() - reloff = ntkrnlmp.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 - @classmethod def list_timers( cls, context: interfaces.context.ContextInterface, + kernel_module_name: str, layer_name: str, symbol_table: str, ) -> Iterable[Tuple[str, int, str]]: @@ -113,21 +54,24 @@ class Timers(interfaces.plugins.PluginInterface): 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 Yields: A _KTIMER entry """ - ntkrnlmp = cls.get_kernel_module(context, layer_name, symbol_table) + kernel = context.modules[kernel_module_name] if versions.is_windows_7( context=context, symbol_table=symbol_table ) or versions.is_windows_8_or_later(context=context, symbol_table=symbol_table): # Starting with Windows 7, there is no more KiTimerTableListHead. The list is # at _KPCR.PrcbData.TimerTable.TimerEntries # See http://pastebin.com/FiRsGW3f - for kpcr in cls.get_kpcrs(context, layer_name, symbol_table): + for kpcr in kpcrs.KPCRs.list_kpcrs( + context, kernel_module_name, layer_name, symbol_table + ): if hasattr(kpcr.Prcb.TimerTable, "TableState"): for timer_entries in kpcr.Prcb.TimerTable.TimerEntries: for timer_entry in timer_entries: @@ -160,10 +104,10 @@ class Timers(interfaces.plugins.PluginInterface): # is an array of 256 _LIST_ENTRY for _KTIMERs. array_size = 256 - timer_table_list_head = ntkrnlmp.object( + timer_table_list_head = kernel.object( object_type="array", - offset=ntkrnlmp.get_symbol("KiTimerTableListHead").address, - subtype=ntkrnlmp.get_type("_LIST_ENTRY"), + offset=kernel.get_symbol("KiTimerTableListHead").address, + subtype=kernel.get_type("_LIST_ENTRY"), count=array_size, ) for table in timer_table_list_head: @@ -185,7 +129,9 @@ class Timers(interfaces.plugins.PluginInterface): self.context, kernel.layer_name, kernel.symbol_table_name ) - for timer in self.list_timers(self.context, layer_name, symbol_table): + for timer in self.list_timers( + self.context, self.config["kernel"], layer_name, symbol_table + ): if not timer.valid_type(): continue try: diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index b9eabcda6..86ea2febb 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -1011,12 +1011,12 @@ class KTIMER(objects.StructType): def get_raw_dpc(self): """Returns the encoded DPC since it may not look like a pointer after encoding""" symbol_table_name = self.get_symbol_table_name() - ulonglong_type = self._context.symbol_space.get_type( - symbol_table_name + constants.BANG + "unsigned long long" + pointer_type = self._context.symbol_space.get_type( + symbol_table_name + constants.BANG + "pointer" ) return self._context.object( - object_type=ulonglong_type, + object_type=pointer_type, layer_name=self.vol.layer_name, offset=self.Dpc.vol.offset, ) From 7e3615335c44f79b2b39e9f669a33df6f3edf2fc Mon Sep 17 00:00:00 2001 From: Dave Lassalle Date: Mon, 15 Jul 2024 15:40:22 -0500 Subject: [PATCH 31/85] #1175 - config_path change --- volatility3/framework/plugins/windows/unloadedmodules.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/windows/unloadedmodules.py b/volatility3/framework/plugins/windows/unloadedmodules.py index 1fd4914ac..0d88e96ff 100644 --- a/volatility3/framework/plugins/windows/unloadedmodules.py +++ b/volatility3/framework/plugins/windows/unloadedmodules.py @@ -10,6 +10,7 @@ from volatility3.framework import constants from volatility3.framework import interfaces, symbols from volatility3.framework import renderers from volatility3.framework.configuration import requirements +from volatility3.framework.interfaces import configuration from volatility3.framework.renderers import format_hints, conversion from volatility3.framework.symbols import intermed from volatility3.plugins import timeliner @@ -60,7 +61,7 @@ class UnloadedModules(interfaces.plugins.PluginInterface, timeliner.TimeLinerInt return intermed.IntermediateSymbolTable.create( context, - config_path, + configuration.path_join(config_path, "unloadedmodules"), "windows", symbol_filename, native_types=native_types, From 99cf48597abbc1126ef06731ed7836c04614514d Mon Sep 17 00:00:00 2001 From: Dave Lassalle Date: Mon, 15 Jul 2024 15:46:27 -0500 Subject: [PATCH 32/85] #118 - use canonicalize for offset --- volatility3/framework/symbols/windows/extensions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 86ea2febb..d5c3d3f96 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -1052,7 +1052,7 @@ class KTIMER(objects.StructType): low_byte = (wait_never) & 0xFF entry = utility.rol(self.get_raw_dpc() ^ wait_never, low_byte) - swap_xor = self.vol.offset | 0xFFFF000000000000 + swap_xor = self._context.layers[self.vol.native_layer_name].canonicalize(self.vol.offset) entry = utility.bswap_64(entry ^ swap_xor) dpc = entry ^ wait_always From cf6c7fb1f40c4bfa1c60cc050cfd346014a854ca Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 16 Jul 2024 09:38:49 +1000 Subject: [PATCH 33/85] linux.netfilter.Netfilter: Add lsmod version requirement check on AbstractNetfilter --- volatility3/framework/plugins/linux/netfilter.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index e0fbcbd70..d392a8371 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -80,7 +80,14 @@ class AbstractNetfilter(ABC): self.ptr_size = self.vmlinux.get_type("pointer").size self.list_head_size = self.vmlinux.get_type("list_head").size - modules = lsmod.Lsmod.list_modules(context, kernel_module_name) + lsmod_required_version = Netfilter._required_lsmod_version + lsmod_current_version = lsmod.Lsmod._version + if not requirements.VersionRequirement.matches_required( + lsmod_required_version, lsmod_current_version + ): + raise exceptions.PluginRequirementException( + f"linux.lsmod.Lsmod version not suitable: required {lsmod_required_version} found {lsmod_current_version}" + ) linuxutils_required_version = Netfilter._required_linuxutils_version linuxutils_current_version = linux.LinuxUtilities._version @@ -91,6 +98,7 @@ class AbstractNetfilter(ABC): f"linux.LinuxUtilities version not suitable: required {linuxutils_required_version} found {linuxutils_current_version}" ) + modules = lsmod.Lsmod.list_modules(context, kernel_module_name) self.handlers = linux.LinuxUtilities.generate_kernel_handler_info( context, kernel_module_name, modules ) @@ -670,6 +678,7 @@ class Netfilter(interfaces.plugins.PluginInterface): _version = (1, 0, 0) _required_linuxutils_version = (2, 1, 0) + _required_lsmod_version = (2, 0, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -680,7 +689,7 @@ class Netfilter(interfaces.plugins.PluginInterface): architectures=["Intel32", "Intel64"], ), requirements.PluginRequirement( - name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + name="lsmod", plugin=lsmod.Lsmod, version=cls._required_lsmod_version ), requirements.VersionRequirement( name="linuxutils", From 43e22d72bdaa2cbb6366b1911a6ff38ced83394f Mon Sep 17 00:00:00 2001 From: Dave Lassalle Date: Tue, 16 Jul 2024 09:23:06 -0500 Subject: [PATCH 34/85] #118 - black formatting --- volatility3/framework/symbols/windows/extensions/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index d5c3d3f96..b333755f7 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -1052,7 +1052,9 @@ class KTIMER(objects.StructType): low_byte = (wait_never) & 0xFF entry = utility.rol(self.get_raw_dpc() ^ wait_never, low_byte) - swap_xor = self._context.layers[self.vol.native_layer_name].canonicalize(self.vol.offset) + swap_xor = self._context.layers[self.vol.native_layer_name].canonicalize( + self.vol.offset + ) entry = utility.bswap_64(entry ^ swap_xor) dpc = entry ^ wait_always From be5423f786827ee4056b2c638b2d1214a419c47c Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Wed, 17 Jul 2024 17:08:15 +0100 Subject: [PATCH 35/85] Windows: Fix up broken imports in info plugin --- volatility3/framework/plugins/windows/info.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/plugins/windows/info.py b/volatility3/framework/plugins/windows/info.py index 100a677c2..137d29c22 100644 --- a/volatility3/framework/plugins/windows/info.py +++ b/volatility3/framework/plugins/windows/info.py @@ -10,7 +10,7 @@ from volatility3.framework.configuration import requirements from volatility3.framework.interfaces import plugins from volatility3.framework.renderers import TreeGrid from volatility3.framework.symbols import intermed -from volatility3.framework.symbols.windows import extensions +from volatility3.framework.symbols.windows.extensions import kdbg, pe class Info(plugins.PluginInterface): @@ -94,16 +94,16 @@ class Info(plugins.PluginInterface): "windows", "kdbg", native_types=native_types, - class_types=extensions.kdbg.class_types, + class_types=kdbg.class_types, ) - kdbg = context.object( + kdbg_obj = context.object( kdbg_table_name + constants.BANG + "_KDDEBUGGER_DATA64", offset=ntkrnlmp.offset + kdbg_offset, layer_name=layer_name, ) - return kdbg + return kdbg_obj @classmethod def get_kuser_structure( @@ -173,7 +173,7 @@ class Info(plugins.PluginInterface): interfaces.configuration.path_join(config_path, "pe"), "windows", "pe", - class_types=extensions.pe.class_types, + class_types=pe.class_types, ) dos_header = context.object( From a5cf635fd8b81c56f14ee490bcf63e4e5aafd32d Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Wed, 17 Jul 2024 17:31:44 +0100 Subject: [PATCH 36/85] Windows: Fix the vadyarascan sanity check --- volatility3/framework/plugins/windows/vadyarascan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/windows/vadyarascan.py b/volatility3/framework/plugins/windows/vadyarascan.py index 219faf028..dc318dd93 100644 --- a/volatility3/framework/plugins/windows/vadyarascan.py +++ b/volatility3/framework/plugins/windows/vadyarascan.py @@ -56,7 +56,7 @@ class VadYaraScan(interfaces.plugins.PluginInterface): filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) - sanity_check = 0x2000 * 0x1000 * 0x1000 + sanity_check = 1024 * 1024 * 1024 # 1 GB for task in pslist.PsList.list_processes( context=self.context, From a684e284ccca7675d4e3cd07c39679ecba703627 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Fri, 5 Jul 2024 15:45:46 -0500 Subject: [PATCH 37/85] Windows: Adds shimcache symbol files + extensions --- .../framework/symbols/windows/__init__.py | 1 + .../symbols/windows/extensions/__init__.py | 49 +- .../symbols/windows/extensions/shimcache.py | 278 ++++++++++ .../windows/shimcache/shimcache-2003-x64.json | 327 ++++++++++++ .../windows/shimcache/shimcache-2003-x86.json | 334 ++++++++++++ .../shimcache/shimcache-vista-x64.json | 334 ++++++++++++ .../shimcache/shimcache-vista-x86.json | 334 ++++++++++++ .../shimcache/shimcache-win10-x64.json | 371 ++++++++++++++ .../shimcache/shimcache-win10-x86.json | 371 ++++++++++++++ .../windows/shimcache/shimcache-win7-x64.json | 348 +++++++++++++ .../windows/shimcache/shimcache-win7-x86.json | 348 +++++++++++++ .../windows/shimcache/shimcache-win8-x64.json | 392 ++++++++++++++ .../windows/shimcache/shimcache-win8-x86.json | 386 ++++++++++++++ .../shimcache/shimcache-xp-sp2-x86.json | 485 ++++++++++++++++++ .../shimcache/shimcache-xp-sp3-x86.json | 485 ++++++++++++++++++ .../framework/symbols/windows/versions.py | 27 + 16 files changed, 4863 insertions(+), 7 deletions(-) create mode 100644 volatility3/framework/symbols/windows/extensions/shimcache.py create mode 100644 volatility3/framework/symbols/windows/shimcache/shimcache-2003-x64.json create mode 100644 volatility3/framework/symbols/windows/shimcache/shimcache-2003-x86.json create mode 100644 volatility3/framework/symbols/windows/shimcache/shimcache-vista-x64.json create mode 100644 volatility3/framework/symbols/windows/shimcache/shimcache-vista-x86.json create mode 100644 volatility3/framework/symbols/windows/shimcache/shimcache-win10-x64.json create mode 100644 volatility3/framework/symbols/windows/shimcache/shimcache-win10-x86.json create mode 100644 volatility3/framework/symbols/windows/shimcache/shimcache-win7-x64.json create mode 100644 volatility3/framework/symbols/windows/shimcache/shimcache-win7-x86.json create mode 100644 volatility3/framework/symbols/windows/shimcache/shimcache-win8-x64.json create mode 100644 volatility3/framework/symbols/windows/shimcache/shimcache-win8-x86.json create mode 100644 volatility3/framework/symbols/windows/shimcache/shimcache-xp-sp2-x86.json create mode 100644 volatility3/framework/symbols/windows/shimcache/shimcache-xp-sp3-x86.json diff --git a/volatility3/framework/symbols/windows/__init__.py b/volatility3/framework/symbols/windows/__init__.py index abf9f6da3..e43a2486b 100755 --- a/volatility3/framework/symbols/windows/__init__.py +++ b/volatility3/framework/symbols/windows/__init__.py @@ -17,6 +17,7 @@ class WindowsKernelIntermedSymbols(intermed.IntermediateSymbolTable): self.set_type_class("_KTHREAD", extensions.KTHREAD) self.set_type_class("_LIST_ENTRY", extensions.LIST_ENTRY) self.set_type_class("_EPROCESS", extensions.EPROCESS) + self.set_type_class("_ERESOURCE", extensions.ERESOURCE) self.set_type_class("_UNICODE_STRING", extensions.UNICODE_STRING) self.set_type_class("_EX_FAST_REF", extensions.EX_FAST_REF) self.set_type_class("_TOKEN", extensions.TOKEN) diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index 93c19599e..39b50e180 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -306,16 +306,19 @@ class MMVAD_SHORT(objects.StructType): raise AttributeError("Unable to find the private memory member") + @property + def Protection(self): + if self.has_member("u"): + return self.u.VadFlags.Protection + elif self.has_member("Core"): + return self.Core.u.VadFlags.Protection + else: + return None + def get_protection(self, protect_values, winnt_protections): """Get the VAD's protection constants as a string.""" - protect = None - - if self.has_member("u"): - protect = self.u.VadFlags.Protection - - elif self.has_member("Core"): - protect = self.Core.u.VadFlags.Protection + protect = self.Protection try: value = protect_values[protect] @@ -593,6 +596,38 @@ class UNICODE_STRING(objects.StructType): String = property(get_string) +class ERESOURCE(objects.StructType): + def is_valid(self) -> bool: + vollog.debug(f"Checking ERESOURCE Validity: {hex(self.vol.offset)}") + + if not self._context.layers[self.vol.layer_name].is_valid(self.vol.offset): + return False + + sym_table = self.get_symbol_table_name() + + waiters_valid = self.SharedWaiters == 0 or self._context.layers[ + self.vol.layer_name + ].is_valid( + self.SharedWaiters.vol.offset, + self._context.symbol_space.get_type( + sym_table + constants.BANG + "_KSEMAPHORE" + ).size, + ) + + try: + return ( + waiters_valid + and self.SystemResourcesList.Flink is not None + and self.SystemResourcesList.Blink is not None + and self.SystemResourcesList.Flink != self.SystemResourcesList.Blink + and self.SystemResourcesList.Flink.Blink == self.vol.offset + and self.SystemResourcesList.Blink.Flink == self.vol.offset + and self.NumberOfSharedWaiters == 0 + ) + except exceptions.InvalidAddressException: + return False + + class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): """A class for executive kernel processes objects.""" diff --git a/volatility3/framework/symbols/windows/extensions/shimcache.py b/volatility3/framework/symbols/windows/extensions/shimcache.py new file mode 100644 index 000000000..b84a7df6f --- /dev/null +++ b/volatility3/framework/symbols/windows/extensions/shimcache.py @@ -0,0 +1,278 @@ +# This file is Copyright 2019 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 +import struct +from datetime import datetime +from typing import Dict, Optional, Tuple, Union + +from volatility3.framework import constants, exceptions, interfaces, objects, renderers +from volatility3.framework.symbols.windows.extensions import conversion + +vollog = logging.getLogger(__name__) + + +class SHIM_CACHE_ENTRY(objects.StructType): + """Class for abstracting variations in the shimcache LRU list entry structure""" + + def __init__( + self, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + size: int, + members: Dict[str, Tuple[int, interfaces.objects.Template]], + ) -> None: + super().__init__(context, type_name, object_info, size, members) + self._exec_flag = None + self._file_path = None + self._file_size = None + self._last_modified = None + self._last_updated = None + + @property + def exec_flag(self) -> Union[bool, interfaces.renderers.BaseAbsentValue]: + """Checks if InsertFlags fields has been bitwise OR'd with a value of 2. + This behavior was observed when processes are created by CSRSS.""" + if self._exec_flag is not None: + return self._exec_flag + + if hasattr(self, "ListEntryDetail") and hasattr( + self.ListEntryDetail, "InsertFlags" + ): + self._exec_flag = self.ListEntryDetail.InsertFlags & 0x2 == 2 + + elif hasattr(self, "InsertFlags"): + self._exec_flag = self.InsertFlags & 0x2 == 2 + + elif hasattr(self, "ListEntryDetail") and hasattr( + self.ListEntryDetail, "BlobBuffer" + ): + blob_offset = self.ListEntryDetail.BlobBuffer + blob_size = self.ListEntryDetail.BlobSize + + if not self._context.layers[self.vol.native_layer_name].is_valid( + blob_offset, blob_size + ): + self._exec_flag = renderers.UnparsableValue() + + raw_flag = self._context.layers[self.vol.native_layer_name].read( + blob_offset, blob_size + ) + if not raw_flag: + self._exec_flag = renderers.UnparsableValue() + + try: + self._exec_flag = bool(struct.unpack(" Union[int, interfaces.renderers.BaseAbsentValue]: + if self._file_size is not None: + return self._file_size + try: + self._file_size = self.FileSize + if self._file_size < 0: + self._file_size = 0 + + except AttributeError: + self._file_size = renderers.NotApplicableValue() + except exceptions.InvalidAddressException: + self._file_size = renderers.UnreadableValue() + + return self._file_size + + @property + def last_modified(self) -> Union[datetime, interfaces.renderers.BaseAbsentValue]: + if self._last_modified is not None: + return self._last_modified + try: + self._last_modified = conversion.wintime_to_datetime( + self.ListEntryDetail.LastModified.QuadPart + ) + except AttributeError: + self._last_modified = conversion.wintime_to_datetime( + self.LastModified.QuadPart + ) + except exceptions.InvalidAddressException: + self._last_modified = renderers.UnreadableValue() + + return self._last_modified + + @property + def last_update(self) -> Union[datetime, interfaces.renderers.BaseAbsentValue]: + if self._last_updated is not None: + return self._last_updated + + try: + self._last_updated = conversion.wintime_to_datetime( + self.LastUpdate.QuadPart + ) + except AttributeError: + self._last_updated = renderers.NotApplicableValue() + + return self._last_updated + + @property + def file_path(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: + if self._file_path is not None: + return self._file_path + + if not hasattr(self.Path, "Buffer"): + return self.Path.cast( + "string", max_length=self.Path.vol.count, encoding="utf-16le" + ) + + try: + file_path_raw = ( + self._context.layers[self.vol.native_layer_name].read( + self.Path.Buffer, self.Path.Length + ) + or b"" + ) + self._file_path = file_path_raw.decode("utf-16", errors="replace") + except exceptions.InvalidAddressException: + self._file_path = renderers.UnreadableValue() + + return self._file_path + + def is_valid(self) -> bool: + """Shim cache validation is limited to ensuring that a subset of the + pointers in the LIST_ENTRY field are valid (similar to validation of + ERESOURCE)""" + + # shim entries on Windows XP do not have list entry attributes; in this case, + # perform a different set of validations + try: + if not hasattr(self, "ListEntry"): + return bool(self.last_modified and self.last_update and self.file_size) + + # on some platforms ListEntry.Blink is null, so this cannot be validated + if ( + self.ListEntry.Flink != 0 + and ( + self.ListEntry.Blink.dereference() + != self.ListEntry.Flink.dereference() + ) + and ( + self.ListEntry.Flink.Blink + == self.ListEntry.Flink.Blink.dereference().vol.offset + ) + ): + + return True + else: + return False + except exceptions.InvalidAddressException: + return False + + +class SHIM_CACHE_HANDLE(objects.StructType): + def __init__( + self, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + size: int, + members: Dict[str, Tuple[int, interfaces.objects.Template]], + ) -> None: + super().__init__(context, type_name, object_info, size, members) + + @property + def head(self) -> Optional[SHIM_CACHE_ENTRY]: + try: + if not self.eresource.is_valid(): + return None + except exceptions.InvalidAddressException: + return None + + rtl_avl_table = self._context.object( + self.get_symbol_table_name() + constants.BANG + "_RTL_AVL_TABLE", + self.vol.layer_name, + self.rtl_avl_table, + self.vol.native_layer_name, + ) + + if not self._context.layers[self.vol.layer_name].is_valid( + self.rtl_avl_table.vol.offset + ): + return None + + offset_head = rtl_avl_table.vol.offset + rtl_avl_table.vol.size + + head = self._context.object( + self.get_symbol_table_name() + constants.BANG + "SHIM_CACHE_ENTRY", + self.vol.layer_name, + offset_head, + ) + + if not head.is_valid(): + return None + + return head + + def is_valid(self, avl_section_start: int, avl_section_end: int) -> bool: + if self.vol.offset == 0: + return False + + vollog.debug(f"Checking SHIM_CACHE_HANDLE validity @ {hex(self.vol.offset)}") + + if not ( + self._context.layers[self.vol.layer_name].is_valid(self.vol.offset) + and self.eresource.is_valid() + and self.rtl_avl_table.is_valid(avl_section_start, avl_section_end) + and self.head + ): + return False + + return self.head.is_valid() + + +class RTL_AVL_TABLE(objects.StructType): + def is_valid(self, page_start: int, page_end: int) -> bool: + try: + if self.BalancedRoot.Parent != self.BalancedRoot.vol.offset: + vollog.debug( + f"RTL_AVL_TABLE @ {self.vol.offset} Invalid: Failed BalancedRoot parent equality check" + ) + return False + + elif self.AllocateRoutine < page_start or self.AllocateRoutine > page_end: + vollog.debug( + f"RTL_AVL_TABLE @ {self.vol.offset} Invalid: Failed AllocateRoutine range check" + ) + return False + + elif self.CompareRoutine < page_start or self.CompareRoutine > page_end: + vollog.debug( + f"RTL_AVL_TABLE @ {self.vol.offset} Invalid: Failed CompareRoutine range check" + ) + return False + + elif ( + (self.AllocateRoutine.vol.offset == self.CompareRoutine.vol.offset) + or (self.AllocateRoutine.vol.offset == self.FreeRoutine.vol.offset) + or (self.CompareRoutine.vol.offset == self.FreeRoutine.vol.offset) + ): + vollog.debug( + f"RTL_AVL_TABLE @ {self.vol.offset} Invalid: Failed (Compare|Allocate|Free)Routine uniqueness check" + ) + return False + + return True + except exceptions.InvalidAddressException: + return False + + +class_types = { + "SHIM_CACHE_HANDLE": SHIM_CACHE_HANDLE, + "SHIM_CACHE_ENTRY": SHIM_CACHE_ENTRY, + "_RTL_AVL_TABLE": RTL_AVL_TABLE, +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-2003-x64.json b/volatility3/framework/symbols/windows/shimcache/shimcache-2003-x64.json new file mode 100644 index 000000000..d1540f12f --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-2003-x64.json @@ -0,0 +1,327 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_RTL_BALANCED_LINKS": { + "fields": { + "Parent": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 0 + }, + "LeftChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 8 + }, + "RightChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 16 + }, + "Balance": { + "type": { + "kind": "base", + "name": "unsigned char" + }, + "offset": 24 + }, + "Reserved": { + "type": { + "kind": "array", + "count": 3, + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 25 + } + }, + "kind": "struct", + "size": 32 + }, + "_RTL_AVL_TABLE": { + "fields": { + "BalancedRoot": { + "type": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + }, + "offset": 0 + }, + "OrderedPointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 32 + }, + "WhichOrderedElement": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 40 + }, + "NumberGenericTableElements": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 44 + }, + "DepthOfTree": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 48 + }, + "RestartKey": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 56 + }, + "DeleteCount": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 64 + }, + "CompareRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 72 + }, + "AllocateRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 80 + }, + "FreeRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 88 + }, + "TableContext": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 96 + } + }, + "kind": "struct", + "size": 104 + }, + "SHIM_CACHE_HANDLE": { + "fields": { + "eresource": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_ERESOURCE" + } + }, + "offset": 0 + }, + "rtl_avl_table": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_AVL_TABLE" + } + }, + "offset": 8 + } + }, + "kind": "struct", + "size": 16 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + }, + "__unnamed_2": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "SHIM_CACHE_ENTRY": { + "fields": { + "ListEntry": { + "offset": 0, + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "Path": { + "offset": 16, + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + } + }, + "LastModified": { + "offset": 32, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "FileSize": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 48 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dgmcdona by hand", + "datetime": "2024-07-05T18:28:00.000000+00:00" + }, + "format": "4.0.0" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-2003-x86.json b/volatility3/framework/symbols/windows/shimcache/shimcache-2003-x86.json new file mode 100644 index 000000000..e4739c4a0 --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-2003-x86.json @@ -0,0 +1,334 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_RTL_BALANCED_LINKS": { + "fields": { + "Parent": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 0 + }, + "LeftChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 4 + }, + "RightChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 8 + }, + "Balance": { + "type": { + "kind": "base", + "name": "unsigned char" + }, + "offset": 12 + }, + "Reserved": { + "type": { + "kind": "array", + "count": 3, + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 16 + }, + "_RTL_AVL_TABLE": { + "fields": { + "BalancedRoot": { + "type": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + }, + "offset": 0 + }, + "OrderedPointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 16 + }, + "WhichOrderedElement": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 20 + }, + "NumberGenericTableElements": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 24 + }, + "DepthOfTree": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 28 + }, + "RestartKey": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 32 + }, + "DeleteCount": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 36 + }, + "CompareRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 40 + }, + "AllocateRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 44 + }, + "FreeRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 48 + }, + "TableContext": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 52 + } + }, + "kind": "struct", + "size": 56 + }, + "SHIM_CACHE_HANDLE": { + "fields": { + "eresource": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_ERESOURCE" + } + }, + "offset": 0 + }, + "rtl_avl_table": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_AVL_TABLE" + } + }, + "offset": 8 + } + }, + "kind": "struct", + "size": 8 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + }, + "__unnamed_2": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "SHIM_CACHE_ENTRY": { + "fields": { + "ListEntry": { + "offset": 0, + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "Path": { + "offset": 8, + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + } + }, + "LastModified": { + "offset": 16, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "FileSize": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Padding": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 36 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dgmcdona by hand", + "datetime": "2024-07-05T18:28:00.000000+00:00" + }, + "format": "4.0.0" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-vista-x64.json b/volatility3/framework/symbols/windows/shimcache/shimcache-vista-x64.json new file mode 100644 index 000000000..0c5183a4a --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-vista-x64.json @@ -0,0 +1,334 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_RTL_BALANCED_LINKS": { + "fields": { + "Parent": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 0 + }, + "LeftChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 8 + }, + "RightChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 16 + }, + "Balance": { + "type": { + "kind": "base", + "name": "unsigned char" + }, + "offset": 24 + }, + "Reserved": { + "type": { + "kind": "array", + "count": 3, + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 25 + } + }, + "kind": "struct", + "size": 32 + }, + "_RTL_AVL_TABLE": { + "fields": { + "BalancedRoot": { + "type": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + }, + "offset": 0 + }, + "OrderedPointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 32 + }, + "WhichOrderedElement": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 40 + }, + "NumberGenericTableElements": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 44 + }, + "DepthOfTree": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 48 + }, + "RestartKey": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 56 + }, + "DeleteCount": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 64 + }, + "CompareRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 72 + }, + "AllocateRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 80 + }, + "FreeRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 88 + }, + "TableContext": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 96 + } + }, + "kind": "struct", + "size": 104 + }, + "SHIM_CACHE_HANDLE": { + "fields": { + "eresource": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_ERESOURCE" + } + }, + "offset": 0 + }, + "rtl_avl_table": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_AVL_TABLE" + } + }, + "offset": 8 + } + }, + "kind": "struct", + "size": 16 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + }, + "__unnamed_2": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "SHIM_CACHE_ENTRY": { + "fields": { + "ListEntry": { + "offset": 0, + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "Path": { + "offset": 16, + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + } + }, + "LastModified": { + "offset": 32, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "InsertFlags": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned int" + } + }, + "ShimFlags": { + "offset": 44, + "type": { + "kind": "base", + "name": "unsigned int" + } + } + }, + "kind": "struct", + "size": 48 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dgmcdona by hand", + "datetime": "2024-07-05T18:28:00.000000+00:00" + }, + "format": "4.0.0" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-vista-x86.json b/volatility3/framework/symbols/windows/shimcache/shimcache-vista-x86.json new file mode 100644 index 000000000..91290b1ba --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-vista-x86.json @@ -0,0 +1,334 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_RTL_BALANCED_LINKS": { + "fields": { + "Parent": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 0 + }, + "LeftChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 4 + }, + "RightChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 8 + }, + "Balance": { + "type": { + "kind": "base", + "name": "unsigned char" + }, + "offset": 12 + }, + "Reserved": { + "type": { + "kind": "array", + "count": 3, + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 16 + }, + "_RTL_AVL_TABLE": { + "fields": { + "BalancedRoot": { + "type": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + }, + "offset": 0 + }, + "OrderedPointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 16 + }, + "WhichOrderedElement": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 20 + }, + "NumberGenericTableElements": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 24 + }, + "DepthOfTree": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 28 + }, + "RestartKey": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 32 + }, + "DeleteCount": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 36 + }, + "CompareRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 40 + }, + "AllocateRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 44 + }, + "FreeRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 48 + }, + "TableContext": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 52 + } + }, + "kind": "struct", + "size": 56 + }, + "SHIM_CACHE_HANDLE": { + "fields": { + "eresource": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_ERESOURCE" + } + }, + "offset": 0 + }, + "rtl_avl_table": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_AVL_TABLE" + } + }, + "offset": 8 + } + }, + "kind": "struct", + "size": 8 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + }, + "__unnamed_2": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "SHIM_CACHE_ENTRY": { + "fields": { + "ListEntry": { + "offset": 0, + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "Path": { + "offset": 8, + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + } + }, + "LastModified": { + "offset": 16, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "InsertFlags": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned int" + } + }, + "ShimFlags": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned int" + } + } + }, + "kind": "struct", + "size": 36 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dgmcdona by hand", + "datetime": "2024-07-05T18:28:00.000000+00:00" + }, + "format": "4.0.0" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-win10-x64.json b/volatility3/framework/symbols/windows/shimcache/shimcache-win10-x64.json new file mode 100644 index 000000000..fe9593af3 --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-win10-x64.json @@ -0,0 +1,371 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_RTL_BALANCED_LINKS": { + "fields": { + "Parent": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 0 + }, + "LeftChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 8 + }, + "RightChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 16 + }, + "Balance": { + "type": { + "kind": "base", + "name": "unsigned char" + }, + "offset": 24 + }, + "Reserved": { + "type": { + "kind": "array", + "count": 3, + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 25 + } + }, + "kind": "struct", + "size": 32 + }, + "_RTL_AVL_TABLE": { + "fields": { + "BalancedRoot": { + "type": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + }, + "offset": 0 + }, + "OrderedPointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 32 + }, + "WhichOrderedElement": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 40 + }, + "NumberGenericTableElements": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 44 + }, + "DepthOfTree": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 48 + }, + "RestartKey": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 56 + }, + "DeleteCount": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 64 + }, + "CompareRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 72 + }, + "AllocateRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 80 + }, + "FreeRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 88 + }, + "TableContext": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 96 + } + }, + "kind": "struct", + "size": 104 + }, + "SHIM_CACHE_HANDLE": { + "fields": { + "eresource": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_ERESOURCE" + } + }, + "offset": 0 + }, + "rtl_avl_table": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_AVL_TABLE" + } + }, + "offset": 8 + } + }, + "kind": "struct", + "size": 16 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + }, + "__unnamed_2": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "SHIM_CACHE_ENTRY": { + "fields": { + "ListEntry": { + "offset": 0, + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "u1": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "Path": { + "offset": 24, + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + } + }, + "ListEntryDetail": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "SHIM_CACHE_ENTRY_DETAIL" + } + } + } + }, + "kind": "struct", + "size": 48 + }, + "SHIM_CACHE_ENTRY_DETAIL": { + "fields": { + "u1": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "LastModified": { + "offset": 8, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "BlobSize": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "u2": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "BlobBuffer": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long long" + } + } + }, + "kind": "struct", + "size": 32 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dgmcdona by hand", + "datetime": "2024-07-05T18:28:00.000000+00:00" + }, + "format": "4.0.0" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-win10-x86.json b/volatility3/framework/symbols/windows/shimcache/shimcache-win10-x86.json new file mode 100644 index 000000000..26d493c37 --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-win10-x86.json @@ -0,0 +1,371 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "SHIM_CACHE_HANDLE": { + "fields": { + "eresource": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_ERESOURCE" + } + }, + "offset": 0 + }, + "rtl_avl_table": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_AVL_TABLE" + } + }, + "offset": 4 + } + }, + "kind": "struct", + "size": 8 + }, + "_RTL_BALANCED_LINKS": { + "fields": { + "Parent": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 0 + }, + "LeftChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 4 + }, + "RightChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 8 + }, + "Balance": { + "type": { + "kind": "base", + "name": "unsigned char" + }, + "offset": 12 + }, + "Reserved": { + "type": { + "kind": "array", + "count": 3, + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 16 + }, + "_RTL_AVL_TABLE": { + "fields": { + "BalancedRoot": { + "type": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + }, + "offset": 0 + }, + "OrderedPointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 16 + }, + "WhichOrderedElement": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 20 + }, + "NumberGenericTableElements": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 24 + }, + "DepthOfTree": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 28 + }, + "RestartKey": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 32 + }, + "DeleteCount": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 36 + }, + "CompareRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 40 + }, + "AllocateRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 44 + }, + "FreeRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 48 + }, + "TableContext": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 52 + } + }, + "kind": "struct", + "size": 56 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + }, + "__unnamed_2": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "SHIM_CACHE_ENTRY": { + "fields": { + "ListEntry": { + "offset": 0, + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "u1": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Path": { + "offset": 12, + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + } + }, + "ListEntryDetail": { + "offset": 20, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "SHIM_CACHE_ENTRY_DETAIL" + } + } + } + }, + "kind": "struct", + "size": 24 + }, + "SHIM_CACHE_ENTRY_DETAIL": { + "fields": { + "u1": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "InsertFlags": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "LastModified": { + "offset": 8, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "BlobSize": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "BlobBuffer": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 24 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dgmcdona by hand", + "datetime": "2024-07-05T18:28:00.000000+00:00" + }, + "format": "4.0.0" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-win7-x64.json b/volatility3/framework/symbols/windows/shimcache/shimcache-win7-x64.json new file mode 100644 index 000000000..eac5407ba --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-win7-x64.json @@ -0,0 +1,348 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_RTL_BALANCED_LINKS": { + "fields": { + "Parent": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 0 + }, + "LeftChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 8 + }, + "RightChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 16 + }, + "Balance": { + "type": { + "kind": "base", + "name": "unsigned char" + }, + "offset": 24 + }, + "Reserved": { + "type": { + "kind": "array", + "count": 3, + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 25 + } + }, + "kind": "struct", + "size": 32 + }, + "_RTL_AVL_TABLE": { + "fields": { + "BalancedRoot": { + "type": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + }, + "offset": 0 + }, + "OrderedPointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 32 + }, + "WhichOrderedElement": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 40 + }, + "NumberGenericTableElements": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 44 + }, + "DepthOfTree": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 48 + }, + "RestartKey": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 56 + }, + "DeleteCount": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 64 + }, + "CompareRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 72 + }, + "AllocateRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 80 + }, + "FreeRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 88 + }, + "TableContext": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 96 + } + }, + "kind": "struct", + "size": 104 + }, + "SHIM_CACHE_HANDLE": { + "fields": { + "eresource": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_ERESOURCE" + } + }, + "offset": 0 + }, + "rtl_avl_table": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_AVL_TABLE" + } + }, + "offset": 8 + } + }, + "kind": "struct", + "size": 8 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + }, + "__unnamed_2": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "SHIM_CACHE_ENTRY": { + "fields": { + "ListEntry": { + "offset": 0, + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "Path": { + "offset": 16, + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + } + }, + "LastModified": { + "offset": 32, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "InsertFlags": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned int" + } + }, + "ShimFlags": { + "offset": 44, + "type": { + "kind": "base", + "name": "unsigned int" + } + }, + "BlobSize": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "BlobBuffer": { + "offset": 56, + "type": { + "kind": "base", + "name": "unsigned long long" + } + } + }, + "kind": "struct", + "size": 64 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dgmcdona by hand", + "datetime": "2024-07-05T18:28:00.000000+00:00" + }, + "format": "4.0.0" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-win7-x86.json b/volatility3/framework/symbols/windows/shimcache/shimcache-win7-x86.json new file mode 100644 index 000000000..423f7e255 --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-win7-x86.json @@ -0,0 +1,348 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_RTL_BALANCED_LINKS": { + "fields": { + "Parent": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 0 + }, + "LeftChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 4 + }, + "RightChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 8 + }, + "Balance": { + "type": { + "kind": "base", + "name": "unsigned char" + }, + "offset": 12 + }, + "Reserved": { + "type": { + "kind": "array", + "count": 3, + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 16 + }, + "_RTL_AVL_TABLE": { + "fields": { + "BalancedRoot": { + "type": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + }, + "offset": 0 + }, + "OrderedPointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 16 + }, + "WhichOrderedElement": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 20 + }, + "NumberGenericTableElements": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 24 + }, + "DepthOfTree": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 28 + }, + "RestartKey": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 32 + }, + "DeleteCount": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 36 + }, + "CompareRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 40 + }, + "AllocateRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 44 + }, + "FreeRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 48 + }, + "TableContext": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 52 + } + }, + "kind": "struct", + "size": 56 + }, + "SHIM_CACHE_HANDLE": { + "fields": { + "eresource": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_ERESOURCE" + } + }, + "offset": 0 + }, + "rtl_avl_table": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_AVL_TABLE" + } + }, + "offset": 4 + } + }, + "kind": "struct", + "size": 8 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + }, + "__unnamed_2": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "SHIM_CACHE_ENTRY": { + "fields": { + "ListEntry": { + "offset": 0, + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "Path": { + "offset": 8, + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + } + }, + "LastModified": { + "offset": 16, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "InsertFlags": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned int" + } + }, + "ShimFlags": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned int" + } + }, + "BlobSize": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned int" + } + }, + "BlobBuffer": { + "offset": 36, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 40 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dgmcdona by hand", + "datetime": "2024-07-05T18:28:00.000000+00:00" + }, + "format": "4.0.0" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-win8-x64.json b/volatility3/framework/symbols/windows/shimcache/shimcache-win8-x64.json new file mode 100644 index 000000000..a40c8d680 --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-win8-x64.json @@ -0,0 +1,392 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_RTL_BALANCED_LINKS": { + "fields": { + "Parent": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 0 + }, + "LeftChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 8 + }, + "RightChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 16 + }, + "Balance": { + "type": { + "kind": "base", + "name": "unsigned char" + }, + "offset": 24 + }, + "Reserved": { + "type": { + "kind": "array", + "count": 3, + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 25 + } + }, + "kind": "struct", + "size": 32 + }, + "_RTL_AVL_TABLE": { + "fields": { + "BalancedRoot": { + "type": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + }, + "offset": 0 + }, + "OrderedPointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 32 + }, + "WhichOrderedElement": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 40 + }, + "NumberGenericTableElements": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 44 + }, + "DepthOfTree": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 48 + }, + "RestartKey": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 56 + }, + "DeleteCount": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 64 + }, + "CompareRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 72 + }, + "AllocateRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 80 + }, + "FreeRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 88 + }, + "TableContext": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 96 + } + }, + "kind": "struct", + "size": 104 + }, + "SHIM_CACHE_HANDLE": { + "fields": { + "eresource": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_ERESOURCE" + } + }, + "offset": 0 + }, + "rtl_avl_table": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_AVL_TABLE" + } + }, + "offset": 8 + } + }, + "kind": "struct", + "size": 8 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + }, + "__unnamed_2": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "SHIM_CACHE_ENTRY": { + "fields": { + "ListEntry": { + "offset": 0, + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "u1": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "Path": { + "offset": 24, + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + } + }, + "u2": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "u3": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "ListEntryDetail": { + "offset": 56, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "SHIM_CACHE_ENTRY_DETAIL" + } + } + } + }, + "kind": "struct", + "size": 64 + }, + "SHIM_CACHE_ENTRY_DETAIL": { + "fields": { + "LastModified": { + "offset": 0, + "type": { + "kind": "struct", + "name": "_LARGE_INTEGER" + } + }, + "InsertFlags": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned int" + } + }, + "ShimFlags": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned int" + } + }, + "BlobSize": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "Padding": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "BlobBuffer": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long long" + } + } + }, + "kind": "struct", + "size": 40 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dgmcdona by hand", + "datetime": "2024-07-05T18:28:00.000000+00:00" + }, + "format": "4.0.0" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-win8-x86.json b/volatility3/framework/symbols/windows/shimcache/shimcache-win8-x86.json new file mode 100644 index 000000000..c3cee5feb --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-win8-x86.json @@ -0,0 +1,386 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_RTL_BALANCED_LINKS": { + "fields": { + "Parent": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 0 + }, + "LeftChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 4 + }, + "RightChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 8 + }, + "Balance": { + "type": { + "kind": "base", + "name": "unsigned char" + }, + "offset": 12 + }, + "Reserved": { + "type": { + "kind": "array", + "count": 3, + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 16 + }, + "_RTL_AVL_TABLE": { + "fields": { + "BalancedRoot": { + "type": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + }, + "offset": 0 + }, + "OrderedPointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 16 + }, + "WhichOrderedElement": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 20 + }, + "NumberGenericTableElements": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 24 + }, + "DepthOfTree": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 28 + }, + "RestartKey": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 32 + }, + "DeleteCount": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 36 + }, + "CompareRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 40 + }, + "AllocateRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 44 + }, + "FreeRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 48 + }, + "TableContext": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 52 + } + }, + "kind": "struct", + "size": 56 + }, + "SHIM_CACHE_HANDLE": { + "fields": { + "eresource": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_ERESOURCE" + } + }, + "offset": 0 + }, + "rtl_avl_table": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_AVL_TABLE" + } + }, + "offset": 4 + } + }, + "kind": "struct", + "size": 8 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + }, + "__unnamed_2": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "SHIM_CACHE_ENTRY": { + "fields": { + "ListEntry": { + "offset": 0, + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "u1": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "u2": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Path": { + "offset": 16, + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + } + }, + "u3": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "ListEntryDetail": { + "offset": 32, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "SHIM_CACHE_ENTRY_DETAIL" + } + } + } + }, + "kind": "struct", + "size": 36 + }, + "SHIM_CACHE_ENTRY_DETAIL": { + "fields": { + "LastModified": { + "offset": 0, + "type": { + "kind": "struct", + "name": "_LARGE_INTEGER" + } + }, + "InsertFlags": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned int" + } + }, + "ShimFlags": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "BlobSize": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "BlobBuffer": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 24 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dgmcdona by hand", + "datetime": "2024-07-05T18:28:00.000000+00:00" + }, + "format": "4.0.0" + } +} + diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-xp-sp2-x86.json b/volatility3/framework/symbols/windows/shimcache/shimcache-xp-sp2-x86.json new file mode 100644 index 000000000..6114e6c85 --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-xp-sp2-x86.json @@ -0,0 +1,485 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + }, + "__unnamed_2": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "_RTL_BALANCED_LINKS": { + "fields": { + "Parent": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 0 + }, + "LeftChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 4 + }, + "RightChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 8 + }, + "Balance": { + "type": { + "kind": "base", + "name": "unsigned char" + }, + "offset": 12 + }, + "Reserved": { + "type": { + "kind": "array", + "count": 3, + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 16 + }, + "_RTL_AVL_TABLE": { + "fields": { + "BalancedRoot": { + "type": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + }, + "offset": 0 + }, + "OrderedPointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 16 + }, + "WhichOrderedElement": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 20 + }, + "NumberGenericTableElements": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 24 + }, + "DepthOfTree": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 28 + }, + "RestartKey": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 32 + }, + "DeleteCount": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 36 + }, + "CompareRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 40 + }, + "AllocateRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 44 + }, + "FreeRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 48 + }, + "TableContext": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 52 + } + }, + "kind": "struct", + "size": 56 + }, + "SHIM_CACHE_HANDLE": { + "fields": { + "eresource": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_ERESOURCE" + } + }, + "offset": 0 + }, + "rtl_avl_table": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_AVL_TABLE" + } + }, + "offset": 4 + } + }, + "kind": "struct", + "size": 8 + }, + "SHIM_CACHE_HEADER": { + "fields": { + "Magic": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 0 + }, + "u1": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 4 + }, + "NumEntries": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 8 + }, + "u2": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 400 + }, + "SHIM_CACHE_ENTRY": { + "fields": { + "Path": { + "type": { + "count": 520, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 0 + }, + "LastModified": { + "type": { + "kind": "union", + "name": "LARGE_INTEGER" + }, + "offset": 4 + }, + "FileSize": { + "type": { + "kind": "base", + "name": "long long" + }, + "offset": 8 + }, + "LastUpdate": { + "type": { + "kind": "union", + "name": "LARGE_INTEGER" + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 552 + }, + "_SEGMENT": { + "fields": { + "ControlArea": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_CONTROL_AREA" + } + } + }, + "TotalNumberOfPtes": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NonExtendedPtes": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "WritableUserReferences": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SizeOfSegment": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SegmentPteTemplate": { + "offset": 24, + "type": { + "kind": "struct", + "name": "nt_symbols!_MMPTE" + } + }, + "NumberOfCommittedPages": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ExtendInfo": { + "offset": 32, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_MMEXTEND_INFO" + } + } + }, + "SystemImageBase": { + "offset": 36, + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + } + }, + "BasedAddress": { + "offset": 40, + "type": { + "kind": "base", + "name": "long" + } + }, + "u1": { + "offset": 44, + "type": { + "kind": "base", + "name": "long" + } + }, + "u2": { + "offset": 48, + "type": { + "kind": "base", + "name": "long" + } + }, + "PrototypePte": { + "offset": 52, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_MMPTE" + } + } + }, + "ThePtes": { + "offset": 60, + "type": { + "kind": "array", + "count": 1, + "subtype": { + "kind": "base", + "name": "nt_symbols!_MMPTE" + } + } + } + }, + "kind": "struct", + "size": 64 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dgmcdona by hand", + "datetime": "2024-07-05T18:28:00.000000+00:00" + }, + "format": "4.0.0" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-xp-sp3-x86.json b/volatility3/framework/symbols/windows/shimcache/shimcache-xp-sp3-x86.json new file mode 100644 index 000000000..a9b86d93c --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-xp-sp3-x86.json @@ -0,0 +1,485 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + }, + "__unnamed_2": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "_RTL_BALANCED_LINKS": { + "fields": { + "Parent": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 0 + }, + "LeftChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 4 + }, + "RightChild": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 8 + }, + "Balance": { + "type": { + "kind": "base", + "name": "unsigned char" + }, + "offset": 12 + }, + "Reserved": { + "type": { + "kind": "array", + "count": 3, + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 16 + }, + "_RTL_AVL_TABLE": { + "fields": { + "BalancedRoot": { + "type": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + }, + "offset": 0 + }, + "OrderedPointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 16 + }, + "WhichOrderedElement": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 20 + }, + "NumberGenericTableElements": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 24 + }, + "DepthOfTree": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 28 + }, + "RestartKey": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_BALANCED_LINKS" + } + }, + "offset": 32 + }, + "DeleteCount": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 36 + }, + "CompareRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 40 + }, + "AllocateRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 44 + }, + "FreeRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 48 + }, + "TableContext": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 52 + } + }, + "kind": "struct", + "size": 56 + }, + "SHIM_CACHE_HEADER": { + "fields": { + "Magic": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 0 + }, + "u1": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 4 + }, + "NumEntries": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 8 + }, + "u2": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 400 + }, + "SHIM_CACHE_ENTRY": { + "fields": { + "Path": { + "type": { + "count": 520, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 0 + }, + "LastModified": { + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + }, + "offset": 528 + }, + "FileSize": { + "type": { + "kind": "base", + "name": "long long" + }, + "offset": 536 + }, + "LastUpdate": { + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + }, + "offset": 544 + } + }, + "kind": "struct", + "size": 552 + }, + "SHIM_CACHE_HANDLE": { + "fields": { + "eresource": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!ERESOURCE" + } + }, + "offset": 0 + }, + "rtl_avl_table": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_RTL_AVL_TABLE" + } + }, + "offset": 4 + } + }, + "kind": "struct", + "size": 8 + }, + "_SEGMENT": { + "fields": { + "ControlArea": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_CONTROL_AREA" + } + } + }, + "TotalNumberOfPtes": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NonExtendedPtes": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "WritableUserReferences": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SizeOfSegment": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SegmentPteTemplate": { + "offset": 24, + "type": { + "kind": "struct", + "name": "nt_symbols!_MMPTE" + } + }, + "NumberOfCommittedPages": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ExtendInfo": { + "offset": 36, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_MMEXTEND_INFO" + } + } + }, + "SystemImageBase": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + } + }, + "BasedAddress": { + "offset": 44, + "type": { + "kind": "base", + "name": "long" + } + }, + "u1": { + "offset": 48, + "type": { + "kind": "base", + "name": "long" + } + }, + "u2": { + "offset": 52, + "type": { + "kind": "base", + "name": "long" + } + }, + "PrototypePte": { + "offset": 56, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_MMPTE" + } + } + }, + "ThePtes": { + "offset": 64, + "type": { + "kind": "array", + "count": 1, + "subtype": { + "kind": "base", + "name": "nt_symbols!_MMPTE" + } + } + } + }, + "kind": "struct", + "size": 72 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dgmcdona by hand", + "datetime": "2024-07-05T18:28:00.000000+00:00" + }, + "format": "4.0.0" + } +} diff --git a/volatility3/framework/symbols/windows/versions.py b/volatility3/framework/symbols/windows/versions.py index e1e74afc0..78e90a1d4 100644 --- a/volatility3/framework/symbols/windows/versions.py +++ b/volatility3/framework/symbols/windows/versions.py @@ -114,6 +114,24 @@ is_windows_xp = OsDistinguisher( ], ) +is_windows_xp_sp2 = OsDistinguisher( + version_check=lambda x: (5, 1) <= x < (5, 2), + fallback_checks=[ + ("KdCopyDataBlock", None, False), + ("_MMFREE_POOL_ENTRY", None, False), + ("_HANDLE_TABLE", "HandleCount", True), + ], +) + +is_windows_xp_sp3 = OsDistinguisher( + version_check=lambda x: (5, 1) <= x < (5, 2), + fallback_checks=[ + ("KdCopyDataBlock", None, False), + ("_MMFREE_POOL_ENTRY", None, True), + ("_HANDLE_TABLE", "HandleCount", True), + ], +) + is_xp_or_2003 = OsDistinguisher( version_check=lambda x: (5, 1) <= x < (6, 0), fallback_checks=[ @@ -122,6 +140,15 @@ is_xp_or_2003 = OsDistinguisher( ], ) +is_2003 = OsDistinguisher( + version_check=lambda x: (5, 2) <= x < (5, 3), + fallback_checks=[ + ("KdCopyDataBlock", None, False), + ("_HANDLE_TABLE", "HandleCount", True), + ("_MM_AVL_TABLE", None, True), + ], +) + is_win10_up_to_15063 = OsDistinguisher( version_check=lambda x: (10, 0) <= x < (10, 0, 15063), fallback_checks=[ From 4048d0b122eb9030a011012fd4f1ff59d1e0ca46 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Tue, 9 Jul 2024 11:43:29 -0500 Subject: [PATCH 38/85] Windows: Adds shimcachemem plugin --- .../framework/plugins/windows/shimcachemem.py | 610 ++++++++++++++++++ 1 file changed, 610 insertions(+) create mode 100644 volatility3/framework/plugins/windows/shimcachemem.py diff --git a/volatility3/framework/plugins/windows/shimcachemem.py b/volatility3/framework/plugins/windows/shimcachemem.py new file mode 100644 index 000000000..6afaf4356 --- /dev/null +++ b/volatility3/framework/plugins/windows/shimcachemem.py @@ -0,0 +1,610 @@ +# This file is Copyright 2020 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 +import os +from datetime import datetime +from itertools import count +from typing import Iterator, List, Optional, Tuple + +from volatility3.framework import constants, exceptions, interfaces, renderers, symbols +from volatility3.framework.configuration import requirements +from volatility3.framework.objects.utility import array_to_string +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 pe, shimcache +from volatility3.plugins import timeliner +from volatility3.plugins.windows import modules, pslist, vadinfo + +# from volatility3.plugins.windows import pslist, vadinfo, modules + +vollog = logging.getLogger(__name__) + + +class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): + """Reads Shimcache entries from the ahcache.sys AVL tree""" + + _required_framework_version = (2, 0, 0) + + # These checks must be completed from newest -> oldest OS version. + _win_version_file_map: List[Tuple[versions.OsDistinguisher, bool, str]] = [ + (versions.is_win10, True, "shimcache-win10-x64"), + (versions.is_win10, False, "shimcache-win10-x86"), + (versions.is_windows_8_or_later, True, "shimcache-win8-x64"), + (versions.is_windows_8_or_later, False, "shimcache-win8-x86"), + (versions.is_windows_7, True, "shimcache-win7-x64"), + (versions.is_windows_7, False, "shimcache-win7-x86"), + (versions.is_vista_or_later, True, "shimcache-vista-x64"), + (versions.is_vista_or_later, False, "shimcache-vista-x86"), + (versions.is_2003, False, "shimcache-2003-x86"), + (versions.is_2003, True, "shimcache-2003-x64"), + (versions.is_windows_xp_sp3, False, "shimcache-xp-sp3-x86"), + (versions.is_windows_xp_sp2, False, "shimcache-xp-sp2-x86"), + (versions.is_xp_or_2003, True, "shimcache-xp-2003-x64"), + (versions.is_xp_or_2003, False, "shimcache-xp-2003-x86"), + ] + + NT_KRNL_MODS = ["ntoskrnl.exe", "ntkrnlpa.exe", "ntkrnlmp.exe", "ntkrpamp.exe"] + + def generate_timeline( + self, + ) -> Iterator[Tuple[str, timeliner.TimeLinerType, datetime]]: + for _, (_, last_modified, last_update, _, _, file_path) in self._generator(): + if isinstance(last_update, datetime): + yield f"Shimcache: File {file_path} executed", timeliner.TimeLinerType.ACCESSED, last_update + if isinstance(last_modified, datetime): + yield f"Shimcache: File {file_path} modified", timeliner.TimeLinerType.MODIFIED, last_modified + + @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="pslist", plugin=pslist.PsList, version=(2, 0, 0) + ), + requirements.VersionRequirement( + name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0) + ), + requirements.VersionRequirement( + name="modules", component=modules.Modules, version=(2, 0, 0) + ), + ] + + @staticmethod + def create_shimcache_table( + context: interfaces.context.ContextInterface, + symbol_table: str, + config_path: str, + ) -> str: + """Creates a shimcache symbol table + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + symbol_table: The name of an existing symbol table containing the kernel symbols + config_path: The configuration path within the context of the symbol table to create + + 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} + + 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) + ) + except StopIteration: + raise NotImplementedError("This version of Windows is not supported!") + + vollog.debug(f"Using shimcache table {symbol_filename}") + + return intermed.IntermediateSymbolTable.create( + context, + config_path, + os.path.join("windows", "shimcache"), + symbol_filename, + class_types=shimcache.class_types, + native_types=native_types, + table_mapping=table_mapping, + ) + + @classmethod + def find_shimcache_win_xp( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + kernel_symbol_table: str, + shimcache_symbol_table: str, + ) -> Iterator[shimcache.SHIM_CACHE_ENTRY]: + """Attempts to find the shimcache in a Windows XP memory image + + :param context: The context to retrieve required elements (layers, symbol tables) from + :param layer_name: The name of the memory layer on which to operate. + :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 + """ + + SHIM_NUM_ENTRIES_OFFSET = 0x8 + SHIM_MAX_ENTRIES = 0x60 # 96 max entries in XP shim cache + SHIM_LRU_OFFSET = 0x10 + SHIM_HEADER_SIZE = 0x190 + SHIM_CACHE_ENTRY_SIZE = 0x228 + + seen = set() + + for process in pslist.PsList.list_processes( + context, layer_name, kernel_symbol_table + ): + pid = process.UniqueProcessId + vollog.debug("checking process %d" % pid) + for vad in vadinfo.VadInfo.list_vads( + process, lambda x: x.get_tag() == b"Vad " and x.Protection == 4 + ): + try: + proc_layer_name = process.add_process_layer() + proc_layer = context.layers[proc_layer_name] + except exceptions.InvalidAddressException: + continue + + try: + if proc_layer.read(vad.get_start(), 4) != b"\xEF\xBE\xAD\xDE": + if pid == 624: + vollog.debug("VAD magic bytes don't match DEADBEEF") + continue + except exceptions.InvalidAddressException: + continue + + num_entries = context.object( + shimcache_symbol_table + constants.BANG + "unsigned int", + proc_layer_name, + vad.get_start() + SHIM_NUM_ENTRIES_OFFSET, + ) + + if num_entries > SHIM_MAX_ENTRIES: + continue + + cache_idx_ptr = vad.get_start() + SHIM_LRU_OFFSET + + for _ in range(num_entries): + cache_idx_val = proc_layer.context.object( + shimcache_symbol_table + constants.BANG + "unsigned long", + proc_layer_name, + cache_idx_ptr, + ) + + cache_idx_ptr += 4 + + if cache_idx_val > SHIM_MAX_ENTRIES - 1: + continue + + shim_entry_offset = ( + vad.get_start() + + SHIM_HEADER_SIZE + + (SHIM_CACHE_ENTRY_SIZE * cache_idx_val) + ) + + if not proc_layer.is_valid(shim_entry_offset): + continue + + physical_addr = proc_layer.translate(shim_entry_offset) + + if physical_addr in seen: + continue + seen.add(physical_addr) + + shim_entry = proc_layer.context.object( + shimcache_symbol_table + constants.BANG + "SHIM_CACHE_ENTRY", + proc_layer_name, + shim_entry_offset, + ) + if not proc_layer.is_valid(shim_entry.vol.offset): + continue + if not shim_entry.is_valid(): + continue + + yield shim_entry + + @classmethod + def find_shimcache_win_2k3_to_7( + cls, + context: interfaces.context.ContextInterface, + config_path: str, + kernel_layer_name: str, + nt_symbol_table: str, + shimcache_symbol_table: str, + ) -> Iterator[shimcache.SHIM_CACHE_ENTRY]: + """Implements the algorithm to search for the shim cache on Windows 2000 + (x64) through Windows 7 / 2008 R2. The algorithm consists of the following: + + 1) Find the NT kernel module's .data and PAGE sections + 2) Iterate over every 4/8 bytes (depending on OS bitness) in the .data + section and test for the following: + a) offset represents a valid RTL_AVL_TABLE object + b) RTL_AVL_TABLE is preceeded by an ERESOURCE object + c) RTL_AVL_TABLE is followed by the beginning of the SHIM LRU list + + :param context: The context to retrieve required elements (layers, symbol tables) from + :param layer_name: The name of the memory layer on which to operate. + :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 + """ + + data_sec = cls.get_module_section_range( + context, + config_path, + kernel_layer_name, + nt_symbol_table, + cls.NT_KRNL_MODS, + ".data", + ) + mod_page = cls.get_module_section_range( + context, + config_path, + kernel_layer_name, + nt_symbol_table, + cls.NT_KRNL_MODS, + "PAGE", + ) + + # We require both in order to accurately handle AVL table + if not (data_sec and mod_page): + return None + + 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 + + shim_head = None + for offset in range( + data_sec_offset, data_sec_offset + data_sec_size, addr_size + ): + shim_head = cls.try_get_shim_head_at_offset( + context, + shimcache_symbol_table, + nt_symbol_table, + kernel_layer_name, + mod_page_offset, + mod_page_offset + mod_page_size, + offset, + ) + + if shim_head: + break + + if not shim_head: + return + + for shim_entry in shim_head.ListEntry.to_list( + shimcache_symbol_table + constants.BANG + "SHIM_CACHE_ENTRY", "ListEntry" + ): + yield shim_entry + + @classmethod + def try_get_shim_head_at_offset( + cls, + context: interfaces.context.ContextInterface, + symbol_table: str, + kernel_symbol_table: str, + layer_name: str, + mod_page_start: int, + mod_page_end: int, + offset: int, + ) -> Optional[shimcache.SHIM_CACHE_ENTRY]: + """Attempts to construct a SHIM_CACHE_HEAD within a layer of the given context, + using the provided offset within that layer, as well as the start and end offsets + of the kernel module's `PAGE` section start and end offsets. + + If a number of validity checks are passed, this method will return the `SHIM_CACHE_HEAD` + object. Otherwise, `None` is returned. + """ + # print("checking RTL_AVL_TABLE at offset %s" % hex(offset)) + rtl_avl_table = context.object( + symbol_table + constants.BANG + "_RTL_AVL_TABLE", layer_name, offset + ) + if not rtl_avl_table.is_valid(mod_page_start, mod_page_end): + return None + + vollog.debug(f"Candidate RTL_AVL_TABLE found at offset {hex(offset)}") + + ersrc_size = context.symbol_space.get_type( + kernel_symbol_table + constants.BANG + "_ERESOURCE" + ).size + ersrc_alignment = ( + 0x20 + if symbols.symbol_table_is_64bit(context, kernel_symbol_table) + else 0x10 + # 0x20 if context.symbol_space.get_type("pointer").size == 8 else 0x10 + ) + vollog.debug( + f"ERESOURCE size: {hex(ersrc_size)}, ERESOURCE alignment: {hex(ersrc_alignment)}" + ) + + eresource_rel_off = ersrc_size + ((offset - ersrc_size) % ersrc_alignment) + eresource_offset = offset - eresource_rel_off + + vollog.debug("Constructing ERESOURCE at %s" % hex(eresource_offset)) + eresource = context.object( + kernel_symbol_table + constants.BANG + "_ERESOURCE", + layer_name, + eresource_offset, + ) + if not eresource.is_valid(): + vollog.debug("ERESOURCE Invalid") + return None + + shim_head_offset = offset + rtl_avl_table.vol.size + + if not context.layers[layer_name].is_valid(shim_head_offset): + return None + + shim_head = context.object( + symbol_table + constants.BANG + "SHIM_CACHE_ENTRY", + layer_name, + shim_head_offset, + ) + + if not shim_head.is_valid(): + vollog.debug("shim head invalid") + return None + else: + vollog.debug("returning shim head") + return shim_head + + @classmethod + def find_shimcache_win_8_or_later( + cls, + context: interfaces.context.ContextInterface, + config_path: str, + kernel_layer_name: str, + nt_symbol_table: 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. + + :param context: The context to retrieve required elements (layers, symbol tables) from + :param layer_name: The name of the memory layer on which to operate. + :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 + """ + + is_8_1_or_later = versions.is_windows_8_1_or_later( + context, nt_symbol_table + ) or versions.is_win10(context, nt_symbol_table) + + module_names = ["ahcache.sys"] if is_8_1_or_later else cls.NT_KRNL_MODS + vollog.debug(f"Searching for modules {module_names}") + + data_sec = cls.get_module_section_range( + context, + config_path, + kernel_layer_name, + nt_symbol_table, + module_names, + ".data", + ) + mod_page = cls.get_module_section_range( + context, + config_path, + kernel_layer_name, + nt_symbol_table, + module_names, + "PAGE", + ) + + if not (data_sec and mod_page): + return None + + mod_page_offset, mod_page_size = mod_page + data_sec_offset, data_sec_size = data_sec + + # iterate over ahcache kernel module's .data section in search of *two* SHIM handles + shim_heads = [] + + vollog.debug(f"PAGE offset: {hex(mod_page_offset)}") + vollog.debug(f".data offset: {hex(data_sec_offset)}") + + handle_type = context.symbol_space.get_type( + shimcache_symbol_table + constants.BANG + "SHIM_CACHE_HANDLE" + ) + 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, + ): + vollog.debug(f"Building shim handle pointer at {hex(offset)}") + shim_handle = context.object( + object_type=shimcache_symbol_table + constants.BANG + "pointer", + layer_name=kernel_layer_name, + subtype=handle_type, + offset=offset, + ) + + if shim_handle.is_valid(mod_page_offset, mod_page_offset + mod_page_size): + if shim_handle.head is not None: + vollog.debug( + f"Found valid shim handle @ {hex(shim_handle.vol.offset)}" + ) + shim_heads.append(shim_handle.head) + if len(shim_heads) == 2: + break + + if len(shim_heads) != 2: + vollog.debug("Failed to identify two valid SHIM_CACHE_HANDLE structures") + return + + # On Windows 8 x64, the frist 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) + and not is_8_1_or_later + ): + valid_head = shim_heads[1] + elif not is_8_1_or_later: + valid_head = shim_heads[0] + else: + valid_head = shim_heads[1] + + for shim_entry in valid_head.ListEntry.to_list( + shimcache_symbol_table + constants.BANG + "SHIM_CACHE_ENTRY", "ListEntry" + ): + if shim_entry.is_valid(): + yield shim_entry + + def _generator(self): + kernel = self.context.modules[self.config["kernel"]] + + shimcache_table_name = self.create_shimcache_table( + self.context, kernel.symbol_table_name, self.config_path + ) + + c = count() + + if versions.is_windows_8_or_later(self._context, kernel.symbol_table_name): + vollog.info("Finding shimcache entries for Windows 8.0+") + entries = self.find_shimcache_win_8_or_later( + self.context, + self.config_path, + kernel.layer_name, + kernel.symbol_table_name, + shimcache_table_name, + ) + + elif ( + versions.is_2003(self.context, kernel.symbol_table_name) + or versions.is_vista_or_later(self.context, kernel.symbol_table_name) + or versions.is_windows_7(self.context, kernel.symbol_table_name) + ): + vollog.info("Finding shimcache entries for Windows 2k3/Vista/7") + entries = self.find_shimcache_win_2k3_to_7( + self.context, + self.config_path, + kernel.layer_name, + kernel.symbol_table_name, + shimcache_table_name, + ) + + elif versions.is_windows_xp_sp2( + self._context, kernel.symbol_table_name + ) or versions.is_windows_xp_sp3(self.context, kernel.symbol_table_name): + vollog.info("Finding shimcache entries for WinXP") + entries = self.find_shimcache_win_xp( + self._context, + kernel.layer_name, + kernel.symbol_table_name, + shimcache_table_name, + ) + else: + vollog.warn("Cannot parse shimcache entries for this version of Windows") + return + + for entry in entries: + try: + vollog.debug(f"SHIM_CACHE_ENTRY type: {entry.__class__}") + shim_entry = ( + entry.last_modified, + entry.last_update, + entry.exec_flag, + ( + format_hints.Hex(entry.file_size) + if isinstance(entry.file_size, int) + else entry.file_size + ), + entry.file_path, + ) + except exceptions.InvalidAddressException: + continue + + yield ( + 0, + (next(c), *shim_entry), + ) + + def run(self): + return renderers.TreeGrid( + [ + ("Order", int), + ("Last Modified", datetime), + ("Last Update", datetime), + ("Exec Flag", bool), + ("File Size", format_hints.Hex), + ("File Path", str), + ], + self._generator(), + ) + + @classmethod + def get_module_section_range( + cls, + context: interfaces.context.ContextInterface, + config_path: str, + layer_name: str, + symbol_table: str, + module_list: List[str], + section_name: str, + ) -> Optional[Tuple[int, int]]: + """Locates the size and offset of the first found module section + specified by name from the list of modules. + + :param context: The context to operate on + :param layer_name: The memory layer to read from + :param module_list: A list of module names to search for the given section + :param section_name: The name of the section to search for. + + :return: The offset and size of the module, if found; Otherwise, returns `None` + """ + + try: + krnl_mod = next( + module + for module in modules.Modules.list_modules( + context, layer_name, symbol_table + ) + if module.BaseDllName.String in module_list + ) + except StopIteration: + return None + + pe_table_name = intermed.IntermediateSymbolTable.create( + context, + interfaces.configuration.path_join(config_path, "pe"), + "windows", + "pe", + class_types=pe.class_types, + ) + + # code taken from Win32KBase._section_chunks (win32_core.py) + dos_header = context.object( + pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER", + layer_name, + offset=krnl_mod.DllBase, + ) + + if not dos_header: + return None + + nt_header = dos_header.get_nt_header() + + try: + section = next( + sec + for sec in nt_header.get_sections() + if section_name.lower() == array_to_string(sec.Name).lower() + ) + except StopIteration: + return None + + section_offset = krnl_mod.DllBase + section.VirtualAddress + section_size = section.Misc.VirtualSize + + return section_offset, section_size From 1508a414992ec958859ec5de84c3fbb51209d55f Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Thu, 18 Jul 2024 11:33:30 -0500 Subject: [PATCH 39/85] Move registry table init into the generator function --- .../framework/plugins/windows/registry/userassist.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/volatility3/framework/plugins/windows/registry/userassist.py b/volatility3/framework/plugins/windows/registry/userassist.py index cf345c901..bd832b20c 100644 --- a/volatility3/framework/plugins/windows/registry/userassist.py +++ b/volatility3/framework/plugins/windows/registry/userassist.py @@ -286,6 +286,10 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac 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" + ) + # get all the user hive offsets or use the one specified for hive in hivelist.HiveList.list_hives( context=self.context, @@ -337,10 +341,6 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac yield result def generate_timeline(self): - self._reg_table_name = intermed.IntermediateSymbolTable.create( - self.context, self._config_path, "windows", "registry" - ) - for row in self._generator(): _depth, row_data = row # check the name and the timestamp to not be empty @@ -351,10 +351,6 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac yield (description, timeliner.TimeLinerType.MODIFIED, row_data[10]) def run(self): - self._reg_table_name = intermed.IntermediateSymbolTable.create( - self.context, self._config_path, "windows", "registry" - ) - return renderers.TreeGrid( [ ("Hive Offset", renderers.format_hints.Hex), From 8da046530fd7ccd3f8a75fbe4e0b63d213207117 Mon Sep 17 00:00:00 2001 From: atcuno Date: Thu, 18 Jul 2024 14:57:31 -0500 Subject: [PATCH 40/85] Address feedback --- .../framework/plugins/windows/svcdiff.py | 22 ++++--- .../framework/plugins/windows/svclist.py | 58 +++++++++++-------- .../framework/plugins/windows/svcscan.py | 43 ++++++++------ 3 files changed, 69 insertions(+), 54 deletions(-) diff --git a/volatility3/framework/plugins/windows/svcdiff.py b/volatility3/framework/plugins/windows/svcdiff.py index 809064946..c41a7b86c 100644 --- a/volatility3/framework/plugins/windows/svcdiff.py +++ b/volatility3/framework/plugins/windows/svcdiff.py @@ -20,8 +20,7 @@ from volatility3.framework.symbols.windows import versions vollog = logging.getLogger(__name__) - -class SvcDiff(svclist.SvcList, svcscan.SvcScan): +class SvcDiff(svcscan.SvcScan): """Compares services found through list walking versus scanning to find rootkits""" _required_framework_version = (2, 4, 0) @@ -39,22 +38,23 @@ class SvcDiff(svclist.SvcList, svcscan.SvcScan): name="svclist", component=svclist.SvcList, version=(1, 0, 0) ), requirements.VersionRequirement( - name="svcscan", component=svcscan.SvcScan, version=(2, 0, 0) + name="svcscan", component=svcscan.SvcScan, version=(3, 0, 0) ), ] def _generator(self): """ - Finds services by walking the services.exe list on supported Windows 10 versions + On Windows 10 version 15063+ 64bit Windows memory samples, walk the services list + and scan for services then report differences """ - kernel = self.context.modules[self.config["kernel"]] + kernel, service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info() if not symbols.symbol_table_is_64bit( self.context, kernel.symbol_table_name ) or not versions.is_win10_15063_or_later( context=self.context, symbol_table=kernel.symbol_table_name ): - vollog.info( + vollog.warning( "This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples" ) return @@ -63,18 +63,16 @@ class SvcDiff(svclist.SvcList, svcscan.SvcScan): from_list = set() records = {} - service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info() - # collect unique service names from scanning - for service in self.service_scan( - service_table_name, service_binary_dll_map, filter_func + for service in svcscan.SvcScan.service_scan( + self.context, kernel, service_table_name, service_binary_dll_map, filter_func ): from_scan.add(service[6]) records[service[6]] = service # collect services from listing walking - for service in self.service_list( - service_table_name, service_binary_dll_map, filter_func + for service in svclist.SvcList.service_list( + self.context, kernel, service_table_name, service_binary_dll_map, filter_func ): from_list.add(service[6]) diff --git a/volatility3/framework/plugins/windows/svclist.py b/volatility3/framework/plugins/windows/svclist.py index 1938dd182..832b3d129 100644 --- a/volatility3/framework/plugins/windows/svclist.py +++ b/volatility3/framework/plugins/windows/svclist.py @@ -4,7 +4,7 @@ import logging -from typing import List +from typing import List, Optional, Tuple from volatility3.framework import interfaces, exceptions, symbols from volatility3.framework.configuration import requirements @@ -20,16 +20,26 @@ class SvcList(svcscan.SvcScan): _version = (1, 0, 0) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._enumeration_method = self.service_list + @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.PluginRequirement( - name="svcscan", plugin=svcscan.SvcScan, version=(2, 0, 0) + name="svcscan", plugin=svcscan.SvcScan, version=(3, 0, 0) + ), + requirements.ModuleRequirement( + name="kernel", + description="Windows kernel", + architectures=["Intel32", "Intel64"], ), ] - def _get_exe_range(self, proc): + @classmethod + def _get_exe_range(cls, proc) -> Optional[Tuple[int, int]]: """ Returns a tuple of starting,ending address for the VAD containing services.exe @@ -45,21 +55,27 @@ class SvcList(svcscan.SvcScan): return None - def service_list(self, service_table_name, service_binary_dll_map, filter_func): - kernel = self.context.modules[self.config["kernel"]] - + @classmethod + def service_list( + cls, + context: interfaces.context.ContextInterface, + kernel, + service_table_name: str, + service_binary_dll_map, + filter_func, + ): if not symbols.symbol_table_is_64bit( - self.context, kernel.symbol_table_name + context, kernel.symbol_table_name ) or not versions.is_win10_15063_or_later( - context=self.context, symbol_table=kernel.symbol_table_name + context=context, symbol_table=kernel.symbol_table_name ): - vollog.info( + vollog.warning( "This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples" ) return for proc in pslist.PsList.list_processes( - context=self.context, + context=context, layer_name=kernel.layer_name, symbol_table=kernel.symbol_table_name, filter_func=filter_func, @@ -74,9 +90,9 @@ class SvcList(svcscan.SvcScan): ) continue - layer = self.context.layers[layer_name] + layer = context.layers[layer_name] - exe_range = self._get_exe_range(proc) + exe_range = cls._get_exe_range(proc) if not exe_range: vollog.warning( "Could not find the application executable VAD for services.exe. Unable to proceed." @@ -84,19 +100,15 @@ class SvcList(svcscan.SvcScan): continue for offset in layer.scan( - context=self.context, + context=context, scanner=scanners.BytesScanner(needle=b"Sc27"), sections=exe_range, ): - for record in self.enumerate_vista_or_later_header( - service_table_name, service_binary_dll_map, layer_name, offset + for record in cls.enumerate_vista_or_later_header( + context, + service_table_name, + service_binary_dll_map, + layer_name, + offset, ): yield record - - def _generator(self): - service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info() - - for record in self.service_list( - service_table_name, service_binary_dll_map, filter_func - ): - yield (0, record) diff --git a/volatility3/framework/plugins/windows/svcscan.py b/volatility3/framework/plugins/windows/svcscan.py index 07274f03d..4368b83ce 100644 --- a/volatility3/framework/plugins/windows/svcscan.py +++ b/volatility3/framework/plugins/windows/svcscan.py @@ -39,7 +39,11 @@ class SvcScan(interfaces.plugins.PluginInterface): """Scans for windows services.""" _required_framework_version = (2, 0, 0) - _version = (2, 0, 0) + _version = (3, 0, 0) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._enumeration_method = self.service_scan @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -232,13 +236,14 @@ class SvcScan(interfaces.plugins.PluginInterface): for service_key in services } + @classmethod def enumerate_vista_or_later_header( - self, service_table_name, service_binary_dll_map, proc_layer_name, offset + cls, context, service_table_name, service_binary_dll_map, proc_layer_name, offset ): if offset % 8: return - service_header = self.context.object( + service_header =context.object( service_table_name + constants.BANG + "_SERVICE_HEADER", offset=offset, layer_name=proc_layer_name, @@ -257,17 +262,16 @@ class SvcScan(interfaces.plugins.PluginInterface): renderers.UnreadableValue(), renderers.UnreadableValue() ), ) - yield self.get_record_tuple(service_record, service_info) + yield cls.get_record_tuple(service_record, service_info) - def service_scan(self, service_table_name, service_binary_dll_map, filter_func): - kernel = self.context.modules[self.config["kernel"]] - - relative_tag_offset = self.context.symbol_space.get_type( + @classmethod + def service_scan(cls, context: interfaces.context.ContextInterface, kernel, service_table_name: str, service_binary_dll_map, filter_func): + 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=self.context, symbol_table=kernel.symbol_table_name + context=context, symbol_table=kernel.symbol_table_name ) if is_vista_or_later: @@ -278,7 +282,7 @@ class SvcScan(interfaces.plugins.PluginInterface): seen = [] for task in pslist.PsList.list_processes( - context=self.context, + context=context, layer_name=kernel.layer_name, symbol_table=kernel.symbol_table_name, filter_func=filter_func, @@ -295,15 +299,15 @@ class SvcScan(interfaces.plugins.PluginInterface): ) continue - layer = self.context.layers[proc_layer_name] + layer = context.layers[proc_layer_name] for offset in layer.scan( - context=self.context, + context=context, scanner=scanners.BytesScanner(needle=service_tag), sections=vadyarascan.VadYaraScan.get_vad_maps(task), ): if not is_vista_or_later: - service_record = self.context.object( + service_record = context.object( service_table_name + constants.BANG + "_SERVICE_RECORD", offset=offset - relative_tag_offset, layer_name=proc_layer_name, @@ -318,9 +322,10 @@ class SvcScan(interfaces.plugins.PluginInterface): renderers.UnreadableValue(), renderers.UnreadableValue() ), ) - yield self.get_record_tuple(service_record, service_info) + yield cls.get_record_tuple(service_record, service_info) else: - for service_record in self.enumerate_vista_or_later_header( + for service_record in cls.enumerate_vista_or_later_header( + context, service_table_name, service_binary_dll_map, proc_layer_name, @@ -351,13 +356,13 @@ class SvcScan(interfaces.plugins.PluginInterface): filter_func = pslist.PsList.create_name_filter(["services.exe"]) - return service_table_name, service_binary_dll_map, filter_func + return kernel, service_table_name, service_binary_dll_map, filter_func def _generator(self): - service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info() + kernel, service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info() - for record in self.service_scan( - service_table_name, service_binary_dll_map, filter_func + for record in self._enumeration_method( + self.context, kernel, service_table_name, service_binary_dll_map, filter_func ): yield (0, record) From a7af052509417c08eae702cd59fbfaa5306cc686 Mon Sep 17 00:00:00 2001 From: atcuno Date: Thu, 18 Jul 2024 14:59:40 -0500 Subject: [PATCH 41/85] Black fixes --- .../framework/plugins/windows/svcdiff.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/windows/svcdiff.py b/volatility3/framework/plugins/windows/svcdiff.py index c41a7b86c..4325771db 100644 --- a/volatility3/framework/plugins/windows/svcdiff.py +++ b/volatility3/framework/plugins/windows/svcdiff.py @@ -20,6 +20,7 @@ from volatility3.framework.symbols.windows import versions vollog = logging.getLogger(__name__) + class SvcDiff(svcscan.SvcScan): """Compares services found through list walking versus scanning to find rootkits""" @@ -47,7 +48,9 @@ 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, service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info() + kernel, service_table_name, service_binary_dll_map, filter_func = ( + self.get_prereq_info() + ) if not symbols.symbol_table_is_64bit( self.context, kernel.symbol_table_name @@ -65,14 +68,22 @@ class SvcDiff(svcscan.SvcScan): # collect unique service names from scanning for service in svcscan.SvcScan.service_scan( - self.context, kernel, service_table_name, service_binary_dll_map, filter_func + self.context, + kernel, + service_table_name, + service_binary_dll_map, + filter_func, ): from_scan.add(service[6]) records[service[6]] = service # collect services from listing walking for service in svclist.SvcList.service_list( - self.context, kernel, service_table_name, service_binary_dll_map, filter_func + self.context, + kernel, + service_table_name, + service_binary_dll_map, + filter_func, ): from_list.add(service[6]) From 2a5e94a0946e4c74f92588df6ceafdd89acd0be4 Mon Sep 17 00:00:00 2001 From: atcuno Date: Thu, 18 Jul 2024 15:01:10 -0500 Subject: [PATCH 42/85] Black fixes --- .../framework/plugins/windows/svcscan.py | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/plugins/windows/svcscan.py b/volatility3/framework/plugins/windows/svcscan.py index 4368b83ce..8b9706625 100644 --- a/volatility3/framework/plugins/windows/svcscan.py +++ b/volatility3/framework/plugins/windows/svcscan.py @@ -238,12 +238,17 @@ class SvcScan(interfaces.plugins.PluginInterface): @classmethod def enumerate_vista_or_later_header( - cls, context, service_table_name, service_binary_dll_map, proc_layer_name, offset + cls, + context, + service_table_name, + service_binary_dll_map, + proc_layer_name, + offset, ): if offset % 8: return - service_header =context.object( + service_header = context.object( service_table_name + constants.BANG + "_SERVICE_HEADER", offset=offset, layer_name=proc_layer_name, @@ -265,7 +270,14 @@ class SvcScan(interfaces.plugins.PluginInterface): yield cls.get_record_tuple(service_record, service_info) @classmethod - def service_scan(cls, context: interfaces.context.ContextInterface, kernel, service_table_name: str, service_binary_dll_map, filter_func): + def service_scan( + cls, + context: interfaces.context.ContextInterface, + kernel, + service_table_name: str, + service_binary_dll_map, + filter_func, + ): relative_tag_offset = context.symbol_space.get_type( service_table_name + constants.BANG + "_SERVICE_RECORD" ).relative_child_offset("Tag") @@ -359,10 +371,16 @@ class SvcScan(interfaces.plugins.PluginInterface): return kernel, service_table_name, service_binary_dll_map, filter_func def _generator(self): - kernel, service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info() + kernel, service_table_name, service_binary_dll_map, filter_func = ( + self.get_prereq_info() + ) for record in self._enumeration_method( - self.context, kernel, service_table_name, service_binary_dll_map, filter_func + self.context, + kernel, + service_table_name, + service_binary_dll_map, + filter_func, ): yield (0, record) From b0a89f210977663c8d106c50ac4602eb48b7b211 Mon Sep 17 00:00:00 2001 From: atcuno Date: Thu, 18 Jul 2024 15:47:37 -0500 Subject: [PATCH 43/85] Address feedback --- volatility3/framework/plugins/windows/processghosting.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/windows/processghosting.py b/volatility3/framework/plugins/windows/processghosting.py index b29ff04f0..dda0d7675 100644 --- a/volatility3/framework/plugins/windows/processghosting.py +++ b/volatility3/framework/plugins/windows/processghosting.py @@ -2,6 +2,7 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # import logging +import contextlib from volatility3.framework import interfaces, exceptions from volatility3.framework import renderers @@ -57,14 +58,15 @@ class ProcessGhosting(interfaces.plugins.PluginInterface): else: file_object = 0 + if isinstance(delete_pending, int) and delete_pending not in [0, 1]: + vollog.debug(f"Invalid delete_pending value {delete_pending} found for {process_name} {proc.UniqueProcessId}") + # delete_pending besides 0 or 1 = smear if file_object == 0 or delete_pending == 1: path = renderers.UnreadableValue() if file_object: - try: + with contextlib.suppress(exceptions.InvalidAddressException): path = file_object.FileName.String - except exceptions.InvalidAddressException: - path = renderers.UnreadableValue() yield ( 0, From 920b3ec615b91ec4dcc4bdf0ef9d15715a5b1c8d Mon Sep 17 00:00:00 2001 From: atcuno Date: Thu, 18 Jul 2024 15:48:21 -0500 Subject: [PATCH 44/85] Black fix --- volatility3/framework/plugins/windows/processghosting.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/windows/processghosting.py b/volatility3/framework/plugins/windows/processghosting.py index dda0d7675..50f02c926 100644 --- a/volatility3/framework/plugins/windows/processghosting.py +++ b/volatility3/framework/plugins/windows/processghosting.py @@ -59,7 +59,9 @@ class ProcessGhosting(interfaces.plugins.PluginInterface): file_object = 0 if isinstance(delete_pending, int) and delete_pending not in [0, 1]: - vollog.debug(f"Invalid delete_pending value {delete_pending} found for {process_name} {proc.UniqueProcessId}") + vollog.debug( + f"Invalid delete_pending value {delete_pending} found for {process_name} {proc.UniqueProcessId}" + ) # delete_pending besides 0 or 1 = smear if file_object == 0 or delete_pending == 1: From 75ccf1bfab5ee6bc3d0f84baf427c11b8e61137c Mon Sep 17 00:00:00 2001 From: atcuno Date: Thu, 18 Jul 2024 17:54:33 -0500 Subject: [PATCH 45/85] Add dedicated plugin and API for extracting PE files from kernel and process memory --- .../framework/plugins/windows/dlllist.py | 76 +----- .../framework/plugins/windows/modscan.py | 11 +- .../framework/plugins/windows/modules.py | 12 +- .../framework/plugins/windows/pedump.py | 239 ++++++++++++++++++ 4 files changed, 258 insertions(+), 80 deletions(-) create mode 100644 volatility3/framework/plugins/windows/pedump.py diff --git a/volatility3/framework/plugins/windows/dlllist.py b/volatility3/framework/plugins/windows/dlllist.py index d48a53663..eef826ed5 100644 --- a/volatility3/framework/plugins/windows/dlllist.py +++ b/volatility3/framework/plugins/windows/dlllist.py @@ -1,10 +1,9 @@ -# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# 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 contextlib import datetime import logging -import ntpath import re from typing import List, Optional, Type @@ -14,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 +from volatility3.plugins.windows import info, pslist, psscan, pedump vollog = logging.getLogger(__name__) @@ -23,7 +22,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): """Lists the loaded modules in a particular windows memory image.""" _required_framework_version = (2, 0, 0) - _version = (2, 0, 1) + _version = (3, 0, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -76,66 +75,10 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): default=False, optional=True, ), - ] - - @classmethod - def dump_pe( - cls, - context: interfaces.context.ContextInterface, - pe_table_name: str, - dll_entry: interfaces.objects.ObjectInterface, - open_method: Type[interfaces.plugins.FileHandlerInterface], - layer_name: str = None, - prefix: str = "", - ) -> Optional[interfaces.plugins.FileHandlerInterface]: - """Extracts the complete data for a process as a FileInterface - - Args: - context: the context to operate upon - pe_table_name: the name for the symbol table containing the PE format symbols - dll_entry: the object representing the module - layer_name: the layer that the DLL lives within - open_method: class for constructing output files - - Returns: - An open FileHandlerInterface object containing the complete data for the DLL or None in the case of failure - """ - try: - try: - name = dll_entry.FullDllName.get_string() - except exceptions.InvalidAddressException: - name = "UnreadableDLLName" - - if layer_name is None: - layer_name = dll_entry.vol.layer_name - - file_handle = open_method( - "{}{}.{:#x}.{:#x}.dmp".format( - prefix, - ntpath.basename(name), - dll_entry.vol.offset, - dll_entry.DllBase, - ) - ) - - dos_header = context.object( - pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER", - offset=dll_entry.DllBase, - layer_name=layer_name, - ) - - for offset, data in dos_header.reconstruct(): - file_handle.seek(offset) - file_handle.write(data) - except ( - IOError, - exceptions.VolatilityException, - OverflowError, - ValueError, - ) as excp: - vollog.debug(f"Unable to dump dll at offset {dll_entry.DllBase}: {excp}") - return None - return file_handle + requirements.VersionRequirement( + name="pedump", component=pedump.PEDump, version=(1, 0, 0) + ), + ] def _generator(self, procs): pe_table_name = intermed.IntermediateSymbolTable.create( @@ -204,7 +147,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): file_output = "Disabled" if self.config["dump"]: - file_handle = self.dump_pe( + file_handle = pedump.PEDump.dump_ldr_entry( self.context, pe_table_name, entry, @@ -214,8 +157,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): ) file_output = "Error outputting file" if file_handle: - file_handle.close() - file_output = file_handle.preferred_filename + file_output = file_handle try: dllbase = format_hints.Hex(entry.DllBase) except exceptions.InvalidAddressException: diff --git a/volatility3/framework/plugins/windows/modscan.py b/volatility3/framework/plugins/windows/modscan.py index 98546bc9c..fc45e6913 100644 --- a/volatility3/framework/plugins/windows/modscan.py +++ b/volatility3/framework/plugins/windows/modscan.py @@ -6,7 +6,7 @@ from typing import Iterable from volatility3.framework import interfaces from volatility3.framework.configuration import requirements -from volatility3.plugins.windows import poolscanner, dlllist, pslist, modules +from volatility3.plugins.windows import poolscanner, modules, pedump vollog = logging.getLogger(__name__) @@ -35,12 +35,6 @@ class ModScan(modules.Modules): requirements.VersionRequirement( name="modules", component=modules.Modules, version=(2, 0, 0) ), - requirements.VersionRequirement( - name="pslist", component=pslist.PsList, version=(2, 0, 0) - ), - requirements.VersionRequirement( - name="dlllist", component=dlllist.DllList, version=(2, 0, 0) - ), requirements.BooleanRequirement( name="dump", description="Extract listed modules", @@ -58,6 +52,9 @@ class ModScan(modules.Modules): optional=True, default=None, ), + requirements.VersionRequirement( + name="pedump", component=pedump.PEDump, version=(1, 0, 0) + ), ] @classmethod diff --git a/volatility3/framework/plugins/windows/modules.py b/volatility3/framework/plugins/windows/modules.py index 79eea1cd7..283d4dcb9 100644 --- a/volatility3/framework/plugins/windows/modules.py +++ b/volatility3/framework/plugins/windows/modules.py @@ -9,7 +9,7 @@ from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import intermed from volatility3.framework.symbols.windows.extensions import pe -from volatility3.plugins.windows import pslist, dlllist +from volatility3.plugins.windows import pslist, pedump vollog = logging.getLogger(__name__) @@ -35,9 +35,6 @@ class Modules(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(2, 0, 0) ), - requirements.VersionRequirement( - name="dlllist", component=dlllist.DllList, version=(2, 0, 0) - ), requirements.BooleanRequirement( name="dump", description="Extract listed modules", @@ -55,6 +52,9 @@ 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): @@ -63,7 +63,7 @@ class Modules(interfaces.plugins.PluginInterface): ) file_output = f"Cannot find a viable session layer for {mod.DllBase:#x}" if session_layer_name: - file_handle = dlllist.DllList.dump_pe( + file_handle = pedump.PEDump.dump_ldr_entry( self.context, pe_table_name, mod, @@ -72,7 +72,7 @@ class Modules(interfaces.plugins.PluginInterface): ) file_output = "Error outputting file" if file_handle: - file_output = file_handle.preferred_filename + file_output = file_handle return file_output diff --git a/volatility3/framework/plugins/windows/pedump.py b/volatility3/framework/plugins/windows/pedump.py new file mode 100644 index 000000000..395d28ce5 --- /dev/null +++ b/volatility3/framework/plugins/windows/pedump.py @@ -0,0 +1,239 @@ +# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging +import ntpath +from typing import List, Type, Optional + +from volatility3.framework import constants, exceptions, interfaces, renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.symbols import intermed +from volatility3.framework.symbols.windows.extensions import pe +from volatility3.plugins.windows import pslist, modules + +vollog = logging.getLogger(__name__) + + +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) + + @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.VersionRequirement( + name="pslist", component=pslist.PsList, version=(2, 0, 0) + ), + requirements.ListRequirement( + name="pid", + element_type=int, + description="Process IDs to include (all other processes are excluded)", + optional=True, + ), + requirements.IntRequirement( + name="base", + description="Base address to reconstruct a PE file", + optional=False, + ), + requirements.BooleanRequirement( + name="kernel_module", + description="Extract from kernel address space.", + default=False, + optional=True, + ), + ] + + @classmethod + def dump_pe( + cls, + context: interfaces.context.ContextInterface, + pe_table_name: str, + layer_name: str, + open_method: Type[interfaces.plugins.FileHandlerInterface], + file_name: str, + base: int + ) -> Optional[str]: + """ + Returns the filename of the dump file or None + """ + try: + file_handle = open_method(file_name) + + dos_header = context.object( + pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER", + offset=base, + layer_name=layer_name, + ) + + for offset, data in dos_header.reconstruct(): + file_handle.seek(offset) + file_handle.write(data) + except ( + IOError, + exceptions.VolatilityException, + OverflowError, + ValueError, + ) as excp: + vollog.debug(f"Unable to dump PE file at offset {base}: {excp}") + return None + finally: + file_handle.close() + + return file_handle.preferred_filename + + @classmethod + def dump_ldr_entry( + cls, + context: interfaces.context.ContextInterface, + pe_table_name: str, + ldr_entry: interfaces.objects.ObjectInterface, + open_method: Type[interfaces.plugins.FileHandlerInterface], + layer_name: str = None, + prefix: str = "", + ) -> Optional[str]: + """Extracts the PE file referenced an LDR_DATA_TABLE_ENTRY (DLL, kernel module) instance + + Args: + context: the context to operate upon + pe_table_name: the name for the symbol table containing the PE format symbols + ldr_entry: the object representing the module + open_method: class for constructing output files + layer_name: the layer that the DLL lives within + prefix: optional string to prepend to filename + Returns: + The output file name or None in the case of failure + """ + try: + name = ldr_entry.FullDllName.get_string() + except exceptions.InvalidAddressException: + name = "UnreadableDLLName" + + if layer_name is None: + layer_name = ldr_entry.vol.layer_name + + file_name = "{}{}.{:#x}.{:#x}.dmp".format( + prefix, + ntpath.basename(name), + ldr_entry.vol.offset, + ldr_entry.DllBase, + ) + + return PEDump.dump_pe(context, pe_table_name, layer_name, open_method, file_name, ldr_entry.DllBase) + + @classmethod + def dump_pe_at_base( + cls, + context: interfaces.context.ContextInterface, + pe_table_name: str, + layer_name: str, + open_method: Type[interfaces.plugins.FileHandlerInterface], + proc_offset: int, + pid: int, + base: int, + ) -> Optional[str]: + file_name = "PE.{:#x}.{:d}.{:#x}.dmp".format( + proc_offset, + pid, + base, + ) + + return PEDump.dump_pe(context, pe_table_name, layer_name, open_method, file_name, base) + + @classmethod + def dump_kernel_pe_at_base(cls, context, kernel, pe_table_name, open_method, base): + session_layers = modules.Modules.get_session_layers( + context, kernel.layer_name, kernel.symbol_table_name + ) + + session_layer_name = modules.Modules.find_session_layer( + context, session_layers, base + ) + + if session_layer_name: + system_pid = 4 + + file_output = PEDump.dump_pe_at_base( + context, pe_table_name, session_layer_name, open_method, 0, system_pid, base + ) + + if file_output: + yield system_pid, "Kernel", file_output + else: + vollog.warning( + "Unable to find a session layer with the provided base address mapped in the kernel." + ) + + @classmethod + def dump_processes(cls, context, kernel, pe_table_name, open_method, filter_func, base): + """ + """ + + for proc in pslist.PsList.list_processes( + context=context, + layer_name=kernel.layer_name, + symbol_table=kernel.symbol_table_name, + filter_func=filter_func, + ): + pid = proc.UniqueProcessId + proc_name = proc.ImageFileName.cast( + "string", + max_length=proc.ImageFileName.vol.count, + errors="replace", + ) + proc_layer_name = proc.add_process_layer() + + file_output = PEDump.dump_pe_at_base( + context, pe_table_name, proc_layer_name, open_method, proc.vol.offset, pid, base + ) + + if file_output: + yield pid, proc_name, file_output + + def _generator(self): + kernel = self.context.modules[self.config["kernel"]] + + pe_table_name = intermed.IntermediateSymbolTable.create( + self.context, self.config_path, "windows", "pe", class_types=pe.class_types + ) + + if self.config["kernel_module"] and self.config["pid"]: + vollog.error("Only --kernel_module or --pid should be set. Not both") + return + + if not self.config["kernel_module"] and not self.config["pid"]: + vollog.error("--kernel_module or --pid must be set") + return + + if self.config["kernel_module"]: + pe_files = self.dump_kernel_pe_at_base(self.context, kernel, pe_table_name, self.open, self.config["base"]) + else: + filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) + pe_files = self.dump_processes(self.context, kernel, pe_table_name, self.open, filter_func, self.config["base"]) + + for pid, proc_name, file_output in pe_files: + yield ( + 0, + ( + pid, + proc_name, + file_output, + ), + ) + + def run(self): + return renderers.TreeGrid( + [ + ("PID", int), + ("Process", str), + ("File output", str), + ], + self._generator(), + ) From 12f3beacfcb3ab16f8418cee2821ad74c3738045 Mon Sep 17 00:00:00 2001 From: atcuno Date: Thu, 18 Jul 2024 17:56:24 -0500 Subject: [PATCH 46/85] Add dedicated plugin and API for extracting PE files from kernel and process memory --- .../framework/plugins/windows/dlllist.py | 2 +- .../framework/plugins/windows/pedump.py | 69 ++++++++++++++----- 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/volatility3/framework/plugins/windows/dlllist.py b/volatility3/framework/plugins/windows/dlllist.py index eef826ed5..6c8c96dc3 100644 --- a/volatility3/framework/plugins/windows/dlllist.py +++ b/volatility3/framework/plugins/windows/dlllist.py @@ -78,7 +78,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): requirements.VersionRequirement( name="pedump", component=pedump.PEDump, version=(1, 0, 0) ), - ] + ] def _generator(self, procs): pe_table_name = intermed.IntermediateSymbolTable.create( diff --git a/volatility3/framework/plugins/windows/pedump.py b/volatility3/framework/plugins/windows/pedump.py index 395d28ce5..0b4649abb 100644 --- a/volatility3/framework/plugins/windows/pedump.py +++ b/volatility3/framework/plugins/windows/pedump.py @@ -59,7 +59,7 @@ class PEDump(interfaces.plugins.PluginInterface): layer_name: str, open_method: Type[interfaces.plugins.FileHandlerInterface], file_name: str, - base: int + base: int, ) -> Optional[str]: """ Returns the filename of the dump file or None @@ -120,13 +120,20 @@ class PEDump(interfaces.plugins.PluginInterface): layer_name = ldr_entry.vol.layer_name file_name = "{}{}.{:#x}.{:#x}.dmp".format( - prefix, - ntpath.basename(name), - ldr_entry.vol.offset, - ldr_entry.DllBase, - ) + prefix, + ntpath.basename(name), + ldr_entry.vol.offset, + ldr_entry.DllBase, + ) - return PEDump.dump_pe(context, pe_table_name, layer_name, open_method, file_name, ldr_entry.DllBase) + return PEDump.dump_pe( + context, + pe_table_name, + layer_name, + open_method, + file_name, + ldr_entry.DllBase, + ) @classmethod def dump_pe_at_base( @@ -140,12 +147,14 @@ class PEDump(interfaces.plugins.PluginInterface): base: int, ) -> Optional[str]: file_name = "PE.{:#x}.{:d}.{:#x}.dmp".format( - proc_offset, - pid, - base, - ) + proc_offset, + pid, + base, + ) - return PEDump.dump_pe(context, pe_table_name, layer_name, open_method, file_name, base) + return PEDump.dump_pe( + context, pe_table_name, layer_name, open_method, file_name, base + ) @classmethod def dump_kernel_pe_at_base(cls, context, kernel, pe_table_name, open_method, base): @@ -161,7 +170,13 @@ class PEDump(interfaces.plugins.PluginInterface): system_pid = 4 file_output = PEDump.dump_pe_at_base( - context, pe_table_name, session_layer_name, open_method, 0, system_pid, base + context, + pe_table_name, + session_layer_name, + open_method, + 0, + system_pid, + base, ) if file_output: @@ -172,9 +187,10 @@ class PEDump(interfaces.plugins.PluginInterface): ) @classmethod - def dump_processes(cls, context, kernel, pe_table_name, open_method, filter_func, base): - """ - """ + def dump_processes( + cls, context, kernel, pe_table_name, open_method, filter_func, base + ): + """ """ for proc in pslist.PsList.list_processes( context=context, @@ -191,7 +207,13 @@ class PEDump(interfaces.plugins.PluginInterface): proc_layer_name = proc.add_process_layer() file_output = PEDump.dump_pe_at_base( - context, pe_table_name, proc_layer_name, open_method, proc.vol.offset, pid, base + context, + pe_table_name, + proc_layer_name, + open_method, + proc.vol.offset, + pid, + base, ) if file_output: @@ -213,10 +235,19 @@ class PEDump(interfaces.plugins.PluginInterface): return if self.config["kernel_module"]: - pe_files = self.dump_kernel_pe_at_base(self.context, kernel, pe_table_name, self.open, self.config["base"]) + pe_files = self.dump_kernel_pe_at_base( + self.context, kernel, pe_table_name, self.open, self.config["base"] + ) else: filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) - pe_files = self.dump_processes(self.context, kernel, pe_table_name, self.open, filter_func, self.config["base"]) + pe_files = self.dump_processes( + self.context, + kernel, + pe_table_name, + self.open, + filter_func, + self.config["base"], + ) for pid, proc_name, file_output in pe_files: yield ( From 9454181b9892f5c4245435bb868f24aaee23ae01 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sun, 21 Jul 2024 09:42:19 -0500 Subject: [PATCH 47/85] Address feedback --- volatility3/framework/plugins/windows/dlllist.py | 9 +++++---- volatility3/framework/plugins/windows/modules.py | 7 +++---- volatility3/framework/plugins/windows/pedump.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/volatility3/framework/plugins/windows/dlllist.py b/volatility3/framework/plugins/windows/dlllist.py index 6c8c96dc3..5a1b37fcf 100644 --- a/volatility3/framework/plugins/windows/dlllist.py +++ b/volatility3/framework/plugins/windows/dlllist.py @@ -147,7 +147,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): file_output = "Disabled" if self.config["dump"]: - file_handle = pedump.PEDump.dump_ldr_entry( + file_output = pedump.PEDump.dump_ldr_entry( self.context, pe_table_name, entry, @@ -155,9 +155,10 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): proc_layer_name, prefix=f"pid.{proc_id}.", ) - file_output = "Error outputting file" - if file_handle: - file_output = file_handle + + if not file_output: + file_output = "Error outputting file" + try: dllbase = format_hints.Hex(entry.DllBase) except exceptions.InvalidAddressException: diff --git a/volatility3/framework/plugins/windows/modules.py b/volatility3/framework/plugins/windows/modules.py index 283d4dcb9..ba45834d5 100644 --- a/volatility3/framework/plugins/windows/modules.py +++ b/volatility3/framework/plugins/windows/modules.py @@ -63,16 +63,15 @@ class Modules(interfaces.plugins.PluginInterface): ) file_output = f"Cannot find a viable session layer for {mod.DllBase:#x}" if session_layer_name: - file_handle = pedump.PEDump.dump_ldr_entry( + file_output = pedump.PEDump.dump_ldr_entry( self.context, pe_table_name, mod, self.open, layer_name=session_layer_name, ) - file_output = "Error outputting file" - if file_handle: - file_output = file_handle + if not file_output: + file_output = "Error outputting file" return file_output diff --git a/volatility3/framework/plugins/windows/pedump.py b/volatility3/framework/plugins/windows/pedump.py index 0b4649abb..858d0615a 100644 --- a/volatility3/framework/plugins/windows/pedump.py +++ b/volatility3/framework/plugins/windows/pedump.py @@ -126,7 +126,7 @@ class PEDump(interfaces.plugins.PluginInterface): ldr_entry.DllBase, ) - return PEDump.dump_pe( + return cls.dump_pe( context, pe_table_name, layer_name, From 21396185d0faaab15a217cff2072aa7de2652b67 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sun, 21 Jul 2024 11:10:38 -0500 Subject: [PATCH 48/85] Address feedback --- .../framework/plugins/windows/svcdiff.py | 37 ++++++++++----- .../framework/plugins/windows/svclist.py | 11 +++-- .../framework/plugins/windows/svcscan.py | 47 +++++++++++-------- 3 files changed, 58 insertions(+), 37 deletions(-) diff --git a/volatility3/framework/plugins/windows/svcdiff.py b/volatility3/framework/plugins/windows/svcdiff.py index 4325771db..84d06a695 100644 --- a/volatility3/framework/plugins/windows/svcdiff.py +++ b/volatility3/framework/plugins/windows/svcdiff.py @@ -13,7 +13,7 @@ import logging -from volatility3.framework import symbols +from volatility3.framework import symbols, interfaces from volatility3.framework.configuration import requirements from volatility3.plugins.windows import svclist, svcscan from volatility3.framework.symbols.windows import versions @@ -26,6 +26,10 @@ class SvcDiff(svcscan.SvcScan): _required_framework_version = (2, 4, 0) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._enumeration_method = self.service_diff + @classmethod def get_requirements(cls): # Since we're calling the plugin, make sure we have the plugin's requirements @@ -43,19 +47,24 @@ class SvcDiff(svcscan.SvcScan): ), ] - def _generator(self): + @classmethod + def service_diff( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str, + service_table_name: str, + service_binary_dll_map, + filter_func, + ): """ On Windows 10 version 15063+ 64bit Windows memory samples, walk the services list and scan for services then report differences """ - kernel, service_table_name, service_binary_dll_map, filter_func = ( - self.get_prereq_info() - ) - if not symbols.symbol_table_is_64bit( - self.context, kernel.symbol_table_name + context, symbol_table ) or not versions.is_win10_15063_or_later( - context=self.context, symbol_table=kernel.symbol_table_name + context=context, symbol_table=symbol_table ): vollog.warning( "This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples" @@ -68,8 +77,9 @@ class SvcDiff(svcscan.SvcScan): # collect unique service names from scanning for service in svcscan.SvcScan.service_scan( - self.context, - kernel, + context, + layer_name, + symbol_table, service_table_name, service_binary_dll_map, filter_func, @@ -79,8 +89,9 @@ class SvcDiff(svcscan.SvcScan): # collect services from listing walking for service in svclist.SvcList.service_list( - self.context, - kernel, + context, + layer_name, + symbol_table, service_table_name, service_binary_dll_map, filter_func, @@ -89,4 +100,4 @@ class SvcDiff(svcscan.SvcScan): # report services found from scanning but not list walking for hidden_service in from_scan - from_list: - yield (0, records[hidden_service]) + yield records[hidden_service] diff --git a/volatility3/framework/plugins/windows/svclist.py b/volatility3/framework/plugins/windows/svclist.py index 832b3d129..a59581063 100644 --- a/volatility3/framework/plugins/windows/svclist.py +++ b/volatility3/framework/plugins/windows/svclist.py @@ -59,15 +59,16 @@ class SvcList(svcscan.SvcScan): def service_list( cls, context: interfaces.context.ContextInterface, - kernel, + layer_name: str, + symbol_table: str, service_table_name: str, service_binary_dll_map, filter_func, ): if not symbols.symbol_table_is_64bit( - context, kernel.symbol_table_name + context, symbol_table ) or not versions.is_win10_15063_or_later( - context=context, symbol_table=kernel.symbol_table_name + context=context, symbol_table=symbol_table ): vollog.warning( "This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples" @@ -76,8 +77,8 @@ class SvcList(svcscan.SvcScan): for proc in pslist.PsList.list_processes( context=context, - layer_name=kernel.layer_name, - symbol_table=kernel.symbol_table_name, + layer_name=layer_name, + symbol_table=symbol_table, filter_func=filter_func, ): try: diff --git a/volatility3/framework/plugins/windows/svcscan.py b/volatility3/framework/plugins/windows/svcscan.py index 8b9706625..bf676acb7 100644 --- a/volatility3/framework/plugins/windows/svcscan.py +++ b/volatility3/framework/plugins/windows/svcscan.py @@ -148,14 +148,17 @@ class SvcScan(interfaces.plugins.PluginInterface): native_types=native_types, ) - def _get_service_key(self, kernel) -> Optional[objects.StructType]: + @classmethod + def _get_service_key( + cls, context, config_path: str, layer_name: str, symbol_table: str + ) -> Optional[objects.StructType]: for hive in hivelist.HiveList.list_hives( - context=self.context, + context=context, base_config_path=interfaces.configuration.path_join( - self.config_path, "hivelist" + config_path, "hivelist" ), - layer_name=kernel.layer_name, - symbol_table=kernel.symbol_table_name, + layer_name=layer_name, + symbol_table=symbol_table, filter_string="machine\\system", ): # Get ControlSet\Services. @@ -273,7 +276,8 @@ class SvcScan(interfaces.plugins.PluginInterface): def service_scan( cls, context: interfaces.context.ContextInterface, - kernel, + layer_name: str, + symbol_table: str, service_table_name: str, service_binary_dll_map, filter_func, @@ -283,7 +287,7 @@ class SvcScan(interfaces.plugins.PluginInterface): ).relative_child_offset("Tag") is_vista_or_later = versions.is_vista_or_later( - context=context, symbol_table=kernel.symbol_table_name + context=context, symbol_table=symbol_table ) if is_vista_or_later: @@ -295,8 +299,8 @@ class SvcScan(interfaces.plugins.PluginInterface): for task in pslist.PsList.list_processes( context=context, - layer_name=kernel.layer_name, - symbol_table=kernel.symbol_table_name, + layer_name=layer_name, + symbol_table=symbol_table, filter_func=filter_func, ): proc_id = "Unknown" @@ -348,36 +352,41 @@ class SvcScan(interfaces.plugins.PluginInterface): seen.append(service_record) yield service_record - def get_prereq_info(self): + @classmethod + def get_prereq_info(cls, context, config_path, layer_name: str, symbol_table: str): """ Data structures and information needed to analyze service information """ - kernel = self.context.modules[self.config["kernel"]] - service_table_name = self.create_service_table( - self.context, kernel.symbol_table_name, self.config_path + service_table_name = cls.create_service_table( + context, symbol_table, config_path ) - services_key = self._get_service_key(kernel) + services_key = cls._get_service_key( + context, config_path, layer_name, symbol_table + ) service_binary_dll_map = ( - self._get_service_binary_map(services_key) + cls._get_service_binary_map(services_key) if services_key is not None else {} ) filter_func = pslist.PsList.create_name_filter(["services.exe"]) - return kernel, service_table_name, service_binary_dll_map, filter_func + return service_table_name, service_binary_dll_map, filter_func def _generator(self): - kernel, service_table_name, service_binary_dll_map, filter_func = ( - self.get_prereq_info() + 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 ) for record in self._enumeration_method( self.context, - kernel, + kernel.layer_name, + kernel.symbol_table_name, service_table_name, service_binary_dll_map, filter_func, From 111873e173b1bc639754b851ce46ff5056f8edb8 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Sun, 21 Jul 2024 11:54:06 -0500 Subject: [PATCH 49/85] Fix class vs static method and leading underscores --- volatility3/framework/plugins/windows/svcscan.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/volatility3/framework/plugins/windows/svcscan.py b/volatility3/framework/plugins/windows/svcscan.py index bf676acb7..52ed5e759 100644 --- a/volatility3/framework/plugins/windows/svcscan.py +++ b/volatility3/framework/plugins/windows/svcscan.py @@ -110,7 +110,7 @@ class SvcScan(interfaces.plugins.PluginInterface): ] @staticmethod - def create_service_table( + def _create_service_table( context: interfaces.context.ContextInterface, symbol_table: str, config_path: str, @@ -148,9 +148,9 @@ class SvcScan(interfaces.plugins.PluginInterface): native_types=native_types, ) - @classmethod + @staticmethod def _get_service_key( - cls, context, config_path: str, layer_name: str, symbol_table: str + context, config_path: str, layer_name: str, symbol_table: str ) -> Optional[objects.StructType]: for hive in hivelist.HiveList.list_hives( context=context, @@ -358,7 +358,7 @@ class SvcScan(interfaces.plugins.PluginInterface): Data structures and information needed to analyze service information """ - service_table_name = cls.create_service_table( + service_table_name = cls._create_service_table( context, symbol_table, config_path ) From 5e96327cb0851fe75d1c7a4cb9ef27641bf119f7 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 21 Jul 2024 22:58:43 +0100 Subject: [PATCH 50/85] Add in threads that only provides an implmentation method --- .../framework/plugins/windows/thrdscan.py | 32 +++++++++++------ .../framework/plugins/windows/threads.py | 34 +++++++++++++------ 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/volatility3/framework/plugins/windows/thrdscan.py b/volatility3/framework/plugins/windows/thrdscan.py index bbf65cd6c..be7097347 100644 --- a/volatility3/framework/plugins/windows/thrdscan.py +++ b/volatility3/framework/plugins/windows/thrdscan.py @@ -3,7 +3,7 @@ ## import logging import datetime -from typing import Iterable +from typing import Callable, Iterable from volatility3.framework import renderers, interfaces, exceptions from volatility3.framework.configuration import requirements @@ -21,6 +21,10 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) _required_framework_version = (2, 6, 0) _version = (1, 0, 0) + def __init__(self, *args, **kwargs): + self.implementation = self.scan_threads + super().__init__(*args, **kwargs) + @classmethod def get_requirements(cls): return [ @@ -38,8 +42,7 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) def scan_threads( cls, context: interfaces.context.ContextInterface, - layer_name: str, - symbol_table: str, + module_name: str, ) -> Iterable[interfaces.objects.ObjectInterface]: """Scans for threads using the poolscanner module and constraints. @@ -52,6 +55,10 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) A list of _ETHREAD objects found by scanning memory for the "Thre" / "Thr\\xE5" pool signatures """ + module = context.modules[module_name] + layer_name = module.layer_name + symbol_table = module.symbol_table_name + constraints = poolscanner.PoolScanner.builtin_constraints( symbol_table, [b"Thr\xe5", b"Thre"] ) @@ -76,7 +83,7 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) ethread.get_exit_time() ) # datetime.datetime object / volatility3.framework.renderers.UnparsableValue object except exceptions.InvalidAddressException: - vollog.debug("Thread invalid address {:#x}".format(thread.vol.offset)) + vollog.debug("Thread invalid address {:#x}".format(ethread.vol.offset)) return None return ( @@ -88,12 +95,10 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) thread_exit_time, ) - def _generator(self): - kernel = self.context.modules[self.config["kernel"]] + def _generator(self, filter_func: Callable): + kernel_name = self.config["kernel"] - for ethread in self.scan_threads( - self.context, kernel.layer_name, kernel.symbol_table_name - ): + for ethread in self.implementation(self.context, kernel_name): info = self.gather_thread_info(ethread) if info: yield (0, info) @@ -126,7 +131,14 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) row_dict["ExitTime"], ) + @classmethod + def filter_func(cls, config: interfaces.configuration.HierarchicalDict) -> Callable: + """Returns a function that can filter this plugin's implementation method based on the config""" + return lambda x: False + def run(self): + filt_func = self.filter_func(self.config) + return renderers.TreeGrid( [ ("Offset", format_hints.Hex), @@ -136,5 +148,5 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) ("CreateTime", datetime.datetime), ("ExitTime", datetime.datetime), ], - self._generator(), + self._generator(filt_func), ) diff --git a/volatility3/framework/plugins/windows/threads.py b/volatility3/framework/plugins/windows/threads.py index 83d231abb..55720c6f3 100644 --- a/volatility3/framework/plugins/windows/threads.py +++ b/volatility3/framework/plugins/windows/threads.py @@ -3,7 +3,7 @@ # import logging -from typing import List, Generator +from typing import Callable, Iterable, List, Generator from volatility3.framework import interfaces, constants from volatility3.framework.configuration import requirements @@ -18,6 +18,9 @@ class Threads(thrdscan.ThrdScan): _required_framework_version = (2, 4, 0) _version = (1, 0, 0) + def __init__(self): + self.implementation = self.list_process_threads + @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: # Since we're calling the plugin, make sure we have the plugin's requirements @@ -60,18 +63,27 @@ class Threads(thrdscan.ThrdScan): seen.add(thread.vol.offset) yield thread - def _generator(self): - kernel = self.context.modules[self.config["kernel"]] + @classmethod + def filter_func(cls, config: interfaces.configuration.HierarchicalDict) -> Callable: + return pslist.PsList.create_pid_filter(config.get("pid", None)) - filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) + @classmethod + def list_process_threads( + cls, + context: interfaces.context.ContextInterface, + module_name: str, + filter_func: Callable, + ) -> Iterable[interfaces.objects.ObjectInterface]: + """Runs through all processes and lists threads for each process""" + module = context.modules[module_name] + layer_name = module.layer_name + symbol_table_name = module.symbol_table_name for proc in pslist.PsList.list_processes( - context=self.context, - layer_name=kernel.layer_name, - symbol_table=kernel.symbol_table_name, + context=context, + layer_name=layer_name, + symbol_table=symbol_table_name, filter_func=filter_func, ): - for thread in self.list_threads(kernel, proc): - info = self.gather_thread_info(thread) - if info: - yield (0, info) + for thread in cls.list_threads(module, proc): + yield thread From b8b146a4441f054bccf5697868c26250ea99d86d Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 24 Jul 2024 10:59:52 +0100 Subject: [PATCH 51/85] Windows: update handles plugin to use a default SAR value of 0x10 if decoding fails. Produce warnings when this happens. Ref issue #1147 --- volatility3/framework/plugins/windows/handles.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/plugins/windows/handles.py b/volatility3/framework/plugins/windows/handles.py index a19e7a397..2edf28e86 100644 --- a/volatility3/framework/plugins/windows/handles.py +++ b/volatility3/framework/plugins/windows/handles.py @@ -25,7 +25,7 @@ class Handles(interfaces.plugins.PluginInterface): """Lists process open handles.""" _required_framework_version = (2, 0, 0) - _version = (1, 0, 1) + _version = (1, 0, 2) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -175,10 +175,11 @@ class Handles(interfaces.plugins.PluginInterface): virtual_layer_name, func_addr_to_read, num_bytes_to_read ) except exceptions.InvalidAddressException: - vollog.debug( - f"Failed to read {hex(num_bytes_to_read)} bytes at symbol {hex(func_addr_to_read)}" + vollog.warning( + f"Failed to read {hex(num_bytes_to_read)} bytes at symbol {hex(func_addr_to_read)}. Unable to decode SAR value. Failing back to a common value of 0x10" ) - return None + self._sar_value = 0x10 + return self._sar_value md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64) @@ -198,9 +199,10 @@ class Handles(interfaces.plugins.PluginInterface): break if self._sar_value is None: - vollog.debug( - f"Failed to to locate SAR value having parsed {instruction_count} instructions" + vollog.warning( + f"Failed to to locate SAR value having parsed {instruction_count} instructions, failing back to a common value of 0x10" ) + self._sar_value = 0x10 return self._sar_value From 7d52f7992d187e4c18425ca71171dc27a9710903 Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 24 Jul 2024 11:06:26 +0100 Subject: [PATCH 52/85] Windows: Make the default sar value used in handles plugin a variable so if it needs to be changed it gets updated in one place only --- volatility3/framework/plugins/windows/handles.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/windows/handles.py b/volatility3/framework/plugins/windows/handles.py index 2edf28e86..abe154fa1 100644 --- a/volatility3/framework/plugins/windows/handles.py +++ b/volatility3/framework/plugins/windows/handles.py @@ -142,6 +142,7 @@ class Handles(interfaces.plugins.PluginInterface): pointers in the _HANDLE_TABLE_ENTRY which allows us to find the associated _OBJECT_HEADER. """ + DEFAULT_SAR_VALUE = 0x10 # to be used only when decoding fails if self._sar_value is None: if not has_capstone: @@ -178,7 +179,7 @@ class Handles(interfaces.plugins.PluginInterface): vollog.warning( f"Failed to read {hex(num_bytes_to_read)} bytes at symbol {hex(func_addr_to_read)}. Unable to decode SAR value. Failing back to a common value of 0x10" ) - self._sar_value = 0x10 + self._sar_value = DEFAULT_SAR_VALUE return self._sar_value md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64) @@ -202,7 +203,7 @@ class Handles(interfaces.plugins.PluginInterface): vollog.warning( f"Failed to to locate SAR value having parsed {instruction_count} instructions, failing back to a common value of 0x10" ) - self._sar_value = 0x10 + self._sar_value = DEFAULT_SAR_VALUE return self._sar_value From e1065f9e788322faded83520da29cd5cbcd5d5ed Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Wed, 24 Jul 2024 20:32:36 +0100 Subject: [PATCH 53/85] Fix up a missing super which @atcuno spotted --- volatility3/framework/plugins/windows/threads.py | 1 + 1 file changed, 1 insertion(+) diff --git a/volatility3/framework/plugins/windows/threads.py b/volatility3/framework/plugins/windows/threads.py index 55720c6f3..d57911650 100644 --- a/volatility3/framework/plugins/windows/threads.py +++ b/volatility3/framework/plugins/windows/threads.py @@ -20,6 +20,7 @@ class Threads(thrdscan.ThrdScan): def __init__(self): self.implementation = self.list_process_threads + super().__init__() @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: From f4cbed856bcfb011707588d5563cd7144450ba1e Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Wed, 24 Jul 2024 20:47:05 +0100 Subject: [PATCH 54/85] Fix up missing filter parameter --- volatility3/framework/plugins/windows/thrdscan.py | 7 +++++-- volatility3/framework/plugins/windows/threads.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/windows/thrdscan.py b/volatility3/framework/plugins/windows/thrdscan.py index be7097347..6e664e052 100644 --- a/volatility3/framework/plugins/windows/thrdscan.py +++ b/volatility3/framework/plugins/windows/thrdscan.py @@ -19,7 +19,7 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) # version 2.6.0 adds support for scanning for 'Ethread' structures by pool tags _required_framework_version = (2, 6, 0) - _version = (1, 0, 0) + _version = (1, 1, 0) def __init__(self, *args, **kwargs): self.implementation = self.scan_threads @@ -100,11 +100,14 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface) for ethread in self.implementation(self.context, kernel_name): info = self.gather_thread_info(ethread) + if info: yield (0, info) def generate_timeline(self): - for row in self._generator(): + filt_func = self.filter_func(self.config) + + for row in self._generator(filt_func): _depth, row_data = row row_dict = {} ( diff --git a/volatility3/framework/plugins/windows/threads.py b/volatility3/framework/plugins/windows/threads.py index d57911650..ae70e717b 100644 --- a/volatility3/framework/plugins/windows/threads.py +++ b/volatility3/framework/plugins/windows/threads.py @@ -38,7 +38,7 @@ class Threads(thrdscan.ThrdScan): optional=True, ), requirements.PluginRequirement( - name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 0, 0) + name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 1, 0) ), ] From 6f3f645dbcfb94b797f2e244e56e43dd8e4cba0a Mon Sep 17 00:00:00 2001 From: Eve Date: Thu, 25 Jul 2024 17:59:10 +0100 Subject: [PATCH 55/85] Windows: update handles plugin sar warnings to use DEFAULT_SAR_VALUE var --- volatility3/framework/plugins/windows/handles.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/windows/handles.py b/volatility3/framework/plugins/windows/handles.py index abe154fa1..6010b4c72 100644 --- a/volatility3/framework/plugins/windows/handles.py +++ b/volatility3/framework/plugins/windows/handles.py @@ -177,7 +177,7 @@ class Handles(interfaces.plugins.PluginInterface): ) except exceptions.InvalidAddressException: vollog.warning( - f"Failed to read {hex(num_bytes_to_read)} bytes at symbol {hex(func_addr_to_read)}. Unable to decode SAR value. Failing back to a common value of 0x10" + f"Failed to read {hex(num_bytes_to_read)} bytes at symbol {hex(func_addr_to_read)}. Unable to decode SAR value. Failing back to a common value of {hex(DEFAULT_SAR_VALUE)}}" ) self._sar_value = DEFAULT_SAR_VALUE return self._sar_value @@ -201,7 +201,7 @@ class Handles(interfaces.plugins.PluginInterface): if self._sar_value is None: vollog.warning( - f"Failed to to locate SAR value having parsed {instruction_count} instructions, failing back to a common value of 0x10" + f"Failed to to locate SAR value having parsed {instruction_count} instructions, failing back to a common value of {hex(DEFAULT_SAR_VALUE)}" ) self._sar_value = DEFAULT_SAR_VALUE From 799afe6e51558dc4fe3828276f28b888e866e708 Mon Sep 17 00:00:00 2001 From: Eve Date: Thu, 25 Jul 2024 18:02:30 +0100 Subject: [PATCH 56/85] Windows: fix type in handles plugin --- volatility3/framework/plugins/windows/handles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/windows/handles.py b/volatility3/framework/plugins/windows/handles.py index 6010b4c72..3e5a2fd82 100644 --- a/volatility3/framework/plugins/windows/handles.py +++ b/volatility3/framework/plugins/windows/handles.py @@ -177,7 +177,7 @@ class Handles(interfaces.plugins.PluginInterface): ) except exceptions.InvalidAddressException: vollog.warning( - f"Failed to read {hex(num_bytes_to_read)} bytes at symbol {hex(func_addr_to_read)}. Unable to decode SAR value. Failing back to a common value of {hex(DEFAULT_SAR_VALUE)}}" + f"Failed to read {hex(num_bytes_to_read)} bytes at symbol {hex(func_addr_to_read)}. Unable to decode SAR value. Failing back to a common value of {hex(DEFAULT_SAR_VALUE)}" ) self._sar_value = DEFAULT_SAR_VALUE return self._sar_value From 7a03e9deabc6f743eb8e201fe75dcdd810339635 Mon Sep 17 00:00:00 2001 From: Eve Date: Thu, 25 Jul 2024 18:21:12 +0100 Subject: [PATCH 57/85] Windows: Add a _version to the filescan plugin --- volatility3/framework/plugins/windows/filescan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/volatility3/framework/plugins/windows/filescan.py b/volatility3/framework/plugins/windows/filescan.py index 34c0c60d3..82566361d 100644 --- a/volatility3/framework/plugins/windows/filescan.py +++ b/volatility3/framework/plugins/windows/filescan.py @@ -14,6 +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) @classmethod def get_requirements(cls): From f44ceb321d0c3b10249c1c697b3fb0c40f38ba39 Mon Sep 17 00:00:00 2001 From: qpalzmz112 <68213464+qpalzmz112@users.noreply.github.com> Date: Sat, 27 Jul 2024 20:11:39 -0500 Subject: [PATCH 58/85] Added psxview --- .../framework/plugins/windows/psxview.py | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 volatility3/framework/plugins/windows/psxview.py diff --git a/volatility3/framework/plugins/windows/psxview.py b/volatility3/framework/plugins/windows/psxview.py new file mode 100644 index 000000000..3bf6c27f5 --- /dev/null +++ b/volatility3/framework/plugins/windows/psxview.py @@ -0,0 +1,188 @@ +import datetime, logging + +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.plugins.windows import handles, info, pslist, psscan, sessions, thrdscan + +vollog = logging.getLogger(__name__) + +class PsXView(plugins.PluginInterface): + """Lists all processes found via 6 of the methods described in \"The Art of Memory Forensics,\" which may help + identify processes that are trying to hide themselves. I recommend using -r pretty if you are looking at this + plugin's output in a terminal.""" + # I've omitted the desktop thread scanning method because Volatility3 doesn't appear to have the funcitonality + # which the original plugin to do it. + + # I don't think it's worth including the sessions method either because both the original psxview plugin + # and Volatility3's sessions plugin begin with the list of processes found by PsList. + # The original psxview plugin's session code essentially just filters the pslist for processes + # whose session ID is not None. I've matched this in my code, but again, it doesn't seem worth including. + + # Lastly, I've omitted the pspcid method because I could not for the life of me get it to work. I saved the + # code I do have from it, and will happily share it if anyone else wants to add it. + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls): + return [requirements.ModuleRequirement(name="kernel", description="Windows kernel", architectures=["Intel32", "Intel64"]), + requirements.VersionRequirement(name="info", component=info.Info, version=(1, 0, 0)), + requirements.VersionRequirement(name="pslist", component=pslist.PsList, version=(2, 0, 0)), + requirements.VersionRequirement(name="psscan", component=psscan.PsScan, version=(1, 0, 0)), + requirements.VersionRequirement(name="thrdscan", component=thrdscan.ThrdScan, version=(1, 0, 0)), + requirements.VersionRequirement(name="handles", component=handles.Handles, version=(1, 0, 0)), + requirements.VersionRequirement(name="sessions", component=sessions.Sessions, version=(0, 0, 0)), + requirements.BooleanRequirement(name="identify-expected", description="In the plugin's output, replace false with \ + normal where false is the expected result for a Windows machine running normally. \ + Keep in mind that this plugin uses simple checks to identify \"normal\" behavior, \ + so you may want to double-check the legitimacy of these processes yourself.", optional=True), + requirements.BooleanRequirement(name="physical-offsets", description="List processes with phyiscall offsets instead of virtual offsets.", optional=True)] + + def proc_name_to_string(self, proc): + return proc.ImageFileName.cast("string", max_length=proc.ImageFileName.vol.count, errors="replace") + + def is_ascii(self, str): + return str.split('.')[0].isalnum() + + def filter_garbage_procs(self, proc_list): + return [p for p in proc_list if p.is_valid() and self.is_ascii(self.proc_name_to_string(p))] + + def translate_offset(self, offset): + if self.config["physical-offsets"]: + return offset + + kernel = self.context.modules[self.config["kernel"]] + layer_name = kernel.layer_name + + try: + offset = list(self.context.layers[layer_name].mapping(offset=offset, length=0))[0][2] + except: + # already have physical address + pass + + return offset + + def proc_list_to_dict(self, tasks): + return {self.translate_offset(proc.vol.offset):proc for proc in tasks} + + def check_pslist(self, tasks): + res = self.filter_garbage_procs(tasks) + return self.proc_list_to_dict(tasks) + + def check_psscan(self, layer_name, symbol_table): + res = psscan.PsScan.scan_processes(context=self.context, layer_name=layer_name, symbol_table=symbol_table) + res = self.filter_garbage_procs(res) + + return self.proc_list_to_dict(res) + + def check_thrdscan(self): + ret = [] + + for ethread in thrdscan.ThrdScan.scan_threads(self.context, module_name='kernel'): + process = None + try: + process = ethread.owning_process() + if not process.is_valid(): + continue + + ret.append(process) + except AttributeError: + vollog.log(constants.LOGLEVEL_VVV, "Unable to find the owning process of ethread") + + return self.proc_list_to_dict(ret) + + def check_csrss_handles(self, tasks, layer_name, symbol_table): + ret = [] + + 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")) + + except exceptions.InvalidAddressException: + vollog.log(constants.LOGLEVEL_VVV, "Cannot access eprocess object table") + + ret = self.filter_garbage_procs(ret) + return self.proc_list_to_dict(ret) + + def check_session(self, pslist_procs): + procs = [p for p in pslist_procs if p.get_session_id() != None] + + return self.proc_list_to_dict(procs) + + 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)) + + processes = {} + + processes['pslist'] = self.check_pslist(kdbg_list_processes) + processes['psscan'] = self.check_psscan(layer_name, symbol_table) + processes['thrdscan'] = self.check_thrdscan() + processes['csrss'] = self.check_csrss_handles(kdbg_list_processes, layer_name, symbol_table) + processes['sessions'] = self.check_session(kdbg_list_processes) + + seen_offsets = set() + for source in processes: + for offset in processes[source]: + if offset not in seen_offsets: + seen_offsets.add(offset) + proc = processes[source][offset] + + pid = proc.UniqueProcessId + name = self.proc_name_to_string(proc) + + exit_time = proc.get_exit_time() + if (type(exit_time) != datetime.datetime): + exit_time = "" + else: + exit_time = str(exit_time) + + in_sources = {src:str(offset in processes[src]) for src in processes} + + if self.config["identify-expected"]: + f = "False" + n = "Normal" + + if in_sources["pslist"] == f: + if exit_time != "": + in_sources["pslist"] = n + + if in_sources["thrdscan"] == f: + if exit_time != "": + in_sources["thrdscan"] = n + + if in_sources["csrss"] == f: + if name.lower() in ["system", "smss.exe", "csrss.exe"]: + in_sources["csrss"] = n + elif exit_time != "": + in_sources["csrss"] = n + + if in_sources["sessions"] == f: + if name.lower() in ["system", "smss.exe"]: + in_sources["sessions"] = n + + yield (0, (format_hints.Hex(offset), name, pid, in_sources["pslist"], + in_sources["psscan"], in_sources["thrdscan"], in_sources["csrss"], + in_sources["sessions"], exit_time)) + + + def run(self): + offset_type = "(Physical)" if self.config["physical-offsets"] else "(Virtual)" + offset_str = "Offset" + offset_type + + return TreeGrid([(offset_str, format_hints.Hex), ("Name", str), ("PID", int), ("pslist", str), ("psscan", str), + ("thrdscan", str), ("csrss", str), ("sessions", str), ("Exit Time", str) ], self._generator()) \ No newline at end of file From 824b0599f20d2a1f0e7f16ca08f9b498898e4c52 Mon Sep 17 00:00:00 2001 From: qpalzmz112 <68213464+qpalzmz112@users.noreply.github.com> Date: Sat, 27 Jul 2024 20:35:08 -0500 Subject: [PATCH 59/85] formatted --- .../framework/plugins/windows/psxview.py | 203 +++++++++++++----- 1 file changed, 147 insertions(+), 56 deletions(-) diff --git a/volatility3/framework/plugins/windows/psxview.py b/volatility3/framework/plugins/windows/psxview.py index 3bf6c27f5..1fde5b12d 100644 --- a/volatility3/framework/plugins/windows/psxview.py +++ b/volatility3/framework/plugins/windows/psxview.py @@ -4,19 +4,28 @@ 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.plugins.windows import handles, info, pslist, psscan, sessions, thrdscan +from volatility3.plugins.windows import ( + handles, + info, + pslist, + psscan, + sessions, + thrdscan, +) vollog = logging.getLogger(__name__) + class PsXView(plugins.PluginInterface): - """Lists all processes found via 6 of the methods described in \"The Art of Memory Forensics,\" which may help + """Lists all processes found via 6 of the methods described in \"The Art of Memory Forensics,\" which may help identify processes that are trying to hide themselves. I recommend using -r pretty if you are looking at this plugin's output in a terminal.""" - # I've omitted the desktop thread scanning method because Volatility3 doesn't appear to have the funcitonality - # which the original plugin to do it. - # I don't think it's worth including the sessions method either because both the original psxview plugin - # and Volatility3's sessions plugin begin with the list of processes found by PsList. + # I've omitted the desktop thread scanning method because Volatility3 doesn't appear to have the funcitonality + # which the original plugin to do it. + + # I don't think it's worth including the sessions method either because both the original psxview plugin + # and Volatility3's sessions plugin begin with the list of processes found by PsList. # The original psxview plugin's session code essentially just filters the pslist for processes # whose session ID is not None. I've matched this in my code, but again, it doesn't seem worth including. @@ -28,52 +37,88 @@ class PsXView(plugins.PluginInterface): @classmethod def get_requirements(cls): - return [requirements.ModuleRequirement(name="kernel", description="Windows kernel", architectures=["Intel32", "Intel64"]), - requirements.VersionRequirement(name="info", component=info.Info, version=(1, 0, 0)), - requirements.VersionRequirement(name="pslist", component=pslist.PsList, version=(2, 0, 0)), - requirements.VersionRequirement(name="psscan", component=psscan.PsScan, version=(1, 0, 0)), - requirements.VersionRequirement(name="thrdscan", component=thrdscan.ThrdScan, version=(1, 0, 0)), - requirements.VersionRequirement(name="handles", component=handles.Handles, version=(1, 0, 0)), - requirements.VersionRequirement(name="sessions", component=sessions.Sessions, version=(0, 0, 0)), - requirements.BooleanRequirement(name="identify-expected", description="In the plugin's output, replace false with \ + return [ + requirements.ModuleRequirement( + name="kernel", + description="Windows kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.VersionRequirement( + name="info", component=info.Info, version=(1, 0, 0) + ), + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(2, 0, 0) + ), + requirements.VersionRequirement( + name="psscan", component=psscan.PsScan, version=(1, 0, 0) + ), + requirements.VersionRequirement( + name="thrdscan", component=thrdscan.ThrdScan, version=(1, 0, 0) + ), + requirements.VersionRequirement( + name="handles", component=handles.Handles, version=(1, 0, 0) + ), + requirements.VersionRequirement( + name="sessions", component=sessions.Sessions, version=(0, 0, 0) + ), + requirements.BooleanRequirement( + name="identify-expected", + description='In the plugin\'s output, replace false with \ normal where false is the expected result for a Windows machine running normally. \ - Keep in mind that this plugin uses simple checks to identify \"normal\" behavior, \ - so you may want to double-check the legitimacy of these processes yourself.", optional=True), - requirements.BooleanRequirement(name="physical-offsets", description="List processes with phyiscall offsets instead of virtual offsets.", optional=True)] - + Keep in mind that this plugin uses simple checks to identify "normal" behavior, \ + so you may want to double-check the legitimacy of these processes yourself.', + optional=True, + ), + requirements.BooleanRequirement( + name="physical-offsets", + description="List processes with phyiscall offsets instead of virtual offsets.", + optional=True, + ), + ] + def proc_name_to_string(self, proc): - return proc.ImageFileName.cast("string", max_length=proc.ImageFileName.vol.count, errors="replace") + return proc.ImageFileName.cast( + "string", max_length=proc.ImageFileName.vol.count, errors="replace" + ) def is_ascii(self, str): - return str.split('.')[0].isalnum() - + return str.split(".")[0].isalnum() + def filter_garbage_procs(self, proc_list): - return [p for p in proc_list if p.is_valid() and self.is_ascii(self.proc_name_to_string(p))] - + return [ + p + for p in proc_list + if p.is_valid() and self.is_ascii(self.proc_name_to_string(p)) + ] + def translate_offset(self, offset): if self.config["physical-offsets"]: return offset - + kernel = self.context.modules[self.config["kernel"]] layer_name = kernel.layer_name try: - offset = list(self.context.layers[layer_name].mapping(offset=offset, length=0))[0][2] + offset = list( + self.context.layers[layer_name].mapping(offset=offset, length=0) + )[0][2] except: # already have physical address pass return offset - + def proc_list_to_dict(self, tasks): - return {self.translate_offset(proc.vol.offset):proc for proc in tasks} - + return {self.translate_offset(proc.vol.offset): proc for proc in tasks} + def check_pslist(self, tasks): res = self.filter_garbage_procs(tasks) return self.proc_list_to_dict(tasks) - + def check_psscan(self, layer_name, symbol_table): - res = psscan.PsScan.scan_processes(context=self.context, layer_name=layer_name, symbol_table=symbol_table) + res = psscan.PsScan.scan_processes( + context=self.context, layer_name=layer_name, symbol_table=symbol_table + ) res = self.filter_garbage_procs(res) return self.proc_list_to_dict(res) @@ -81,7 +126,9 @@ class PsXView(plugins.PluginInterface): def check_thrdscan(self): ret = [] - for ethread in thrdscan.ThrdScan.scan_threads(self.context, module_name='kernel'): + for ethread in thrdscan.ThrdScan.scan_threads( + self.context, module_name="kernel" + ): process = None try: process = ethread.owning_process() @@ -90,50 +137,70 @@ class PsXView(plugins.PluginInterface): ret.append(process) except AttributeError: - vollog.log(constants.LOGLEVEL_VVV, "Unable to find the owning process of ethread") + vollog.log( + constants.LOGLEVEL_VVV, + "Unable to find the owning process of ethread", + ) return self.proc_list_to_dict(ret) - + def check_csrss_handles(self, tasks, layer_name, symbol_table): ret = [] for p in tasks: name = self.proc_name_to_string(p) - if name == 'csrss.exe': + if name == "csrss.exe": try: if p.has_member("ObjectTable"): - handles_plugin = handles.Handles(context=self.context, config_path=self.config_path) + 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"): + if ( + h.get_object_type( + handles_plugin.get_type_map( + self.context, layer_name, symbol_table + ) + ) + == "Process" + ): ret.append(h.Body.cast("_EPROCESS")) except exceptions.InvalidAddressException: - vollog.log(constants.LOGLEVEL_VVV, "Cannot access eprocess object table") + vollog.log( + constants.LOGLEVEL_VVV, "Cannot access eprocess object table" + ) ret = self.filter_garbage_procs(ret) return self.proc_list_to_dict(ret) def check_session(self, pslist_procs): procs = [p for p in pslist_procs if p.get_session_id() != None] - + return self.proc_list_to_dict(procs) def _generator(self): kernel = self.context.modules[self.config["kernel"]] layer_name = kernel.layer_name - symbol_table = kernel.symbol_table_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 + ) + ) - kdbg_list_processes = list(pslist.PsList.list_processes(context=self.context, layer_name=layer_name, symbol_table=symbol_table)) - processes = {} - processes['pslist'] = self.check_pslist(kdbg_list_processes) - processes['psscan'] = self.check_psscan(layer_name, symbol_table) - processes['thrdscan'] = self.check_thrdscan() - processes['csrss'] = self.check_csrss_handles(kdbg_list_processes, layer_name, symbol_table) - processes['sessions'] = self.check_session(kdbg_list_processes) + processes["pslist"] = self.check_pslist(kdbg_list_processes) + processes["psscan"] = self.check_psscan(layer_name, symbol_table) + processes["thrdscan"] = self.check_thrdscan() + processes["csrss"] = self.check_csrss_handles( + kdbg_list_processes, layer_name, symbol_table + ) + processes["sessions"] = self.check_session(kdbg_list_processes) seen_offsets = set() for source in processes: @@ -146,12 +213,14 @@ class PsXView(plugins.PluginInterface): name = self.proc_name_to_string(proc) exit_time = proc.get_exit_time() - if (type(exit_time) != datetime.datetime): + if type(exit_time) != datetime.datetime: exit_time = "" else: exit_time = str(exit_time) - in_sources = {src:str(offset in processes[src]) for src in processes} + in_sources = { + src: str(offset in processes[src]) for src in processes + } if self.config["identify-expected"]: f = "False" @@ -173,16 +242,38 @@ class PsXView(plugins.PluginInterface): if in_sources["sessions"] == f: if name.lower() in ["system", "smss.exe"]: - in_sources["sessions"] = n + in_sources["sessions"] = n + + yield ( + 0, + ( + format_hints.Hex(offset), + name, + pid, + in_sources["pslist"], + in_sources["psscan"], + in_sources["thrdscan"], + in_sources["csrss"], + in_sources["sessions"], + exit_time, + ), + ) - yield (0, (format_hints.Hex(offset), name, pid, in_sources["pslist"], - in_sources["psscan"], in_sources["thrdscan"], in_sources["csrss"], - in_sources["sessions"], exit_time)) - - def run(self): offset_type = "(Physical)" if self.config["physical-offsets"] else "(Virtual)" offset_str = "Offset" + offset_type - return TreeGrid([(offset_str, format_hints.Hex), ("Name", str), ("PID", int), ("pslist", str), ("psscan", str), - ("thrdscan", str), ("csrss", str), ("sessions", str), ("Exit Time", str) ], self._generator()) \ No newline at end of file + return TreeGrid( + [ + (offset_str, format_hints.Hex), + ("Name", str), + ("PID", int), + ("pslist", str), + ("psscan", str), + ("thrdscan", str), + ("csrss", str), + ("sessions", str), + ("Exit Time", str), + ], + self._generator(), + ) From 44f26c928eddaaf6743ac07b22331ad082e86bc1 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 28 Jul 2024 12:35:02 +0100 Subject: [PATCH 60/85] Add in shtab autocompletion --- volatility3/cli/__init__.py | 22 +++++++++++++++++++--- volatility3/cli/volargparse.py | 6 ++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/volatility3/cli/__init__.py b/volatility3/cli/__init__.py index 6b17edac0..883b54ac4 100644 --- a/volatility3/cli/__init__.py +++ b/volatility3/cli/__init__.py @@ -22,6 +22,13 @@ import traceback from typing import Any, Dict, List, Tuple, Type, Union from urllib import parse, request +try: + import shtab + + HAS_SHTAB = True +except ImportError: + HAS_SHTAB = False + from volatility3.cli import text_filter import volatility3.plugins import volatility3.symbols @@ -106,6 +113,9 @@ class CommandLine: ] ) + # Argument for doing autocompletion + print_completion_arg = "--print-completion" + # Load up system defaults delayed_logs, default_config = self.load_system_defaults("vol.json") @@ -246,10 +256,12 @@ class CommandLine: # We have to filter out help, otherwise parse_known_args will trigger the help message before having # processed the plugin choice or had the plugin subparser added. known_args = [arg for arg in sys.argv if arg != "--help" and arg != "-h"] - partial_args, _ = parser.parse_known_args(known_args) - + partial_args, unknown_args = parser.parse_known_args(known_args) banner_output = sys.stdout - if renderers[partial_args.renderer].structured_output: + if ( + renderers[partial_args.renderer].structured_output + or print_completion_arg in unknown_args + ): banner_output = sys.stderr banner_output.write(f"Volatility 3 Framework {constants.PACKAGE_VERSION}\n") @@ -351,6 +363,10 @@ class CommandLine: # Hand the plugin requirements over to the CLI (us) and let it construct the config tree # Run the argparser + if HAS_SHTAB: + # The autocompletion line must be after the partial_arg handling, so that it doesn't trip it + # before all the plugins have been added + shtab.add_argument_to(parser, [print_completion_arg]) args = parser.parse_args() if args.plugin is None: parser.error("Please select a plugin to run") diff --git a/volatility3/cli/volargparse.py b/volatility3/cli/volargparse.py index 3048a0885..3e7eb6751 100644 --- a/volatility3/cli/volargparse.py +++ b/volatility3/cli/volargparse.py @@ -21,8 +21,6 @@ class HelpfulSubparserAction(argparse._SubParsersAction): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - # We don't want the action self-check to kick in, so we remove the choices list, the check happens in __call__ - self.choices = None def __call__( self, @@ -100,3 +98,7 @@ class HelpfulArgParser(argparse.ArgumentParser): # return the number of arguments matched return len(match.group(1)) + + def _check_value(self, action, value): + if not isinstance(action, HelpfulSubparserAction): + return super()._check_value(action, value) From e89e77637776b14c61516d2c47a7148a2f13860a Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 28 Jul 2024 12:41:31 +0100 Subject: [PATCH 61/85] Try out argcomplete as well --- vol.py | 1 + volatility3/cli/__init__.py | 21 ++++++++------------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/vol.py b/vol.py index ff420cad5..c49d5985d 100755 --- a/vol.py +++ b/vol.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# PYTHON_ARGCOMPLETE_OK # This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 diff --git a/volatility3/cli/__init__.py b/volatility3/cli/__init__.py index 883b54ac4..1209d7cdc 100644 --- a/volatility3/cli/__init__.py +++ b/volatility3/cli/__init__.py @@ -23,11 +23,11 @@ from typing import Any, Dict, List, Tuple, Type, Union from urllib import parse, request try: - import shtab + import argcomplete - HAS_SHTAB = True + HAS_ARGCOMPLETE = True except ImportError: - HAS_SHTAB = False + HAS_ARGCOMPLETE = False from volatility3.cli import text_filter import volatility3.plugins @@ -113,9 +113,6 @@ class CommandLine: ] ) - # Argument for doing autocompletion - print_completion_arg = "--print-completion" - # Load up system defaults delayed_logs, default_config = self.load_system_defaults("vol.json") @@ -256,12 +253,10 @@ class CommandLine: # We have to filter out help, otherwise parse_known_args will trigger the help message before having # processed the plugin choice or had the plugin subparser added. known_args = [arg for arg in sys.argv if arg != "--help" and arg != "-h"] - partial_args, unknown_args = parser.parse_known_args(known_args) + partial_args, _ = parser.parse_known_args(known_args) + banner_output = sys.stdout - if ( - renderers[partial_args.renderer].structured_output - or print_completion_arg in unknown_args - ): + if renderers[partial_args.renderer].structured_output: banner_output = sys.stderr banner_output.write(f"Volatility 3 Framework {constants.PACKAGE_VERSION}\n") @@ -363,10 +358,10 @@ class CommandLine: # Hand the plugin requirements over to the CLI (us) and let it construct the config tree # Run the argparser - if HAS_SHTAB: + if HAS_ARGCOMPLETE: # The autocompletion line must be after the partial_arg handling, so that it doesn't trip it # before all the plugins have been added - shtab.add_argument_to(parser, [print_completion_arg]) + argcomplete.autocomplete(parser) args = parser.parse_args() if args.plugin is None: parser.error("Please select a plugin to run") From 0d8fb76b3a48599ac9beb8eea5d79e46fe7b877e Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 28 Jul 2024 19:57:18 +0100 Subject: [PATCH 62/85] Renderers: Allow BaseAbsentValues in value results Fixes #1216 --- volatility3/framework/renderers/format_hints.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/volatility3/framework/renderers/format_hints.py b/volatility3/framework/renderers/format_hints.py index 6120b77c9..194e38099 100644 --- a/volatility3/framework/renderers/format_hints.py +++ b/volatility3/framework/renderers/format_hints.py @@ -10,6 +10,8 @@ Text renderers should attempt to honour all hints provided in this module where """ from typing import Type, Union +from volatility3.framework import interfaces + class Bin(int): """A class to indicate that the integer value should be represented as a @@ -66,3 +68,17 @@ class MultiTypeData(bytes): and self.split_nulls == other.split_nulls and self.show_hex == other.show_hex ) + + +BinOrAbsent = lambda x: ( + Bin(x) if not isinstance(x, interfaces.renderers.BaseAbsentValue) else x +) +HexOrAbsent = lambda x: ( + Hex(x) if not isinstance(x, interfaces.renderers.BaseAbsentValue) else x +) +HexBytesOrAbsent = lambda x: ( + HexBytes(x) if not isinstance(x, interfaces.renderers.BaseAbsentValue) else x +) +MultiTypeDataOrAbsent = lambda x: ( + MultiTypeData(x) if not isinstance(x, interfaces.renderers.BaseAbsentValue) else x +) From b659a060bd6caf37eb0e51560986cc58aab067de Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 28 Jul 2024 21:57:48 +0100 Subject: [PATCH 63/85] Renderers: Ensure the version is bumped so plugins can require the format_hints properly --- volatility3/framework/constants/_version.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/volatility3/framework/constants/_version.py b/volatility3/framework/constants/_version.py index 21c339a6e..f219fb0af 100644 --- a/volatility3/framework/constants/_version.py +++ b/volatility3/framework/constants/_version.py @@ -1,11 +1,9 @@ # We use the SemVer 2.0.0 versioning scheme VERSION_MAJOR = 2 # Number of releases of the library with a breaking change -VERSION_MINOR = 7 # Number of changes that only add to the interface -VERSION_PATCH = 2 # Number of changes that do not change the interface +VERSION_MINOR = 8 # Number of changes that only add to the interface +VERSION_PATCH = 0 # Number of changes that do not change the interface VERSION_SUFFIX = "" -# TODO: At version 2.0.0, remove the symbol_shift feature - PACKAGE_VERSION = ( ".".join([str(x) for x in [VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH]]) + VERSION_SUFFIX From 76414d3246717ba9dde2a3cdd94a8c1037a7374b Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 29 Jul 2024 14:13:54 +1000 Subject: [PATCH 64/85] Renderers conversion fix: Create aware datetimes to represent times in UTC. Fix Python 3.12 datetime.utcfromtimestamp() deprecation. See warning note on https://docs.python.org/3/library/datetime.html#datetime.datetime.utcfromtimestamp: Because naive datetime objects are treated by many datetime methods as local times, it is preferred to use aware datetimes to represent times in UTC. As such, the recommended way to create an object representing a specific timestamp in UTC is by calling datetime.fromtimestamp(timestamp, tz=timezone.utc). Additionaly, datetime.utcfromtimestamp() is deprecated since 3.12 --- volatility3/framework/renderers/conversion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/renderers/conversion.py b/volatility3/framework/renderers/conversion.py index bb18fcc8a..c833cc2cf 100644 --- a/volatility3/framework/renderers/conversion.py +++ b/volatility3/framework/renderers/conversion.py @@ -19,7 +19,7 @@ def wintime_to_datetime( return renderers.NotApplicableValue() unix_time = unix_time - 11644473600 try: - return datetime.datetime.utcfromtimestamp(unix_time) + return datetime.datetime.fromtimestamp(unix_time, datetime.timezone.utc) # Windows sometimes throws OSErrors rather than ValueErrors when it can't convert a value except (ValueError, OSError): return renderers.UnparsableValue() @@ -34,7 +34,7 @@ def unixtime_to_datetime( if unixtime > 0: with contextlib.suppress(ValueError): - ret = datetime.datetime.utcfromtimestamp(unixtime) + ret = datetime.datetime.fromtimestamp(unixtime, datetime.timezone.utc) return ret From ece15c914f27e6fbb651e8b3f3ae7e525aa50138 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 29 Jul 2024 14:18:10 +1000 Subject: [PATCH 65/85] Renderers conversion exceptions fix: Since version 3.3 utcfromtimestamp() and fromtimestamp() Python datimetime module raises OverflowError instead of ValueError. As of today, Volatility3 requires Python 3.7.3 so we should only include OverflowError --- volatility3/framework/renderers/conversion.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/renderers/conversion.py b/volatility3/framework/renderers/conversion.py index c833cc2cf..c94201681 100644 --- a/volatility3/framework/renderers/conversion.py +++ b/volatility3/framework/renderers/conversion.py @@ -20,8 +20,8 @@ def wintime_to_datetime( unix_time = unix_time - 11644473600 try: return datetime.datetime.fromtimestamp(unix_time, datetime.timezone.utc) - # Windows sometimes throws OSErrors rather than ValueErrors when it can't convert a value - except (ValueError, OSError): + # Windows sometimes throws OSErrors rather than OverflowError when it can't convert a value + except (OverflowError, OSError): return renderers.UnparsableValue() @@ -33,7 +33,7 @@ def unixtime_to_datetime( ) if unixtime > 0: - with contextlib.suppress(ValueError): + with contextlib.suppress(OverflowError): ret = datetime.datetime.fromtimestamp(unixtime, datetime.timezone.utc) return ret From b343734bae0d3090a5477997d27885d237096e50 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Mon, 29 Jul 2024 15:16:34 +1000 Subject: [PATCH 66/85] Renderers conversion exceptions fix: Even though the documentation states that OverflowError should be raised starting from version 3.3, it has been observed that ValueError is still being triggered. Also, in Linux, we noticed that OSError is also being raised in some cases. --- volatility3/framework/renderers/conversion.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/renderers/conversion.py b/volatility3/framework/renderers/conversion.py index c94201681..864794860 100644 --- a/volatility3/framework/renderers/conversion.py +++ b/volatility3/framework/renderers/conversion.py @@ -20,8 +20,10 @@ def wintime_to_datetime( unix_time = unix_time - 11644473600 try: return datetime.datetime.fromtimestamp(unix_time, datetime.timezone.utc) - # Windows sometimes throws OSErrors rather than OverflowError when it can't convert a value - except (OverflowError, OSError): + # Windows sometimes throws OSErrors rather than ValueError/OverflowError when it can't convert a value + # Since Python 3.3, this should raise OverflowError instead of ValueError. However, it was observed + # that even in Python 3.7.17, ValueError is still being raised. + except (ValueError, OverflowError, OSError): return renderers.UnparsableValue() @@ -33,7 +35,9 @@ def unixtime_to_datetime( ) if unixtime > 0: - with contextlib.suppress(OverflowError): + # Since Python 3.3, this should raise OverflowError instead of ValueError. However, it was observed + # that even in Python 3.7.17, ValueError is still being raised. OSError is also raised on Linux + with contextlib.suppress(ValueError, OverflowError, OSError): ret = datetime.datetime.fromtimestamp(unixtime, datetime.timezone.utc) return ret From ba7ec0059960fd6470254e8b3cd4ae15e89ea174 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Mon, 29 Jul 2024 17:55:56 -0500 Subject: [PATCH 67/85] Windows: Fixes bad structure member in callbacks This fixes a bug in the x64 callbacks symbols. The `NotificationRoutine` is currently an `unsigned int` instead of a void pointer. This prevents the correct mapping of the notification routine to the kernel module that contains it. --- volatility3/framework/symbols/windows/callbacks-x64.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/windows/callbacks-x64.json b/volatility3/framework/symbols/windows/callbacks-x64.json index 3f891b94f..705f2361d 100644 --- a/volatility3/framework/symbols/windows/callbacks-x64.json +++ b/volatility3/framework/symbols/windows/callbacks-x64.json @@ -105,8 +105,11 @@ }, "NotificationRoutine": { "type": { - "kind": "base", - "name": "unsigned int" + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } }, "offset": 24 } From d97fd777f37d58fff19df48334546c2e709f0d27 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Mon, 29 Jul 2024 18:36:43 -0500 Subject: [PATCH 68/85] Windows: Bumps netstat module version requirement This is a bump of the version number for the netstat plugin's `modules` requirement - it didn't get updated after #1173 was merged. --- volatility3/framework/plugins/windows/netstat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/windows/netstat.py b/volatility3/framework/plugins/windows/netstat.py index 24eb02018..0908767fc 100644 --- a/volatility3/framework/plugins/windows/netstat.py +++ b/volatility3/framework/plugins/windows/netstat.py @@ -35,7 +35,7 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): name="netscan", component=netscan.NetScan, version=(1, 0, 0) ), requirements.VersionRequirement( - name="modules", component=modules.Modules, version=(1, 0, 0) + name="modules", component=modules.Modules, version=(2, 0, 0) ), requirements.VersionRequirement( name="pdbutil", component=pdbutil.PDBUtility, version=(1, 0, 0) From efc48d5831d00fa7db45e853d380165731736841 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 30 Jul 2024 17:18:14 +1000 Subject: [PATCH 69/85] Make timeliner able to sort aware datetimes. Otherwise, it will raise an exception when comparing the plugin output data with this naive datetime --- volatility3/framework/plugins/timeliner.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/timeliner.py b/volatility3/framework/plugins/timeliner.py index 26a100f53..f657a2918 100644 --- a/volatility3/framework/plugins/timeliner.py +++ b/volatility3/framework/plugins/timeliner.py @@ -105,7 +105,9 @@ class Timeliner(interfaces.plugins.PluginInterface): data = item[1] def sortable(timestamp): - max_date = datetime.datetime(day=1, month=12, year=datetime.MAXYEAR) + max_date = datetime.datetime( + day=1, month=12, year=datetime.MAXYEAR, tzinfo=datetime.timezone.utc + ) if isinstance(timestamp, interfaces.renderers.BaseAbsentValue): return max_date return timestamp From 56b618f5c3345984c10af7e2868254e2d2bb7ea8 Mon Sep 17 00:00:00 2001 From: David McDonald Date: Tue, 30 Jul 2024 13:00:38 -0500 Subject: [PATCH 70/85] Windows: Updates netscan with new symbol file `netscan` was missing coverage for Windows 10 Build 20348, causing owners and create times for `_TCP_ENDPOINTS` to be missing. This adds a symbol file and the necessary version check in the netscan plugin. Testing confirms that this returns the correct creation time and owner process. --- .../framework/plugins/windows/netscan.py | 1 + .../netscan/netscan-win10-20348-x64.json | 582 ++++++++++++++++++ 2 files changed, 583 insertions(+) create mode 100644 volatility3/framework/symbols/windows/netscan/netscan-win10-20348-x64.json diff --git a/volatility3/framework/plugins/windows/netscan.py b/volatility3/framework/plugins/windows/netscan.py index 62ead3ab7..868bd8bcd 100644 --- a/volatility3/framework/plugins/windows/netscan.py +++ b/volatility3/framework/plugins/windows/netscan.py @@ -218,6 +218,7 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): (10, 0, 18362, 0): "netscan-win10-18362-x64", (10, 0, 18363, 0): "netscan-win10-18363-x64", (10, 0, 19041, 0): "netscan-win10-19041-x64", + (10, 0, 20348, 0): "netscan-win10-20348-x64", } # we do not need to check for tcpip's specific FileVersion in every case diff --git a/volatility3/framework/symbols/windows/netscan/netscan-win10-20348-x64.json b/volatility3/framework/symbols/windows/netscan/netscan-win10-20348-x64.json new file mode 100644 index 000000000..bd574b2b7 --- /dev/null +++ b/volatility3/framework/symbols/windows/netscan/netscan-win10-20348-x64.json @@ -0,0 +1,582 @@ +{ + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned be short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "big" + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "symbols": {}, + "user_types": { + "_UDP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 88, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "Next": { + "offset": 112, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_UDP_ENDPOINT" + } + } + }, + "LocalAddr": { + "offset": 168, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS_WIN10_UDP" + } + } + }, + "InetAF": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 160, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 168 + }, + "_TCP_LISTENER": { + "fields": { + "Owner": { + "offset": 48, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 64, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 96, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + + } + }, + "InetAF": { + "offset": 40, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Next": { + "offset": 120, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TCP_LISTENER" + } + } + }, + "Port": { + "offset": 114, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 128 + }, + "_TCP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 752, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + }, + "CreateTime": { + "offset": 776, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "AddrInfo": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ADDRINFO" + } + } + }, + "ListEntry": { + "offset": 40, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 16, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + } + }, + "LocalPort": { + "offset": 112, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 114, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "State": { + "offset": 108, + "type": { + "kind": "enum", + "name": "TCPStateEnum" + } + } + }, + "kind": "struct", + "size": 632 + }, + "_LOCAL_ADDRESS": { + "fields": { + "pData": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + } + }, + "kind": "struct", + "size": 20 + }, + "_LOCAL_ADDRESS_WIN10_UDP": { + "fields": { + "pData": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 4 + }, + "_ADDRINFO": { + "fields": { + "Local": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "Remote": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 4 + }, + "_IN_ADDR": { + "fields": { + "addr4": { + "offset": 0, + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + }, + "addr6": { + "offset": 0, + "type": { + "count": 16, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + } + }, + "kind": "struct", + "size": 6 + }, + "_INETAF": { + "fields": { + "AddressFamily": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 26 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + }, + "_INET_COMPARTMENT_SET": { + "fields": { + "InetCompartment": { + "offset": 328, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INET_COMPARTMENT" + } + } + } + }, + "kind": "struct", + "size": 384 + }, + "_INET_COMPARTMENT": { + "fields": { + "ProtocolCompartment": { + "offset": 32, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_PROTOCOL_COMPARTMENT" + } + } + } + }, + "kind": "struct", + "size": 48 + }, + "_PROTOCOL_COMPARTMENT": { + "fields": { + "PortPool": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INET_PORT_POOL" + } + } + } + }, + "kind": "struct", + "size": 16 + }, + "_PORT_ASSIGNMENT_ENTRY": { + "fields": { + "Entry": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + } + } + }, + "kind": "struct", + "size": 32 + }, + "_PORT_ASSIGNMENT_LIST": { + "fields": { + "Assignments": { + "offset": 0, + "type": { + "count": 256, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_PORT_ASSIGNMENT_ENTRY" + } + } + } + }, + "kind": "struct", + "size": 6144 + }, + "_PORT_ASSIGNMENT": { + "fields": { + "InPaBigPoolBase": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_PORT_ASSIGNMENT_LIST" + } + } + } + }, + "kind": "struct", + "size": 32 + }, + "_INET_PORT_POOL": { + "fields": { + "PortAssignments": { + "offset": 224, + "type": { + "count": 256, + "kind": "array", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_PORT_ASSIGNMENT" + } + } + } + }, + "PortBitMap": { + "offset": 208, + "type": { + "kind": "struct", + "name": "nt_symbols!_RTL_BITMAP" + } + } + }, + "kind": "struct", + "size": 11200 + }, + "_PARTITION": { + "fields": { + "Endpoints" : { + "offset": 8, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_RTL_DYNAMIC_HASH_TABLE" + } + } + }, + "UnknownHashTable" : { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_RTL_DYNAMIC_HASH_TABLE" + } + } + } + }, + "kind": "struct", + "size": 192 + }, + "_PARTITION_TABLE": { + "fields": { + "Partitions": { + "offset": 0, + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_PARTITION" + } + } + } + }, + "kind": "struct", + "size": 128 + } + }, + "enums": { + "TCPStateEnum": { + "base": "long", + "constants": { + "CLOSED": 0, + "LISTENING": 1, + "SYN_SENT": 2, + "SYN_RCVD": 3, + "ESTABLISHED": 4, + "FIN_WAIT1": 5, + "FIN_WAIT2": 6, + "CLOSE_WAIT": 7, + "CLOSING": 8, + "LAST_ACK": 9, + "TIME_WAIT": 12, + "DELETE_TCB": 13 + }, + "size": 4 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dgmcdona-by-hand", + "datetime": "2024-07-30T13:00:00" + }, + "format": "6.0.0" + } +} From a84a3706c5acee2c93df650a167ab058fe657053 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Tue, 30 Jul 2024 14:00:41 -0500 Subject: [PATCH 71/85] Remove errant filter on ldrmodule checks --- volatility3/framework/plugins/windows/ldrmodules.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/volatility3/framework/plugins/windows/ldrmodules.py b/volatility3/framework/plugins/windows/ldrmodules.py index ffd84ea4a..a888f22e1 100644 --- a/volatility3/framework/plugins/windows/ldrmodules.py +++ b/volatility3/framework/plugins/windows/ldrmodules.py @@ -47,14 +47,6 @@ class LdrModules(interfaces.plugins.PluginInterface): self.context, self.config_path, "windows", "pe", class_types=pe.class_types ) - def filter_function(x: interfaces.objects.ObjectInterface) -> bool: - try: - return not (x.get_private_memory() == 0 and x.ControlArea) - except AttributeError: - return False - - filter_func = filter_function - for proc in procs: proc_layer_name = proc.add_process_layer() @@ -69,7 +61,7 @@ class LdrModules(interfaces.plugins.PluginInterface): # Build dictionary of mapped files, where the VAD start address is the key and value is the file name of the mapped file mapped_files = {} - for vad in vadinfo.VadInfo.list_vads(proc, filter_func=filter_func): + for vad in vadinfo.VadInfo.list_vads(proc): dos_header = self.context.object( pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER", offset=vad.get_start(), From 73bc10c2834d9a8fa2ab04c098a4735542226170 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Wed, 31 Jul 2024 21:46:57 +0100 Subject: [PATCH 72/85] Wire the argcomplete into volshell too --- volatility3/cli/volshell/__init__.py | 12 ++++++++++++ volshell.py | 1 + 2 files changed, 13 insertions(+) diff --git a/volatility3/cli/volshell/__init__.py b/volatility3/cli/volshell/__init__.py index 035ed9b2e..2bf1958e2 100644 --- a/volatility3/cli/volshell/__init__.py +++ b/volatility3/cli/volshell/__init__.py @@ -21,6 +21,14 @@ from volatility3.framework import ( plugins, ) +try: + import argcomplete + + HAS_ARGCOMPLETE = True +except ImportError: + HAS_ARGCOMPLETE = False + + # Make sure we log everything rootlog = logging.getLogger() @@ -276,6 +284,10 @@ class VolShell(cli.CommandLine): # Hand the plugin requirements over to the CLI (us) and let it construct the config tree # Run the argparser + if HAS_ARGCOMPLETE: + # The autocompletion line must be after the partial_arg handling, so that it doesn't trip it + # before all the plugins have been added + argcomplete.autocomplete(parser) args = parser.parse_args() vollog.log( diff --git a/volshell.py b/volshell.py index 71d35a47c..65b11885e 100755 --- a/volshell.py +++ b/volshell.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# PYTHON_ARGCOMPLETE_OK # This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 From d576f8cb48d064ce3dc87936df642481aa1f3186 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Wed, 31 Jul 2024 21:49:07 +0100 Subject: [PATCH 73/85] Fix CodeQL error --- volatility3/cli/volargparse.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/volatility3/cli/volargparse.py b/volatility3/cli/volargparse.py index 3e7eb6751..2bd53077b 100644 --- a/volatility3/cli/volargparse.py +++ b/volatility3/cli/volargparse.py @@ -99,6 +99,7 @@ class HelpfulArgParser(argparse.ArgumentParser): # return the number of arguments matched return len(match.group(1)) - def _check_value(self, action, value): + def _check_value(self, action: argparse.Action, value: Any) -> None: if not isinstance(action, HelpfulSubparserAction): return super()._check_value(action, value) + return None From fd6e4bec5cab84035491f629d234522ac6d5a8af Mon Sep 17 00:00:00 2001 From: qpalzmz112 <68213464+qpalzmz112@users.noreply.github.com> Date: Wed, 31 Jul 2024 17:53:29 -0500 Subject: [PATCH 74/85] Updated with feedback from the PR --- .../framework/plugins/windows/psxview.py | 172 ++++++++---------- 1 file changed, 74 insertions(+), 98 deletions(-) diff --git a/volatility3/framework/plugins/windows/psxview.py b/volatility3/framework/plugins/windows/psxview.py index 1fde5b12d..dc5e77ec3 100644 --- a/volatility3/framework/plugins/windows/psxview.py +++ b/volatility3/framework/plugins/windows/psxview.py @@ -1,4 +1,4 @@ -import datetime, logging +import datetime, logging, string from volatility3.framework import constants, exceptions from volatility3.framework.interfaces import plugins @@ -22,7 +22,7 @@ class PsXView(plugins.PluginInterface): plugin's output in a terminal.""" # I've omitted the desktop thread scanning method because Volatility3 doesn't appear to have the funcitonality - # which the original plugin to do it. + # which the original plugin used to do it. # I don't think it's worth including the sessions method either because both the original psxview plugin # and Volatility3's sessions plugin begin with the list of processes found by PsList. @@ -35,6 +35,10 @@ class PsXView(plugins.PluginInterface): _required_framework_version = (2, 0, 0) _version = (1, 0, 0) + valid_proc_name_chars = set( + string.ascii_lowercase + string.ascii_uppercase + "." + " " + ) + @classmethod def get_requirements(cls): return [ @@ -58,17 +62,6 @@ class PsXView(plugins.PluginInterface): requirements.VersionRequirement( name="handles", component=handles.Handles, version=(1, 0, 0) ), - requirements.VersionRequirement( - name="sessions", component=sessions.Sessions, version=(0, 0, 0) - ), - requirements.BooleanRequirement( - name="identify-expected", - description='In the plugin\'s output, replace false with \ - normal where false is the expected result for a Windows machine running normally. \ - Keep in mind that this plugin uses simple checks to identify "normal" behavior, \ - so you may want to double-check the legitimacy of these processes yourself.', - optional=True, - ), requirements.BooleanRequirement( name="physical-offsets", description="List processes with phyiscall offsets instead of virtual offsets.", @@ -76,54 +69,56 @@ class PsXView(plugins.PluginInterface): ), ] - def proc_name_to_string(self, proc): + def _proc_name_to_string(self, proc): return proc.ImageFileName.cast( "string", max_length=proc.ImageFileName.vol.count, errors="replace" ) - def is_ascii(self, str): - return str.split(".")[0].isalnum() + 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 filter_garbage_procs(self, proc_list): + def _filter_garbage_procs(self, proc_list): return [ p for p in proc_list - if p.is_valid() and self.is_ascii(self.proc_name_to_string(p)) + if p.is_valid() and self._is_valid_proc_name(self._proc_name_to_string(p)) ] - def translate_offset(self, offset): - if self.config["physical-offsets"]: + def _translate_offset(self, offset): + if not self.config["physical-offsets"]: return offset kernel = self.context.modules[self.config["kernel"]] layer_name = kernel.layer_name try: - offset = list( + _, _, offset, _, _ = list( self.context.layers[layer_name].mapping(offset=offset, length=0) - )[0][2] + )[0] except: # already have physical address pass return offset - def proc_list_to_dict(self, tasks): - return {self.translate_offset(proc.vol.offset): proc for proc in tasks} + def _proc_list_to_dict(self, tasks): + tasks = self._filter_garbage_procs(tasks) + return {self._translate_offset(proc.vol.offset): proc for proc in tasks} - def check_pslist(self, tasks): - res = self.filter_garbage_procs(tasks) - return self.proc_list_to_dict(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, symbol_table): res = psscan.PsScan.scan_processes( context=self.context, layer_name=layer_name, symbol_table=symbol_table ) - res = self.filter_garbage_procs(res) - return self.proc_list_to_dict(res) + return self._proc_list_to_dict(res) - def check_thrdscan(self): + def _check_thrdscan(self): ret = [] for ethread in thrdscan.ThrdScan.scan_threads( @@ -142,13 +137,13 @@ class PsXView(plugins.PluginInterface): "Unable to find the owning process of ethread", ) - return self.proc_list_to_dict(ret) + return self._proc_list_to_dict(ret) - def check_csrss_handles(self, tasks, layer_name, symbol_table): + def _check_csrss_handles(self, tasks, layer_name, symbol_table): ret = [] for p in tasks: - name = self.proc_name_to_string(p) + name = self._proc_name_to_string(p) if name == "csrss.exe": try: if p.has_member("ObjectTable"): @@ -172,13 +167,7 @@ class PsXView(plugins.PluginInterface): constants.LOGLEVEL_VVV, "Cannot access eprocess object table" ) - ret = self.filter_garbage_procs(ret) - return self.proc_list_to_dict(ret) - - def check_session(self, pslist_procs): - procs = [p for p in pslist_procs if p.get_session_id() != None] - - return self.proc_list_to_dict(procs) + return self._proc_list_to_dict(ret) def _generator(self): kernel = self.context.modules[self.config["kernel"]] @@ -192,72 +181,60 @@ class PsXView(plugins.PluginInterface): ) ) + # get processes from each source processes = {} - processes["pslist"] = self.check_pslist(kdbg_list_processes) - processes["psscan"] = self.check_psscan(layer_name, symbol_table) - processes["thrdscan"] = self.check_thrdscan() - processes["csrss"] = self.check_csrss_handles( + processes["pslist"] = self._check_pslist(kdbg_list_processes) + processes["psscan"] = self._check_psscan(layer_name, symbol_table) + processes["thrdscan"] = self._check_thrdscan() + processes["csrss"] = self._check_csrss_handles( kdbg_list_processes, layer_name, symbol_table ) - processes["sessions"] = self.check_session(kdbg_list_processes) - seen_offsets = set() - for source in processes: - for offset in processes[source]: - if offset not in seen_offsets: - seen_offsets.add(offset) - proc = processes[source][offset] + # print results - pid = proc.UniqueProcessId - name = self.proc_name_to_string(proc) + # list of lists of offsets + todo_offsets = [list(processes[source].keys()) for source in processes] - exit_time = proc.get_exit_time() - if type(exit_time) != datetime.datetime: - exit_time = "" - else: - exit_time = str(exit_time) + # flatten to one list + todo_offsets = sum(todo_offsets, []) - in_sources = { - src: str(offset in processes[src]) for src in processes - } + # remove duplicates + todo_offsets = set(todo_offsets) - if self.config["identify-expected"]: - f = "False" - n = "Normal" + for offset in todo_offsets: + proc = None - if in_sources["pslist"] == f: - if exit_time != "": - in_sources["pslist"] = n + in_sources = {src: False for src in processes} - if in_sources["thrdscan"] == f: - if exit_time != "": - in_sources["thrdscan"] = n + for source in processes: + if offset in processes[source]: + in_sources[source] = True + if not proc: + proc = processes[source][offset] - if in_sources["csrss"] == f: - if name.lower() in ["system", "smss.exe", "csrss.exe"]: - in_sources["csrss"] = n - elif exit_time != "": - in_sources["csrss"] = n + pid = proc.UniqueProcessId + name = self._proc_name_to_string(proc) - if in_sources["sessions"] == f: - if name.lower() in ["system", "smss.exe"]: - in_sources["sessions"] = n + exit_time = proc.get_exit_time() + if type(exit_time) != datetime.datetime: + exit_time = "" + else: + exit_time = str(exit_time) - yield ( - 0, - ( - format_hints.Hex(offset), - name, - pid, - in_sources["pslist"], - in_sources["psscan"], - in_sources["thrdscan"], - in_sources["csrss"], - in_sources["sessions"], - exit_time, - ), - ) + yield ( + 0, + ( + format_hints.Hex(offset), + name, + pid, + in_sources["pslist"], + in_sources["psscan"], + in_sources["thrdscan"], + in_sources["csrss"], + exit_time, + ), + ) def run(self): offset_type = "(Physical)" if self.config["physical-offsets"] else "(Virtual)" @@ -268,11 +245,10 @@ class PsXView(plugins.PluginInterface): (offset_str, format_hints.Hex), ("Name", str), ("PID", int), - ("pslist", str), - ("psscan", str), - ("thrdscan", str), - ("csrss", str), - ("sessions", str), + ("pslist", bool), + ("psscan", bool), + ("thrdscan", bool), + ("csrss", bool), ("Exit Time", str), ], self._generator(), From 9e8864521c3fb06e80536a8d0f249ac19fd9a9c0 Mon Sep 17 00:00:00 2001 From: qpalzmz112 <68213464+qpalzmz112@users.noreply.github.com> Date: Wed, 31 Jul 2024 18:02:52 -0500 Subject: [PATCH 75/85] Added debug log for failed address translation --- volatility3/framework/plugins/windows/psxview.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/windows/psxview.py b/volatility3/framework/plugins/windows/psxview.py index dc5e77ec3..918eb44ba 100644 --- a/volatility3/framework/plugins/windows/psxview.py +++ b/volatility3/framework/plugins/windows/psxview.py @@ -98,9 +98,8 @@ class PsXView(plugins.PluginInterface): _, _, offset, _, _ = list( self.context.layers[layer_name].mapping(offset=offset, length=0) )[0] - except: - # already have physical address - pass + except exceptions.PagedInvalidAddressException: + vollog.debug(f"Page fault: unable to translate {offset:0x}") return offset From 3017a7d00c3a9cfca3f94b9e7dfb8ba08d48fd0e Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Thu, 1 Aug 2024 11:46:10 +1000 Subject: [PATCH 76/85] Linux: Add inode, timespec, and timespec64 object extensions to support different kernel versions, ensuring we will get aware datetimes when using them. --- .../framework/symbols/linux/__init__.py | 6 + .../symbols/linux/extensions/__init__.py | 132 ++++++++++++++++++ 2 files changed, 138 insertions(+) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index c4e2587f4..03353135d 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -29,12 +29,18 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable): self.set_type_class("files_struct", extensions.files_struct) self.set_type_class("kobject", extensions.kobject) self.set_type_class("cred", extensions.cred) + self.set_type_class("inode", extensions.inode) # Might not exist in the current symbols self.optional_set_type_class("module", extensions.module) self.optional_set_type_class("bpf_prog", extensions.bpf_prog) self.optional_set_type_class("kernel_cap_struct", extensions.kernel_cap_struct) self.optional_set_type_class("kernel_cap_t", extensions.kernel_cap_t) + # kernels >= 4.18 + self.optional_set_type_class("timespec64", extensions.timespec64) + # kernels < 4.18. Reuses timespec64 obj extension, since both has the same members + self.optional_set_type_class("timespec", extensions.timespec64) + # Mount self.set_type_class("vfsmount", extensions.vfsmount) # Might not exist in older kernels or the current symbols diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index e7c6b66d7..be31e298c 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -4,10 +4,13 @@ import collections.abc import logging +import stat +from datetime import datetime import socket as socket_module from typing import Generator, Iterable, Iterator, Optional, Tuple, List from volatility3.framework import constants, exceptions, objects, interfaces, symbols +from volatility3.framework import renderers from volatility3.framework.constants.linux import SOCK_TYPES, SOCK_FAMILY from volatility3.framework.constants.linux import IP_PROTOCOLS, IPV6_PROTOCOLS from volatility3.framework.constants.linux import TCP_STATES, NETLINK_PROTOCOLS @@ -1761,3 +1764,132 @@ class kernel_cap_t(kernel_cap_struct): ) return cap_value & self.get_kernel_cap_full() + + +class timespec64(objects.StructType): + def to_datetime(self) -> datetime: + """Returns the respective aware datetime""" + + dt = renderers.conversion.unixtime_to_datetime(self.tv_sec + self.tv_nsec / 1e9) + return dt + + +class inode(objects.StructType): + def is_valid(self) -> bool: + # i_count is a 'signed' counter (atomic_t). Smear, or essentially a wrong inode + # pointer, will easily cause an integer overflow here. + return self.i_ino > 0 and self.i_count.counter >= 0 + + def is_dir(self) -> bool: + """Returns True if the inode is a directory""" + return stat.S_ISDIR(self.i_mode) != 0 + + def is_reg(self) -> bool: + """Returns True if the inode is a regular file""" + return stat.S_ISREG(self.i_mode) != 0 + + def is_link(self) -> bool: + """Returns True if the inode is a symlink""" + return stat.S_ISLNK(self.i_mode) != 0 + + def is_fifo(self) -> bool: + """Returns True if the inode is a FIFO""" + return stat.S_ISFIFO(self.i_mode) != 0 + + def is_sock(self) -> bool: + """Returns True if the inode is a socket""" + return stat.S_ISSOCK(self.i_mode) != 0 + + def is_block(self) -> bool: + """Returns True if the inode is a block device""" + return stat.S_ISBLK(self.i_mode) != 0 + + def is_char(self) -> bool: + """Returns True if the inode is a char device""" + return stat.S_ISCHR(self.i_mode) != 0 + + def is_sticky(self) -> bool: + """Returns True if the sticky bit is set""" + return (self.i_mode & stat.S_ISVTX) != 0 + + def get_inode_type(self) -> str: + """Returns inode type name + + Returns: + The inode type name + """ + if self.is_dir(): + return "DIR" + elif self.is_reg(): + return "REG" + elif self.is_link(): + return "LNK" + elif self.is_fifo(): + return "FIFO" + elif self.is_sock(): + return "SOCK" + elif self.is_char(): + return "CHR" + elif self.is_block(): + return "BLK" + else: + return renderers.UnparsableValue() + + def get_inode_number(self) -> int: + """Returns the inode number""" + return int(self.i_ino) + + def ___time_member_to_datetime(self, member) -> datetime: + if self.has_member(f"{member}_sec") and self.has_member(f"{member}_nsec"): + # kernels >= 6.11 it's i_*_sec -> time64_t and i_*_nsec -> u32 + # Ref Linux commit 3aa63a569c64e708df547a8913c84e64a06e7853 + return renderers.conversion.unixtime_to_datetime( + self.member(f"{member}_sec") + self.has_member(f"{member}_nsec") / 1e9 + ) + elif self.has_member(f"__{member}"): + # 6.6 <= kernels < 6.11 it's a timespec64 + # Ref Linux commit 13bc24457850583a2e7203ded05b7209ab4bc5ef / 12cd44023651666bd44baa36a5c999698890debb + return self.member(f"__{member}").to_datetime() + elif self.has_member(member): + # In kernels < 6.6 it's a timespec64 or timespec + return self.member(member).to_datetime() + else: + raise exceptions.VolatilityException( + "Unsupported kernel inode type implementation" + ) + + def get_access_time(self) -> datetime: + """Returns the inode's last access time + This is updated when inode contents are read + + Returns: + A datetime with the inode's last access time + """ + return self.___time_member_to_datetime("i_atime") + + def get_modification_time(self) -> datetime: + """Returns the inode's last modification time + This is updated when the inode contents change + + Returns: + A datetime with the inode's last data modification time + """ + + return self.___time_member_to_datetime("i_mtime") + + def get_change_time(self) -> datetime: + """Returns the inode's last change time + This is updated when the inode metadata changes + + Returns: + A datetime with the inode's last change time + """ + return self.___time_member_to_datetime("i_ctime") + + def get_file_mode(self) -> str: + """Returns the inode's file mode as string of the form '-rwxrwxrwx'. + + Returns: + The inode's file mode string + """ + return stat.filemode(self.i_mode) From e6308a6035156cab5abb6f0cdf537fb75e5881e8 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Thu, 1 Aug 2024 21:04:24 +0100 Subject: [PATCH 77/85] Make suggested changes by gcmoreira --- volatility3/cli/volargparse.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/volatility3/cli/volargparse.py b/volatility3/cli/volargparse.py index 2bd53077b..5ce2646ed 100644 --- a/volatility3/cli/volargparse.py +++ b/volatility3/cli/volargparse.py @@ -100,6 +100,11 @@ class HelpfulArgParser(argparse.ArgumentParser): return len(match.group(1)) def _check_value(self, action: argparse.Action, value: Any) -> None: + """This is called to ensure a value is correct/valid + This fails when we want to accept partial values for the plugin name, + so we disable the check (which will throw ArgumentErrors for failed checks) + but only for our plugin subparser, so all other arguments are checked correctly + """ if not isinstance(action, HelpfulSubparserAction): - return super()._check_value(action, value) + super()._check_value(action, value) return None From 0bda1543854d8f92e7fb2bf4c5eff3afdf117819 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Thu, 1 Aug 2024 21:08:18 +0100 Subject: [PATCH 78/85] Clarify the documentation a little --- volatility3/cli/volargparse.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/volatility3/cli/volargparse.py b/volatility3/cli/volargparse.py index 5ce2646ed..fd61ddce0 100644 --- a/volatility3/cli/volargparse.py +++ b/volatility3/cli/volargparse.py @@ -101,9 +101,16 @@ class HelpfulArgParser(argparse.ArgumentParser): def _check_value(self, action: argparse.Action, value: Any) -> None: """This is called to ensure a value is correct/valid - This fails when we want to accept partial values for the plugin name, - so we disable the check (which will throw ArgumentErrors for failed checks) - but only for our plugin subparser, so all other arguments are checked correctly + + In normal operation, it would check that a value provided is valid and return None + If it was not valid, it would throw an ArgumentError + + When people provide a partial plugin name, we want to look for a matching plugin name + which happens in the HelpfulSubparserAction's __call_method + + To get there without tripping the check_value failure, we have to prevent the exception + being thrown when the value is a HelpfulSubparserAction. This therefore affects no other + checks for normal parameters. """ if not isinstance(action, HelpfulSubparserAction): super()._check_value(action, value) From d19013c85261ea48dd774dcda66dcb8d1b36782d Mon Sep 17 00:00:00 2001 From: qpalzmz112 <68213464+qpalzmz112@users.noreply.github.com> Date: Thu, 1 Aug 2024 16:19:24 -0500 Subject: [PATCH 79/85] fixed typo, updated plugin docstring, and updated comment --- volatility3/framework/plugins/windows/psxview.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/plugins/windows/psxview.py b/volatility3/framework/plugins/windows/psxview.py index 918eb44ba..cab27f3e3 100644 --- a/volatility3/framework/plugins/windows/psxview.py +++ b/volatility3/framework/plugins/windows/psxview.py @@ -17,17 +17,14 @@ vollog = logging.getLogger(__name__) class PsXView(plugins.PluginInterface): - """Lists all processes found via 6 of the methods described in \"The Art of Memory Forensics,\" which may help + """Lists all processes found via four of the methods described in \"The Art of Memory Forensics,\" which may help identify processes that are trying to hide themselves. I recommend using -r pretty if you are looking at this plugin's output in a terminal.""" # I've omitted the desktop thread scanning method because Volatility3 doesn't appear to have the funcitonality # which the original plugin used to do it. - # I don't think it's worth including the sessions method either because both the original psxview plugin - # and Volatility3's sessions plugin begin with the list of processes found by PsList. - # The original psxview plugin's session code essentially just filters the pslist for processes - # whose session ID is not None. I've matched this in my code, but again, it doesn't seem worth including. + # The sessions method is omitted because it begins with the list of processes found by Pslist anyway. # Lastly, I've omitted the pspcid method because I could not for the life of me get it to work. I saved the # code I do have from it, and will happily share it if anyone else wants to add it. @@ -64,7 +61,7 @@ class PsXView(plugins.PluginInterface): ), requirements.BooleanRequirement( name="physical-offsets", - description="List processes with phyiscall offsets instead of virtual offsets.", + description="List processes with physical offsets instead of virtual offsets.", optional=True, ), ] From 98c0094da5cfb8a99c9e211004952c9104c619cd Mon Sep 17 00:00:00 2001 From: qpalzmz112 <68213464+qpalzmz112@users.noreply.github.com> Date: Thu, 1 Aug 2024 18:51:11 -0500 Subject: [PATCH 80/85] Updated unpacked variable names --- volatility3/framework/plugins/windows/psxview.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/plugins/windows/psxview.py b/volatility3/framework/plugins/windows/psxview.py index cab27f3e3..71919c410 100644 --- a/volatility3/framework/plugins/windows/psxview.py +++ b/volatility3/framework/plugins/windows/psxview.py @@ -92,7 +92,7 @@ class PsXView(plugins.PluginInterface): layer_name = kernel.layer_name try: - _, _, offset, _, _ = list( + _original_offset, _original_length, offset, _length, _layer_name = list( self.context.layers[layer_name].mapping(offset=offset, length=0) )[0] except exceptions.PagedInvalidAddressException: @@ -190,15 +190,15 @@ class PsXView(plugins.PluginInterface): # print results # list of lists of offsets - todo_offsets = [list(processes[source].keys()) for source in processes] + offsets = [list(processes[source].keys()) for source in processes] # flatten to one list - todo_offsets = sum(todo_offsets, []) + offsets = sum(offsets, []) # remove duplicates - todo_offsets = set(todo_offsets) + offsets = set(offsets) - for offset in todo_offsets: + for offset in offsets: proc = None in_sources = {src: False for src in processes} From 79529fb8153b3a58b4a1d1c6192cb92cf107800f Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Fri, 2 Aug 2024 13:50:51 +1000 Subject: [PATCH 81/85] PR review fixes: Rename method name from private to internal --- .../framework/symbols/linux/extensions/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index be31e298c..599fedb6f 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1839,7 +1839,7 @@ class inode(objects.StructType): """Returns the inode number""" return int(self.i_ino) - def ___time_member_to_datetime(self, member) -> datetime: + def _time_member_to_datetime(self, member) -> datetime: if self.has_member(f"{member}_sec") and self.has_member(f"{member}_nsec"): # kernels >= 6.11 it's i_*_sec -> time64_t and i_*_nsec -> u32 # Ref Linux commit 3aa63a569c64e708df547a8913c84e64a06e7853 @@ -1865,7 +1865,7 @@ class inode(objects.StructType): Returns: A datetime with the inode's last access time """ - return self.___time_member_to_datetime("i_atime") + return self._time_member_to_datetime("i_atime") def get_modification_time(self) -> datetime: """Returns the inode's last modification time @@ -1875,7 +1875,7 @@ class inode(objects.StructType): A datetime with the inode's last data modification time """ - return self.___time_member_to_datetime("i_mtime") + return self._time_member_to_datetime("i_mtime") def get_change_time(self) -> datetime: """Returns the inode's last change time @@ -1884,7 +1884,7 @@ class inode(objects.StructType): Returns: A datetime with the inode's last change time """ - return self.___time_member_to_datetime("i_ctime") + return self._time_member_to_datetime("i_ctime") def get_file_mode(self) -> str: """Returns the inode's file mode as string of the form '-rwxrwxrwx'. From b8d68b9a5ee0c643b68eaa9f33bd051279374eb3 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Fri, 2 Aug 2024 14:15:01 +1000 Subject: [PATCH 82/85] PR review fixes: Avoid using renderers in core functions. --- .../framework/symbols/linux/extensions/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 599fedb6f..1b5e1d286 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -7,10 +7,10 @@ import logging import stat from datetime import datetime import socket as socket_module -from typing import Generator, Iterable, Iterator, Optional, Tuple, List +from typing import Generator, Iterable, Iterator, Optional, Tuple, List, Union from volatility3.framework import constants, exceptions, objects, interfaces, symbols -from volatility3.framework import renderers +from volatility3.framework.renderers import conversion from volatility3.framework.constants.linux import SOCK_TYPES, SOCK_FAMILY from volatility3.framework.constants.linux import IP_PROTOCOLS, IPV6_PROTOCOLS from volatility3.framework.constants.linux import TCP_STATES, NETLINK_PROTOCOLS @@ -1770,7 +1770,7 @@ class timespec64(objects.StructType): def to_datetime(self) -> datetime: """Returns the respective aware datetime""" - dt = renderers.conversion.unixtime_to_datetime(self.tv_sec + self.tv_nsec / 1e9) + dt = conversion.unixtime_to_datetime(self.tv_sec + self.tv_nsec / 1e9) return dt @@ -1812,7 +1812,7 @@ class inode(objects.StructType): """Returns True if the sticky bit is set""" return (self.i_mode & stat.S_ISVTX) != 0 - def get_inode_type(self) -> str: + def get_inode_type(self) -> Union[str, None]: """Returns inode type name Returns: @@ -1833,7 +1833,7 @@ class inode(objects.StructType): elif self.is_block(): return "BLK" else: - return renderers.UnparsableValue() + return None def get_inode_number(self) -> int: """Returns the inode number""" @@ -1843,7 +1843,7 @@ class inode(objects.StructType): if self.has_member(f"{member}_sec") and self.has_member(f"{member}_nsec"): # kernels >= 6.11 it's i_*_sec -> time64_t and i_*_nsec -> u32 # Ref Linux commit 3aa63a569c64e708df547a8913c84e64a06e7853 - return renderers.conversion.unixtime_to_datetime( + return conversion.unixtime_to_datetime( self.member(f"{member}_sec") + self.has_member(f"{member}_nsec") / 1e9 ) elif self.has_member(f"__{member}"): From 933a41fa3a60f3f02185b530a1a710b6dcae895c Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Fri, 2 Aug 2024 14:17:57 +1000 Subject: [PATCH 83/85] PR review fixes: Convert inode's is_* functions to properties --- .../symbols/linux/extensions/__init__.py | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 1b5e1d286..00f6730eb 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1780,34 +1780,42 @@ class inode(objects.StructType): # pointer, will easily cause an integer overflow here. return self.i_ino > 0 and self.i_count.counter >= 0 + @property def is_dir(self) -> bool: """Returns True if the inode is a directory""" return stat.S_ISDIR(self.i_mode) != 0 + @property def is_reg(self) -> bool: """Returns True if the inode is a regular file""" return stat.S_ISREG(self.i_mode) != 0 + @property def is_link(self) -> bool: """Returns True if the inode is a symlink""" return stat.S_ISLNK(self.i_mode) != 0 + @property def is_fifo(self) -> bool: """Returns True if the inode is a FIFO""" return stat.S_ISFIFO(self.i_mode) != 0 + @property def is_sock(self) -> bool: """Returns True if the inode is a socket""" return stat.S_ISSOCK(self.i_mode) != 0 + @property def is_block(self) -> bool: """Returns True if the inode is a block device""" return stat.S_ISBLK(self.i_mode) != 0 + @property def is_char(self) -> bool: """Returns True if the inode is a char device""" return stat.S_ISCHR(self.i_mode) != 0 + @property def is_sticky(self) -> bool: """Returns True if the sticky bit is set""" return (self.i_mode & stat.S_ISVTX) != 0 @@ -1818,19 +1826,19 @@ class inode(objects.StructType): Returns: The inode type name """ - if self.is_dir(): + if self.is_dir: return "DIR" - elif self.is_reg(): + elif self.is_reg: return "REG" - elif self.is_link(): + elif self.is_link: return "LNK" - elif self.is_fifo(): + elif self.is_fifo: return "FIFO" - elif self.is_sock(): + elif self.is_sock: return "SOCK" - elif self.is_char(): + elif self.is_char: return "CHR" - elif self.is_block(): + elif self.is_block: return "BLK" else: return None From ed208347630428e21dec91f90eb431ed02595bc3 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Fri, 2 Aug 2024 19:46:23 +1000 Subject: [PATCH 84/85] PR review fixes: Remove get_inode_number. It's better to use the type's original member name and handle the casting on the consumer side. --- volatility3/framework/symbols/linux/extensions/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 00f6730eb..05679523f 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -1843,10 +1843,6 @@ class inode(objects.StructType): else: return None - def get_inode_number(self) -> int: - """Returns the inode number""" - return int(self.i_ino) - def _time_member_to_datetime(self, member) -> datetime: if self.has_member(f"{member}_sec") and self.has_member(f"{member}_nsec"): # kernels >= 6.11 it's i_*_sec -> time64_t and i_*_nsec -> u32 From 1e3e9e2c78cbfc4e24362c323432660c98373516 Mon Sep 17 00:00:00 2001 From: Arcuri Davide Date: Tue, 6 Aug 2024 16:28:59 +0200 Subject: [PATCH 85/85] add args and kwargs to threads.py init Without args and kwargs there were an issue with timeliner plugin that tried to pass additional parameters like progress_callback raising TypeError --- volatility3/framework/plugins/windows/threads.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/windows/threads.py b/volatility3/framework/plugins/windows/threads.py index ae70e717b..39f3b7d77 100644 --- a/volatility3/framework/plugins/windows/threads.py +++ b/volatility3/framework/plugins/windows/threads.py @@ -18,9 +18,9 @@ class Threads(thrdscan.ThrdScan): _required_framework_version = (2, 4, 0) _version = (1, 0, 0) - def __init__(self): + def __init__(self, *args, **kwargs): self.implementation = self.list_process_threads - super().__init__() + super().__init__(*args, **kwargs) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: