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 all 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
38 changes: 32 additions & 6 deletions test/test_volatility.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
#

import os
import re
import subprocess
import sys
import shutil
import tempfile
import hashlib
import ntpath
import json

#
Expand Down Expand Up @@ -124,11 +124,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 @@ -331,6 +327,36 @@ def test_linux_tty_check(image, volatility, python):
assert rc == 0


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
47 changes: 47 additions & 0 deletions volatility3/framework/constants/linux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

Linux-specific values that aren't found in debug symbols
"""
from enum import Enum

KERNEL_NAME = "__kernel__"

Expand Down Expand Up @@ -281,3 +282,49 @@
)

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):
"""RFC 2863 - Network interface operational status"""

UNKNOWN = 0
NOTPRESENT = 1
DOWN = 2
LOWERLAYERDOWN = 3
TESTING = 4
DORMANT = 5
UP = 6
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 @@ -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
Loading
Loading