Skip to content

Commit d70c571

Browse files
authored
Merge pull request #1624 from volatilityfoundation/feature/linux_ifconfig_plugin
Feature/linux ifconfig plugin
2 parents 37bdb6c + 5fabd7a commit d70c571

File tree

11 files changed

+1001
-323
lines changed

11 files changed

+1001
-323
lines changed

test/test_volatility.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import shutil
1313
import tempfile
1414
import hashlib
15-
import ntpath
1615
import json
1716
import contextlib
1817

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

185184
failed_chksms = 0
186-
187-
if sys.platform == "win32":
188-
file_name = ntpath.basename(image)
189-
else:
190-
file_name = os.path.basename(image)
185+
file_name = os.path.basename(image)
191186

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

844839

840+
def test_linux_ip_addr(image, volatility, python):
841+
rc, out, err = runvol_plugin("linux.ip.Addr", image, volatility, python)
842+
843+
assert re.search(
844+
rb"2\s+eth0\s+00:0c:29:8f:ed:ca\s+False\s+192.168.201.161\s+24\s+global\s+UP",
845+
out,
846+
)
847+
assert re.search(
848+
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",
849+
out,
850+
)
851+
assert out.count(b"\n") >= 8
852+
assert rc == 0
853+
854+
855+
def test_linux_ip_link(image, volatility, python):
856+
rc, out, err = runvol_plugin("linux.ip.Link", image, volatility, python)
857+
858+
assert re.search(
859+
rb"-\s+lo\s+00:00:00:00:00:00\s+UNKNOWN\s+16436\s+noqueue\s+0\s+LOOPBACK,LOWER_UP,UP",
860+
out,
861+
)
862+
assert re.search(
863+
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",
864+
out,
865+
)
866+
assert out.count(b"\n") >= 6
867+
assert rc == 0
868+
869+
845870
def test_linux_kallsyms(image, volatility, python):
846871
rc, out, _err = runvol_plugin(
847872
"linux.kallsyms.Kallsyms",

volatility3/framework/constants/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# We use the SemVer 2.0.0 versioning scheme
22
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
3-
VERSION_MINOR = 21 # Number of changes that only add to the interface
3+
VERSION_MINOR = 22 # Number of changes that only add to the interface
44
VERSION_PATCH = 0 # Number of changes that do not change the interface
55
VERSION_SUFFIX = ""
66

volatility3/framework/constants/linux/__init__.py

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
Linux-specific values that aren't found in debug symbols
77
"""
8-
from enum import IntEnum, Flag
8+
import enum
99
from dataclasses import dataclass
1010

1111
KERNEL_NAME = "__kernel__"
@@ -282,8 +282,54 @@
282282

283283
ELF_MAX_EXTRACTION_SIZE = 1024 * 1024 * 1024 * 4 - 1
284284

285+
# For IFA_* below - Ref: include/net/ipv6.h
286+
IPV6_ADDR_LOOPBACK = 0x0010
287+
IPV6_ADDR_LINKLOCAL = 0x0020
288+
IPV6_ADDR_SITELOCAL = 0x0040
289+
# For inet6_ifaddr - Ref: include/net/if_inet6.h
290+
IFA_HOST = IPV6_ADDR_LOOPBACK
291+
IFA_LINK = IPV6_ADDR_LINKLOCAL
292+
IFA_SITE = IPV6_ADDR_SITELOCAL
293+
294+
# Only for kernels < 3.15 when the net_device_flags enum didn't exist
295+
# ref include/uapi/linux/if.h
296+
NET_DEVICE_FLAGS = {
297+
"IFF_UP": 0x1,
298+
"IFF_BROADCAST": 0x2,
299+
"IFF_DEBUG": 0x4,
300+
"IFF_LOOPBACK": 0x8,
301+
"IFF_POINTOPOINT": 0x10,
302+
"IFF_NOTRAILERS": 0x20,
303+
"IFF_RUNNING": 0x40,
304+
"IFF_NOARP": 0x80,
305+
"IFF_PROMISC": 0x100,
306+
"IFF_ALLMULTI": 0x200,
307+
"IFF_MASTER": 0x400,
308+
"IFF_SLAVE": 0x800,
309+
"IFF_MULTICAST": 0x1000,
310+
"IFF_PORTSEL": 0x2000,
311+
"IFF_AUTOMEDIA": 0x4000,
312+
"IFF_DYNAMIC": 0x8000,
313+
"IFF_LOWER_UP": 0x10000,
314+
"IFF_DORMANT": 0x20000,
315+
"IFF_ECHO": 0x40000,
316+
}
317+
318+
319+
# Kernels >= 2.6.17. See IF_OPER_* in include/uapi/linux/if.h
320+
class IF_OPER_STATES(enum.Enum):
321+
"""RFC 2863 - Network interface operational status"""
285322

286-
class ELF_IDENT(IntEnum):
323+
UNKNOWN = 0
324+
NOTPRESENT = 1
325+
DOWN = 2
326+
LOWERLAYERDOWN = 3
327+
TESTING = 4
328+
DORMANT = 5
329+
UP = 6
330+
331+
332+
class ELF_IDENT(enum.IntEnum):
287333
"""ELF header e_ident indexes"""
288334

289335
EI_MAG0 = 0
@@ -297,7 +343,7 @@ class ELF_IDENT(IntEnum):
297343
EI_PAD = 8
298344

299345

300-
class ELF_CLASS(IntEnum):
346+
class ELF_CLASS(enum.IntEnum):
301347
"""ELF header class types"""
302348

303349
ELFCLASSNONE = 0
@@ -320,8 +366,9 @@ class ELF_CLASS(IntEnum):
320366
PTRACE_O_SUSPEND_SECCOMP = 1 << 21
321367

322368

323-
class PT_FLAGS(Flag):
369+
class PT_FLAGS(enum.Flag):
324370
"PTrace flags"
371+
325372
PT_PTRACED = 0x00001
326373
PT_SEIZED = 0x10000
327374

volatility3/framework/interfaces/objects.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def __init__(
133133

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

138138
@property
139139
def vol(self) -> ReadOnlyMapping:
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# This file is Copyright 2023 Volatility Foundation and licensed under the Volatility Software License 1.0
2+
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
3+
#
4+
5+
from typing import List
6+
from volatility3.framework import interfaces, renderers, constants
7+
from volatility3.framework.configuration import requirements
8+
from volatility3.framework.interfaces import plugins
9+
from volatility3.framework.symbols.linux import network
10+
from volatility3.framework.symbols.linux.extensions import network as net_extensions
11+
12+
13+
class Addr(plugins.PluginInterface):
14+
"""Lists network interface information for all devices"""
15+
16+
_required_framework_version = (2, 22, 0)
17+
18+
_version = (1, 0, 1)
19+
20+
@classmethod
21+
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
22+
return [
23+
requirements.ModuleRequirement(
24+
name="kernel",
25+
description="Linux kernel",
26+
architectures=["Intel32", "Intel64"],
27+
),
28+
requirements.VersionRequirement(
29+
name="Net", component=network.NetSymbols, version=(1, 0, 0)
30+
),
31+
]
32+
33+
def _gather_net_dev_info(self, net_dev: net_extensions.net_device):
34+
mac_addr = net_dev.get_mac_address()
35+
promisc = net_dev.promisc
36+
operational_state = net_dev.get_operational_state()
37+
iface_name = net_dev.get_device_name()
38+
iface_ifindex = net_dev.ifindex
39+
try:
40+
net_ns_id = net_dev.get_net_namespace_id()
41+
except AttributeError:
42+
net_ns_id = renderers.NotAvailableValue()
43+
44+
# Interface IPv4 Addresses
45+
in_device = net_dev.ip_ptr.dereference().cast("in_device")
46+
for in_ifaddr in in_device.get_addresses():
47+
prefix_len = in_ifaddr.get_prefix_len()
48+
scope_type = in_ifaddr.get_scope_type()
49+
ip_addr = in_ifaddr.get_address()
50+
yield net_ns_id, iface_ifindex, iface_name, mac_addr, promisc, ip_addr, prefix_len, scope_type, operational_state
51+
52+
# Interface IPv6 Addresses
53+
inet6_dev = net_dev.ip6_ptr.dereference().cast("inet6_dev")
54+
for inet6_ifaddr in inet6_dev.get_addresses():
55+
prefix_len = inet6_ifaddr.get_prefix_len()
56+
scope_type = inet6_ifaddr.get_scope_type()
57+
ip6_addr = inet6_ifaddr.get_address()
58+
yield net_ns_id, iface_ifindex, iface_name, mac_addr, promisc, ip6_addr, prefix_len, scope_type, operational_state
59+
60+
def _generator(self):
61+
vmlinux = self.context.modules[self.config["kernel"]]
62+
63+
net_type_symname = vmlinux.symbol_table_name + constants.BANG + "net"
64+
net_device_symname = vmlinux.symbol_table_name + constants.BANG + "net_device"
65+
network.NetSymbols.apply(self.context.symbol_space[vmlinux.symbol_table_name])
66+
67+
# 'net_namespace_list' exists from kernels >= 2.6.24
68+
net_namespace_list = vmlinux.object_from_symbol("net_namespace_list")
69+
for net_ns in net_namespace_list.to_list(net_type_symname, "list"):
70+
for net_dev in net_ns.dev_base_head.to_list(net_device_symname, "dev_list"):
71+
for fields in self._gather_net_dev_info(net_dev):
72+
yield 0, fields
73+
74+
def run(self):
75+
headers = [
76+
("NetNS", int),
77+
("Index", int),
78+
("Interface", str),
79+
("MAC", str),
80+
("Promiscuous", bool),
81+
("IP", str),
82+
("Prefix", int),
83+
("Scope Type", str),
84+
("State", str),
85+
]
86+
87+
return renderers.TreeGrid(headers, self._generator())
88+
89+
90+
class Link(plugins.PluginInterface):
91+
"""Lists information about network interfaces similar to `ip link show`"""
92+
93+
_required_framework_version = (2, 0, 0)
94+
_version = (1, 0, 0)
95+
96+
@classmethod
97+
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
98+
return [
99+
requirements.ModuleRequirement(
100+
name="kernel",
101+
description="Linux kernel",
102+
architectures=["Intel32", "Intel64"],
103+
),
104+
requirements.VersionRequirement(
105+
name="Net", component=network.NetSymbols, version=(1, 0, 0)
106+
),
107+
]
108+
109+
def _gather_net_dev_link_info(self, net_device):
110+
mac_addr = net_device.get_mac_address()
111+
operational_state = net_device.get_operational_state()
112+
iface_name = net_device.get_device_name()
113+
mtu = net_device.mtu
114+
qdisc_name = net_device.get_qdisc_name()
115+
qlen = net_device.get_queue_length()
116+
try:
117+
net_ns_id = net_device.get_net_namespace_id()
118+
except AttributeError:
119+
net_ns_id = renderers.NotAvailableValue()
120+
121+
# Format flags to string. Drop IFF_ to match iproute2 'ip link' output.
122+
# Also, note that iproute2 removes IFF_RUNNING, see print_link_flags()
123+
flags_list = [
124+
flag.replace("IFF_", "")
125+
for flag in net_device.get_flag_names()
126+
if flag != "IFF_RUNNING"
127+
]
128+
flags_str = ",".join(flags_list)
129+
130+
yield net_ns_id, iface_name, mac_addr, operational_state, mtu, qdisc_name, qlen, flags_str
131+
132+
def _generator(self):
133+
vmlinux = self.context.modules[self.config["kernel"]]
134+
135+
network.NetSymbols.apply(self.context.symbol_space[vmlinux.symbol_table_name])
136+
137+
net_type_symname = vmlinux.symbol_table_name + constants.BANG + "net"
138+
net_device_symname = vmlinux.symbol_table_name + constants.BANG + "net_device"
139+
140+
# 'net_namespace_list' exists from kernels >= 2.6.24
141+
net_namespace_list = vmlinux.object_from_symbol("net_namespace_list")
142+
for net_ns in net_namespace_list.to_list(net_type_symname, "list"):
143+
for net_dev in net_ns.dev_base_head.to_list(net_device_symname, "dev_list"):
144+
for fields in self._gather_net_dev_link_info(net_dev):
145+
yield 0, fields
146+
147+
def run(self):
148+
headers = [
149+
("NS", int),
150+
("Interface", str),
151+
("MAC", str),
152+
("State", str),
153+
("MTU", int),
154+
("Qdisc", str),
155+
("Qlen", int),
156+
("Flags", str),
157+
]
158+
159+
return renderers.TreeGrid(headers, self._generator())

0 commit comments

Comments
 (0)