Files
volatility3/volatility3/framework/symbols/linux/extensions/network.py
T
2025-05-23 10:52:33 +02:00

682 lines
23 KiB
Python

import logging
from typing import Dict, Generator, List, Optional, Union
from volatility3.framework import objects, exceptions, renderers, interfaces, constants
from volatility3.framework.objects import utility
from volatility3.framework.constants import linux as linux_constants
from volatility3.framework.symbols import wrappers
from volatility3.framework.symbols import linux
from volatility3.framework.renderers import conversion
import socket as socket_module
vollog = logging.getLogger(__name__)
class net(objects.StructType):
def get_inode(self) -> int:
"""Get the namespace id for this network namespace.
Raises:
AttributeError: If it cannot find the network namespace id for the
current kernel.
Returns:
int: the namespace id
"""
if self.has_member("proc_inum"):
# 3.8.13 <= kernel < 3.19.8
return self.proc_inum
elif self.has_member("ns") and self.ns.has_member("inum"):
# kernel >= 3.19.8
return self.ns.inum
else:
# kernel < 3.8.13
raise AttributeError("Unable to find net_namespace inode")
class net_device(objects.StructType):
def get_device_name(self) -> str:
"""Return the network device name
Returns:
str: The network device name
"""
return utility.array_to_string(self.name)
def _format_as_mac_address(self, hwaddr) -> str:
return ":".join([f"{x:02x}" for x in hwaddr[: self.addr_len]])
def get_mac_address(self) -> Optional[str]:
"""Get the MAC address of this network interface.
Returns:
str: the MAC address of this network interface.
"""
if self.has_member("perm_addr"):
null_mac_addr_bytes = b"\x00" * self.addr_len
null_mac_addr = self._format_as_mac_address(null_mac_addr_bytes)
mac_addr = self._format_as_mac_address(self.perm_addr)
if mac_addr != null_mac_addr:
return mac_addr
parent_layer = self._context.layers[self.vol.layer_name]
try:
hwaddr = parent_layer.read(self.dev_addr, self.addr_len, pad=True)
except exceptions.InvalidAddressException:
vollog.debug(
f"Unable to read network interface mac address from {self.dev_addr:#x}"
)
return None
return self._format_as_mac_address(hwaddr)
def _get_flag_choices(self) -> Dict[str, int]:
"""Return the net_device flags as a list of strings"""
vmlinux = linux.LinuxUtilities.get_module_from_volobj_type(self._context, self)
try:
# kernels >= 3.15
net_device_flags_enum = vmlinux.get_enumeration("net_device_flags")
choices = net_device_flags_enum.choices
except exceptions.SymbolError:
# kernels < 3.15
choices = linux_constants.NET_DEVICE_FLAGS
return choices
def _get_net_device_flag_value(
self, name
) -> Union[int, interfaces.renderers.BaseAbsentValue]:
"""Return the net_device flag value based on the flag name"""
return self._get_flag_choices().get(name, renderers.UnparsableValue())
def _get_netdev_state_t(self):
vmlinux = linux.LinuxUtilities.get_module_from_volobj_type(self._context, self)
try:
# At least from kernels 2.6.30
return vmlinux.get_enumeration("netdev_state_t")
except exceptions.SymbolError:
raise exceptions.VolatilityException(
"Unsupported kernel or wrong ISF. Cannot find 'netdev_state_t' enumeration"
)
def is_running(self) -> bool:
"""Test if the network device has been brought up
Based on netif_running()
Returns:
bool: True if the device is UP
"""
netdev_state_t_enum = self._get_netdev_state_t()
# It should be safe. netdev_state_t::__LINK_STATE_START has been available since
# at least kernels 2.6.30
return (
self.state & (1 << netdev_state_t_enum.choices["__LINK_STATE_START"]) != 0
)
def is_carrier_ok(self) -> bool:
"""Check if carrier is present on network device
Based on netif_carrier_ok()
Returns:
bool: True if carrier present
"""
netdev_state_t_enum = self._get_netdev_state_t()
# It should be safe. netdev_state_t::__LINK_STATE_NOCARRIER has been available
# since at least kernels 2.6.30
return (
self.state & (1 << netdev_state_t_enum.choices["__LINK_STATE_NOCARRIER"])
== 0
)
def is_dormant(self) -> bool:
"""Check if the network device is dormant
Based on netif_dormant(()
Returns:
bool: True if the network device is dormant
"""
netdev_state_t_enum = self._get_netdev_state_t()
# It should be safe. netdev_state_t::__LINK_STATE_DORMANT has been available
# since at least kernels 2.6.30
return (
self.state & (1 << netdev_state_t_enum.choices["__LINK_STATE_DORMANT"]) != 0
)
def is_operational(self) -> bool:
"""Test if the carrier is operational
Based on netif_oper_up()
Returns:
bool: True if the device is UP
"""
return self.get_operational_state() in ("UP", "UNKNOWN")
def get_flag_names(self) -> List[str]:
"""Return the net_device flags as a list of strings.
This is the combination of flags exported through kernel APIs to userspace.
Based on dev_get_flags()
Returns:
List[str]: A list of flag names
"""
choices = self._get_flag_choices()
clear_flags = choices.get("IFF_PROMISC", 0)
clear_flags |= choices.get("IFF_ALLMULTI", 0)
clear_flags |= choices.get("IFF_RUNNING", 0)
clear_flags |= choices.get("IFF_LOWER_UP", 0)
clear_flags |= choices.get("IFF_DORMANT", 0)
clear_gflags = choices.get("IFF_PROMISC", 0)
clear_gflags |= choices.get("IFF_ALLMULTI)", 0)
flags = (self.flags & ~clear_flags) | (self.gflags & ~clear_gflags)
if self.is_running():
if self.is_operational():
flags |= choices.get("IFF_RUNNING", 0)
if self.is_carrier_ok():
flags |= choices.get("IFF_LOWER_UP", 0)
if self.is_dormant():
flags |= choices.get("IFF_DORMANT", 0)
net_device_flags_enum_flags = wrappers.Flags(choices)
net_device_flags = net_device_flags_enum_flags(flags)
# It's preferable to provide a deterministic list of items. i.e. for testing
return sorted(net_device_flags)
@property
def promisc(self) -> bool:
"""Return if this network interface is in promiscuous mode.
Returns:
bool: True if this network interface is in promiscuous mode. Otherwise, False
"""
return self.flags & self._get_net_device_flag_value("IFF_PROMISC") != 0
def _do_get_net_namespace_id(self) -> int:
"""Return the network namespace id for this network interface.
Returns:
int: the network namespace id for this network interface
"""
nd_net = self.nd_net
if nd_net.has_member("net"):
# In kernel 4.1.52 the 'nd_net' member type was changed from
# 'struct net*' to 'possible_net_t' which has a 'struct net *net' member.
net_ns_id = nd_net.net.get_inode()
else:
# In kernels < 4.1.52 the 'nd_net'member type was 'struct net*'
net_ns_id = nd_net.get_inode()
return net_ns_id
def get_net_namespace_id(self) -> Optional[int]:
"""Return the network namespace id for this network interface.
Returns:
int: the network namespace id for this network interface
"""
try:
return self._do_get_net_namespace_id()
except exceptions.InvalidAddressException:
vollog.debug(
f"Encountered an invalid address exception when getting the namespace for {self.vol.offset:#x}"
)
return None
def get_operational_state(self) -> Union[str, interfaces.renderers.BaseAbsentValue]:
"""Return the netwok device oprational state (RFC 2863) string
Returns:
str: A string with the operational state
"""
try:
return linux_constants.IF_OPER_STATES(self.operstate).name
except ValueError:
vollog.warning(f"Invalid net_device operational state '{self.operstate}'")
return renderers.UnparsableValue()
def get_qdisc_name(self) -> Optional[str]:
"""Return the network device queuing discipline (qdisc) name
Returns:
str: A string with the queuing discipline (qdisc) name
"""
try:
return utility.array_to_string(self.qdisc.ops.id)
except exceptions.InvalidAddressException:
vollog.debug(f"Unable to get qdisc name for {self.vol.offset:#x}")
return None
def get_queue_length(self) -> int:
"""Return the network device transmission queue length (qlen)
Returns:
int: the network device transmission queue length (qlen)
"""
return self.tx_queue_len
class in_device(objects.StructType):
def get_addresses(
self, max_devices=128
) -> Generator[interfaces.objects.ObjectInterface, None, None]:
"""Yield the IPv4 ifaddr addresses
Yields:
in_ifaddr: An IPv4 ifaddr address
"""
seen = set()
try:
cur = self.ifa_list
except exceptions.InvalidAddressException:
return
while cur and cur.vol.offset:
if len(seen) > max_devices:
break
if cur.vol.offset in seen:
break
seen.add(cur.vol.offset)
yield cur
try:
cur = cur.ifa_next
except exceptions.InvalidAddressException:
break
class inet6_dev(objects.StructType):
def get_addresses(
self,
) -> Generator[interfaces.objects.ObjectInterface, None, None]:
"""Yield the IPv6 ifaddr addresses
Yields:
inet6_ifaddr: An IPv6 ifaddr address
"""
if not self.has_member(
"addr_list"
) or not self.addr_list.vol.type_name.endswith(constants.BANG + "list_head"):
# kernels < 3.0
# FIXME: struct inet6_ifaddr *addr_list;
vollog.warning(
"IPv6 is unsupported for this kernel. Check if the ISF contains the appropriate 'inet6_dev' type"
)
return
symbol_space = self._context.symbol_space
table_name = self.get_symbol_table_name()
inet6_ifaddr_symname = table_name + constants.BANG + "inet6_ifaddr"
if not symbol_space.has_type(inet6_ifaddr_symname) or not symbol_space.get_type(
inet6_ifaddr_symname
).has_member("if_list"):
vollog.warning(
"IPv6 is unsupported for this kernel. Check if the ISF contains the appropriate 'inet6_ifaddr' type"
)
return
# 'if_list' member was added to 'inet6_ifaddr' type in kernels 3.0
yield from self.addr_list.to_list(inet6_ifaddr_symname, "if_list")
class in_ifaddr(objects.StructType):
# Translation to text based on iproute2 package. See 'rtnl_rtscope_tab' in lib/rt_names.c
_rtnl_rtscope_tab = {
"RT_SCOPE_UNIVERSE": "global",
"RT_SCOPE_NOWHERE": "nowhere",
"RT_SCOPE_HOST": "host",
"RT_SCOPE_LINK": "link",
"RT_SCOPE_SITE": "site",
}
def get_scope_type(self) -> str:
"""Get the scope type for this IPv4 address
Returns:
str: the IPv4 scope type.
"""
table_name = self.get_symbol_table_name()
rt_scope_enum = self._context.symbol_space.get_enumeration(
table_name + constants.BANG + "rt_scope_t"
)
try:
rt_scope = rt_scope_enum.lookup(self.ifa_scope)
except ValueError:
return "unknown"
return self._rtnl_rtscope_tab.get(rt_scope, "unknown")
def get_address(self) -> str:
"""Get an string with the IPv4 address
Returns:
str: the IPv4 address
"""
return conversion.convert_ipv4(self.ifa_address)
def get_prefix_len(self) -> int:
"""Get the IPv4 address prefix len
Returns:
int: the IPv4 address prefix len
"""
return self.ifa_prefixlen
class inet6_ifaddr(objects.StructType):
def get_scope_type(self) -> str:
"""Get the scope type for this IPv6 address
Returns:
str: the IPv6 scope type.
"""
if (self.scope & linux_constants.IFA_HOST) != 0:
return "host"
elif (self.scope & linux_constants.IFA_LINK) != 0:
return "link"
elif (self.scope & linux_constants.IFA_SITE) != 0:
return "site"
else:
return "global"
def get_address(self) -> str:
"""Get an string with the IPv6 address
Returns:
str: the IPv6 address
"""
return conversion.convert_ipv6(self.addr.in6_u.u6_addr32)
def get_prefix_len(self) -> int:
"""Get the IPv6 address prefix len
Returns:
int: the IPv6 address prefix len
"""
return self.prefix_len
class socket(objects.StructType):
def _get_vol_kernel(self) -> interfaces.context.ModuleInterface:
symbol_table_arr = self.vol.type_name.split("!", 1)
symbol_table = symbol_table_arr[0] if len(symbol_table_arr) == 2 else None
if symbol_table is None:
raise ValueError(f"No module using the symbol table {symbol_table}")
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}")
kernel_module_name = module_names[0]
kernel = self._context.modules[kernel_module_name]
return kernel
def get_inode(self) -> int:
try:
kernel = self._get_vol_kernel()
except ValueError:
return 0
socket_alloc = linux.LinuxUtilities.container_of(
self.vol.offset, "socket_alloc", "socket", kernel
)
if socket_alloc is None:
return 0
vfs_inode = socket_alloc.vfs_inode
return vfs_inode.i_ino
def get_state(self) -> str:
socket_state_idx = self.state
if 0 <= socket_state_idx < len(linux_constants.SOCKET_STATES):
return linux_constants.SOCKET_STATES[socket_state_idx]
return "Unknown socket state"
class sock(objects.StructType):
def get_family(self) -> str:
family_idx = self.__sk_common.skc_family
if 0 <= family_idx < len(linux_constants.SOCK_FAMILY):
return linux_constants.SOCK_FAMILY[family_idx]
return "Unknown socket family"
def get_type(self) -> str:
return linux_constants.SOCK_TYPES.get(self.sk_type, "")
def get_inode(self) -> int:
if not self.sk_socket:
return 0
return self.sk_socket.get_inode()
def get_protocol(self) -> Optional[str]:
return None
def get_state(self) -> str:
# Return the generic socket state
if self.has_member("sk"):
return self.sk.sk_socket.get_state()
return self.sk_socket.get_state()
class unix_sock(objects.StructType):
def get_name(self) -> Optional[str]:
if not self.addr:
return None
sockaddr_un = self.addr.name.cast("sockaddr_un")
saddr = str(utility.array_to_string(sockaddr_un.sun_path))
return saddr
def get_protocol(self) -> Optional[str]:
return None
def get_state(self) -> str:
"""Return a string representing the sock state."""
# Unix socket states reuse (a subset) of the inet_sock states contants
if self.sk.get_type() == "STREAM":
state_idx = self.sk.__sk_common.skc_state
if 0 <= state_idx < len(linux_constants.TCP_STATES):
return linux_constants.TCP_STATES[state_idx]
else:
return "Unknown unix_sock stream state"
# Return the generic socket state
return self.sk.sk_socket.get_state()
def get_inode(self) -> int:
return self.sk.get_inode()
class inet_sock(objects.StructType):
def get_family(self) -> str:
family_idx = self.sk.__sk_common.skc_family
if 0 <= family_idx < len(linux_constants.SOCK_FAMILY):
return linux_constants.SOCK_FAMILY[family_idx]
return "Unknown inet_sock family"
def get_protocol(self) -> Optional[str]:
# If INET6 family and a proto is defined, we use that specific IPv6 protocol.
# Otherwise, we use the standard IP protocol.
protocol = linux_constants.IP_PROTOCOLS.get(self.sk.sk_protocol)
if self.get_family() == "AF_INET6":
protocol = linux_constants.IPV6_PROTOCOLS.get(self.sk.sk_protocol, protocol)
return protocol
def get_state(self) -> str:
"""Return a string representing the sock state."""
if self.sk.get_type() == "STREAM":
state_idx = self.sk.__sk_common.skc_state
if 0 <= state_idx < len(linux_constants.TCP_STATES):
return linux_constants.TCP_STATES[state_idx]
else:
return "Unknown inet_sock stream state"
# Return the generic socket state
return self.sk.sk_socket.get_state()
def get_src_port(self) -> Optional[int]:
sport_le = getattr(self, "sport", getattr(self, "inet_sport", None))
if sport_le is not None:
return socket_module.htons(sport_le)
return None
def get_dst_port(self) -> Optional[int]:
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 None
return socket_module.htons(dport_le)
def get_src_addr(self) -> Optional[str]:
sk_common = self.sk.__sk_common
family = sk_common.skc_family
if family == socket_module.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_module.AF_INET6:
addr_size = 16
saddr = self.pinet6.saddr
else:
return None
parent_layer = self._context.layers[self.vol.layer_name]
try:
addr_bytes = parent_layer.read(saddr.vol.offset, addr_size)
except exceptions.InvalidAddressException:
vollog.debug(
f"Unable to read socket src address from {saddr.vol.offset:#x}"
)
return None
return socket_module.inet_ntop(family, addr_bytes)
def get_dst_addr(self) -> Optional[str]:
sk_common = self.sk.__sk_common
family = sk_common.skc_family
if family == socket_module.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_module.AF_INET6:
if hasattr(self.pinet6, "daddr"):
daddr = self.pinet6.daddr
else:
daddr = sk_common.skc_v6_daddr
addr_size = 16
else:
return None
parent_layer = self._context.layers[self.vol.layer_name]
try:
addr_bytes = parent_layer.read(daddr.vol.offset, addr_size)
except exceptions.InvalidAddressException:
vollog.debug(
f"Unable to read socket dst address from {daddr.vol.offset:#x}"
)
return None
return socket_module.inet_ntop(family, addr_bytes)
class netlink_sock(objects.StructType):
def get_protocol(self) -> str:
protocol_idx = self.sk.sk_protocol
if 0 <= protocol_idx < len(linux_constants.NETLINK_PROTOCOLS):
return linux_constants.NETLINK_PROTOCOLS[protocol_idx]
return "Unknown netlink_sock protocol"
def get_state(self):
# Return the generic socket state
return self.sk.sk_socket.get_state()
def get_portid(self) -> int:
if self.has_member("pid"):
# kernel < 3.7.10
return self.pid
if self.has_member("portid"):
# kernel >= 3.7.10
return self.portid
else:
raise AttributeError("Unable to find a source port id")
def get_dst_portid(self) -> int:
if self.has_member("dst_pid"):
# kernel < 3.7.10
return self.dst_pid
if self.has_member("dst_portid"):
# kernel >= 3.7.10
return self.dst_portid
else:
raise AttributeError("Unable to find a destination port id")
class vsock_sock(objects.StructType):
def get_protocol(self):
# The protocol should always be 0 for vsocks
return None
def get_state(self):
# Return the generic socket state
return self.sk.sk_socket.get_state()
class packet_sock(objects.StructType):
def get_protocol(self) -> Optional[str]:
eth_proto = socket_module.htons(self.num)
if eth_proto == 0:
return None
elif eth_proto in linux_constants.ETH_PROTOCOLS:
return linux_constants.ETH_PROTOCOLS[eth_proto]
else:
return f"0x{eth_proto:x}"
def get_state(self):
# Return the generic socket state
return self.sk.sk_socket.get_state()
class bt_sock(objects.StructType):
def get_protocol(self) -> Optional[str]:
type_idx = self.sk.sk_protocol
if 0 <= type_idx < len(linux_constants.BLUETOOTH_PROTOCOLS):
return linux_constants.BLUETOOTH_PROTOCOLS[type_idx]
return None
def get_state(self) -> Optional[str]:
state_idx = self.sk.__sk_common.skc_state
if 0 <= state_idx < len(linux_constants.BLUETOOTH_STATES):
return linux_constants.BLUETOOTH_STATES[state_idx]
return None
class xdp_sock(objects.StructType):
def get_protocol(self):
# The protocol should always be 0 for xdp_sock
return None
def get_state(self):
# xdp_sock.state is an enum
return self.state.lookup()