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 2 commits
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
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):
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"):
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):
Fixed Show fixed Hide fixed
"""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