Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linux ip.Addr and ip.Link plugins #1079

Open
wants to merge 15 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions volatility3/framework/constants/linux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also looks like it could be a python Enum?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm here changing this to an Enum makes the code a bit complicated... have a look at how it's used in _get_flag_choices() and _get_net_device_flag_value() to support both kernels <3.15 and >=3.15.

"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 = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be Enums rather than just a list of strings? If may potentially lead to typos?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. This is used by value, so I don't think at the moment being an Enum offers any usability advantage. Although I reckon it may look better to Pythonic eyes ;)

"UNKNOWN",
"NOTPRESENT",
"DOWN",
"LOWERLAYERDOWN",
"TESTING",
"DORMANT",
"UP",
)
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand All @@ -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"]]
Expand All @@ -75,6 +76,7 @@ def run(self):
("IP", str),
("Prefix", int),
("Scope Type", str),
("State", str),
]

return renderers.TreeGrid(headers, self._generator())
83 changes: 53 additions & 30 deletions volatility3/framework/symbols/linux/extensions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@

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
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, 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
Expand Down Expand Up @@ -1236,24 +1237,25 @@


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:
Fixed Show fixed Hide fixed
"""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}"
Expand All @@ -1262,16 +1264,41 @@

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]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be happier if this were a get with a default value? Having said that, it's such a short function, perhaps it's not worth having at all (given it's private so can't be used by anyone else?)

Copy link
Contributor Author

@gcmoreira gcmoreira Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default value and error managed.
I prefer to keep it as a function, even though it's short and private. This way, it serves the purpose of documenting what it does and increases the code's readability. I would let the language to implement the bytecode inline if needed.
Anyway, if you still think it should be inline, let me know, and I will change it.


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.

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:
Expand All @@ -1288,6 +1315,17 @@

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"
gcmoreira marked this conversation as resolved.
Show resolved Hide resolved

return IF_OPER_STATES[self.operstate]

class in_device(objects.StructType):
def get_addresses(self):
Expand Down Expand Up @@ -1320,7 +1358,7 @@
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
Expand Down Expand Up @@ -1351,7 +1389,7 @@
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"
)
Expand All @@ -1368,8 +1406,7 @@
Returns:
str: the IPv4 address
"""
ipv4_bytes = struct.pack("<I", self.ifa_address)
return socket_module.inet_ntop(socket_module.AF_INET, ipv4_bytes)
return conversion.convert_ipv4(self.ifa_address)

def get_prefix_len(self):
"""Get the IPv4 address prefix len
Expand Down Expand Up @@ -1402,21 +1439,7 @@
Returns:
str: the IPv6 address
"""
symbol_space = self._context.symbol_space
table_name = self.vol.type_name.split(constants.BANG)[0]
in6_addr_symname = table_name + constants.BANG + "in6_addr"
in6_addr_size = symbol_space.get_type(in6_addr_symname).size

parent_layer = self._context.layers[self.vol.layer_name]
try:
ip6_addr_bytes = parent_layer.read(self.addr.vol.offset, in6_addr_size)
except exceptions.InvalidAddressException:
vollog.debug(
f"Unable to read network IPv6 address from {self.addr.vol.offset:#x}"
)
return

return socket_module.inet_ntop(socket_module.AF_INET6, ip6_addr_bytes)
return conversion.convert_ipv6(self.addr.in6_u.u6_addr32)

def get_prefix_len(self):
"""Get the IPv6 address prefix len
Expand Down
Loading