diff --git a/volatility3/framework/constants/linux/__init__.py b/volatility3/framework/constants/linux/__init__.py index aa0692365d..21e0b47f76 100644 --- a/volatility3/framework/constants/linux/__init__.py +++ b/volatility3/framework/constants/linux/__init__.py @@ -291,5 +291,37 @@ IFA_LINK = IPV6_ADDR_LINKLOCAL IFA_SITE = IPV6_ADDR_SITELOCAL -# Promiscous mode -IFF_PROMISC = 0x100 +# Only for kernels < 3.15 when the net_device_flags enum didn't exist +# ref include/uapi/linux/if.h +NET_DEVICE_FLAGS = { + "IFF_UP": 0x1, + "IFF_BROADCAST": 0x2, + "IFF_DEBUG": 0x4, + "IFF_LOOPBACK": 0x8, + "IFF_POINTOPOINT": 0x10, + "IFF_NOTRAILERS": 0x20, + "IFF_RUNNING": 0x40, + "IFF_NOARP": 0x80, + "IFF_PROMISC": 0x100, + "IFF_ALLMULTI": 0x200, + "IFF_MASTER": 0x400, + "IFF_SLAVE": 0x800, + "IFF_MULTICAST": 0x1000, + "IFF_PORTSEL": 0x2000, + "IFF_AUTOMEDIA": 0x4000, + "IFF_DYNAMIC": 0x8000, + "IFF_LOWER_UP": 0x10000, + "IFF_DORMANT": 0x20000, + "IFF_ECHO": 0x40000, +} + +# RFC 2863 operational status. Kernels >= 2.6.17. See IF_OPER_* in include/uapi/linux/if.h +IF_OPER_STATES = ( + "UNKNOWN", + "NOTPRESENT", + "DOWN", + "LOWERLAYERDOWN", + "TESTING", + "DORMANT", + "UP", +) diff --git a/volatility3/framework/plugins/linux/ifconfig.py b/volatility3/framework/plugins/linux/ip.py similarity index 88% rename from volatility3/framework/plugins/linux/ifconfig.py rename to volatility3/framework/plugins/linux/ip.py index 39f4208bfe..19393a853e 100644 --- a/volatility3/framework/plugins/linux/ifconfig.py +++ b/volatility3/framework/plugins/linux/ip.py @@ -9,7 +9,7 @@ from volatility3.framework.objects import utility -class Ifconfig(plugins.PluginInterface): +class Addr(plugins.PluginInterface): """Lists network interface information for all devices""" _required_framework_version = (2, 0, 0) @@ -29,6 +29,7 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] def _gather_net_dev_info(self, net_dev): mac_addr = net_dev.get_mac_address() promisc = net_dev.promisc + operational_state = net_dev.get_operational_state() iface_name = utility.array_to_string(net_dev.name) iface_ifindex = net_dev.ifindex try: @@ -42,15 +43,15 @@ def _gather_net_dev_info(self, net_dev): prefix_len = in_ifaddr.get_prefix_len() scope_type = in_ifaddr.get_scope_type() ip_addr = in_ifaddr.get_address() - yield net_ns_id, iface_ifindex, iface_name, mac_addr, promisc, ip_addr, prefix_len, scope_type + yield net_ns_id, iface_ifindex, iface_name, mac_addr, promisc, ip_addr, prefix_len, scope_type, operational_state # Interface IPv6 Addresses - ip6_ptr = net_dev.ip6_ptr.dereference().cast("inet6_dev") - for inet6_ifaddr in ip6_ptr.get_addresses(): + inet6_dev = net_dev.ip6_ptr.dereference().cast("inet6_dev") + for inet6_ifaddr in inet6_dev.get_addresses(): prefix_len = inet6_ifaddr.get_prefix_len() scope_type = inet6_ifaddr.get_scope_type() ip6_addr = inet6_ifaddr.get_address() - yield net_ns_id, iface_ifindex, iface_name, mac_addr, promisc, ip6_addr, prefix_len, scope_type + yield net_ns_id, iface_ifindex, iface_name, mac_addr, promisc, ip6_addr, prefix_len, scope_type, operational_state def _generator(self): vmlinux = self.context.modules[self.config["kernel"]] @@ -75,6 +76,7 @@ def run(self): ("IP", str), ("Prefix", int), ("Scope Type", str), + ("State", str), ] return renderers.TreeGrid(headers, self._generator()) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 085d91bff4..3a088a959f 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -4,9 +4,8 @@ import collections.abc import logging -import struct import socket as socket_module -from typing import Generator, Iterable, Iterator, Optional, Tuple, List +from typing import Generator, Iterable, Iterator, Optional, Tuple, List, Dict from volatility3.framework import constants from volatility3.framework.constants.linux import SOCK_TYPES, SOCK_FAMILY @@ -14,8 +13,10 @@ 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, SOCKET_STATES -from volatility3.framework.constants.linux import CAPABILITIES, IFF_PROMISC +from volatility3.framework.constants.linux import CAPABILITIES, NET_DEVICE_FLAGS from volatility3.framework.constants.linux import IFA_HOST, IFA_LINK, IFA_SITE +from volatility3.framework.constants.linux import IF_OPER_STATES +from volatility3.framework.renderers import conversion from volatility3.framework import exceptions, objects, interfaces, symbols from volatility3.framework.layers import linear from volatility3.framework.objects import utility @@ -1236,24 +1237,25 @@ def get_inode(self): class net_device(objects.StructType): - @staticmethod - def _format_as_mac_address(hwaddr): - return ":".join([f"{x:02x}" for x in hwaddr[:6]]) + def _format_as_mac_address(self, hwaddr): + return ":".join([f"{x:02x}" for x in hwaddr[: self.addr_len]]) - def get_mac_address(self): + def get_mac_address(self) -> 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 != "00:00:00:00:00:00": + 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, 6) + hwaddr = parent_layer.read(self.dev_addr, self.addr_len, pad=True) except exceptions.InvalidAddressException: vollog.debug( f"Unable to read network inteface mac address from {self.dev_addr:#x}" @@ -1262,6 +1264,31 @@ def get_mac_address(self): return self._format_as_mac_address(hwaddr) + def _get_flag_choices(self) -> Dict: + """Return the net_deivce 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 = NET_DEVICE_FLAGS + + return choices + + def _get_net_device_flag_value(self, name): + """Return the net_deivce flag value based on the flag name""" + return self._get_flag_choices()[name] + + def get_flag_names(self) -> List[str]: + """Return the net_deivce flags as a list of strings + + Returns: + List[str]: A list of flag names + """ + return list(self._get_flag_choices()) + @property def promisc(self): """Return if this network interface is in promiscuous mode. @@ -1269,9 +1296,9 @@ def promisc(self): Returns: bool: True if this network interface is in promiscuous mode. Otherwise, False """ - return self.flags & IFF_PROMISC == IFF_PROMISC + return self.flags & self._get_net_device_flag_value("IFF_PROMISC") != 0 - def get_net_namespace_id(self): + def get_net_namespace_id(self) -> int: """Return the network namespace id for this network interface. Returns: @@ -1288,6 +1315,17 @@ def get_net_namespace_id(self): return net_ns_id + def get_operational_state(self) -> str: + """Return the netwok device oprational state (RFC 2863) string + + Returns: + str: A string with the operational state + """ + if self.operstate >= len(IF_OPER_STATES): + vollog.warning(f"Invalid net_device operational state '{self.operstate}'") + return "INVALID" + + return IF_OPER_STATES[self.operstate] class in_device(objects.StructType): def get_addresses(self): @@ -1320,7 +1358,7 @@ def get_addresses(self): return symbol_space = self._context.symbol_space - table_name = self.vol.type_name.split(constants.BANG)[0] + 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 @@ -1351,7 +1389,7 @@ def get_scope_type(self): Returns: str: the IPv4 scope type. """ - table_name = self.vol.type_name.split(constants.BANG)[0] + table_name = self.get_symbol_table_name() rt_scope_enum = self._context.symbol_space.get_enumeration( table_name + constants.BANG + "rt_scope_t" ) @@ -1368,8 +1406,7 @@ def get_address(self): Returns: str: the IPv4 address """ - ipv4_bytes = struct.pack("