Skip to content

Commit d98c7eb

Browse files
committed
linux: hidden_modules: Make the fast method the default. Remove vol2 and fall back to a 1-byte alignment scan if addresses aren't aligned to the L1 cache size
1 parent f455c30 commit d98c7eb

File tree

1 file changed

+26
-190
lines changed

1 file changed

+26
-190
lines changed

volatility3/framework/plugins/linux/hidden_modules.py

Lines changed: 26 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,6 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
3232
requirements.PluginRequirement(
3333
name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0)
3434
),
35-
requirements.BooleanRequirement(
36-
name="fast",
37-
description="Fast scan method. Recommended only for kernels 4.2 and above",
38-
optional=True,
39-
default=False,
40-
),
4135
]
4236

4337
@staticmethod
@@ -86,134 +80,13 @@ def get_modules_memory_boundaries(
8680

8781
return modules_addr_min, modules_addr_max
8882

89-
@staticmethod
90-
def _get_module_state_values_bytes(
91-
context: interfaces.context.ContextInterface,
92-
vmlinux_module_name: str,
93-
) -> List[bytes]:
94-
"""Retrieve the module state values bytes by introspecting its enum type
95-
96-
Args:
97-
context: The context to retrieve required elements (layers, symbol tables) from
98-
vmlinux_module_name: The name of the kernel module on which to operate
99-
100-
Returns:
101-
A list with the module state values bytes
102-
"""
103-
vmlinux = context.modules[vmlinux_module_name]
104-
module_state_type_template = vmlinux.get_type("module").vol.members["state"][1]
105-
data_format = module_state_type_template.base_type.vol.data_format
106-
values = module_state_type_template.choices.values()
107-
values_bytes = [
108-
objects.convert_value_to_data(value, int, data_format)
109-
for value in sorted(values)
110-
]
111-
return values_bytes
112-
113-
@classmethod
114-
def _get_hidden_modules_vol2(
115-
cls,
116-
context: interfaces.context.ContextInterface,
117-
vmlinux_module_name: str,
118-
known_module_addresses: Set[int],
119-
modules_memory_boundaries: Tuple,
120-
) -> Iterable[interfaces.objects.ObjectInterface]:
121-
"""Enumerate hidden modules using the traditional implementation.
122-
123-
This is a port of the Volatility2 plugin, with minor code improvements.
124-
125-
Args:
126-
context: The context to retrieve required elements (layers, symbol tables) from
127-
vmlinux_module_name: The name of the kernel module on which to operate
128-
known_module_addresses: Set with known module addresses
129-
modules_memory_boundaries: Minimum and maximum address boundaries for module allocation.
130-
131-
Yields:
132-
module objects
133-
"""
134-
vmlinux = context.modules[vmlinux_module_name]
135-
vmlinux_layer = context.layers[vmlinux.layer_name]
136-
137-
check_nums = (
138-
3000,
139-
2800,
140-
2700,
141-
2500,
142-
2300,
143-
2100,
144-
2000,
145-
1500,
146-
1300,
147-
1200,
148-
1024,
149-
512,
150-
256,
151-
128,
152-
96,
153-
64,
154-
48,
155-
32,
156-
24,
157-
)
158-
modules_addr_min, modules_addr_max = modules_memory_boundaries
159-
modules_addr_min = modules_addr_min & ~0xFFF
160-
modules_addr_max = (modules_addr_max & ~0xFFF) + vmlinux_layer.page_size
161-
162-
check_bufs = []
163-
replace_bufs = []
164-
minus_size = vmlinux.get_type("pointer").size
165-
null_pointer_bytes = b"\x00" * minus_size
166-
for num in check_nums:
167-
check_bufs.append(b"\x00" * num)
168-
replace_bufs.append((b"\xff" * (num - minus_size)) + null_pointer_bytes)
169-
170-
all_ffs = b"\xff" * 4096
171-
scan_list = []
172-
for page_addr in range(
173-
modules_addr_min, modules_addr_max, vmlinux_layer.page_size
174-
):
175-
content_fixed = all_ffs
176-
with contextlib.suppress(
177-
exceptions.InvalidAddressException,
178-
exceptions.PagedInvalidAddressException,
179-
):
180-
content = vmlinux_layer.read(page_addr, vmlinux_layer.page_size)
181-
182-
all_nulls = all(x == 0 for x in content)
183-
if content and not all_nulls:
184-
content_fixed = content
185-
for check_bytes, replace_bytes in zip(check_bufs, replace_bufs):
186-
content_fixed = content_fixed.replace(
187-
check_bytes, replace_bytes
188-
)
189-
190-
scan_list.append(content_fixed)
191-
192-
scan_buf = b"".join(scan_list)
193-
del scan_list
194-
195-
module_state_values_bytes = cls._get_module_state_values_bytes(
196-
context, vmlinux_module_name
197-
)
198-
values_bytes_pattern = b"|".join(module_state_values_bytes)
199-
# f'strings cannot be combined with bytes literals
200-
for cur_addr in re.finditer(b"(?=(%s))" % values_bytes_pattern, scan_buf):
201-
module_addr = modules_addr_min + cur_addr.start()
202-
203-
if module_addr in known_module_addresses:
204-
continue
205-
206-
module = vmlinux.object("module", offset=module_addr, absolute=True)
207-
if module and module.is_valid():
208-
yield module
209-
21083
@classmethod
21184
def _get_module_address_alignment(
21285
cls,
21386
context: interfaces.context.ContextInterface,
21487
vmlinux_module_name: str,
21588
) -> int:
216-
"""Obtain the module memory address alignment. This is only used with the fast scan method.
89+
"""Obtain the module memory address alignment.
21790
21891
struct module is aligned to the L1 cache line, which is typically 64 bytes for most
21992
common i386/AMD64/ARM64 configurations. In some cases, it can be 128 bytes, but this
@@ -231,8 +104,24 @@ def _get_module_address_alignment(
231104
# essential for retrieving type metadata in the future.
232105
return 64
233106

107+
@staticmethod
108+
def _validate_alignment_patterns(
109+
addresses: Iterable[int],
110+
address_alignment: int,
111+
) -> bool:
112+
"""Check if the memory addresses meet our alignments patterns
113+
114+
Args:
115+
addresses: Iterable with the address values
116+
address_alignment: Number of bytes for alignment validation
117+
118+
Returns:
119+
True if all the addresses meet the alignment
120+
"""
121+
return all(addr % address_alignment == 0 for addr in addresses)
122+
234123
@classmethod
235-
def _get_hidden_modules_fast(
124+
def get_hidden_modules(
236125
cls,
237126
context: interfaces.context.ContextInterface,
238127
vmlinux_module_name: str,
@@ -268,6 +157,14 @@ def _get_hidden_modules_fast(
268157
module_address_alignment = cls._get_module_address_alignment(
269158
context, vmlinux_module_name
270159
)
160+
if not cls._validate_alignment_patterns(
161+
known_module_addresses, module_address_alignment
162+
):
163+
vollog.warning(
164+
f"Module addresses aren't aligned to {module_address_alignment} bytes. "
165+
"Switching to 1 byte aligment scan method."
166+
)
167+
module_address_alignment = 1
271168

272169
mkobj_offset = vmlinux.get_type("module").relative_child_offset("mkobj")
273170
mod_offset = vmlinux.get_type("module_kobject").relative_child_offset("mod")
@@ -302,66 +199,6 @@ def _get_hidden_modules_fast(
302199
if module and module.is_valid():
303200
yield module
304201

305-
@staticmethod
306-
def _validate_alignment_patterns(
307-
addresses: Iterable[int],
308-
address_alignment: int,
309-
) -> bool:
310-
"""Check if the memory addresses meet our alignments patterns
311-
312-
Args:
313-
addresses: Iterable with the address values
314-
address_alignment: Number of bytes for alignment validation
315-
316-
Returns:
317-
True if all the addresses meet the alignment
318-
"""
319-
return all(addr % address_alignment == 0 for addr in addresses)
320-
321-
@classmethod
322-
def get_hidden_modules(
323-
cls,
324-
context: interfaces.context.ContextInterface,
325-
vmlinux_module_name: str,
326-
known_module_addresses: Set[int],
327-
modules_memory_boundaries: Tuple,
328-
fast_method: bool = False,
329-
) -> Iterable[interfaces.objects.ObjectInterface]:
330-
"""Enumerate hidden modules
331-
332-
Args:
333-
context: The context to retrieve required elements (layers, symbol tables) from
334-
vmlinux_module_name: The name of the kernel module on which to operate
335-
known_module_addresses: Set with known module addresses
336-
modules_memory_boundaries: Minimum and maximum address boundaries for module allocation.
337-
fast_method: If True, it uses the fast method. Otherwise, it uses the traditional one.
338-
Yields:
339-
module objects
340-
"""
341-
if fast_method:
342-
module_address_alignment = cls._get_module_address_alignment(
343-
context, vmlinux_module_name
344-
)
345-
if cls._validate_alignment_patterns(
346-
known_module_addresses, module_address_alignment
347-
):
348-
scan_method = cls._get_hidden_modules_fast
349-
else:
350-
vollog.warning(
351-
f"Module addresses aren't aligned to {module_address_alignment} bytes. "
352-
"Switching to the traditional scan method."
353-
)
354-
scan_method = cls._get_hidden_modules_vol2
355-
else:
356-
scan_method = cls._get_hidden_modules_vol2
357-
358-
yield from scan_method(
359-
context,
360-
vmlinux_module_name,
361-
known_module_addresses,
362-
modules_memory_boundaries,
363-
)
364-
365202
@classmethod
366203
def get_lsmod_module_addresses(
367204
cls,
@@ -399,7 +236,6 @@ def _generator(self):
399236
vmlinux_module_name,
400237
known_module_addresses,
401238
modules_memory_boundaries,
402-
fast_method=self.config.get("fast"),
403239
):
404240
module_addr = module.vol.offset
405241
module_name = module.get_name() or renderers.NotAvailableValue()

0 commit comments

Comments
 (0)