Skip to content

Commit e8e8e76

Browse files
authored
Merge pull request #1740 from volatilityfoundation/lkm_extraction
Add module extraction API. Add plugin to directly extract modules. Ho…
2 parents f33e9da + 61575ca commit e8e8e76

File tree

9 files changed

+1004
-28
lines changed

9 files changed

+1004
-28
lines changed

volatility3/framework/constants/linux/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,3 +484,28 @@ class TaintFlag:
484484
- taint_flag kernel struct
485485
- taint_flags kernel constant
486486
"""
487+
488+
## ELF related constants
489+
490+
# Elf Symbol Bindings
491+
STB_LOCAL = 0
492+
STB_GLOBAL = 1
493+
494+
# Elf Symbol Types
495+
STT_NOTYPE = 0
496+
STT_OBJECT = 1
497+
STT_FUNC = 2
498+
STT_SECTION = 3
499+
500+
# Elf Section Types
501+
SHT_NULL = 0
502+
SHT_PROGBITS = 1
503+
SHT_SYMTAB = 2
504+
SHT_STRTAB = 3
505+
SHT_RELA = 4
506+
SHT_NOTE = 7
507+
508+
# Elf Section Attributes
509+
SHF_WRITE = 1
510+
SHF_ALLOC = 2
511+
SHF_EXECINSTR = 4

volatility3/framework/plugins/linux/check_modules.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
class Check_modules(plugins.PluginInterface):
1919
"""Compares module list to sysfs info, if available"""
2020

21-
_version = (3, 0, 0)
21+
_version = (3, 0, 1)
2222
_required_framework_version = (2, 0, 0)
2323

2424
@classmethod
@@ -46,17 +46,12 @@ def compare_kset_and_lsmod(
4646
@classmethod
4747
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
4848
return [
49-
requirements.ModuleRequirement(
50-
name="kernel",
51-
description="Linux kernel",
52-
architectures=["Intel32", "Intel64"],
53-
),
5449
requirements.VersionRequirement(
5550
name="linux_utilities_modules_module_display_plugin",
5651
component=linux_utilities_modules.ModuleDisplayPlugin,
5752
version=(1, 0, 0),
5853
),
59-
]
54+
] + linux_utilities_modules.ModuleDisplayPlugin.get_requirements()
6055

6156
@classmethod
6257
@deprecation.deprecated_method(

volatility3/framework/plugins/linux/hidden_modules.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
modules as linux_utilities_modules,
88
)
99
from volatility3.framework import interfaces, exceptions, deprecation
10-
from volatility3.framework.constants import architectures
1110
from volatility3.framework.configuration import requirements
1211
from volatility3.framework.symbols.linux import extensions
1312
from volatility3.framework.interfaces import plugins
@@ -19,7 +18,7 @@ class Hidden_modules(plugins.PluginInterface):
1918
"""Carves memory to find hidden kernel modules"""
2019

2120
_required_framework_version = (2, 10, 0)
22-
_version = (3, 0, 0)
21+
_version = (3, 0, 1)
2322

2423
@classmethod
2524
def get_hidden_modules(
@@ -62,17 +61,12 @@ def get_hidden_modules(
6261
@classmethod
6362
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
6463
return [
65-
requirements.ModuleRequirement(
66-
name="kernel",
67-
description="Linux kernel",
68-
architectures=architectures.LINUX_ARCHS,
69-
),
7064
requirements.VersionRequirement(
7165
name="linux_utilities_modules_module_display_plugin",
7266
component=linux_utilities_modules.ModuleDisplayPlugin,
7367
version=(1, 0, 0),
7468
),
75-
]
69+
] + linux_utilities_modules.ModuleDisplayPlugin.get_requirements()
7670

7771
@staticmethod
7872
@deprecation.deprecated_method(

volatility3/framework/plugins/linux/lsmod.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class Lsmod(plugins.PluginInterface):
1818
"""Lists loaded kernel modules."""
1919

2020
_required_framework_version = (2, 0, 0)
21-
_version = (3, 0, 0)
21+
_version = (3, 0, 1)
2222

2323
run = linux_utilities_modules.ModuleDisplayPlugin.run
2424
_generator = linux_utilities_modules.ModuleDisplayPlugin.generator
@@ -27,22 +27,12 @@ class Lsmod(plugins.PluginInterface):
2727
@classmethod
2828
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
2929
return [
30-
requirements.ModuleRequirement(
31-
name="kernel",
32-
description="Linux kernel",
33-
architectures=["Intel32", "Intel64"],
34-
),
35-
requirements.VersionRequirement(
36-
name="linux_utilities_modules",
37-
component=linux_utilities_modules.Modules,
38-
version=(3, 0, 0),
39-
),
4030
requirements.VersionRequirement(
4131
name="linux_utilities_modules_module_display_plugin",
4232
component=linux_utilities_modules.ModuleDisplayPlugin,
4333
version=(1, 0, 0),
4434
),
45-
]
35+
] + linux_utilities_modules.ModuleDisplayPlugin.get_requirements()
4636

4737
@classmethod
4838
@deprecation.deprecated_method(
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# This file is Copyright 2025 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+
import logging
5+
from typing import List
6+
7+
from volatility3 import framework
8+
import volatility3.framework.symbols.linux.utilities.module_extract as linux_utilities_module_extract
9+
from volatility3.framework import interfaces, renderers
10+
from volatility3.framework.configuration import requirements
11+
from volatility3.framework.renderers import format_hints
12+
from volatility3.framework.objects import utility
13+
14+
vollog = logging.getLogger(__name__)
15+
16+
17+
class ModuleExtract(interfaces.plugins.PluginInterface):
18+
"""Recreates an ELF file from a specific address in the kernel"""
19+
20+
_version = (1, 0, 0)
21+
_required_framework_version = (2, 25, 0)
22+
23+
framework.require_interface_version(*_required_framework_version)
24+
25+
@classmethod
26+
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
27+
# Since we're calling the plugin, make sure we have the plugin's requirements
28+
return [
29+
requirements.ModuleRequirement(
30+
name="kernel",
31+
description="Windows kernel",
32+
architectures=["Intel32", "Intel64"],
33+
),
34+
requirements.IntRequirement(
35+
name="base",
36+
description="Base virtual address to reconstruct an ELF file",
37+
optional=False,
38+
),
39+
]
40+
41+
def _generator(self):
42+
kernel = self.context.modules[self.config["kernel"]]
43+
44+
base_address = self.config["base"]
45+
46+
kernel_layer = self.context.layers[kernel.layer_name]
47+
48+
if not kernel_layer.is_valid(base_address):
49+
vollog.error(
50+
f"Given base address ({base_address:#x}) is not valid in the kernel address space. Unable to extract file."
51+
)
52+
return
53+
54+
module = kernel.object(object_type="module", offset=base_address, absolute=True)
55+
56+
elf_data = linux_utilities_module_extract.ModuleExtract.extract_module(
57+
self.context, self.config["kernel"], module
58+
)
59+
if not elf_data:
60+
vollog.error(
61+
f"Unable to reconstruct the ELF for module struct at {base_address:#x}"
62+
)
63+
return
64+
65+
module_name = utility.array_to_string(module.name)
66+
file_name = self.open.sanitize_filename(
67+
f"kernel_module.{module_name}.{base_address:#x}.elf"
68+
)
69+
70+
with self.open(file_name) as file_handle:
71+
file_handle.write(elf_data)
72+
73+
yield 0, (
74+
format_hints.Hex(base_address),
75+
len(elf_data),
76+
file_handle.preferred_filename,
77+
)
78+
79+
def run(self):
80+
return renderers.TreeGrid(
81+
[
82+
("Base", format_hints.Hex),
83+
("File Size", int),
84+
("File output", str),
85+
],
86+
self._generator(),
87+
)

volatility3/framework/symbols/linux/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ def __init__(self, *args, **kwargs) -> None:
5252
self.set_type_class("idr", extensions.IDR)
5353
self.set_type_class("address_space", extensions.address_space)
5454
self.set_type_class("page", extensions.page)
55+
self.set_type_class("module_sect_attr", extensions.module_sect_attr)
56+
5557
# Might not exist in the current symbols
5658
self.optional_set_type_class("module", extensions.module)
5759
self.optional_set_type_class("bpf_prog", extensions.bpf_prog)
@@ -79,6 +81,7 @@ def __init__(self, *args, **kwargs) -> None:
7981
self.set_type_class("sock", extensions.network.sock)
8082
self.set_type_class("inet_sock", extensions.network.inet_sock)
8183
self.set_type_class("unix_sock", extensions.network.unix_sock)
84+
8285
# Might not exist in older kernels or the current symbols
8386
self.optional_set_type_class("netlink_sock", extensions.network.netlink_sock)
8487
self.optional_set_type_class("vsock_sock", extensions.network.vsock_sock)

volatility3/framework/symbols/linux/extensions/__init__.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3121,3 +3121,47 @@ def get_namespace(self) -> Optional[str]:
31213121
return self._do_get_namespace()
31223122
except exceptions.InvalidAddressException:
31233123
return None
3124+
3125+
3126+
class module_sect_attr(objects.StructType):
3127+
def get_name(self) -> Optional[str]:
3128+
"""
3129+
Performs careful extraction of the section name
3130+
The `name` member has changed type and meaning over time
3131+
It also was present even in cases with `mattr` present, which
3132+
holds the name the kernel uses
3133+
"""
3134+
if hasattr(self, "battr"):
3135+
try:
3136+
return utility.pointer_to_string(self.battr.attr.name, count=32)
3137+
except exceptions.InvalidAddressException:
3138+
# if battr is present then its name attribute needs to be valid
3139+
vollog.debug(f"Invalid battr name for section at {self.vol.offset:#x}")
3140+
return None
3141+
3142+
elif self.name.vol.type_name == "array":
3143+
try:
3144+
return utility.array_to_string(self.name, count=32)
3145+
except exceptions.InvalidAddressException:
3146+
# specifically do not return here to give `mattr` a chance
3147+
vollog.debug(f"Invalid direct name for section at {self.vol.offset:#x}")
3148+
3149+
elif self.name.vol.type_name == "pointer":
3150+
try:
3151+
return utility.pointer_to_string(self.name, count=32)
3152+
except exceptions.InvalidAddressException:
3153+
# specifically do not return here to give `mattr` a chance
3154+
vollog.debug(
3155+
f"Invalid pointer name for section at {self.vol.offset:#x}"
3156+
)
3157+
3158+
# if everything else failed...
3159+
if hasattr(self, "mattr"):
3160+
try:
3161+
return utility.pointer_to_string(self.mattr.attr.name, count=32)
3162+
except exceptions.InvalidAddressException:
3163+
vollog.debug(
3164+
f"Unresolvable name for for section at {self.vol.offset:#x}"
3165+
)
3166+
3167+
return None

0 commit comments

Comments
 (0)