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 16 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
37 changes: 31 additions & 6 deletions test/test_volatility.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import shutil
import tempfile
import hashlib
import ntpath
import json
import contextlib

Expand Down Expand Up @@ -183,11 +182,7 @@ def test_windows_dumpfiles(image, volatility, python):
known_files = json.load(json_file)

failed_chksms = 0

if sys.platform == "win32":
file_name = ntpath.basename(image)
else:
file_name = os.path.basename(image)
file_name = os.path.basename(image)

try:
for addr in known_files["windows_dumpfiles"][file_name]:
Expand Down Expand Up @@ -842,6 +837,36 @@ def test_linux_hidden_modules(image, volatility, python):
assert out.count(b"\n") >= 4


def test_linux_ip_addr(image, volatility, python):
rc, out, err = runvol_plugin("linux.ip.Addr", image, volatility, python)

assert re.search(
rb"2\s+eth0\s+00:0c:29:8f:ed:ca\s+False\s+192.168.201.161\s+24\s+global\s+UP",
out,
)
assert re.search(
rb"2\s+eth0\s+00:0c:29:8f:ed:ca\s+False\s+fe80::20c:29ff:fe8f:edca\s+64\s+link\s+UP",
out,
)
assert out.count(b"\n") >= 8
assert rc == 0


def test_linux_ip_link(image, volatility, python):
rc, out, err = runvol_plugin("linux.ip.Link", image, volatility, python)

assert re.search(
rb"-\s+lo\s+00:00:00:00:00:00\s+UNKNOWN\s+16436\s+noqueue\s+0\s+LOOPBACK,LOWER_UP,UP",
out,
)
assert re.search(
rb"-\s+eth0\s+00:0c:29:8f:ed:ca\s+UP\s+1500\s+pfifo_fast\s+1000\s+BROADCAST,LOWER_UP,MULTICAST,UP",
out,
)
assert out.count(b"\n") >= 6
assert rc == 0


# MAC


Expand Down
48 changes: 46 additions & 2 deletions volatility3/framework/constants/linux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,51 @@

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

# 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,
}


# Kernels >= 2.6.17. See IF_OPER_* in include/uapi/linux/if.h
class IF_OPER_STATES(Enum):

Check failure on line 320 in volatility3/framework/constants/linux/__init__.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (F821)

volatility3/framework/constants/linux/__init__.py:320:22: F821 Undefined name `Enum`
"""RFC 2863 - Network interface operational status"""

UNKNOWN = 0
NOTPRESENT = 1
DOWN = 2
LOWERLAYERDOWN = 3
TESTING = 4
DORMANT = 5
UP = 6

class ELF_IDENT(IntEnum):
"""ELF header e_ident indexes"""
Expand All @@ -304,7 +349,6 @@
ELFCLASS32 = 1
ELFCLASS64 = 2


# PTrace
PT_OPT_FLAG_SHIFT = 3

Expand Down Expand Up @@ -415,4 +459,4 @@
- https://www.kernel.org/doc/Documentation/admin-guide/tainted-kernels.rst#:~:text=More%20detailed%20explanation%20for%20tainting
- taint_flag kernel struct
- taint_flags kernel constant
"""
"""

Check failure on line 462 in volatility3/framework/constants/linux/__init__.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (W292)

volatility3/framework/constants/linux/__init__.py:462:4: W292 No newline at end of file
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
148 changes: 148 additions & 0 deletions volatility3/framework/plugins/linux/ip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# 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


class Addr(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
operational_state = net_dev.get_operational_state()
iface_name = net_dev.get_device_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, operational_state

# Interface IPv6 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, operational_state

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),
("State", str),
]

return renderers.TreeGrid(headers, self._generator())


class Link(plugins.PluginInterface):
"""Lists information about network interfaces similar to `ip link show`"""

_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_link_info(self, net_device):
mac_addr = net_device.get_mac_address()
operational_state = net_device.get_operational_state()
iface_name = net_device.get_device_name()
mtu = net_device.mtu
qdisc_name = net_device.get_qdisc_name()
qlen = net_device.get_queue_length()
try:
net_ns_id = net_device.get_net_namespace_id()
except AttributeError:
net_ns_id = renderers.NotAvailableValue()

# Format flags to string. Drop IFF_ to match iproute2 'ip link' output.
# Also, note that iproute2 removes IFF_RUNNING, see print_link_flags()
flags_list = [
flag.replace("IFF_", "")
for flag in net_device.get_flag_names()
if flag != "IFF_RUNNING"
]
flags_str = ",".join(flags_list)

yield net_ns_id, iface_name, mac_addr, operational_state, mtu, qdisc_name, qlen, flags_str

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_link_info(net_dev):
yield 0, fields

def run(self):
headers = [
("NS", int),
("Interface", str),
("MAC", str),
("State", str),
("MTU", int),
("Qdisc", str),
("Qlen", int),
("Flags", 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 @@ -73,6 +73,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
Loading
Loading