mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-28 20:44:52 +02:00
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.
This commit is contained in:
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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())
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user