From aae3f5bfef27a9bf4c56408df3a6a38f70186e21 Mon Sep 17 00:00:00 2001 From: Eve Date: Tue, 26 Mar 2024 06:43:22 +0000 Subject: [PATCH 01/41] Linux: add first draft of sockscan plugin --- .../framework/plugins/linux/sockscan.py | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 volatility3/framework/plugins/linux/sockscan.py diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py new file mode 100644 index 000000000..52b33d9c1 --- /dev/null +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -0,0 +1,318 @@ +# 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 struct +from typing import Callable, Tuple, List, Dict + +from volatility3.framework import interfaces, exceptions, constants, objects +from volatility3.framework.renderers import TreeGrid, NotAvailableValue, format_hints +from volatility3.framework.configuration import requirements +from volatility3.framework.interfaces import plugins +from volatility3.framework.objects import utility +from volatility3.framework.symbols import linux +from volatility3.plugins.linux import sockstat +from volatility3.framework import symbols +from volatility3.framework import symbols, constants +from volatility3.framework.layers import scanners + +vollog = logging.getLogger(__name__) + + +class Sockscan(plugins.PluginInterface): + """Scans for network connections found in memory layer.""" + + _required_framework_version = (2, 6, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.VersionRequirement( + name="SockHandlers", component=sockstat.SockHandlers, version=(1, 0, 0) + ), + requirements.VersionRequirement( + name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) + ), + ] + + def _generator(self, symbol_table_name: str): + """Scans for sockets. Each row represents a kernel socket. + + Args: + symbol_table_name: The name of the kernel module on which to operate + + Yields: + family: Socket family string (AF_UNIX, AF_INET, etc) + sock_type: Socket type string (STREAM, DGRAM, etc) + protocol: Protocol string (UDP, TCP, etc) + source addr: Source address string + source port: Source port string (not all of them are int) + destination addr: Destination address string + destination port: Destination port (not all of them are int) + state: State strings (LISTEN, CONNECTED, etc) + """ + + # get vmlinux module from context in order to build objects and read symbols + vmlinux = self.context.modules[symbol_table_name] + + # get kernel layer from context so that it's dependencies can be found, and therefore scanned. + # kernel layer will be virtual and built ontop of a physical layer. + kernel_layer = self.context.layers[vmlinux.layer_name] + + # detmine if kernel is 64bit or not. The plugin scans for pointers and these need to formated + # to the correct size so that they can be accurately located in the physical layer. + if symbols.symbol_table_is_64bit(self.context, vmlinux.symbol_table_name): + pack_format = "Q" # 64 bit + else: + pack_format = "I" # 32 bit + + # TODO: Update plugin to support multiple dependencies. e.g. a memory layer and swap file. + # This is a shared problem with psscan and having a generic solution would be useful. + # Find the memory layer to scan, and provide warnings if more than one is located. + if len(kernel_layer.dependencies) > 1: + vollog.warning( + f"Kernel layer depends on multiple layers however only {kernel_layer.dependencies[0]} will be scanned by this plugin." + ) + elif len(kernel_layer.dependencies) == 0: + vollog.error( + f"Kernel layer has no dependencies, meaning there is no memory layer for this plugin to scan." + ) + raise exceptions.LayerException( + vmlinux.layer_name, f"Layer {vmlinux.layer_name} has no dependencies" + ) + memory_layer_name = kernel_layer.dependencies[0] + memory_layer = self.context.layers[kernel_layer.dependencies[0]] + + # use the init process to build a sock handler + # TODO: look into options so that sockstat.SockHandlers so that process_sock can + # be used without a task object. + init_task = vmlinux.object_from_symbol(symbol_name="init_task") + sock_handler = sockstat.SockHandlers(vmlinux, init_task) + + # set to track seen sockets so that results are not duplicated between methods + sock_physical_addresses = set() + + # get progress_callback in order to use this in the scanners. + # TODO: perhaps add more detail to progress, showing method in progress and number of hits found + progress_callback = self._progress_callback + + # TODO: update scanning logic so that all needles can be scanned for at the same time + # this would allow the results to be shown as the scanning is happening and would + # make the plugin faster. It would require working out which needle caused the match + # and applying the logic at that point to get to the socket. + + # Method 1 - find sockets by file operations, then follow pointers to sockets + file_ops_symbol_names = ["socket_file_ops", "sockfs_dentry_operations"] + file_ops_needles = [] + for symbol_name in file_ops_symbol_names: + + # TODO: handle cases where symbol is not found + needle_addr = vmlinux.object_from_symbol(symbol_name).vol.offset + # use canonicalize to set the appropriate sign extension for the addr + addr = kernel_layer.canonicalize(needle_addr) + packed_addr = struct.pack(pack_format, addr) + file_ops_needles.append(packed_addr) + vollog.log( + constants.LOGLEVEL_VVVV, + f"Method 1 will scan for {symbol_name} using the bytes: {packed_addr.hex()}", + ) + + # get file struct to find the offset to the f_op pointer + # this is so that the file object can be created at the correct offset, + # the results of the scanner will be for the f_op member within the file + f_op_offset = vmlinux.get_type("file").members["f_op"][0] + + for addr, _ in memory_layer.scan( + self.context, + scanners.MultiStringScanner(file_ops_needles), + progress_callback, + ): + try: + # create file in the memory_layer, the native layer matches the + # kernel so that pointers can be followed + pfile = self.context.object( + vmlinux.symbol_table_name + constants.BANG + "file", + offset=addr - f_op_offset, + layer_name=memory_layer_name, + native_layer_name=vmlinux.layer_name, + ) + dentry = pfile.get_dentry() + if not dentry: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping file at {hex(addr)} as unable to locate dentry", + ) + continue + + d_inode = dentry.d_inode + if not d_inode: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping file at {hex(addr)} as unable to locate inode for dentry", + ) + continue + + socket_alloc = linux.LinuxUtilities.container_of( + d_inode, "socket_alloc", "vfs_inode", vmlinux + ) + socket = socket_alloc.socket + if not (socket and socket.sk): + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping file at {hex(addr)} as socket created by LinuxUtilities.container_of is invalid", + ) + continue + + # sucessfully trversed from file to sock, this will exist in the + # kernel layer, and need to be translated to the memory layer. + sock = socket.sk.dereference() + sock_physical_addresses.add(kernel_layer.translate(sock.vol.offset)[0]) + + except exceptions.InvalidAddressException as error: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Unable to follow file at {hex(addr)} to socket due to invalid address: {error}", + ) + + # Method 2 - find sockets by socket destructor directly inside sock objects + socket_destructor_symbol_names = [ + "sock_def_destruct", + "packet_sock_destruct", + "unix_sock_destructor", + "netlink_sock_destruct", + "inet_sock_destruct", + ] + + socket_destructor_needles = [] + for socket_destructor_symbol_name in socket_destructor_symbol_names: + addr = kernel_layer.canonicalize( + vmlinux.get_symbol(socket_destructor_symbol_name).address + + vmlinux.offset + ) + packed_addr = struct.pack(pack_format, addr) + socket_destructor_needles.append(packed_addr) + vollog.log( + constants.LOGLEVEL_VVVV, + f"Method 2 will scan for {socket_destructor_symbol_name} using the bytes: {packed_addr.hex()}", + ) + + # get sock struct to find the offset to the sk_destruct pointer + # this is so that the sock object can be created at the correct offset, + # the results of the scanner will be for the sk_destruct member within the scock + sk_destruct_offset = vmlinux.get_type("sock").members["sk_destruct"][0] + + for addr, _ in memory_layer.scan( + self.context, + scanners.MultiStringScanner(socket_destructor_needles), + progress_callback, + ): + sock_physical_addresses.add(addr - sk_destruct_offset) + + # TODO Method 3 - find sock by sk_error_report symbols + # sk_error_report_symbol_names = ['sock_def_error_report', 'inet_sk_rebuild_header', 'inet_listen'] + # this would be similar to Method 2, but using a different pointer within sock. + + # now that the set of results has been created, process them and display the results + for addr in sorted(sock_physical_addresses): + psock = self.context.object( + vmlinux.symbol_table_name + constants.BANG + "sock", + offset=addr, + layer_name=memory_layer_name, + native_layer_name=vmlinux.layer_name, + ) + try: + sock_type = psock.get_type() + + family = psock.get_family() + # remove results with no family + if family == None: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping socket at {hex(addr)} as unable to determin family.", + ) + continue + + # TODO: invesitgate options for more invalid address handling in proccess_sock + # and the later formatting on it's results. + sock_fields = sock_handler.process_sock(psock) + # if no sock_fields we're able to be extracted then skip this result. + if not sock_fields: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping socket at {hex(addr)} as unable to process with SockHandlers.", + ) + continue + + sock, sock_stat, extended = sock_fields + src, src_port, dst, dst_port, state = sock_stat + protocol = sock.get_protocol() + + # format results + src = NotAvailableValue() if src is None else str(src) + src_port = NotAvailableValue() if src_port is None else str(src_port) + dst = NotAvailableValue() if dst is None else str(dst) + dst_port = NotAvailableValue() if dst_port is None else str(dst_port) + state = NotAvailableValue() if state is None else str(state) + protocol = NotAvailableValue() if protocol is None else str(protocol) + # extended attributes is a dict, so this is formated to string show each + # key and value pair, seperated with a comma. + socket_filter_str = ( + ",".join(f"{k}={v}" for k, v in extended.items()) + if extended + else NotAvailableValue() + ) + + # remove empty results + if (src == "0.0.0.0" or isinstance(src, NotAvailableValue)) and ( + dst == "0.0.0.0" or isinstance(src, NotAvailableValue) + ): + if state == "UNCONNECTED": + continue + elif src_port == "0" and dst_port == "0": + continue + + fields = ( + format_hints.Hex(sock.vol.offset), + family, + sock_type, + protocol, + src, + src_port, + dst, + dst_port, + state, + socket_filter_str, + ) + + yield (0, fields) + except exceptions.InvalidAddressException as error: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Unable create results for socket at {hex(addr)} to invalid address: {error}", + ) + + def run(self): + + tree_grid_args = [ + ("Sock Offset", format_hints.Hex), + ("Family", str), + ("Type", str), + ("Proto", str), + ("Source Addr", str), + ("Source Port", str), + ("Destination Addr", str), + ("Destination Port", str), + ("State", str), + ("Filter", str), + ] + + return TreeGrid( + tree_grid_args, + self._generator(self.config["kernel"]), + ) From 67291ce90fcbea2462293082b1f5d246dfca1b24 Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 27 Mar 2024 09:29:01 +0000 Subject: [PATCH 02/41] Linux: update sockscan to scan memory layer only once for needles --- .../framework/plugins/linux/sockscan.py | 388 ++++++++++-------- 1 file changed, 210 insertions(+), 178 deletions(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 52b33d9c1..d02757238 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -4,13 +4,12 @@ import logging import struct -from typing import Callable, Tuple, List, Dict +from typing import List, Set -from volatility3.framework import interfaces, exceptions, constants, objects +from volatility3.framework import exceptions, constants from volatility3.framework.renderers import TreeGrid, NotAvailableValue, format_hints from volatility3.framework.configuration import requirements from volatility3.framework.interfaces import plugins -from volatility3.framework.objects import utility from volatility3.framework.symbols import linux from volatility3.plugins.linux import sockstat from volatility3.framework import symbols @@ -41,6 +40,62 @@ class Sockscan(plugins.PluginInterface): ), ] + def _canonicalize_symbol_addrs( + self, symbol_table_name: List[str], symbol_names: str + ) -> Set[bytes]: + """Takes a list of symbol names and converts the address of each to the bytes + as they would appear in memory so that they can be scanned for. + + Symbols that cannot be found are ignored and not included in the results. + + Args: + symbol_table_name: The name of the kernel module on which to operate + symbol_names: A list of symbol names to be looked up + + Returns: + A set of bytes which are the packed addresses. + """ + # get vmlinux module from context in order to build objects and read symbols + vmlinux = self.context.modules[symbol_table_name] + + # get kernel layer from context so that it's dependencies can be found, and therefore scanned. + # kernel layer will be virtual and built ontop of a physical layer. + kernel_layer = self.context.layers[vmlinux.layer_name] + + # detmine if kernel is 64bit or not. The plugin scans for pointers and these need to formated + # to the correct size so that they can be accurately located in the physical layer. + if symbols.symbol_table_is_64bit(self.context, vmlinux.symbol_table_name): + pack_format = "Q" # 64 bit + else: + pack_format = "I" # 32 bit + + packed_needles = set() + for symbol_name in symbol_names: + try: + needle_addr = vmlinux.object_from_symbol(symbol_name).vol.offset + except exceptions.SymbolError: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Unable to find symbol {symbol_name} this will not be scanned for.", + ) + continue + # use canonicalize to set the appropriate sign extension for the addr + addr = kernel_layer.canonicalize(needle_addr) + packed_addr = struct.pack(pack_format, addr) + packed_needles.add(packed_addr) + vollog.log( + constants.LOGLEVEL_VVVV, + f"Will scan for {symbol_name} using the bytes: {packed_addr.hex()}", + ) + + # make a warning if no symbols at all could be resolved. + if len(packed_needles) == 0: + vollog.warning( + f"_canonicalize_symbol_addrs was unable to resolve any symbols, use -vvvv for more information." + ) + + return packed_needles + def _generator(self, symbol_table_name: str): """Scans for sockets. Each row represents a kernel socket. @@ -48,6 +103,7 @@ class Sockscan(plugins.PluginInterface): symbol_table_name: The name of the kernel module on which to operate Yields: + addr: Physical offset family: Socket family string (AF_UNIX, AF_INET, etc) sock_type: Socket type string (STREAM, DGRAM, etc) protocol: Protocol string (UDP, TCP, etc) @@ -65,15 +121,9 @@ class Sockscan(plugins.PluginInterface): # kernel layer will be virtual and built ontop of a physical layer. kernel_layer = self.context.layers[vmlinux.layer_name] - # detmine if kernel is 64bit or not. The plugin scans for pointers and these need to formated - # to the correct size so that they can be accurately located in the physical layer. - if symbols.symbol_table_is_64bit(self.context, vmlinux.symbol_table_name): - pack_format = "Q" # 64 bit - else: - pack_format = "I" # 32 bit - # TODO: Update plugin to support multiple dependencies. e.g. a memory layer and swap file. # This is a shared problem with psscan and having a generic solution would be useful. + # Find the memory layer to scan, and provide warnings if more than one is located. if len(kernel_layer.dependencies) > 1: vollog.warning( @@ -95,91 +145,23 @@ class Sockscan(plugins.PluginInterface): init_task = vmlinux.object_from_symbol(symbol_name="init_task") sock_handler = sockstat.SockHandlers(vmlinux, init_task) - # set to track seen sockets so that results are not duplicated between methods - sock_physical_addresses = set() - # get progress_callback in order to use this in the scanners. # TODO: perhaps add more detail to progress, showing method in progress and number of hits found progress_callback = self._progress_callback - # TODO: update scanning logic so that all needles can be scanned for at the same time - # this would allow the results to be shown as the scanning is happening and would - # make the plugin faster. It would require working out which needle caused the match - # and applying the logic at that point to get to the socket. - # Method 1 - find sockets by file operations, then follow pointers to sockets - file_ops_symbol_names = ["socket_file_ops", "sockfs_dentry_operations"] - file_ops_needles = [] - for symbol_name in file_ops_symbol_names: - - # TODO: handle cases where symbol is not found - needle_addr = vmlinux.object_from_symbol(symbol_name).vol.offset - # use canonicalize to set the appropriate sign extension for the addr - addr = kernel_layer.canonicalize(needle_addr) - packed_addr = struct.pack(pack_format, addr) - file_ops_needles.append(packed_addr) - vollog.log( - constants.LOGLEVEL_VVVV, - f"Method 1 will scan for {symbol_name} using the bytes: {packed_addr.hex()}", - ) - + file_ops_symbol_names = [ + "socket_file_ops", + "sockfs_dentry_operations", + ] + file_ops_needles = self._canonicalize_symbol_addrs( + symbol_table_name, file_ops_symbol_names + ) # get file struct to find the offset to the f_op pointer # this is so that the file object can be created at the correct offset, # the results of the scanner will be for the f_op member within the file f_op_offset = vmlinux.get_type("file").members["f_op"][0] - for addr, _ in memory_layer.scan( - self.context, - scanners.MultiStringScanner(file_ops_needles), - progress_callback, - ): - try: - # create file in the memory_layer, the native layer matches the - # kernel so that pointers can be followed - pfile = self.context.object( - vmlinux.symbol_table_name + constants.BANG + "file", - offset=addr - f_op_offset, - layer_name=memory_layer_name, - native_layer_name=vmlinux.layer_name, - ) - dentry = pfile.get_dentry() - if not dentry: - vollog.log( - constants.LOGLEVEL_VVVV, - f"Skipping file at {hex(addr)} as unable to locate dentry", - ) - continue - - d_inode = dentry.d_inode - if not d_inode: - vollog.log( - constants.LOGLEVEL_VVVV, - f"Skipping file at {hex(addr)} as unable to locate inode for dentry", - ) - continue - - socket_alloc = linux.LinuxUtilities.container_of( - d_inode, "socket_alloc", "vfs_inode", vmlinux - ) - socket = socket_alloc.socket - if not (socket and socket.sk): - vollog.log( - constants.LOGLEVEL_VVVV, - f"Skipping file at {hex(addr)} as socket created by LinuxUtilities.container_of is invalid", - ) - continue - - # sucessfully trversed from file to sock, this will exist in the - # kernel layer, and need to be translated to the memory layer. - sock = socket.sk.dereference() - sock_physical_addresses.add(kernel_layer.translate(sock.vol.offset)[0]) - - except exceptions.InvalidAddressException as error: - vollog.log( - constants.LOGLEVEL_VVVV, - f"Unable to follow file at {hex(addr)} to socket due to invalid address: {error}", - ) - # Method 2 - find sockets by socket destructor directly inside sock objects socket_destructor_symbol_names = [ "sock_def_destruct", @@ -188,114 +170,164 @@ class Sockscan(plugins.PluginInterface): "netlink_sock_destruct", "inet_sock_destruct", ] - - socket_destructor_needles = [] - for socket_destructor_symbol_name in socket_destructor_symbol_names: - addr = kernel_layer.canonicalize( - vmlinux.get_symbol(socket_destructor_symbol_name).address - + vmlinux.offset - ) - packed_addr = struct.pack(pack_format, addr) - socket_destructor_needles.append(packed_addr) - vollog.log( - constants.LOGLEVEL_VVVV, - f"Method 2 will scan for {socket_destructor_symbol_name} using the bytes: {packed_addr.hex()}", - ) - + socket_destructor_needles = self._canonicalize_symbol_addrs( + symbol_table_name, socket_destructor_symbol_names + ) # get sock struct to find the offset to the sk_destruct pointer # this is so that the sock object can be created at the correct offset, # the results of the scanner will be for the sk_destruct member within the scock sk_destruct_offset = vmlinux.get_type("sock").members["sk_destruct"][0] - for addr, _ in memory_layer.scan( - self.context, - scanners.MultiStringScanner(socket_destructor_needles), - progress_callback, - ): - sock_physical_addresses.add(addr - sk_destruct_offset) - # TODO Method 3 - find sock by sk_error_report symbols # sk_error_report_symbol_names = ['sock_def_error_report', 'inet_sk_rebuild_header', 'inet_listen'] # this would be similar to Method 2, but using a different pointer within sock. - # now that the set of results has been created, process them and display the results - for addr in sorted(sock_physical_addresses): - psock = self.context.object( - vmlinux.symbol_table_name + constants.BANG + "sock", - offset=addr, - layer_name=memory_layer_name, - native_layer_name=vmlinux.layer_name, - ) - try: - sock_type = psock.get_type() + # Using the calculated needles, scan the memory layer and attempt to parse the sockets located. + for needle_addr, match in memory_layer.scan( + self.context, + scanners.MultiStringScanner(socket_destructor_needles | file_ops_needles), + progress_callback, + ): + psock = None + sock_physical_addr = None - family = psock.get_family() - # remove results with no family - if family == None: - vollog.log( - constants.LOGLEVEL_VVVV, - f"Skipping socket at {hex(addr)} as unable to determin family.", - ) - continue - - # TODO: invesitgate options for more invalid address handling in proccess_sock - # and the later formatting on it's results. - sock_fields = sock_handler.process_sock(psock) - # if no sock_fields we're able to be extracted then skip this result. - if not sock_fields: - vollog.log( - constants.LOGLEVEL_VVVV, - f"Skipping socket at {hex(addr)} as unable to process with SockHandlers.", - ) - continue - - sock, sock_stat, extended = sock_fields - src, src_port, dst, dst_port, state = sock_stat - protocol = sock.get_protocol() - - # format results - src = NotAvailableValue() if src is None else str(src) - src_port = NotAvailableValue() if src_port is None else str(src_port) - dst = NotAvailableValue() if dst is None else str(dst) - dst_port = NotAvailableValue() if dst_port is None else str(dst_port) - state = NotAvailableValue() if state is None else str(state) - protocol = NotAvailableValue() if protocol is None else str(protocol) - # extended attributes is a dict, so this is formated to string show each - # key and value pair, seperated with a comma. - socket_filter_str = ( - ",".join(f"{k}={v}" for k, v in extended.items()) - if extended - else NotAvailableValue() + # if match is from socket_destructor_needles simply calculate the offset + # to the sock + if match in socket_destructor_needles: + sock_physical_addr = needle_addr - sk_destruct_offset + psock = self.context.object( + vmlinux.symbol_table_name + constants.BANG + "sock", + offset=sock_physical_addr, + layer_name=memory_layer_name, + native_layer_name=vmlinux.layer_name, ) - # remove empty results - if (src == "0.0.0.0" or isinstance(src, NotAvailableValue)) and ( - dst == "0.0.0.0" or isinstance(src, NotAvailableValue) - ): - if state == "UNCONNECTED": - continue - elif src_port == "0" and dst_port == "0": + # if match is from file_ops_needles attempt to walk from file object to + # the sock + if match in file_ops_needles: + try: + # create file in the memory_layer, the native layer matches the + # kernel so that pointers can be followed + sock_physical_addr = needle_addr - f_op_offset + pfile = self.context.object( + vmlinux.symbol_table_name + constants.BANG + "file", + offset=sock_physical_addr, + layer_name=memory_layer_name, + native_layer_name=vmlinux.layer_name, + ) + dentry = pfile.get_dentry() + if not dentry: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping file at {hex(needle_addr)} as unable to locate dentry", + ) continue - fields = ( - format_hints.Hex(sock.vol.offset), - family, - sock_type, - protocol, - src, - src_port, - dst, - dst_port, - state, - socket_filter_str, - ) + d_inode = dentry.d_inode + if not d_inode: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping file at {hex(needle_addr)} as unable to locate inode for dentry", + ) + continue - yield (0, fields) - except exceptions.InvalidAddressException as error: - vollog.log( - constants.LOGLEVEL_VVVV, - f"Unable create results for socket at {hex(addr)} to invalid address: {error}", - ) + socket_alloc = linux.LinuxUtilities.container_of( + d_inode, "socket_alloc", "vfs_inode", vmlinux + ) + socket = socket_alloc.socket + if not (socket and socket.sk): + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping file at {hex(needle_addr)} as socket created by LinuxUtilities.container_of is invalid", + ) + continue + + # sucessfully trversed from file to sock, this will exist in the + # kernel layer, and need to be translated to the memory layer. + psock = socket.sk.dereference() + except exceptions.InvalidAddressException as error: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Unable to follow file at {hex(needle_addr)} to socket due to invalid address: {error}", + ) + + if psock is not None: + try: + sock_type = psock.get_type() + + family = psock.get_family() + # remove results with no family + if family == None: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping socket at {hex(sock_physical_addr)} as unable to determin family.", + ) + continue + + # TODO: invesitgate options for more invalid address handling in proccess_sock + # and the later formatting of it's results. + sock_fields = sock_handler.process_sock(psock) + # if no sock_fields we're able to be extracted then skip this result. + if not sock_fields: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping socket at {hex(sock_physical_addr)} as unable to process with SockHandlers.", + ) + continue + + sock, sock_stat, extended = sock_fields + src, src_port, dst, dst_port, state = sock_stat + protocol = sock.get_protocol() + + # format results + src = NotAvailableValue() if src is None else str(src) + src_port = ( + NotAvailableValue() if src_port is None else str(src_port) + ) + dst = NotAvailableValue() if dst is None else str(dst) + dst_port = ( + NotAvailableValue() if dst_port is None else str(dst_port) + ) + state = NotAvailableValue() if state is None else str(state) + protocol = ( + NotAvailableValue() if protocol is None else str(protocol) + ) + # extended attributes is a dict, so this is formated to string show each + # key and value pair, seperated with a comma. + socket_filter_str = ( + ",".join(f"{k}={v}" for k, v in extended.items()) + if extended + else NotAvailableValue() + ) + + # remove empty results + if (src == "0.0.0.0" or isinstance(src, NotAvailableValue)) and ( + dst == "0.0.0.0" or isinstance(src, NotAvailableValue) + ): + if state == "UNCONNECTED": + continue + elif src_port == "0" and dst_port == "0": + continue + + fields = ( + format_hints.Hex(sock_physical_addr), + family, + sock_type, + protocol, + src, + src_port, + dst, + dst_port, + state, + socket_filter_str, + ) + + yield (0, fields) + except exceptions.InvalidAddressException as error: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Unable create results for socket at {hex(sock_physical_addr)} due to invalid address: {error}", + ) def run(self): From 4f1f48087611818f874a033db4257b162e468c3f Mon Sep 17 00:00:00 2001 From: Eve Date: Wed, 27 Mar 2024 09:32:49 +0000 Subject: [PATCH 03/41] Linux: add test for sockscan plugin --- test/test_volatility.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/test/test_volatility.py b/test/test_volatility.py index 6e54aa053..0f935546d 100644 --- a/test/test_volatility.py +++ b/test/test_volatility.py @@ -196,7 +196,7 @@ def test_windows_thrdscan(image, volatility, python): assert out.find(b"\t4\t8") != -1 assert out.find(b"\t4\t12") != -1 assert out.find(b"\t4\t16") != -1 - #assert out.find(b"this raieses AssertionError") != -1 + # assert out.find(b"this raieses AssertionError") != -1 assert rc == 0 @@ -368,6 +368,27 @@ def test_linux_library_list(image, volatility, python): assert rc == 0 +def test_linux_sockscan(image, volatility, python): + # designed for linux-sample-1.dmp SHA1:1C3A4627EDCA94A7ADE3414592BEF0E62D7D3BB6 + rc, out, err = runvol_plugin("linux.sockscan.Sockscan", image, volatility, python) + + assert re.search( + rb"AF_UNIX\s+STREAM\s+-\s+/tmp/pulse-JldaJj8OxQLa/native\s+14054\s+-\s+14053\s+ESTABLISHED\s+-", + out, + ) + assert re.search( + rb"AF_INET\s+STREAM\s+TCP\s+192.168.201.161\s+22\s+192.168.201.1\s+59982\s+ESTABLISHED\s+-", + out, + ) + assert re.search( + rb"AF_INET\s+STREAM\s+TCP\s+0.0.0.0\s+901\s+0.0.0.0\s+0\s+LISTEN\s+-", + out, + ) + + assert out.count(b"\n") >= 50 + assert rc == 0 + + # MAC From 2c8b757ef02bcf6052705a32da6559789aff4201 Mon Sep 17 00:00:00 2001 From: Eve Date: Thu, 28 Mar 2024 08:55:07 +0000 Subject: [PATCH 04/41] Linux: update sockscan family check to use 'is None' rather than '== None' as per CodeQL --- volatility3/framework/plugins/linux/sockscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index d02757238..89f35e6bb 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -257,7 +257,7 @@ class Sockscan(plugins.PluginInterface): family = psock.get_family() # remove results with no family - if family == None: + if family is None: vollog.log( constants.LOGLEVEL_VVVV, f"Skipping socket at {hex(sock_physical_addr)} as unable to determin family.", From f3f5650a1d4c5456a8aaa61c58d724bbabf47e97 Mon Sep 17 00:00:00 2001 From: Eve Date: Thu, 28 Mar 2024 09:03:39 +0000 Subject: [PATCH 05/41] Linux: update sockscan with checks to reduce possible duplication of results --- volatility3/framework/plugins/linux/sockscan.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 89f35e6bb..d3413dea0 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -182,6 +182,9 @@ class Sockscan(plugins.PluginInterface): # sk_error_report_symbol_names = ['sock_def_error_report', 'inet_sk_rebuild_header', 'inet_listen'] # this would be similar to Method 2, but using a different pointer within sock. + # add a set of seen addresses to stop possible duplication of results. + seen_sock_physical_addr = set() + # Using the calculated needles, scan the memory layer and attempt to parse the sockets located. for needle_addr, match in memory_layer.scan( self.context, @@ -251,7 +254,8 @@ class Sockscan(plugins.PluginInterface): f"Unable to follow file at {hex(needle_addr)} to socket due to invalid address: {error}", ) - if psock is not None: + if psock is not None and sock_physical_addr not in seen_sock_physical_addr: + seen_sock_physical_addr.add(sock_physical_addr) try: sock_type = psock.get_type() From fb67d630835e2f7f78b055193de4cfd8696f8f23 Mon Sep 17 00:00:00 2001 From: Eve Date: Thu, 28 Mar 2024 09:21:54 +0000 Subject: [PATCH 06/41] Linux: update tests for sockscan to be more generic --- test/test_volatility.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/test/test_volatility.py b/test/test_volatility.py index 0f935546d..3d88eeab1 100644 --- a/test/test_volatility.py +++ b/test/test_volatility.py @@ -372,17 +372,26 @@ def test_linux_sockscan(image, volatility, python): # designed for linux-sample-1.dmp SHA1:1C3A4627EDCA94A7ADE3414592BEF0E62D7D3BB6 rc, out, err = runvol_plugin("linux.sockscan.Sockscan", image, volatility, python) - assert re.search( - rb"AF_UNIX\s+STREAM\s+-\s+/tmp/pulse-JldaJj8OxQLa/native\s+14054\s+-\s+14053\s+ESTABLISHED\s+-", - out, + # ensure that multiple unix paths for sockets have been found + assert ( + len( + re.findall( + rb"(/[ -~]+?){1,8}", + out, + ) + ) + >= 10 ) - assert re.search( - rb"AF_INET\s+STREAM\s+TCP\s+192.168.201.161\s+22\s+192.168.201.1\s+59982\s+ESTABLISHED\s+-", - out, - ) - assert re.search( - rb"AF_INET\s+STREAM\s+TCP\s+0.0.0.0\s+901\s+0.0.0.0\s+0\s+LISTEN\s+-", - out, + + # ensure that multiple IPv4 addresses have been found + assert ( + len( + re.findall( + rb"((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}", + out, + ) + ) + >= 10 ) assert out.count(b"\n") >= 50 From 3fa63061ddd2bba4a80c843bbb2a2095a7c1e67c Mon Sep 17 00:00:00 2001 From: ikelos Date: Wed, 3 Apr 2024 20:29:24 +0100 Subject: [PATCH 07/41] Update volatility3/framework/plugins/linux/sockscan.py Co-authored-by: Donghyun Kim --- volatility3/framework/plugins/linux/sockscan.py | 1 - 1 file changed, 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index d3413dea0..dc657c65b 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -12,7 +12,6 @@ from volatility3.framework.configuration import requirements from volatility3.framework.interfaces import plugins from volatility3.framework.symbols import linux from volatility3.plugins.linux import sockstat -from volatility3.framework import symbols from volatility3.framework import symbols, constants from volatility3.framework.layers import scanners From e4ea19debfeb61d0e441309dcb4edabb22c2dd46 Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 2 Aug 2024 11:41:52 +0100 Subject: [PATCH 08/41] Linux: sockscan update based on comments from @gcmoreira, add version, make use of relative_child_offset, fix f string. --- volatility3/framework/plugins/linux/sockscan.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index dc657c65b..cab4b9718 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -22,7 +22,8 @@ class Sockscan(plugins.PluginInterface): """Scans for network connections found in memory layer.""" _required_framework_version = (2, 6, 0) - + _version = (1, 0, 0) + @classmethod def get_requirements(cls): return [ @@ -35,12 +36,12 @@ class Sockscan(plugins.PluginInterface): name="SockHandlers", component=sockstat.SockHandlers, version=(1, 0, 0) ), requirements.VersionRequirement( - name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) + name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0) ), ] def _canonicalize_symbol_addrs( - self, symbol_table_name: List[str], symbol_names: str + self, symbol_table_name: str, symbol_names: List[str] ) -> Set[bytes]: """Takes a list of symbol names and converts the address of each to the bytes as they would appear in memory so that they can be scanned for. @@ -88,9 +89,9 @@ class Sockscan(plugins.PluginInterface): ) # make a warning if no symbols at all could be resolved. - if len(packed_needles) == 0: + if not packed_needles: vollog.warning( - f"_canonicalize_symbol_addrs was unable to resolve any symbols, use -vvvv for more information." + "_canonicalize_symbol_addrs was unable to resolve any symbols, use -vvvv for more information." ) return packed_needles @@ -159,7 +160,7 @@ class Sockscan(plugins.PluginInterface): # get file struct to find the offset to the f_op pointer # this is so that the file object can be created at the correct offset, # the results of the scanner will be for the f_op member within the file - f_op_offset = vmlinux.get_type("file").members["f_op"][0] + f_op_offset = vmlinux.get_type("file").relative_child_offset("f_op") # Method 2 - find sockets by socket destructor directly inside sock objects socket_destructor_symbol_names = [ @@ -175,7 +176,7 @@ class Sockscan(plugins.PluginInterface): # get sock struct to find the offset to the sk_destruct pointer # this is so that the sock object can be created at the correct offset, # the results of the scanner will be for the sk_destruct member within the scock - sk_destruct_offset = vmlinux.get_type("sock").members["sk_destruct"][0] + sk_destruct_offset = vmlinux.get_type("sock").relative_child_offset("sk_destruct") # TODO Method 3 - find sock by sk_error_report symbols # sk_error_report_symbol_names = ['sock_def_error_report', 'inet_sk_rebuild_header', 'inet_listen'] From a3393a971155a0ef095509fdb758f6e2b37994b7 Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 2 Aug 2024 11:46:04 +0100 Subject: [PATCH 09/41] Linux: fix black formatting issues --- volatility3/framework/plugins/linux/sockscan.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index cab4b9718..35e8237df 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -23,7 +23,7 @@ class Sockscan(plugins.PluginInterface): _required_framework_version = (2, 6, 0) _version = (1, 0, 0) - + @classmethod def get_requirements(cls): return [ @@ -176,7 +176,9 @@ class Sockscan(plugins.PluginInterface): # get sock struct to find the offset to the sk_destruct pointer # this is so that the sock object can be created at the correct offset, # the results of the scanner will be for the sk_destruct member within the scock - sk_destruct_offset = vmlinux.get_type("sock").relative_child_offset("sk_destruct") + sk_destruct_offset = vmlinux.get_type("sock").relative_child_offset( + "sk_destruct" + ) # TODO Method 3 - find sock by sk_error_report symbols # sk_error_report_symbol_names = ['sock_def_error_report', 'inet_sk_rebuild_header', 'inet_listen'] From 3c377320e5b2ddfe6f9e6c9101790af88faa0ade Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 2 Aug 2024 15:41:04 +0100 Subject: [PATCH 10/41] Linux: Begin to breakdown the functions in sockscan plugin --- .../framework/plugins/linux/sockscan.py | 338 ++++++++++-------- 1 file changed, 191 insertions(+), 147 deletions(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 35e8237df..57b4daf31 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -96,22 +96,15 @@ class Sockscan(plugins.PluginInterface): return packed_needles - def _generator(self, symbol_table_name: str): - """Scans for sockets. Each row represents a kernel socket. + def _find_memory_layer_name(self, symbol_table_name: str): + """Find the memory layer below the kernel. Only returns a single layer, + and will warn the user if multiple layers are found. Args: - symbol_table_name: The name of the kernel module on which to operate + symbol_table_name: The name of the kernel module on which to operate. - Yields: - addr: Physical offset - family: Socket family string (AF_UNIX, AF_INET, etc) - sock_type: Socket type string (STREAM, DGRAM, etc) - protocol: Protocol string (UDP, TCP, etc) - source addr: Source address string - source port: Source port string (not all of them are int) - destination addr: Destination address string - destination port: Destination port (not all of them are int) - state: State strings (LISTEN, CONNECTED, etc) + Returns: + memory_layer_name: The name of the layer below the kernel to be scanned. """ # get vmlinux module from context in order to build objects and read symbols @@ -136,20 +129,15 @@ class Sockscan(plugins.PluginInterface): raise exceptions.LayerException( vmlinux.layer_name, f"Layer {vmlinux.layer_name} has no dependencies" ) + memory_layer_name = kernel_layer.dependencies[0] - memory_layer = self.context.layers[kernel_layer.dependencies[0]] - # use the init process to build a sock handler - # TODO: look into options so that sockstat.SockHandlers so that process_sock can - # be used without a task object. - init_task = vmlinux.object_from_symbol(symbol_name="init_task") - sock_handler = sockstat.SockHandlers(vmlinux, init_task) + return memory_layer_name - # get progress_callback in order to use this in the scanners. - # TODO: perhaps add more detail to progress, showing method in progress and number of hits found - progress_callback = self._progress_callback + def _find_file_ops_needles(self, symbol_table_name: str): + # get vmlinux module from context in order to read symbols + vmlinux = self.context.modules[symbol_table_name] - # Method 1 - find sockets by file operations, then follow pointers to sockets file_ops_symbol_names = [ "socket_file_ops", "sockfs_dentry_operations", @@ -162,7 +150,12 @@ class Sockscan(plugins.PluginInterface): # the results of the scanner will be for the f_op member within the file f_op_offset = vmlinux.get_type("file").relative_child_offset("f_op") - # Method 2 - find sockets by socket destructor directly inside sock objects + return (file_ops_needles, f_op_offset) + + def _find_sk_destruct_needles(self, symbol_table_name: str): + # get vmlinux module from context in order to read symbols + vmlinux = self.context.modules[symbol_table_name] + socket_destructor_symbol_names = [ "sock_def_destruct", "packet_sock_destruct", @@ -179,6 +172,173 @@ class Sockscan(plugins.PluginInterface): sk_destruct_offset = vmlinux.get_type("sock").relative_child_offset( "sk_destruct" ) + return (socket_destructor_needles, sk_destruct_offset) + + def _walk_file_ops_needles( + self, symbol_table_name, memory_layer_name, needle_addr, f_op_offset + ): + vmlinux = self.context.modules[symbol_table_name] + try: + # create file in the memory_layer, the native layer matches the + # kernel so that pointers can be followed + sock_physical_addr = needle_addr - f_op_offset + pfile = self.context.object( + vmlinux.symbol_table_name + constants.BANG + "file", + offset=sock_physical_addr, + layer_name=memory_layer_name, + native_layer_name=vmlinux.layer_name, + ) + dentry = pfile.get_dentry() + if not dentry: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping file at {hex(needle_addr)} as unable to locate dentry", + ) + return None + + d_inode = dentry.d_inode + if not d_inode: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping file at {hex(needle_addr)} as unable to locate inode for dentry", + ) + return None + + socket_alloc = linux.LinuxUtilities.container_of( + d_inode, "socket_alloc", "vfs_inode", vmlinux + ) + socket = socket_alloc.socket + if not (socket and socket.sk): + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping file at {hex(needle_addr)} as socket created by LinuxUtilities.container_of is invalid", + ) + return None + + # sucessfully trversed from file to sock, this will exist in the + # kernel layer, and need to be translated to the memory layer. + psock = socket.sk.dereference() + return psock + + except exceptions.InvalidAddressException as error: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Unable to follow file at {hex(needle_addr)} to socket due to invalid address: {error}", + ) + + def _extract_sock_fields(self, psock, sock_handler): + try: + sock_physical_addr = psock.vol.offset + sock_type = psock.get_type() + + family = psock.get_family() + # remove results with no family + if family is None: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping socket at {hex(sock_physical_addr)} as unable to determin family.", + ) + return None + + # TODO: invesitgate options for more invalid address handling in proccess_sock + # and the later formatting of it's results. + sock_fields = sock_handler.process_sock(psock) + # if no sock_fields we're able to be extracted then skip this result. + if not sock_fields: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping socket at {hex(sock_physical_addr)} as unable to process with SockHandlers.", + ) + return None + + sock, sock_stat, extended = sock_fields + src, src_port, dst, dst_port, state = sock_stat + protocol = sock.get_protocol() + + # format results + src = NotAvailableValue() if src is None else str(src) + src_port = NotAvailableValue() if src_port is None else str(src_port) + dst = NotAvailableValue() if dst is None else str(dst) + dst_port = NotAvailableValue() if dst_port is None else str(dst_port) + state = NotAvailableValue() if state is None else str(state) + protocol = NotAvailableValue() if protocol is None else str(protocol) + # extended attributes is a dict, so this is formated to string show each + # key and value pair, seperated with a comma. + socket_filter_str = ( + ",".join(f"{k}={v}" for k, v in extended.items()) + if extended + else NotAvailableValue() + ) + + # remove empty results + if (src == "0.0.0.0" or isinstance(src, NotAvailableValue)) and ( + dst == "0.0.0.0" or isinstance(src, NotAvailableValue) + ): + if state == "UNCONNECTED": + return None + elif src_port == "0" and dst_port == "0": + return None + return ( + format_hints.Hex(sock_physical_addr), + family, + sock_type, + protocol, + src, + src_port, + dst, + dst_port, + state, + socket_filter_str, + ) + + except exceptions.InvalidAddressException as error: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Unable create results for socket at {hex(sock_physical_addr)} due to invalid address: {error}", + ) + + def _generator(self, symbol_table_name: str): + """Scans for sockets. Each row represents a kernel socket. + + Args: + symbol_table_name: The name of the kernel module on which to operate + + Yields: + addr: Physical offset + family: Socket family string (AF_UNIX, AF_INET, etc) + sock_type: Socket type string (STREAM, DGRAM, etc) + protocol: Protocol string (UDP, TCP, etc) + source addr: Source address string + source port: Source port string (not all of them are int) + destination addr: Destination address string + destination port: Destination port (not all of them are int) + state: State strings (LISTEN, CONNECTED, etc) + """ + + # get vmlinux module from context in order to build objects and read symbols + vmlinux = self.context.modules[symbol_table_name] + + # get the memory layer that is to be scanned. + memory_layer_name = self._find_memory_layer_name(symbol_table_name) + memory_layer = self.context.layers[memory_layer_name] + + # use the init process to build a sock handler + # TODO: look into options so that sockstat.SockHandlers so that process_sock can + # be used without a task object. + init_task = vmlinux.object_from_symbol(symbol_name="init_task") + sock_handler = sockstat.SockHandlers(vmlinux, init_task) + + # get progress_callback in order to use this in the scanners. + # TODO: perhaps add more detail to progress, showing method in progress and number of hits found + progress_callback = self._progress_callback + + # Method 1 - find sockets by file operations, then follow pointers to sockets + file_ops_needles, f_op_offset = self._find_file_ops_needles(symbol_table_name) + + # Method 2 - find sockets by socket destructor directly inside sock objects + socket_destructor_needles, sk_destruct_offset = self._find_sk_destruct_needles( + symbol_table_name + ) # TODO Method 3 - find sock by sk_error_report symbols # sk_error_report_symbol_names = ['sock_def_error_report', 'inet_sk_rebuild_header', 'inet_listen'] @@ -196,8 +356,7 @@ class Sockscan(plugins.PluginInterface): psock = None sock_physical_addr = None - # if match is from socket_destructor_needles simply calculate the offset - # to the sock + # if match is from socket_destructor_needles simply calculate the offset to the sock if match in socket_destructor_needles: sock_physical_addr = needle_addr - sk_destruct_offset psock = self.context.object( @@ -207,133 +366,18 @@ class Sockscan(plugins.PluginInterface): native_layer_name=vmlinux.layer_name, ) - # if match is from file_ops_needles attempt to walk from file object to - # the sock + # if match is from file_ops_needles attempt to walk from file object to the sock if match in file_ops_needles: - try: - # create file in the memory_layer, the native layer matches the - # kernel so that pointers can be followed - sock_physical_addr = needle_addr - f_op_offset - pfile = self.context.object( - vmlinux.symbol_table_name + constants.BANG + "file", - offset=sock_physical_addr, - layer_name=memory_layer_name, - native_layer_name=vmlinux.layer_name, - ) - dentry = pfile.get_dentry() - if not dentry: - vollog.log( - constants.LOGLEVEL_VVVV, - f"Skipping file at {hex(needle_addr)} as unable to locate dentry", - ) - continue - - d_inode = dentry.d_inode - if not d_inode: - vollog.log( - constants.LOGLEVEL_VVVV, - f"Skipping file at {hex(needle_addr)} as unable to locate inode for dentry", - ) - continue - - socket_alloc = linux.LinuxUtilities.container_of( - d_inode, "socket_alloc", "vfs_inode", vmlinux - ) - socket = socket_alloc.socket - if not (socket and socket.sk): - vollog.log( - constants.LOGLEVEL_VVVV, - f"Skipping file at {hex(needle_addr)} as socket created by LinuxUtilities.container_of is invalid", - ) - continue - - # sucessfully trversed from file to sock, this will exist in the - # kernel layer, and need to be translated to the memory layer. - psock = socket.sk.dereference() - except exceptions.InvalidAddressException as error: - vollog.log( - constants.LOGLEVEL_VVVV, - f"Unable to follow file at {hex(needle_addr)} to socket due to invalid address: {error}", - ) + psock = self._walk_file_ops_needles( + symbol_table_name, memory_layer_name, needle_addr, f_op_offset + ) if psock is not None and sock_physical_addr not in seen_sock_physical_addr: seen_sock_physical_addr.add(sock_physical_addr) - try: - sock_type = psock.get_type() - - family = psock.get_family() - # remove results with no family - if family is None: - vollog.log( - constants.LOGLEVEL_VVVV, - f"Skipping socket at {hex(sock_physical_addr)} as unable to determin family.", - ) - continue - - # TODO: invesitgate options for more invalid address handling in proccess_sock - # and the later formatting of it's results. - sock_fields = sock_handler.process_sock(psock) - # if no sock_fields we're able to be extracted then skip this result. - if not sock_fields: - vollog.log( - constants.LOGLEVEL_VVVV, - f"Skipping socket at {hex(sock_physical_addr)} as unable to process with SockHandlers.", - ) - continue - - sock, sock_stat, extended = sock_fields - src, src_port, dst, dst_port, state = sock_stat - protocol = sock.get_protocol() - - # format results - src = NotAvailableValue() if src is None else str(src) - src_port = ( - NotAvailableValue() if src_port is None else str(src_port) - ) - dst = NotAvailableValue() if dst is None else str(dst) - dst_port = ( - NotAvailableValue() if dst_port is None else str(dst_port) - ) - state = NotAvailableValue() if state is None else str(state) - protocol = ( - NotAvailableValue() if protocol is None else str(protocol) - ) - # extended attributes is a dict, so this is formated to string show each - # key and value pair, seperated with a comma. - socket_filter_str = ( - ",".join(f"{k}={v}" for k, v in extended.items()) - if extended - else NotAvailableValue() - ) - - # remove empty results - if (src == "0.0.0.0" or isinstance(src, NotAvailableValue)) and ( - dst == "0.0.0.0" or isinstance(src, NotAvailableValue) - ): - if state == "UNCONNECTED": - continue - elif src_port == "0" and dst_port == "0": - continue - - fields = ( - format_hints.Hex(sock_physical_addr), - family, - sock_type, - protocol, - src, - src_port, - dst, - dst_port, - state, - socket_filter_str, - ) + fields = self._extract_sock_fields(psock, sock_handler) + if fields: yield (0, fields) - except exceptions.InvalidAddressException as error: - vollog.log( - constants.LOGLEVEL_VVVV, - f"Unable create results for socket at {hex(sock_physical_addr)} due to invalid address: {error}", - ) def run(self): From dcded3a1fc2688c6cba8bf4e1065f12cbe645db3 Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Thu, 24 Apr 2025 09:00:50 +0100 Subject: [PATCH 11/41] Update test/test_volatility.py Co-authored-by: ikelos --- test/test_volatility.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_volatility.py b/test/test_volatility.py index 3d88eeab1..5491a8836 100644 --- a/test/test_volatility.py +++ b/test/test_volatility.py @@ -196,7 +196,7 @@ def test_windows_thrdscan(image, volatility, python): assert out.find(b"\t4\t8") != -1 assert out.find(b"\t4\t12") != -1 assert out.find(b"\t4\t16") != -1 - # assert out.find(b"this raieses AssertionError") != -1 + # assert out.find(b"this raises AssertionError") != -1 assert rc == 0 From 0fbe03912e20e40ce64090a007038b00554ec2e0 Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Thu, 24 Apr 2025 09:01:42 +0100 Subject: [PATCH 12/41] Update volatility3/framework/plugins/linux/sockscan.py Co-authored-by: ikelos --- volatility3/framework/plugins/linux/sockscan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 57b4daf31..0ac424481 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -296,6 +296,7 @@ class Sockscan(plugins.PluginInterface): constants.LOGLEVEL_VVVV, f"Unable create results for socket at {hex(sock_physical_addr)} due to invalid address: {error}", ) + return None def _generator(self, symbol_table_name: str): """Scans for sockets. Each row represents a kernel socket. From 95c54351f09de38c0cf4bf0439547f66a33f0b3d Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Thu, 24 Apr 2025 09:01:53 +0100 Subject: [PATCH 13/41] Update volatility3/framework/plugins/linux/sockscan.py Co-authored-by: ikelos --- volatility3/framework/plugins/linux/sockscan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 0ac424481..9f756ecc9 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -225,6 +225,7 @@ class Sockscan(plugins.PluginInterface): constants.LOGLEVEL_VVVV, f"Unable to follow file at {hex(needle_addr)} to socket due to invalid address: {error}", ) + return None def _extract_sock_fields(self, psock, sock_handler): try: From 5e549b2d9204b106c5acdf3248ceba3ef5503cb9 Mon Sep 17 00:00:00 2001 From: eve Date: Thu, 24 Apr 2025 09:10:51 +0100 Subject: [PATCH 14/41] Linux: update sockstat requirements --- .../framework/plugins/linux/sockscan.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 9f756ecc9..11ba8c5fb 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -11,9 +11,10 @@ from volatility3.framework.renderers import TreeGrid, NotAvailableValue, format_ from volatility3.framework.configuration import requirements from volatility3.framework.interfaces import plugins from volatility3.framework.symbols import linux -from volatility3.plugins.linux import sockstat -from volatility3.framework import symbols, constants +from volatility3.framework import symbols +from volatility3.plugins.linux import lsof, pslist, sockstat from volatility3.framework.layers import scanners +from volatility3.framework.symbols.linux import network vollog = logging.getLogger(__name__) @@ -33,10 +34,19 @@ class Sockscan(plugins.PluginInterface): architectures=["Intel32", "Intel64"], ), requirements.VersionRequirement( - name="SockHandlers", component=sockstat.SockHandlers, version=(1, 0, 0) + name="SockHandlers", component=sockstat.SockHandlers, version=(4, 0, 0) ), requirements.VersionRequirement( - name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0) + name="lsof", component=lsof.Lsof, version=(2, 0, 0) + ), + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(4, 0, 0) + ), + requirements.VersionRequirement( + name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) + ), + requirements.VersionRequirement( + name="linux_net", component=network.NetSymbols, version=(1, 0, 0) ), ] From 1a57a66ce60533bdc9e33e6ed16f8e536189ff95 Mon Sep 17 00:00:00 2001 From: eve Date: Thu, 24 Apr 2025 09:11:36 +0100 Subject: [PATCH 15/41] Linux: update sockstat ruff issue with f-string using no placeholders --- volatility3/framework/plugins/linux/sockscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 11ba8c5fb..27fb5f020 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -134,7 +134,7 @@ class Sockscan(plugins.PluginInterface): ) elif len(kernel_layer.dependencies) == 0: vollog.error( - f"Kernel layer has no dependencies, meaning there is no memory layer for this plugin to scan." + "Kernel layer has no dependencies, meaning there is no memory layer for this plugin to scan." ) raise exceptions.LayerException( vmlinux.layer_name, f"Layer {vmlinux.layer_name} has no dependencies" From e55ab3313f9119a7f455a2066f6f953ea92e9e4c Mon Sep 17 00:00:00 2001 From: eve Date: Thu, 24 Apr 2025 09:16:23 +0100 Subject: [PATCH 16/41] Linux: update tests for sockscan --- test/plugins/linux/linux.py | 34 ++++++++++++++++++++++++++++++++++ test/test_volatility.py | 29 ----------------------------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/test/plugins/linux/linux.py b/test/plugins/linux/linux.py index e39c1d15d..fc01c0c92 100644 --- a/test/plugins/linux/linux.py +++ b/test/plugins/linux/linux.py @@ -615,3 +615,37 @@ class TestLinuxPscallstack: rb"1\s+init\s+39\s+0x88001f999a40.*?0xffff81109039\s+do_select\s+T\s+kernel", out, ) + + +class TestLinuxSockscan: + def test_linux_sockscan(image, volatility, python): + # designed for linux-sample-1.dmp SHA1:1C3A4627EDCA94A7ADE3414592BEF0E62D7D3BB6 + image = LinuxSamples.LINUX_GENERIC.value.path + rc, out, err = test_volatility.runvol_plugin( + "linux.sockscan.Sockscan", image, volatility, python + ) + + # ensure that multiple unix paths for sockets have been found + assert ( + len( + re.findall( + rb"(/[ -~]+?){1,8}", + out, + ) + ) + >= 10 + ) + + # ensure that multiple IPv4 addresses have been found + assert ( + len( + re.findall( + rb"((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}", + out, + ) + ) + >= 10 + ) + + assert out.count(b"\n") >= 50 + assert rc == 0 diff --git a/test/test_volatility.py b/test/test_volatility.py index 195b51357..9b7aff4be 100644 --- a/test/test_volatility.py +++ b/test/test_volatility.py @@ -381,32 +381,3 @@ def test_mac_trustedbsd(image, volatility, python): assert out.count(b"\n") > 10 assert rc == 0 - -def test_linux_sockscan(image, volatility, python): - # designed for linux-sample-1.dmp SHA1:1C3A4627EDCA94A7ADE3414592BEF0E62D7D3BB6 - rc, out, err = runvol_plugin("linux.sockscan.Sockscan", image, volatility, python) - - # ensure that multiple unix paths for sockets have been found - assert ( - len( - re.findall( - rb"(/[ -~]+?){1,8}", - out, - ) - ) - >= 10 - ) - - # ensure that multiple IPv4 addresses have been found - assert ( - len( - re.findall( - rb"((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}", - out, - ) - ) - >= 10 - ) - - assert out.count(b"\n") >= 50 - assert rc == 0 From ca584cce7403dec5367e5fb2b51064277fb12864 Mon Sep 17 00:00:00 2001 From: eve Date: Thu, 24 Apr 2025 09:23:21 +0100 Subject: [PATCH 17/41] Linux: Update linux sockscan to work with SockHandlers v4.0.0 --- volatility3/framework/plugins/linux/sockscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 27fb5f020..915a8e834 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -338,7 +338,7 @@ class Sockscan(plugins.PluginInterface): # TODO: look into options so that sockstat.SockHandlers so that process_sock can # be used without a task object. init_task = vmlinux.object_from_symbol(symbol_name="init_task") - sock_handler = sockstat.SockHandlers(vmlinux, init_task) + sock_handler = sockstat.SockHandlers(self.context, symbol_table_name, init_task) # get progress_callback in order to use this in the scanners. # TODO: perhaps add more detail to progress, showing method in progress and number of hits found From 704f0f023d7507b18a64608935c729f474f57d82 Mon Sep 17 00:00:00 2001 From: eve Date: Thu, 24 Apr 2025 09:35:04 +0100 Subject: [PATCH 18/41] Linux: Fix incorrect first parameter name in test_linux_sockscan --- test/plugins/linux/linux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plugins/linux/linux.py b/test/plugins/linux/linux.py index fc01c0c92..868b439fb 100644 --- a/test/plugins/linux/linux.py +++ b/test/plugins/linux/linux.py @@ -618,7 +618,7 @@ class TestLinuxPscallstack: class TestLinuxSockscan: - def test_linux_sockscan(image, volatility, python): + def test_linux_sockscan(self, volatility, python): # designed for linux-sample-1.dmp SHA1:1C3A4627EDCA94A7ADE3414592BEF0E62D7D3BB6 image = LinuxSamples.LINUX_GENERIC.value.path rc, out, err = test_volatility.runvol_plugin( From d532f3745438907aa654a5436bfcd04846763bf6 Mon Sep 17 00:00:00 2001 From: eve Date: Thu, 24 Apr 2025 09:37:39 +0100 Subject: [PATCH 19/41] Linux: sockstat contiuned work to breakdown large generator function --- .../framework/plugins/linux/sockscan.py | 128 +++++++++--------- 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 915a8e834..530b36c04 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -309,84 +309,86 @@ class Sockscan(plugins.PluginInterface): ) return None - def _generator(self, symbol_table_name: str): - """Scans for sockets. Each row represents a kernel socket. - - Args: - symbol_table_name: The name of the kernel module on which to operate - - Yields: - addr: Physical offset - family: Socket family string (AF_UNIX, AF_INET, etc) - sock_type: Socket type string (STREAM, DGRAM, etc) - protocol: Protocol string (UDP, TCP, etc) - source addr: Source address string - source port: Source port string (not all of them are int) - destination addr: Destination address string - destination port: Destination port (not all of them are int) - state: State strings (LISTEN, CONNECTED, etc) - """ - - # get vmlinux module from context in order to build objects and read symbols + def _build_sock_handler(self, symbol_table_name: str): vmlinux = self.context.modules[symbol_table_name] + init_task = vmlinux.object_from_symbol(symbol_name="init_task") + return sockstat.SockHandlers(self.context, symbol_table_name, init_task) - # get the memory layer that is to be scanned. + def _scan_for_sockets( + self, memory_layer, file_ops_needles, socket_destructor_needles + ): + return memory_layer.scan( + self.context, + scanners.MultiStringScanner(socket_destructor_needles | file_ops_needles), + self._progress_callback, + ) + + def _parse_scanner_result( + self, + match, + needle_addr, + file_ops_needles, + socket_destructor_needles, + symbol_table_name, + memory_layer_name, + f_op_offset, + sk_destruct_offset, + ): + + if match in socket_destructor_needles: + sock_physical_addr = needle_addr - sk_destruct_offset + psock = self._get_socket_from_sk_destruct( + sock_physical_addr, symbol_table_name, memory_layer_name + ) + elif match in file_ops_needles: + psock = self._walk_file_ops_needles( + symbol_table_name, memory_layer_name, needle_addr, f_op_offset + ) + sock_physical_addr = psock.vol.offset if psock else None + else: + psock = sock_physical_addr = None + + return psock, sock_physical_addr + + def _get_socket_from_sk_destruct( + self, sock_physical_addr, symbol_table_name, memory_layer_name + ): + vmlinux = self.context.modules[symbol_table_name] + return self.context.object( + vmlinux.symbol_table_name + constants.BANG + "sock", + offset=sock_physical_addr, + layer_name=memory_layer_name, + native_layer_name=vmlinux.layer_name, + ) + + def _generator(self, symbol_table_name: str): memory_layer_name = self._find_memory_layer_name(symbol_table_name) memory_layer = self.context.layers[memory_layer_name] + sock_handler = self._build_sock_handler(symbol_table_name) - # use the init process to build a sock handler - # TODO: look into options so that sockstat.SockHandlers so that process_sock can - # be used without a task object. - init_task = vmlinux.object_from_symbol(symbol_name="init_task") - sock_handler = sockstat.SockHandlers(self.context, symbol_table_name, init_task) - - # get progress_callback in order to use this in the scanners. - # TODO: perhaps add more detail to progress, showing method in progress and number of hits found - progress_callback = self._progress_callback - - # Method 1 - find sockets by file operations, then follow pointers to sockets file_ops_needles, f_op_offset = self._find_file_ops_needles(symbol_table_name) - - # Method 2 - find sockets by socket destructor directly inside sock objects socket_destructor_needles, sk_destruct_offset = self._find_sk_destruct_needles( symbol_table_name ) - # TODO Method 3 - find sock by sk_error_report symbols - # sk_error_report_symbol_names = ['sock_def_error_report', 'inet_sk_rebuild_header', 'inet_listen'] - # this would be similar to Method 2, but using a different pointer within sock. - - # add a set of seen addresses to stop possible duplication of results. seen_sock_physical_addr = set() - # Using the calculated needles, scan the memory layer and attempt to parse the sockets located. - for needle_addr, match in memory_layer.scan( - self.context, - scanners.MultiStringScanner(socket_destructor_needles | file_ops_needles), - progress_callback, + for needle_addr, match in self._scan_for_sockets( + memory_layer, file_ops_needles, socket_destructor_needles ): - psock = None - sock_physical_addr = None + psock, sock_physical_addr = self._parse_scanner_result( + match, + needle_addr, + file_ops_needles, + socket_destructor_needles, + symbol_table_name, + memory_layer_name, + f_op_offset, + sk_destruct_offset, + ) - # if match is from socket_destructor_needles simply calculate the offset to the sock - if match in socket_destructor_needles: - sock_physical_addr = needle_addr - sk_destruct_offset - psock = self.context.object( - vmlinux.symbol_table_name + constants.BANG + "sock", - offset=sock_physical_addr, - layer_name=memory_layer_name, - native_layer_name=vmlinux.layer_name, - ) - - # if match is from file_ops_needles attempt to walk from file object to the sock - if match in file_ops_needles: - psock = self._walk_file_ops_needles( - symbol_table_name, memory_layer_name, needle_addr, f_op_offset - ) - - if psock is not None and sock_physical_addr not in seen_sock_physical_addr: + if psock and sock_physical_addr not in seen_sock_physical_addr: seen_sock_physical_addr.add(sock_physical_addr) - fields = self._extract_sock_fields(psock, sock_handler) if fields: yield (0, fields) From 787f3b629ae14427576406d5d36b9d234f839eae Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 25 Apr 2025 11:10:05 +0100 Subject: [PATCH 20/41] Revert "Linux: sockstat contiuned work to breakdown large generator function" This reverts commit d532f3745438907aa654a5436bfcd04846763bf6. --- .../framework/plugins/linux/sockscan.py | 130 +++++++++--------- 1 file changed, 64 insertions(+), 66 deletions(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 530b36c04..915a8e834 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -309,86 +309,84 @@ class Sockscan(plugins.PluginInterface): ) return None - def _build_sock_handler(self, symbol_table_name: str): - vmlinux = self.context.modules[symbol_table_name] - init_task = vmlinux.object_from_symbol(symbol_name="init_task") - return sockstat.SockHandlers(self.context, symbol_table_name, init_task) - - def _scan_for_sockets( - self, memory_layer, file_ops_needles, socket_destructor_needles - ): - return memory_layer.scan( - self.context, - scanners.MultiStringScanner(socket_destructor_needles | file_ops_needles), - self._progress_callback, - ) - - def _parse_scanner_result( - self, - match, - needle_addr, - file_ops_needles, - socket_destructor_needles, - symbol_table_name, - memory_layer_name, - f_op_offset, - sk_destruct_offset, - ): - - if match in socket_destructor_needles: - sock_physical_addr = needle_addr - sk_destruct_offset - psock = self._get_socket_from_sk_destruct( - sock_physical_addr, symbol_table_name, memory_layer_name - ) - elif match in file_ops_needles: - psock = self._walk_file_ops_needles( - symbol_table_name, memory_layer_name, needle_addr, f_op_offset - ) - sock_physical_addr = psock.vol.offset if psock else None - else: - psock = sock_physical_addr = None - - return psock, sock_physical_addr - - def _get_socket_from_sk_destruct( - self, sock_physical_addr, symbol_table_name, memory_layer_name - ): - vmlinux = self.context.modules[symbol_table_name] - return self.context.object( - vmlinux.symbol_table_name + constants.BANG + "sock", - offset=sock_physical_addr, - layer_name=memory_layer_name, - native_layer_name=vmlinux.layer_name, - ) - def _generator(self, symbol_table_name: str): + """Scans for sockets. Each row represents a kernel socket. + + Args: + symbol_table_name: The name of the kernel module on which to operate + + Yields: + addr: Physical offset + family: Socket family string (AF_UNIX, AF_INET, etc) + sock_type: Socket type string (STREAM, DGRAM, etc) + protocol: Protocol string (UDP, TCP, etc) + source addr: Source address string + source port: Source port string (not all of them are int) + destination addr: Destination address string + destination port: Destination port (not all of them are int) + state: State strings (LISTEN, CONNECTED, etc) + """ + + # get vmlinux module from context in order to build objects and read symbols + vmlinux = self.context.modules[symbol_table_name] + + # get the memory layer that is to be scanned. memory_layer_name = self._find_memory_layer_name(symbol_table_name) memory_layer = self.context.layers[memory_layer_name] - sock_handler = self._build_sock_handler(symbol_table_name) + # use the init process to build a sock handler + # TODO: look into options so that sockstat.SockHandlers so that process_sock can + # be used without a task object. + init_task = vmlinux.object_from_symbol(symbol_name="init_task") + sock_handler = sockstat.SockHandlers(self.context, symbol_table_name, init_task) + + # get progress_callback in order to use this in the scanners. + # TODO: perhaps add more detail to progress, showing method in progress and number of hits found + progress_callback = self._progress_callback + + # Method 1 - find sockets by file operations, then follow pointers to sockets file_ops_needles, f_op_offset = self._find_file_ops_needles(symbol_table_name) + + # Method 2 - find sockets by socket destructor directly inside sock objects socket_destructor_needles, sk_destruct_offset = self._find_sk_destruct_needles( symbol_table_name ) + # TODO Method 3 - find sock by sk_error_report symbols + # sk_error_report_symbol_names = ['sock_def_error_report', 'inet_sk_rebuild_header', 'inet_listen'] + # this would be similar to Method 2, but using a different pointer within sock. + + # add a set of seen addresses to stop possible duplication of results. seen_sock_physical_addr = set() - for needle_addr, match in self._scan_for_sockets( - memory_layer, file_ops_needles, socket_destructor_needles + # Using the calculated needles, scan the memory layer and attempt to parse the sockets located. + for needle_addr, match in memory_layer.scan( + self.context, + scanners.MultiStringScanner(socket_destructor_needles | file_ops_needles), + progress_callback, ): - psock, sock_physical_addr = self._parse_scanner_result( - match, - needle_addr, - file_ops_needles, - socket_destructor_needles, - symbol_table_name, - memory_layer_name, - f_op_offset, - sk_destruct_offset, - ) + psock = None + sock_physical_addr = None - if psock and sock_physical_addr not in seen_sock_physical_addr: + # if match is from socket_destructor_needles simply calculate the offset to the sock + if match in socket_destructor_needles: + sock_physical_addr = needle_addr - sk_destruct_offset + psock = self.context.object( + vmlinux.symbol_table_name + constants.BANG + "sock", + offset=sock_physical_addr, + layer_name=memory_layer_name, + native_layer_name=vmlinux.layer_name, + ) + + # if match is from file_ops_needles attempt to walk from file object to the sock + if match in file_ops_needles: + psock = self._walk_file_ops_needles( + symbol_table_name, memory_layer_name, needle_addr, f_op_offset + ) + + if psock is not None and sock_physical_addr not in seen_sock_physical_addr: seen_sock_physical_addr.add(sock_physical_addr) + fields = self._extract_sock_fields(psock, sock_handler) if fields: yield (0, fields) From ab5dac10d936c5b668a9c69e6acbada06abcd662 Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 25 Apr 2025 11:54:55 +0100 Subject: [PATCH 21/41] Linux: fix virtual to physical offsets for sockscan file_ops method --- .../framework/plugins/linux/sockscan.py | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 915a8e834..a123e74cd 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -227,7 +227,25 @@ class Sockscan(plugins.PluginInterface): # sucessfully trversed from file to sock, this will exist in the # kernel layer, and need to be translated to the memory layer. - psock = socket.sk.dereference() + vsock = socket.sk.dereference() + + # get virtual offset + virtual_sock_offset = vsock.vol.offset + + # translate this offset to physical + native_layer = self.context.layers[vmlinux.layer_name] + physical_sock_offset, _physical_layer_name = native_layer.translate( + virtual_sock_offset + ) + + # build sock on the memory_layer using the physical_sock_offset + psock = self.context.object( + vmlinux.symbol_table_name + constants.BANG + "sock", + offset=physical_sock_offset, + layer_name=memory_layer_name, + native_layer_name=vmlinux.layer_name, + ) + return psock except exceptions.InvalidAddressException as error: @@ -336,7 +354,7 @@ class Sockscan(plugins.PluginInterface): # use the init process to build a sock handler # TODO: look into options so that sockstat.SockHandlers so that process_sock can - # be used without a task object. + # be used without a task object. init_task = vmlinux.object_from_symbol(symbol_name="init_task") sock_handler = sockstat.SockHandlers(self.context, symbol_table_name, init_task) From 0d32cac06786eb0b6e449ca65057072278213296 Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 25 Apr 2025 11:58:29 +0100 Subject: [PATCH 22/41] Linux: fix sockscan use of direct imports of TreeGrid and NotAvailableValue --- .../framework/plugins/linux/sockscan.py | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index a123e74cd..b9a0d854d 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -7,7 +7,8 @@ import struct from typing import List, Set from volatility3.framework import exceptions, constants -from volatility3.framework.renderers import TreeGrid, NotAvailableValue, format_hints +from volatility3.framework import renderers +from volatility3.framework.renderers import format_hints from volatility3.framework.configuration import requirements from volatility3.framework.interfaces import plugins from volatility3.framework.symbols import linux @@ -285,23 +286,29 @@ class Sockscan(plugins.PluginInterface): protocol = sock.get_protocol() # format results - src = NotAvailableValue() if src is None else str(src) - src_port = NotAvailableValue() if src_port is None else str(src_port) - dst = NotAvailableValue() if dst is None else str(dst) - dst_port = NotAvailableValue() if dst_port is None else str(dst_port) - state = NotAvailableValue() if state is None else str(state) - protocol = NotAvailableValue() if protocol is None else str(protocol) + src = renderers.NotAvailableValue() if src is None else str(src) + src_port = ( + renderers.NotAvailableValue() if src_port is None else str(src_port) + ) + dst = renderers.NotAvailableValue() if dst is None else str(dst) + dst_port = ( + renderers.NotAvailableValue() if dst_port is None else str(dst_port) + ) + state = renderers.NotAvailableValue() if state is None else str(state) + protocol = ( + renderers.NotAvailableValue() if protocol is None else str(protocol) + ) # extended attributes is a dict, so this is formated to string show each # key and value pair, seperated with a comma. socket_filter_str = ( ",".join(f"{k}={v}" for k, v in extended.items()) if extended - else NotAvailableValue() + else renderers.NotAvailableValue() ) # remove empty results - if (src == "0.0.0.0" or isinstance(src, NotAvailableValue)) and ( - dst == "0.0.0.0" or isinstance(src, NotAvailableValue) + if (src == "0.0.0.0" or isinstance(src, renderers.NotAvailableValue)) and ( + dst == "0.0.0.0" or isinstance(src, renderers.NotAvailableValue) ): if state == "UNCONNECTED": return None @@ -424,7 +431,7 @@ class Sockscan(plugins.PluginInterface): ("Filter", str), ] - return TreeGrid( + return renderers.TreeGrid( tree_grid_args, self._generator(self.config["kernel"]), ) From 6f11161bd5388f3bf5ed3b97ff8841e11b65810f Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 25 Apr 2025 11:59:43 +0100 Subject: [PATCH 23/41] Linux: add MultiStringScanner requirement for sockscan --- volatility3/framework/plugins/linux/sockscan.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index b9a0d854d..943e7447b 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -49,6 +49,11 @@ class Sockscan(plugins.PluginInterface): requirements.VersionRequirement( name="linux_net", component=network.NetSymbols, version=(1, 0, 0) ), + requirements.VersionRequirement( + name="multi_string_scanner", + component=scanners.MultiStringScanner, + version=(1, 0, 0), + ), ] def _canonicalize_symbol_addrs( From 7cfc03da5dff89d608a97e08e4313ce88597a20c Mon Sep 17 00:00:00 2001 From: eve Date: Fri, 25 Apr 2025 12:13:16 +0100 Subject: [PATCH 24/41] Linux: update sockscan to use minor version for the requirements this has been tested with --- volatility3/framework/plugins/linux/sockscan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 943e7447b..4f16920cf 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -41,10 +41,10 @@ class Sockscan(plugins.PluginInterface): name="lsof", component=lsof.Lsof, version=(2, 0, 0) ), requirements.VersionRequirement( - name="pslist", component=pslist.PsList, version=(4, 0, 0) + name="pslist", component=pslist.PsList, version=(4, 1, 0) ), requirements.VersionRequirement( - name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) + name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0) ), requirements.VersionRequirement( name="linux_net", component=network.NetSymbols, version=(1, 0, 0) From a2e033dfc8773a5583136752d06fdbe49db3304f Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:18:48 +0000 Subject: [PATCH 25/41] Update volatility3/framework/plugins/linux/sockscan.py Co-authored-by: ikelos --- volatility3/framework/plugins/linux/sockscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 4f16920cf..1709dfbfc 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -339,7 +339,7 @@ class Sockscan(plugins.PluginInterface): ) return None - def _generator(self, symbol_table_name: str): + def _generator(self, kernel_module_name: str): """Scans for sockets. Each row represents a kernel socket. Args: From 5205d2cee0212b0662007152d363393dbf0ed424 Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:19:08 +0000 Subject: [PATCH 26/41] Update volatility3/framework/plugins/linux/sockscan.py Co-authored-by: ikelos --- volatility3/framework/plugins/linux/sockscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 1709dfbfc..b15d7027b 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -343,7 +343,7 @@ class Sockscan(plugins.PluginInterface): """Scans for sockets. Each row represents a kernel socket. Args: - symbol_table_name: The name of the kernel module on which to operate + kernel_module_name: The name of the kernel module on which to operate Yields: addr: Physical offset From c7510f7e00c388c0c316d281ae78b0d4b03b0aff Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:19:37 +0000 Subject: [PATCH 27/41] Update volatility3/framework/plugins/linux/sockscan.py Co-authored-by: ikelos --- volatility3/framework/plugins/linux/sockscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index b15d7027b..02e673d84 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -358,7 +358,7 @@ class Sockscan(plugins.PluginInterface): """ # get vmlinux module from context in order to build objects and read symbols - vmlinux = self.context.modules[symbol_table_name] + vmlinux = self.context.modules[kernel_module_name] # get the memory layer that is to be scanned. memory_layer_name = self._find_memory_layer_name(symbol_table_name) From be11441ef0bd528f8dafc70f174682078cd4ef2d Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:19:50 +0000 Subject: [PATCH 28/41] Update volatility3/framework/plugins/linux/sockscan.py Co-authored-by: ikelos --- volatility3/framework/plugins/linux/sockscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 02e673d84..d30d819aa 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -361,7 +361,7 @@ class Sockscan(plugins.PluginInterface): vmlinux = self.context.modules[kernel_module_name] # get the memory layer that is to be scanned. - memory_layer_name = self._find_memory_layer_name(symbol_table_name) + memory_layer_name = self._find_memory_layer_name(kernel_module_name) memory_layer = self.context.layers[memory_layer_name] # use the init process to build a sock handler From 7ba46943bbd1faa130f7ac3730f5c5b440e64c1d Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:19:59 +0000 Subject: [PATCH 29/41] Update volatility3/framework/plugins/linux/sockscan.py Co-authored-by: ikelos --- volatility3/framework/plugins/linux/sockscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index d30d819aa..c6aaedf12 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -411,7 +411,7 @@ class Sockscan(plugins.PluginInterface): # if match is from file_ops_needles attempt to walk from file object to the sock if match in file_ops_needles: psock = self._walk_file_ops_needles( - symbol_table_name, memory_layer_name, needle_addr, f_op_offset + kernel_module_name, memory_layer_name, needle_addr, f_op_offset ) if psock is not None and sock_physical_addr not in seen_sock_physical_addr: From 3872133f5d99cfca014ca4314b4311a922f0708f Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:20:09 +0000 Subject: [PATCH 30/41] Update volatility3/framework/plugins/linux/sockscan.py Co-authored-by: ikelos --- volatility3/framework/plugins/linux/sockscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index c6aaedf12..caeb3fbf2 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -379,7 +379,7 @@ class Sockscan(plugins.PluginInterface): # Method 2 - find sockets by socket destructor directly inside sock objects socket_destructor_needles, sk_destruct_offset = self._find_sk_destruct_needles( - symbol_table_name + kernel_module_name ) # TODO Method 3 - find sock by sk_error_report symbols From 4fbd54361a43c1bced645adfbcf5dd187d7d28ca Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:20:20 +0000 Subject: [PATCH 31/41] Update volatility3/framework/plugins/linux/sockscan.py Co-authored-by: ikelos --- volatility3/framework/plugins/linux/sockscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index caeb3fbf2..3cf7277b8 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -375,7 +375,7 @@ class Sockscan(plugins.PluginInterface): progress_callback = self._progress_callback # Method 1 - find sockets by file operations, then follow pointers to sockets - file_ops_needles, f_op_offset = self._find_file_ops_needles(symbol_table_name) + file_ops_needles, f_op_offset = self._find_file_ops_needles(kernel_module_name) # Method 2 - find sockets by socket destructor directly inside sock objects socket_destructor_needles, sk_destruct_offset = self._find_sk_destruct_needles( From 35d24d8d1794c5f0f5e322c6104afe6dd51cbd9f Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:20:30 +0000 Subject: [PATCH 32/41] Update volatility3/framework/plugins/linux/sockscan.py Co-authored-by: ikelos --- volatility3/framework/plugins/linux/sockscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 3cf7277b8..7a230df78 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -193,7 +193,7 @@ class Sockscan(plugins.PluginInterface): def _walk_file_ops_needles( self, symbol_table_name, memory_layer_name, needle_addr, f_op_offset ): - vmlinux = self.context.modules[symbol_table_name] + vmlinux = self.context.modules[kernel_module_name] try: # create file in the memory_layer, the native layer matches the # kernel so that pointers can be followed From 13aa0dd2dc2aa80b80928933481c8f1a68570852 Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:20:44 +0000 Subject: [PATCH 33/41] Update volatility3/framework/plugins/linux/sockscan.py Co-authored-by: ikelos --- volatility3/framework/plugins/linux/sockscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 7a230df78..cd2998ce4 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -368,7 +368,7 @@ class Sockscan(plugins.PluginInterface): # TODO: look into options so that sockstat.SockHandlers so that process_sock can # be used without a task object. init_task = vmlinux.object_from_symbol(symbol_name="init_task") - sock_handler = sockstat.SockHandlers(self.context, symbol_table_name, init_task) + sock_handler = sockstat.SockHandlers(self.context, kernel_module_name, init_task) # get progress_callback in order to use this in the scanners. # TODO: perhaps add more detail to progress, showing method in progress and number of hits found From da6a1e54443206fac42e7624a9c3180ed04f7406 Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:20:57 +0000 Subject: [PATCH 34/41] Update volatility3/framework/plugins/linux/sockscan.py Co-authored-by: ikelos --- volatility3/framework/plugins/linux/sockscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index cd2998ce4..38abcbf0a 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -57,7 +57,7 @@ class Sockscan(plugins.PluginInterface): ] def _canonicalize_symbol_addrs( - self, symbol_table_name: str, symbol_names: List[str] + self, kernel_module_name: str, symbol_names: List[str] ) -> Set[bytes]: """Takes a list of symbol names and converts the address of each to the bytes as they would appear in memory so that they can be scanned for. From 4369935be47669f17758015d5b0127be981582d4 Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:21:19 +0000 Subject: [PATCH 35/41] Apply suggestion from @ikelos Co-authored-by: ikelos --- volatility3/framework/plugins/linux/sockscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 38abcbf0a..3091fba4d 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -65,7 +65,7 @@ class Sockscan(plugins.PluginInterface): Symbols that cannot be found are ignored and not included in the results. Args: - symbol_table_name: The name of the kernel module on which to operate + kernel_module_name: The name of the kernel module on which to operate symbol_names: A list of symbol names to be looked up Returns: From 55c0b9b0a84d60e21973f6aa3977ae4c0f7e4e8e Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:21:32 +0000 Subject: [PATCH 36/41] Apply suggestion from @ikelos Co-authored-by: ikelos --- volatility3/framework/plugins/linux/sockscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 3091fba4d..fc65976de 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -72,7 +72,7 @@ class Sockscan(plugins.PluginInterface): A set of bytes which are the packed addresses. """ # get vmlinux module from context in order to build objects and read symbols - vmlinux = self.context.modules[symbol_table_name] + vmlinux = self.context.modules[kernel_module_name] # get kernel layer from context so that it's dependencies can be found, and therefore scanned. # kernel layer will be virtual and built ontop of a physical layer. From c47ab6ba1176ca5da3cf285d952f8df6dd3fd5a9 Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:21:47 +0000 Subject: [PATCH 37/41] Apply suggestion from @ikelos Co-authored-by: ikelos --- volatility3/framework/plugins/linux/sockscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index fc65976de..d99157b3b 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -112,7 +112,7 @@ class Sockscan(plugins.PluginInterface): return packed_needles - def _find_memory_layer_name(self, symbol_table_name: str): + def _find_memory_layer_name(self, kernel_module_name: str): """Find the memory layer below the kernel. Only returns a single layer, and will warn the user if multiple layers are found. From f11347739b065d54a4c72daf32ce31d0c783f501 Mon Sep 17 00:00:00 2001 From: Eve <120014766+eve-mem@users.noreply.github.com> Date: Wed, 31 Dec 2025 12:22:48 +0000 Subject: [PATCH 38/41] Apply suggestions from code review Co-authored-by: ikelos --- .../framework/plugins/linux/sockscan.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index d99157b3b..116dfa4ae 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -117,14 +117,14 @@ class Sockscan(plugins.PluginInterface): and will warn the user if multiple layers are found. Args: - symbol_table_name: The name of the kernel module on which to operate. + kernel_module_name: The name of the kernel module on which to operate. Returns: memory_layer_name: The name of the layer below the kernel to be scanned. """ # get vmlinux module from context in order to build objects and read symbols - vmlinux = self.context.modules[symbol_table_name] + vmlinux = self.context.modules[kernel_module_name] # get kernel layer from context so that it's dependencies can be found, and therefore scanned. # kernel layer will be virtual and built ontop of a physical layer. @@ -150,16 +150,16 @@ class Sockscan(plugins.PluginInterface): return memory_layer_name - def _find_file_ops_needles(self, symbol_table_name: str): + def _find_file_ops_needles(self, kernel_module_name: str): # get vmlinux module from context in order to read symbols - vmlinux = self.context.modules[symbol_table_name] + vmlinux = self.context.modules[kernel_module_name] file_ops_symbol_names = [ "socket_file_ops", "sockfs_dentry_operations", ] file_ops_needles = self._canonicalize_symbol_addrs( - symbol_table_name, file_ops_symbol_names + kernel_module_name, file_ops_symbol_names ) # get file struct to find the offset to the f_op pointer # this is so that the file object can be created at the correct offset, @@ -168,9 +168,9 @@ class Sockscan(plugins.PluginInterface): return (file_ops_needles, f_op_offset) - def _find_sk_destruct_needles(self, symbol_table_name: str): + def _find_sk_destruct_needles(self, kernel_module_name: str): # get vmlinux module from context in order to read symbols - vmlinux = self.context.modules[symbol_table_name] + vmlinux = self.context.modules[kernel_module_name] socket_destructor_symbol_names = [ "sock_def_destruct", @@ -180,7 +180,7 @@ class Sockscan(plugins.PluginInterface): "inet_sock_destruct", ] socket_destructor_needles = self._canonicalize_symbol_addrs( - symbol_table_name, socket_destructor_symbol_names + kernel_module_name, socket_destructor_symbol_names ) # get sock struct to find the offset to the sk_destruct pointer # this is so that the sock object can be created at the correct offset, @@ -191,7 +191,7 @@ class Sockscan(plugins.PluginInterface): return (socket_destructor_needles, sk_destruct_offset) def _walk_file_ops_needles( - self, symbol_table_name, memory_layer_name, needle_addr, f_op_offset + self, kernel_module_name, memory_layer_name, needle_addr, f_op_offset ): vmlinux = self.context.modules[kernel_module_name] try: From 6aa552cd03d14837b593c34bf9a4b99a4f800bae Mon Sep 17 00:00:00 2001 From: eve Date: Wed, 31 Dec 2025 12:25:22 +0000 Subject: [PATCH 39/41] Fix black formatting in sockscan --- volatility3/framework/plugins/linux/sockscan.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 116dfa4ae..984c503b8 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -368,7 +368,9 @@ class Sockscan(plugins.PluginInterface): # TODO: look into options so that sockstat.SockHandlers so that process_sock can # be used without a task object. init_task = vmlinux.object_from_symbol(symbol_name="init_task") - sock_handler = sockstat.SockHandlers(self.context, kernel_module_name, init_task) + sock_handler = sockstat.SockHandlers( + self.context, kernel_module_name, init_task + ) # get progress_callback in order to use this in the scanners. # TODO: perhaps add more detail to progress, showing method in progress and number of hits found From 66ec4f43723af757630f3ca3293cf47825f7ad7f Mon Sep 17 00:00:00 2001 From: eve Date: Wed, 31 Dec 2025 12:49:45 +0000 Subject: [PATCH 40/41] Linux: update sockstat docstring for _walk_file_ops_needles. Rename physical_memory_layer_name for clarity --- .../framework/plugins/linux/sockscan.py | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 984c503b8..0056a1925 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -191,8 +191,28 @@ class Sockscan(plugins.PluginInterface): return (socket_destructor_needles, sk_destruct_offset) def _walk_file_ops_needles( - self, kernel_module_name, memory_layer_name, needle_addr, f_op_offset + self, + kernel_module_name: str, + physical_memory_layer_name: str, + needle_addr: int, + f_op_offset: int, ): + """ + This method attempts to walk from the f_op member of files to the + corresponding socket. If sucessful the socket object is created on the + memory layer and returned. + + Args: + kernel_module_name (str): The name of the kernel module from which, + to retrieve the file operations. + physical_memory_layer_name (str): The name of the physical memory layer that was scanned + needle_addr: The address of the needle that was found during the scanning + f_op_offset: The offset to the f_op member of the file type + + Returns: + psock: The sock object that was built on the memory layer + """ + vmlinux = self.context.modules[kernel_module_name] try: # create file in the memory_layer, the native layer matches the @@ -201,7 +221,7 @@ class Sockscan(plugins.PluginInterface): pfile = self.context.object( vmlinux.symbol_table_name + constants.BANG + "file", offset=sock_physical_addr, - layer_name=memory_layer_name, + layer_name=physical_memory_layer_name, native_layer_name=vmlinux.layer_name, ) dentry = pfile.get_dentry() @@ -248,7 +268,7 @@ class Sockscan(plugins.PluginInterface): psock = self.context.object( vmlinux.symbol_table_name + constants.BANG + "sock", offset=physical_sock_offset, - layer_name=memory_layer_name, + layer_name=physical_memory_layer_name, native_layer_name=vmlinux.layer_name, ) From 94f989be7ab0fe7704ca69d134b29f27d8a3a84b Mon Sep 17 00:00:00 2001 From: eve Date: Wed, 31 Dec 2025 12:54:41 +0000 Subject: [PATCH 41/41] Linux: update sockscan to add docstring to _find_file_ops_needles --- volatility3/framework/plugins/linux/sockscan.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/volatility3/framework/plugins/linux/sockscan.py b/volatility3/framework/plugins/linux/sockscan.py index 0056a1925..9288d85f0 100644 --- a/volatility3/framework/plugins/linux/sockscan.py +++ b/volatility3/framework/plugins/linux/sockscan.py @@ -151,6 +151,16 @@ class Sockscan(plugins.PluginInterface): return memory_layer_name def _find_file_ops_needles(self, kernel_module_name: str): + """Retrieves socket file symbols and the offset to the 'f_op' pointer. + + Args: + kernel_module_name (str): The name of the kernel module to search. + + Returns: + Tuple[List[int], int]: A list of file symbol addresses and, + the offset to the 'f_op' pointer. + """ + # get vmlinux module from context in order to read symbols vmlinux = self.context.modules[kernel_module_name]