From 6456e55ddcd121ba3e570a90c83c0138af5b532c Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Fri, 10 Dec 2021 17:51:27 +1100 Subject: [PATCH] Added Sockstat linux plugin to enumerate all processes sockets. The output format is based on the `ss` tools. It supports: * Unix socket * Inet/Inet6 sockets * Netlink sockets * VSock sockets * Packet sockets * XDP sockets (eBPF) * Bluetooth sockets (When the respective symbols are present) Changes to the linux Lsof plugin were required to be able to reuse its filedescriptor listing capability. --- .../framework/constants/linux/__init__.py | 216 ++++++++++ .../framework/plugins/linux/sockstat.py | 374 ++++++++++++++++++ .../framework/symbols/linux/__init__.py | 23 ++ .../symbols/linux/extensions/__init__.py | 251 ++++++++++++ 4 files changed, 864 insertions(+) create mode 100644 volatility3/framework/plugins/linux/sockstat.py diff --git a/volatility3/framework/constants/linux/__init__.py b/volatility3/framework/constants/linux/__init__.py index c25ea0e2f..6b63de6c5 100644 --- a/volatility3/framework/constants/linux/__init__.py +++ b/volatility3/framework/constants/linux/__init__.py @@ -11,3 +11,219 @@ KERNEL_NAME = "__kernel__" # arch/x86/include/asm/page_types.h PAGE_SHIFT = 12 """The value hard coded from the Linux Kernel (hence not extracted from the layer itself)""" + +# Standard well-defined IP protocols. +# ref: include/uapi/linux/in.h +IP_PROTOCOLS = { + 0: "IP", + 1: "ICMP", + 2: "IGMP", + 4: "IPIP", + 6: "TCP", + 8: "EGP", + 12: "PUP", + 17: "UDP", + 22: "IDP", + 29: "TP", + 33: "DCCP", + 41: "IPV6", + 46: "RSVP", + 47: "GRE", + 50: "ESP", + 51: "AH", + 92: "MTP", + 94: "BEETPH", + 98: "ENCAP", + 103: "PIM", + 108: "COMP", + 132: "SCTP", + 136: "UDPLITE", + 137: "MPLS", + 143: "ETHERNET", + 255: "RAW", + 262: "MPTCP", +} + +# IPV6 extension headers +# ref: include/uapi/linux/in6.h +IPV6_PROTOCOLS = { + 0: "HOPBYHOP_OPTS", + 43: "ROUTING", + 44: "FRAGMENT", + 58: "ICMPv6", + 59: "NO_NEXT", + 60: "DESTINATION_OPTS", + 135: "MOBILITY", +} + +# ref: include/net/tcp_states.h +TCP_STATES = ( + "", + "ESTABLISHED", + "SYN_SENT", + "SYN_RECV", + "FIN_WAIT1", + "FIN_WAIT2", + "TIME_WAIT", + "CLOSE", + "CLOSE_WAIT", + "LAST_ACK", + "LISTEN", + "CLOSING", + "TCP_NEW_SYN_RECV", +) + +# ref: include/linux/net.h (socket_type enum) +SOCK_TYPES = { + 1: "STREAM", + 2: "DGRAM", + 3: "RAW", + 4: "RDM", + 5: "SEQPACKET", + 6: "DCCP", + 10: "PACKET", +} + +# Address families +# ref: include/linux/socket.h +SOCK_FAMILY = ( + "AF_UNSPEC", + "AF_UNIX", + "AF_INET", + "AF_AX25", + "AF_IPX", + "AF_APPLETALK", + "AF_NETROM", + "AF_BRIDGE", + "AF_ATMPVC", + "AF_X25", + "AF_INET6", + "AF_ROSE", + "AF_DECnet", + "AF_NETBEUI", + "AF_SECURITY", + "AF_KEY", + "AF_NETLINK", + "AF_PACKET", + "AF_ASH", + "AF_ECONET", + "AF_ATMSVC", + "AF_RDS", + "AF_SNA", + "AF_IRDA", + "AF_PPPOX", + "AF_WANPIPE", + "AF_LLC", + "AF_IB", + "AF_MPLS", + "AF_CAN", + "AF_TIPC", + "AF_BLUETOOTH", + "AF_IUCV", + "AF_RXRPC", + "AF_ISDN", + "AF_PHONET", + "AF_IEEE802154", + "AF_CAIF", + "AF_ALG", + "AF_NFC", + "AF_VSOCK", + "AF_KCM", + "AF_QIPCRTR", + "AF_SMC", + "AF_XDP", +) + +# Netlink protocols +# ref: include/uapi/linux/netlink.h +NETLINK_PROTOCOLS = ( + "NETLINK_ROUTE", + "NETLINK_UNUSED", + "NETLINK_USERSOCK", + "NETLINK_FIREWALL", + "NETLINK_SOCK_DIAG", + "NETLINK_NFLOG", + "NETLINK_XFRM", + "NETLINK_SELINUX", + "NETLINK_ISCSI", + "NETLINK_AUDIT", + "NETLINK_FIB_LOOKUP", + "NETLINK_CONNECTOR", + "NETLINK_NETFILTER", + "NETLINK_IP6_FW", + "NETLINK_DNRTMSG", + "NETLINK_KOBJECT_UEVENT", + "NETLINK_GENERIC", + "NETLINK_DM", + "NETLINK_SCSITRANSPORT", + "NETLINK_ECRYPTFS", + "NETLINK_RDMA", + "NETLINK_CRYPTO", + "NETLINK_SMC", +) + +# Short list of Ethernet Protocol ID's. +# ref: include/uapi/linux/if_ether.h +# Used in AF_PACKET socket family +ETH_PROTOCOLS = { + 0x0001: "ETH_P_802_3", + 0x0002: "ETH_P_AX25", + 0x0003: "ETH_P_ALL", + 0x0004: "ETH_P_802_2", + 0x0005: "ETH_P_SNAP", + 0x0006: "ETH_P_DDCMP", + 0x0007: "ETH_P_WAN_PPP", + 0x0008: "ETH_P_PPP_MP", + 0x0009: "ETH_P_LOCALTALK", + 0x000c: "ETH_P_CAN", + 0x000f: "ETH_P_CANFD", + 0x0010: "ETH_P_PPPTALK", + 0x0011: "ETH_P_TR_802_2", + 0x0016: "ETH_P_CONTROL", + 0x0017: "ETH_P_IRDA", + 0x0018: "ETH_P_ECONET", + 0x0019: "ETH_P_HDLC", + 0x001a: "ETH_P_ARCNET", + 0x001b: "ETH_P_DSA", + 0x001c: "ETH_P_TRAILER", + 0x0060: "ETH_P_LOOP", + 0x00F6: "ETH_P_IEEE802154", + 0x00F7: "ETH_P_CAIF", + 0x00F8: "ETH_P_XDSA", + 0x00F9: "ETH_P_MAP", + 0x0800: "ETH_P_IP", + 0x0805: "ETH_P_X25", + 0x0806: "ETH_P_ARP", + 0x8035: "ETH_P_RARP", + 0x809B: "ETH_P_ATALK", + 0x80F3: "ETH_P_AARP", + 0x8100: "ETH_P_8021Q", +} + +# Connection and socket states +# ref: include/net/bluetooth/bluetooth.h +BLUETOOTH_STATES = ( + "", + "CONNECTED", + "OPEN", + "BOUND", + "LISTEN", + "CONNECT", + "CONNECT2", + "CONFIG", + "DISCONN", + "CLOSED", +) + +# Bluetooth protocols +# ref: include/net/bluetooth/bluetooth.h +BLUETOOTH_PROTOCOLS = ( + "L2CAP", + "HCI", + "SCO", + "RFCOMM", + "BNEP", + "CMTP", + "HIDP", + "AVDTP", +) diff --git a/volatility3/framework/plugins/linux/sockstat.py b/volatility3/framework/plugins/linux/sockstat.py new file mode 100644 index 000000000..3be359463 --- /dev/null +++ b/volatility3/framework/plugins/linux/sockstat.py @@ -0,0 +1,374 @@ +# This file is Copyright 2021 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +# Author: Gustavo Moreira + +import logging +from typing import Callable + +from volatility3.framework import renderers, interfaces, exceptions, constants +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 lsof + + +vollog = logging.getLogger(__name__) + +class SockHandlers(object): + def __init__(self, vmlinux, task): + self._vmlinux = vmlinux + self._task = task + + netns_id = task.nsproxy.net_ns.get_inode() + self._netdevices = self._build_network_devices_map(netns_id) + + self._sock_family_handlers = { + "AF_UNIX": self._unix_sock, + "AF_INET": self._inet_sock, + "AF_INET6": self._inet_sock, + "AF_NETLINK": self._netlink_sock, + "AF_VSOCK": self._vsock_sock, + "AF_PACKET": self._packet_sock, + "AF_XDP": self._xdp_sock, + "AF_BLUETOOTH": self._bluetooth_sock, + } + + def _build_network_devices_map(self, netns_id): + netdevices_map = {} + nethead = self._vmlinux.object_from_symbol(symbol_name="net_namespace_list") + net_symname = self._vmlinux.symbol_table_name + constants.BANG + "net" + for net in nethead.to_list(net_symname, "list"): + net_device_symname = self._vmlinux.symbol_table_name + constants.BANG + "net_device" + for net_dev in net.dev_base_head.to_list(net_device_symname, "dev_list"): + if net.get_inode() != netns_id: + continue + dev_name = str(utility.array_to_string(net_dev.name)) + netdevices_map[net_dev.ifindex] = dev_name + return netdevices_map + + def process_sock(self, sock): + family = sock.family + extended = {} + sock_handler = self._sock_family_handlers.get(family) + if sock_handler: + try: + sock_fields = sock_handler(sock, extended) + return *sock_fields, extended + except exceptions.SymbolError as e: + # Cannot finds the *_sock type in the symbols + vollog.warning("Error processing socket family '%s': %s", family, e) + else: + vollog.warning("Unsupported family '%s'", family) + + # Even if the sock family is not supported, or the required types + # are not present in the symbols, we can still show some general + # information about the socket that may be helpful. + saddr_tag = daddr_tag = state = "?" + + sock_stat = saddr_tag, daddr_tag, state + + return sock, sock_stat, extended + + def _unix_sock(self, sock, _extended): + unix_sock = sock.cast("unix_sock") + state = unix_sock.state + saddr = unix_sock.name + sinode = unix_sock.inode + if unix_sock.peer != 0: + peer = unix_sock.peer.dereference().cast("unix_sock") + daddr = peer.name + dinode = peer.inode + else: + daddr = dinode = "" + + saddr_tag = f"{saddr} {sinode}" + daddr_tag = f"{daddr} {dinode}" + sock_stat = saddr_tag, daddr_tag, state + return unix_sock, sock_stat + + def _inet_sock(self, sock, _extended): + inet_sock = sock.cast("inet_sock") + saddr = inet_sock.src_addr + sport = inet_sock.src_port + daddr = inet_sock.dst_addr + dport = inet_sock.dst_port + state = inet_sock.state + + if inet_sock.family == "AF_INET6": + saddr = f"[{saddr}]" + + saddr_tag = f"{saddr}:{sport}" + daddr_tag = f"{daddr}:{dport}" + sock_stat = saddr_tag, daddr_tag, state + return inet_sock, sock_stat + + def _netlink_sock(self, sock, _extended): + netlink_sock = sock.cast("netlink_sock") + + saddr_list = [] + src_portid = f"portid:{netlink_sock.portid}" + saddr_list.append(src_portid) + if netlink_sock.groups != 0: + groups_bitmap = netlink_sock.groups.dereference() + groups_str = f"groups:0x{groups_bitmap:08x}" + saddr_list.append(groups_str) + + daddr_list = [] + dst_portid = f"portid:{netlink_sock.dst_portid}" + daddr_list.append(dst_portid) + dst_group = f"group:0x{netlink_sock.dst_group:08x}" + daddr_list.append(dst_group) + module = netlink_sock.module + if module and netlink_sock.module.name: + module_name_str = utility.array_to_string(netlink_sock.module.name) + module_name = f"lkm:{module_name_str}" + daddr_list.append(module_name) + + saddr_tag = ",".join(saddr_list) + daddr_tag = ",".join(daddr_list) + state = netlink_sock.state + + sock_stat = saddr_tag, daddr_tag, state + return netlink_sock, sock_stat + + def _vsock_sock(self, sock, _extended): + vsock_sock = sock.cast("vsock_sock") + saddr = vsock_sock.local_addr.svm_cid + sport = vsock_sock.local_addr.svm_port + daddr = vsock_sock.remote_addr.svm_cid + dport = vsock_sock.remote_addr.svm_port + state = "" # Protocol is always 0 + + saddr_tag = f"{saddr}:{sport}" + daddr_tag = f"{daddr}:{dport}" + sock_stat = saddr_tag, daddr_tag, state + return vsock_sock, sock_stat + + def _packet_sock(self, sock, extended): + packet_sock = sock.cast("packet_sock") + ifindex = packet_sock.ifindex + dev_name = self._netdevices.get(ifindex, "") if ifindex > 0 else "ANY" + + if sock.has_member("sk_filter"): + sock_filter = sock.sk_filter + self.__update_extra_socket_bpf(sock_filter, extended) + + if sock.has_member("sk_reuseport_cb"): + sock_reuseport_cb = sock.sk_reuseport_cb + self.__update_extra_socket_bpf(sock_reuseport_cb, extended) + + saddr_tag = f"{dev_name}" + daddr_tag = "" + state = packet_sock.state + sock_stat = saddr_tag, daddr_tag, state + return packet_sock, sock_stat + + def __update_extra_socket_bpf(self, sock_filter, extended): + if not sock_filter: + return + + extended["bpf_filter_type"] = "cBPF" + + if not sock_filter.has_member("prog"): + return + + bpfprog = sock_filter.prog + if not bpfprog: + return + + BPF_PROG_TYPE_UNSPEC = 0 + if bpfprog.type > BPF_PROG_TYPE_UNSPEC: + extended["bpf_filter_type"] = "eBPF" + bpfprog_aux = bpfprog.aux + if bpfprog_aux: + extended["bpf_filter_id"] = str(bpfprog_aux.id) + bpfprog_name = str(utility.array_to_string(bpfprog.aux.name)) + if bpfprog_name: + extended["bpf_filter_name"] = bpfprog_name + + def _xdp_sock(self, sock, _extended): + xdp_sock = sock.cast("xdp_sock") + device = xdp_sock.dev + if not device: + return + + dev_name = utility.array_to_string(device.name) + saddr_tag = f"{dev_name}" + + bpfprog = device.xdp_prog + if not bpfprog: + return + + bpfprog_aux = bpfprog.aux + if bpfprog_aux: + bpfprog_id = bpfprog_aux.id + daddr_tag = f"ebpf_prog_id:{bpfprog_id}" + bpf_name = utility.array_to_string(bpfprog_aux.name) + if bpf_name: + daddr_tag += f",ebpf_prog_name:{bpf_name}" + else: + daddr_tag = "" + + # Hallelujah, xdp_sock.state is an enum + xsk_state = xdp_sock.state.lookup() + state = xsk_state.replace("XSK_", "") + + sock_stat = saddr_tag, daddr_tag, state + return xdp_sock, sock_stat + + def _bluetooth_sock(self, sock, _extended): + bt_sock = sock.cast("bt_sock") + + def bt_addr(addr): + return ":".join(reversed(["%02x" % x for x in addr.b])) + + saddr_tag = daddr_tag = "" + if bt_sock.protocol == "HCI": + pinfo = bt_sock.cast("hci_pinfo") + elif bt_sock.protocol == "L2CAP": + pinfo = bt_sock.cast("l2cap_pinfo") + src_addr = bt_addr(pinfo.chan.src) + dst_addr = bt_addr(pinfo.chan.dst) + saddr_tag = f"{src_addr}" + daddr_tag = f"{dst_addr}" + elif bt_sock.protocol == "RFCOMM": + pinfo = bt_sock.cast("rfcomm_pinfo") + src_addr = bt_addr(pinfo.src) + dst_addr = bt_addr(pinfo.dst) + channel = pinfo.channel + saddr_tag = f"[{src_addr}]:{channel}" + daddr_tag = f"{dst_addr}" + else: + vollog.warning("Unsupported bluetooth protocol '%s'", bt_sock.protocol) + + state = bt_sock.state + sock_stat = saddr_tag, daddr_tag, state + return bt_sock, sock_stat + +class Sockstat(plugins.PluginInterface): + """Lists all network connections for all processes.""" + + _required_framework_version = (2, 0, 0) + + _version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.ModuleRequirement(name="kernel", description="Linux kernel", + architectures=["Intel32", "Intel64"]), + requirements.PluginRequirement(name="lsof", plugin=lsof.Lsof, version=(2, 0, 0)), + requirements.VersionRequirement(name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0)), + requirements.BooleanRequirement(name="unix", + description=("Show UNIX domain Sockets only"), + default=False, + optional=True), + requirements.ListRequirement(name="pids", + description="Filter results by process IDs. " + "It takes the root PID namespace identifiers.", + element_type=int, + optional=True), + requirements.IntRequirement(name="netns", + description="Filter results by network namespace. " + "Otherwise, all of them are shown.", + optional=True), + ] + + @classmethod + def list_sockets(cls, + context: interfaces.context.ContextInterface, + vmlinux_module_name: str, + filter_func: Callable[[int], bool] = lambda _: False): + """ + Returns every single socket descriptors + """ + vmlinux = context.modules[vmlinux_module_name] + + sfop_addr = vmlinux.object_from_symbol("socket_file_ops").vol.offset + dfop_addr = vmlinux.object_from_symbol("sockfs_dentry_operations").vol.offset + + fd_generator = lsof.Lsof.list_fds(context, vmlinux.name, filter_func) + for _pid, _task_comm, task, fd_fields in fd_generator: + fd_num, filp, _full_path = fd_fields + + if filp.f_op not in (sfop_addr, dfop_addr): + continue + + dentry = filp.get_dentry() + if not dentry: + continue + + d_inode = dentry.d_inode + if not d_inode: + continue + + socket_alloc = linux.LinuxUtilities.container_of(d_inode, "socket_alloc", "vfs_inode", vmlinux) + _socket = socket_alloc.socket + + vfs_inode = socket_alloc.vfs_inode + if not (_socket and vfs_inode): + continue + + sock = _socket.sk.dereference() + + sock_type = sock.type + family = sock.family + + sock_handler = SockHandlers(vmlinux, task) + sock_fields = sock_handler.process_sock(sock) + if not sock_fields: + continue + + child_sock = sock_fields[0] + protocol = child_sock.protocol if hasattr(child_sock, "protocol") else "" + + net = task.nsproxy.net_ns + netns_id = net.proc_inum if net.has_member("proc_inum") else net.ns.inum + yield task, netns_id, fd_num, family, sock_type, protocol, sock_fields + + def _generator(self): + pids = self.config.get('pids') + filter_func = lsof.pslist.PsList.create_pid_filter(pids) + + tasks_per_sock = {} + socket_generator = self.list_sockets(self.context, self.config['kernel'], filter_func=filter_func) + for task, netns, fd_num, family, sock_type, protocol, sock_fields in socket_generator: + if self.config['netns'] and self.config['netns'] != netns: + continue + + sock, sock_stat, extended = sock_fields + + task_comm = utility.array_to_string(task.comm) + task_info = f"{task_comm},pid={task.pid},fd={fd_num}" + if extended: + extended_str = ",".join(f"{k}={v}" for k, v in extended.items()) + task_info = f"{task_info},{extended_str}" + + fields = netns, family, sock_type, protocol, *sock_stat + + sock_addr = sock.vol.offset + tasks_per_sock.setdefault(sock_addr, {}) + tasks_per_sock[sock_addr].setdefault('tasks', []) + tasks_per_sock[sock_addr]['tasks'].append(task_info) + tasks_per_sock[sock_addr]['fields'] = fields + + for data in tasks_per_sock.values(): + task_list = [f"({task})" for task in data['tasks']] + tasks = ",".join(task_list) + + fields = data['fields'] + (tasks,) + yield (0, fields) + + def run(self): + tree_grid_args = [("NetNS", int), + ("Family", str), + ("Type", str), + ("Proto", str), + ("Source Addr:Port", str), + ("Destination Addr:Port", str), + ("State", str), + ("Tasks", str)] + + return renderers.TreeGrid(tree_grid_args, self._generator()) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 36e23a35d..739ecbedb 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -30,6 +30,16 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable): self.set_type_class('vfsmount', extensions.vfsmount) self.set_type_class('kobject', extensions.kobject) + # Network + self.set_type_class('net', extensions.net) + self.set_type_class('sock', extensions.sock) + self.set_type_class('inet_sock', extensions.inet_sock) + self.set_type_class('unix_sock', extensions.unix_sock) + self.set_type_class('netlink_sock', extensions.netlink_sock) + self.set_type_class('packet_sock', extensions.packet_sock) + if 'bt_sock' in self.types: + self.set_type_class('bt_sock', extensions.bt_sock) + if 'module' in self.types: self.set_type_class('module', extensions.module) @@ -183,6 +193,10 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): def files_descriptors_for_process(cls, context: interfaces.context.ContextInterface, symbol_table: str, task: interfaces.objects.ObjectInterface): + # task.files can be null + if not task.files: + return + fd_table = task.files.get_fds() if fd_table == 0: return @@ -267,3 +281,12 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): list_struct = vmlinux.object(object_type = struct_name, offset = list_start.vol.offset) yield list_struct list_start = getattr(list_struct, list_member) + + @classmethod + def container_of(cls, addr, type_name, member_name, vmlinux): + if not addr: + return + type_dec = vmlinux.get_type(type_name) + member_offset = type_dec.relative_child_offset(member_name) + container_addr = addr - member_offset + return vmlinux.object(object_type=type_name, offset=container_addr, absolute=True) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 0edd60608..2af5f56b0 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -4,9 +4,15 @@ import collections.abc import logging +import socket from typing import Generator, Iterable, Iterator, Optional, Tuple from volatility3.framework import constants +from volatility3.framework.constants.linux import SOCK_TYPES, SOCK_FAMILY +from volatility3.framework.constants.linux import IP_PROTOCOLS, IPV6_PROTOCOLS +from volatility3.framework.constants.linux import TCP_STATES, NETLINK_PROTOCOLS +from volatility3.framework.constants.linux import ETH_PROTOCOLS, BLUETOOTH_STATES +from volatility3.framework.constants.linux import BLUETOOTH_PROTOCOLS from volatility3.framework import exceptions, objects, interfaces, symbols from volatility3.framework.layers import linear from volatility3.framework.objects import utility @@ -539,3 +545,248 @@ class kobject(objects.StructType): ret = refcnt.refs.counter return ret + +class mnt_namespace(objects.StructType): + def get_inode(self): + if self.has_member("proc_inum"): + return self.proc_inum + elif self.ns.has_member("inum"): + return self.ns.inum + else: + raise AttributeError("Unable to find mnt_namespace inode") + +class net(objects.StructType): + def get_inode(self): + if self.has_member("proc_inum"): + return self.proc_inum + elif self.ns.has_member("inum"): + return self.ns.inum + else: + raise AttributeError("Unable to find net_namespace inode") + +class sock(objects.StructType): + def __get_vol_kernel_module_name(self): + symbol_table_arr = self.vol.type_name.split("!", 1) + symbol_table = symbol_table_arr[0] if len(symbol_table_arr) == 2 else None + + module_names = list(self._context.modules.get_modules_by_symbol_tables(symbol_table)) + if not module_names: + raise ValueError(f"No module using the symbol table {symbol_table}") + + return module_names[0] + + @property + def family(self): + family_idx = self.__sk_common.skc_family + if 0 <= family_idx < len(SOCK_FAMILY): + return SOCK_FAMILY[family_idx] + else: + return "UNKNOWN" + + @property + def type(self): + return SOCK_TYPES.get(self.sk_type, "") + + @property + def inode(self): + if not self.sk_socket: + return 0 + + kernel_module_name = self.__get_vol_kernel_module_name() + kernel = self._context.modules[kernel_module_name] + socket_alloc = linux.LinuxUtilities.container_of(self.sk_socket, "socket_alloc", "socket", kernel) + vfs_inode = socket_alloc.vfs_inode + + return vfs_inode.i_ino + +class unix_sock(objects.StructType): + @property + def name(self): + if self.addr: + sockaddr_un = self.addr.name.cast("sockaddr_un") + saddr = str(utility.array_to_string(sockaddr_un.sun_path)) + else: + saddr = "" + return saddr + + @property + def protocol(self): + return "" + + @property + def state(self): + """Return a string representing the sock state.""" + + # Unix socket states reuse (a subset) of the inet_sock states contants + if self.sk.type == "STREAM": + state_idx = self.sk.__sk_common.skc_state + if 0 <= state_idx < len(TCP_STATES): + state = TCP_STATES[state_idx] + else: + state = "UNKNOWN" + else: + state = "UNCONNECTED" + + return state + + @property + def inode(self): + return self.sk.inode + +class inet_sock(objects.StructType): + @property + def family(self): + family_idx = self.sk.__sk_common.skc_family + if 0 <= family_idx < len(SOCK_FAMILY): + return SOCK_FAMILY[family_idx] + else: + return "UNKNOWN" + + @property + def protocol(self): + # If INET6 family and a proto is defined, we use that specific IPv6 protocol. + # Otherwise, we use the standard IP protocol. + protocol = IP_PROTOCOLS.get(self.sk.sk_protocol, "UNKNOWN") + if self.family == "AF_INET6": + protocol = IPV6_PROTOCOLS.get(self.sk.sk_protocol, protocol) + return protocol + + @property + def state(self): + """Return a string representing the sock state.""" + + if self.sk.type == "STREAM": + state_idx = self.sk.__sk_common.skc_state + if 0 <= state_idx < len(TCP_STATES): + state = TCP_STATES[state_idx] + else: + state = "UNKNOWN" + else: + state = "UNCONNECTED" + + return state + + @property + def src_port(self): + sport_le = getattr(self, "sport", getattr(self, "inet_sport", None)) + if sport_le is not None: + return socket.htons(sport_le) + + @property + def dst_port(self): + sk_common = self.sk.__sk_common + if hasattr(sk_common, "skc_portpair"): + dport_le = sk_common.skc_portpair & 0xffff + elif hasattr(self, "dport"): + dport_le = self.dport + elif hasattr(self, "inet_dport"): + dport_le = self.inet_dport + elif hasattr(sk_common, "skc_dport"): + dport_le = sk_common.skc_dport + else: + return + + return socket.htons(dport_le) + + @property + def src_addr(self): + sk_common = self.sk.__sk_common + family = sk_common.skc_family + if family == socket.AF_INET: + addr_size = 4 + if hasattr(self, "rcv_saddr"): + saddr = self.rcv_saddr + elif hasattr(self, "inet_rcv_saddr"): + saddr = self.inet_rcv_saddr + else: + saddr = sk_common.skc_rcv_saddr + elif family == socket.AF_INET6: + addr_size = 16 + saddr = self.pinet6.saddr + else: + return + + parent_layer = self._context.layers[self.vol.layer_name] + addr_bytes = parent_layer.read(saddr.vol.offset, addr_size) + return socket.inet_ntop(family, addr_bytes) + + @property + def dst_addr(self): + sk_common = self.sk.__sk_common + family = sk_common.skc_family + if family == socket.AF_INET: + if hasattr(self, "daddr") and self.daddr: + daddr = self.daddr + elif hasattr(self, "inet_daddr") and self.inet_daddr: + daddr = self.inet_daddr + else: + daddr = sk_common.skc_daddr + addr_size = 4 + elif family == socket.AF_INET6: + if hasattr(self.pinet6, "daddr"): + daddr = self.pinet6.daddr + else: + daddr = sk_common.skc_v6_daddr + addr_size = 16 + else: + return + + parent_layer = self._context.layers[self.vol.layer_name] + addr_bytes = parent_layer.read(daddr.vol.offset, addr_size) + return socket.inet_ntop(family, addr_bytes) + +class netlink_sock(objects.StructType): + @property + def protocol(self): + protocol_idx = self.sk.sk_protocol + if 0 <= protocol_idx < len(NETLINK_PROTOCOLS): + return NETLINK_PROTOCOLS[protocol_idx] + else: + return "UNKNOWN" + + @property + def state(self): + # Netlink is a datagram-oriented service. We can only have + # SOCK_RAW or SOCK_DGRAM socket types. + # NOTE: We are overridden the netlink_sock.state member here + return "UNCONNECTED" + + +class packet_sock(objects.StructType): + @property + def protocol(self): + eth_proto = socket.htons(self.num) + if eth_proto == 0: + return "" + elif eth_proto in ETH_PROTOCOLS: + return ETH_PROTOCOLS[eth_proto] + else: + return f"0x{eth_proto:x}" + + @property + def state(self): + # Packet socket types are either SOCK_RAW or SOCK_DGRAM. + # NOTE: We are overriding netlink_sock.state here + return "UNCONNECTED" + + +class bt_sock(objects.StructType): + @property + def protocol(self): + type_idx = self.sk.sk_protocol + if 0 <= type_idx < len(BLUETOOTH_PROTOCOLS): + state = BLUETOOTH_PROTOCOLS[type_idx] + else: + state = "UNKNOWN" + + return state + + @property + def state(self): + state_idx = self.sk.__sk_common.skc_state + if 0 <= state_idx < len(BLUETOOTH_STATES): + state = BLUETOOTH_STATES[state_idx] + else: + state = "UNKNOWN" + + return state