Skip to content

Commit 215ba1d

Browse files
committed
Plugins: categorize ldrmodules as a malware plugin
1 parent f5feee8 commit 215ba1d

File tree

3 files changed

+151
-129
lines changed

3 files changed

+151
-129
lines changed

test/plugins/windows/windows.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def test_windows_specific_pslist(self, volatility, python):
5858
}
5959
assert test_volatility.match_output_row(expected_row, json.loads(out))
6060

61+
6162
class TestWindowsTimeliner:
6263
def test_windows_specific_timeliner(self, volatility, python):
6364
image = WindowsSamples.WINDOWSXP_GENERIC.value.path
@@ -67,6 +68,7 @@ def test_windows_specific_timeliner(self, volatility, python):
6768
assert rc == 0
6869
assert out.count(b"\n") > 10
6970

71+
7072
class TestWindowsPsscan:
7173
def test_windows_specific_psscan(self, volatility, python):
7274
image = WindowsSamples.WINDOWSXP_GENERIC.value.path
@@ -780,19 +782,19 @@ def test_windows_specific_symlinkscan(self, volatility, python):
780782
assert test_volatility.count_entries_flat(json_out) > 5
781783
expected_rows = [
782784
{
783-
"CreateTime": "2005-06-25T16:47:28+00:00",
784-
"From Name": "AUX",
785-
"Offset": 453082584,
786-
"To Name": "\\DosDevices\\COM1",
787-
"__children": []
785+
"CreateTime": "2005-06-25T16:47:28+00:00",
786+
"From Name": "AUX",
787+
"Offset": 453082584,
788+
"To Name": "\\DosDevices\\COM1",
789+
"__children": [],
788790
},
789791
{
790-
"CreateTime": "2005-06-25T16:47:28+00:00",
791-
"From Name": "UNC",
792-
"Offset": 453176664,
793-
"To Name": "\\Device\\Mup",
794-
"__children": []
795-
}
792+
"CreateTime": "2005-06-25T16:47:28+00:00",
793+
"From Name": "UNC",
794+
"Offset": 453176664,
795+
"To Name": "\\Device\\Mup",
796+
"__children": [],
797+
},
796798
]
797799

798800
for expected_row in expected_rows:
@@ -803,7 +805,7 @@ class TestWindowsLdrModules:
803805
def test_windows_specific_ldrmodules(self, volatility, python):
804806
image = WindowsSamples.WINDOWSXP_GENERIC.value.path
805807
rc, out, _err = test_volatility.runvol_plugin(
806-
"windows.ldrmodules.LdrModules",
808+
"windows.malware.ldrmodules.LdrModules",
807809
image,
808810
volatility,
809811
python,
Lines changed: 9 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,20 @@
1-
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
1+
# This file is Copyright 2025 Volatility Foundation and licensed under the Volatility Software License 1.0
22
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
33
#
4-
54
import logging
6-
7-
from volatility3.framework import constants, exceptions, interfaces, renderers
8-
from volatility3.framework.configuration import requirements
9-
from volatility3.framework.renderers import format_hints
10-
from volatility3.framework.symbols import intermed
11-
from volatility3.framework.symbols.windows.extensions import pe
12-
from volatility3.plugins.windows import pslist, vadinfo
5+
from volatility3.framework import interfaces, deprecation
6+
from volatility3.plugins.windows.malware import ldrmodules
137

148
vollog = logging.getLogger(__name__)
159

1610

17-
class LdrModules(interfaces.plugins.PluginInterface):
11+
class LdrModules(
12+
interfaces.plugins.PluginInterface,
13+
deprecation.PluginRenameClass,
14+
replacement_class=ldrmodules.LdrModules,
15+
removal_date="2026-06-07",
16+
):
1817
"""Lists the loaded modules in a particular windows memory image."""
1918

2019
_required_framework_version = (2, 0, 0)
2120
_version = (1, 0, 1)
22-
23-
@classmethod
24-
def get_requirements(cls):
25-
return [
26-
requirements.ModuleRequirement(
27-
name="kernel",
28-
description="Windows kernel",
29-
architectures=["Intel32", "Intel64"],
30-
),
31-
requirements.VersionRequirement(
32-
name="pslist", component=pslist.PsList, version=(3, 0, 0)
33-
),
34-
requirements.VersionRequirement(
35-
name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0)
36-
),
37-
requirements.ListRequirement(
38-
name="pid",
39-
element_type=int,
40-
description="Process IDs to include (all other processes are excluded)",
41-
optional=True,
42-
),
43-
]
44-
45-
def _generator(self, procs):
46-
pe_table_name = intermed.IntermediateSymbolTable.create(
47-
self.context, self.config_path, "windows", "pe", class_types=pe.class_types
48-
)
49-
50-
for proc in procs:
51-
proc_layer_name = proc.add_process_layer()
52-
53-
# Build dictionaries from different module lists, where the DllBase address is the key and value is the module object
54-
load_order_mod = dict(
55-
(mod.DllBase, mod) for mod in proc.load_order_modules()
56-
)
57-
init_order_mod = dict(
58-
(mod.DllBase, mod) for mod in proc.init_order_modules()
59-
)
60-
mem_order_mod = dict((mod.DllBase, mod) for mod in proc.mem_order_modules())
61-
62-
# Build dictionary of mapped files, where the VAD start address is the key and value is the file name of the mapped file
63-
mapped_files = {}
64-
for vad in vadinfo.VadInfo.list_vads(proc):
65-
dos_header = self.context.object(
66-
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
67-
offset=vad.get_start(),
68-
layer_name=proc_layer_name,
69-
)
70-
try:
71-
# Filter out VADs that do not start with a MZ header
72-
if dos_header.e_magic != 0x5A4D:
73-
continue
74-
except exceptions.InvalidAddressException:
75-
vollog.log(
76-
constants.LOGLEVEL_VVVV,
77-
f"Skipping vad at {hex(dos_header.vol.offset)} due to InvalidAddressException",
78-
)
79-
continue
80-
81-
mapped_files[vad.get_start()] = vad.get_file_name()
82-
83-
for base in mapped_files.keys():
84-
# Does the base address exist in the PEB DLL lists?
85-
load_mod = load_order_mod.get(base, None)
86-
init_mod = init_order_mod.get(base, None)
87-
mem_mod = mem_order_mod.get(base, None)
88-
89-
yield (
90-
0,
91-
[
92-
int(proc.UniqueProcessId),
93-
str(
94-
proc.ImageFileName.cast(
95-
"string",
96-
max_length=proc.ImageFileName.vol.count,
97-
errors="replace",
98-
)
99-
),
100-
format_hints.Hex(base),
101-
load_mod is not None,
102-
init_mod is not None,
103-
mem_mod is not None,
104-
mapped_files[base],
105-
],
106-
)
107-
108-
def run(self):
109-
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
110-
111-
return renderers.TreeGrid(
112-
[
113-
("Pid", int),
114-
("Process", str),
115-
("Base", format_hints.Hex),
116-
("InLoad", bool),
117-
("InInit", bool),
118-
("InMem", bool),
119-
("MappedPath", str),
120-
],
121-
self._generator(
122-
pslist.PsList.list_processes(
123-
context=self.context,
124-
kernel_module_name=self.config["kernel"],
125-
filter_func=filter_func,
126-
)
127-
),
128-
)
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# This file is Copyright 2024 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+
import logging
6+
7+
from volatility3.framework import constants, exceptions, interfaces, renderers
8+
from volatility3.framework.configuration import requirements
9+
from volatility3.framework.renderers import format_hints
10+
from volatility3.framework.symbols import intermed
11+
from volatility3.framework.symbols.windows.extensions import pe
12+
from volatility3.plugins.windows import pslist, vadinfo
13+
14+
vollog = logging.getLogger(__name__)
15+
16+
17+
class LdrModules(interfaces.plugins.PluginInterface):
18+
"""Lists the loaded modules in a particular windows memory image."""
19+
20+
_required_framework_version = (2, 0, 0)
21+
_version = (1, 0, 1)
22+
23+
@classmethod
24+
def get_requirements(cls):
25+
return [
26+
requirements.ModuleRequirement(
27+
name="kernel",
28+
description="Windows kernel",
29+
architectures=["Intel32", "Intel64"],
30+
),
31+
requirements.VersionRequirement(
32+
name="pslist", component=pslist.PsList, version=(3, 0, 0)
33+
),
34+
requirements.VersionRequirement(
35+
name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0)
36+
),
37+
requirements.ListRequirement(
38+
name="pid",
39+
element_type=int,
40+
description="Process IDs to include (all other processes are excluded)",
41+
optional=True,
42+
),
43+
]
44+
45+
def _generator(self, procs):
46+
pe_table_name = intermed.IntermediateSymbolTable.create(
47+
self.context, self.config_path, "windows", "pe", class_types=pe.class_types
48+
)
49+
50+
for proc in procs:
51+
proc_layer_name = proc.add_process_layer()
52+
53+
# Build dictionaries from different module lists, where the DllBase address is the key and value is the module object
54+
load_order_mod = dict(
55+
(mod.DllBase, mod) for mod in proc.load_order_modules()
56+
)
57+
init_order_mod = dict(
58+
(mod.DllBase, mod) for mod in proc.init_order_modules()
59+
)
60+
mem_order_mod = dict((mod.DllBase, mod) for mod in proc.mem_order_modules())
61+
62+
# Build dictionary of mapped files, where the VAD start address is the key and value is the file name of the mapped file
63+
mapped_files = {}
64+
for vad in vadinfo.VadInfo.list_vads(proc):
65+
dos_header = self.context.object(
66+
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
67+
offset=vad.get_start(),
68+
layer_name=proc_layer_name,
69+
)
70+
try:
71+
# Filter out VADs that do not start with a MZ header
72+
if dos_header.e_magic != 0x5A4D:
73+
continue
74+
except exceptions.InvalidAddressException:
75+
vollog.log(
76+
constants.LOGLEVEL_VVVV,
77+
f"Skipping vad at {hex(dos_header.vol.offset)} due to InvalidAddressException",
78+
)
79+
continue
80+
81+
mapped_files[vad.get_start()] = vad.get_file_name()
82+
83+
for base in mapped_files.keys():
84+
# Does the base address exist in the PEB DLL lists?
85+
load_mod = load_order_mod.get(base, None)
86+
init_mod = init_order_mod.get(base, None)
87+
mem_mod = mem_order_mod.get(base, None)
88+
89+
yield (
90+
0,
91+
[
92+
int(proc.UniqueProcessId),
93+
str(
94+
proc.ImageFileName.cast(
95+
"string",
96+
max_length=proc.ImageFileName.vol.count,
97+
errors="replace",
98+
)
99+
),
100+
format_hints.Hex(base),
101+
load_mod is not None,
102+
init_mod is not None,
103+
mem_mod is not None,
104+
mapped_files[base],
105+
],
106+
)
107+
108+
def run(self):
109+
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
110+
111+
return renderers.TreeGrid(
112+
[
113+
("Pid", int),
114+
("Process", str),
115+
("Base", format_hints.Hex),
116+
("InLoad", bool),
117+
("InInit", bool),
118+
("InMem", bool),
119+
("MappedPath", str),
120+
],
121+
self._generator(
122+
pslist.PsList.list_processes(
123+
context=self.context,
124+
kernel_module_name=self.config["kernel"],
125+
filter_func=filter_func,
126+
)
127+
),
128+
)

0 commit comments

Comments
 (0)