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

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
82668af
Linux - Added linux.ifconfig.Ifconfig plugin
gcmoreira Jan 9, 2024
6b3bbad
Fix exception. Although it will be auto-instantiated it's better to e…
gcmoreira Jan 9, 2024
6e0ffc9
Plugin renamed. Added net iface status and other improvements from @e…
gcmoreira Jan 27, 2024
97007e8
Minor fixes
gcmoreira Jan 27, 2024
620b9b4
Fix: Explicit returns mixed with implicit (fall through) returns
gcmoreira Jan 27, 2024
cd867bf
Merge branch 'volatilityfoundation:develop' into linux_ifconfig_plugin
gcmoreira Jan 27, 2024
5a8a0de
Fix docstring typos
gcmoreira Jan 30, 2024
c72fa75
Convert IF_OPER_STATES to enum
gcmoreira Jan 30, 2024
1b153cc
Manage net_device flag default value & error
gcmoreira Jan 30, 2024
6f1e7c2
Add test for linux.ip.Addr and linux-sample-1.bin
gcmoreira Feb 2, 2024
260fbd8
Python os.path module precisely does that by checking the current pla…
gcmoreira Feb 2, 2024
7b1c75c
Linux: Add the linux.ip.Link plugin by @eve-mem
gcmoreira Feb 2, 2024
8b6bd0f
Add linux.ip.Link test using linux-sample-1.bin image
gcmoreira Feb 2, 2024
5219e8a
Remove lowercase matching from linux.ip.Addr test
gcmoreira Feb 2, 2024
828b688
Fix issue with net namespace id and improve code
gcmoreira Feb 2, 2024
e66d674
Merge branch 'develop' into linux_ifconfig_plugin
ikelos Jan 30, 2025
d3e55db
Apply suggestions from code review
ikelos Jan 30, 2025
f6106c8
Apply suggestions from code review
ikelos Jan 30, 2025
f83386f
Update volatility3/framework/symbols/linux/extensions/__init__.py
ikelos Jan 30, 2025
51c19fb
Merge branch 'feature/linux_ifconfig_plugin' into linux_ifconfig_plugin
ikelos Feb 16, 2025
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
12 changes: 12 additions & 0 deletions volatility3/framework/constants/linux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,3 +281,15 @@
)

ELF_MAX_EXTRACTION_SIZE = 1024 * 1024 * 1024 * 4 - 1

# For IFA_* below - Ref: include/net/ipv6.h
IPV6_ADDR_LOOPBACK = 0x0010
IPV6_ADDR_LINKLOCAL = 0x0020
IPV6_ADDR_SITELOCAL = 0x0040
# For inet6_ifaddr - Ref: include/net/if_inet6.h
IFA_HOST = IPV6_ADDR_LOOPBACK
IFA_LINK = IPV6_ADDR_LINKLOCAL
IFA_SITE = IPV6_ADDR_SITELOCAL

# Promiscous mode
IFF_PROMISC = 0x100
2 changes: 1 addition & 1 deletion volatility3/framework/interfaces/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def __init__(

def __getattr__(self, attr: str) -> Any:
"""Method for ensuring volatility members can be returned."""
raise AttributeError
raise AttributeError()

@property
def vol(self) -> ReadOnlyMapping:
Expand Down
80 changes: 80 additions & 0 deletions volatility3/framework/plugins/linux/ifconfig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# This file is Copyright 2023 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#

from typing import List
from volatility3.framework import interfaces, renderers, constants
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility


class Ifconfig(plugins.PluginInterface):
"""Lists network interface information for all devices"""

_required_framework_version = (2, 0, 0)

_version = (1, 0, 0)

@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=["Intel32", "Intel64"],
),
]

def _gather_net_dev_info(self, net_dev):
mac_addr = net_dev.get_mac_address()
promisc = net_dev.promisc
iface_name = utility.array_to_string(net_dev.name)
iface_ifindex = net_dev.ifindex
try:
net_ns_id = net_dev.get_net_namespace_id()
except AttributeError:
net_ns_id = renderers.NotAvailableValue()

# Interface IPv4 Addresses
in_device = net_dev.ip_ptr.dereference().cast("in_device")
for in_ifaddr in in_device.get_addresses():
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

# Interface IPv6 Addresses
ip6_ptr = net_dev.ip6_ptr.dereference().cast("inet6_dev")
for inet6_ifaddr in ip6_ptr.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

def _generator(self):
vmlinux = self.context.modules[self.config["kernel"]]

net_type_symname = vmlinux.symbol_table_name + constants.BANG + "net"
net_device_symname = vmlinux.symbol_table_name + constants.BANG + "net_device"

# 'net_namespace_list' exists from kernels >= 2.6.24
net_namespace_list = vmlinux.object_from_symbol("net_namespace_list")
for net_ns in net_namespace_list.to_list(net_type_symname, "list"):
for net_dev in net_ns.dev_base_head.to_list(net_device_symname, "dev_list"):
for fields in self._gather_net_dev_info(net_dev):
yield 0, fields

def run(self):
headers = [
("NetNS", int),
("Index", int),
("Interface", str),
("MAC", str),
("Promiscuous", bool),
("IP", str),
("Prefix", int),
("Scope Type", str),
]

return renderers.TreeGrid(headers, self._generator())
5 changes: 5 additions & 0 deletions volatility3/framework/symbols/linux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ def __init__(self, *args, **kwargs) -> None:

# Network
self.set_type_class("net", extensions.net)
self.set_type_class("net_device", extensions.net_device)
self.set_type_class("in_device", extensions.in_device)
self.set_type_class("in_ifaddr", extensions.in_ifaddr)
self.set_type_class("inet6_dev", extensions.inet6_dev)
self.set_type_class("inet6_ifaddr", extensions.inet6_ifaddr)
self.set_type_class("socket", extensions.socket)
self.set_type_class("sock", extensions.sock)
self.set_type_class("inet_sock", extensions.inet_sock)
Expand Down
205 changes: 204 additions & 1 deletion volatility3/framework/symbols/linux/extensions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import collections.abc
import logging
import struct
import socket as socket_module
from typing import Generator, Iterable, Iterator, Optional, Tuple, List

Expand All @@ -13,7 +14,8 @@
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
from volatility3.framework.constants.linux import CAPABILITIES, IFF_PROMISC
from volatility3.framework.constants.linux import IFA_HOST, IFA_LINK, IFA_SITE
from volatility3.framework import exceptions, objects, interfaces, symbols
from volatility3.framework.layers import linear
from volatility3.framework.objects import utility
Expand Down Expand Up @@ -1213,6 +1215,15 @@

class net(objects.StructType):
def get_inode(self):
"""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
Expand All @@ -1224,6 +1235,198 @@
raise AttributeError("Unable to find net_namespace inode")


class net_device(objects.StructType):
@staticmethod
def _format_as_mac_address(hwaddr):
return ":".join([f"{x:02x}" for x in hwaddr[:6]])

def get_mac_address(self):
"""Get the MAC address of this network interface.

Returns:
str: the MAC address of this network interface.
"""
if self.has_member("perm_addr"):
mac_addr = self._format_as_mac_address(self.perm_addr)
if mac_addr != "00:00:00:00:00:00":
return mac_addr

parent_layer = self._context.layers[self.vol.layer_name]
try:
hwaddr = parent_layer.read(self.dev_addr, 6)
except exceptions.InvalidAddressException:
vollog.debug(
f"Unable to read network inteface mac address from {self.dev_addr:#x}"
)
return

return self._format_as_mac_address(hwaddr)

@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

def get_net_namespace_id(self):
"""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


class in_device(objects.StructType):
def get_addresses(self):
"""Yield the IPv4 ifaddr addresses

Yields:
in_ifaddr: An IPv4 ifaddr address
"""
cur = self.ifa_list
while cur and cur.vol.offset:
yield cur
cur = cur.ifa_next


class inet6_dev(objects.StructType):
def get_addresses(self):
"""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.vol.type_name.split(constants.BANG)[0]
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
for inet6_ifaddr in self.addr_list.to_list(inet6_ifaddr_symname, "if_list"):
yield inet6_ifaddr


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):
"""Get the scope type for this IPv4 address

Returns:
str: the IPv4 scope type.
"""
table_name = self.vol.type_name.split(constants.BANG)[0]
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):
"""Get an string with the IPv4 address

Returns:
str: the IPv4 address
"""
ipv4_bytes = struct.pack("<I", self.ifa_address)
return socket_module.inet_ntop(socket_module.AF_INET, ipv4_bytes)

def get_prefix_len(self):
"""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):
"""Get the scope type for this IPv6 address

Returns:
str: the IPv6 scope type.
"""
if (self.scope & IFA_HOST) != 0:
return "host"
elif (self.scope & IFA_LINK) != 0:
return "link"
elif (self.scope & IFA_SITE) != 0:
return "site"
else:
return "global"

def get_address(self):
"""Get an string with the IPv6 address

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)

def get_prefix_len(self):
"""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):
symbol_table_arr = self.vol.type_name.split("!", 1)
Expand Down