Skip to content

Commit 29e210b

Browse files
authored
Merge pull request #1837 from SolitudePy/svcdiff_malware
Malware categorization: windows.svcdiff
2 parents 385192a + 45934ae commit 29e210b

File tree

2 files changed

+115
-89
lines changed

2 files changed

+115
-89
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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+
# This module compares services found through list walking versus scanning,
5+
# with the aim of finding hidden services.
6+
#
7+
# For background of hidden services and a real-world example of the use of this plugin,
8+
# please see our blogpost:
9+
#
10+
# https://volatilityfoundation.org/memory-forensics-rd-illustrated-detecting-hidden-windows-services/
11+
12+
import logging
13+
14+
from volatility3.framework import symbols, interfaces
15+
from volatility3.framework.configuration import requirements
16+
from volatility3.plugins.windows import svclist, svcscan
17+
from volatility3.framework.symbols.windows import versions
18+
19+
vollog = logging.getLogger(__name__)
20+
21+
22+
class SvcDiff(svcscan.SvcScan):
23+
"""Compares services found through list walking versus scanning to find rootkits"""
24+
25+
_required_framework_version = (2, 4, 0)
26+
27+
_version = (2, 0, 0)
28+
29+
def __init__(self, *args, **kwargs):
30+
super().__init__(*args, **kwargs)
31+
self._enumeration_method = self.service_diff
32+
33+
@classmethod
34+
def get_requirements(cls):
35+
# Since we're calling the plugin, make sure we have the plugin's requirements
36+
return [
37+
requirements.ModuleRequirement(
38+
name="kernel",
39+
description="Windows kernel",
40+
architectures=["Intel32", "Intel64"],
41+
),
42+
requirements.VersionRequirement(
43+
name="svclist", component=svclist.SvcList, version=(2, 0, 0)
44+
),
45+
requirements.VersionRequirement(
46+
name="svcscan", component=svcscan.SvcScan, version=(4, 0, 0)
47+
),
48+
]
49+
50+
@classmethod
51+
def service_diff(
52+
cls,
53+
context: interfaces.context.ContextInterface,
54+
kernel_module_name: str,
55+
service_table_name: str,
56+
service_binary_dll_map,
57+
filter_func,
58+
):
59+
"""
60+
On Windows 10 version 15063+ 64bit Windows memory samples, walk the services list
61+
and scan for services then report differences
62+
"""
63+
kernel = context.modules[kernel_module_name]
64+
65+
if not symbols.symbol_table_is_64bit(
66+
context=context, symbol_table_name=kernel.symbol_table_name
67+
) or not versions.is_win10_15063_or_later(
68+
context=context, symbol_table=kernel.symbol_table_name
69+
):
70+
vollog.warning(
71+
"This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples"
72+
)
73+
return
74+
75+
from_scan = set()
76+
from_list = set()
77+
records = {}
78+
79+
# collect unique service names from scanning
80+
for service in svcscan.SvcScan.service_scan(
81+
context,
82+
kernel_module_name,
83+
service_table_name,
84+
service_binary_dll_map,
85+
filter_func,
86+
):
87+
from_scan.add(service[6])
88+
records[service[6]] = service
89+
90+
# collect services from listing walking
91+
for service in svclist.SvcList.service_list(
92+
context,
93+
kernel_module_name,
94+
service_table_name,
95+
service_binary_dll_map,
96+
filter_func,
97+
):
98+
from_list.add(service[6])
99+
100+
# report services found from scanning but not list walking
101+
for hidden_service in from_scan - from_list:
102+
yield records[hidden_service]
Lines changed: 13 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,26 @@
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-
# This module compares services found through list walking versus scanning,
5-
# with the aim of finding hidden services.
6-
#
7-
# For background of hidden services and a real-world example of the use of this plugin,
8-
# please see our blogpost:
9-
#
10-
# https://volatilityfoundation.org/memory-forensics-rd-illustrated-detecting-hidden-windows-services/
11-
124
import logging
13-
14-
from volatility3.framework import symbols, interfaces
15-
from volatility3.framework.configuration import requirements
16-
from volatility3.plugins.windows import svclist, svcscan
17-
from volatility3.framework.symbols.windows import versions
5+
from volatility3.framework import deprecation
6+
from volatility3.plugins.windows.malware import svcdiff
7+
from volatility3.plugins.windows import svcscan
188

199
vollog = logging.getLogger(__name__)
2010

2111

22-
class SvcDiff(svcscan.SvcScan):
23-
"""Compares services found through list walking versus scanning to find rootkits"""
24-
25-
_required_framework_version = (2, 4, 0)
26-
27-
_version = (2, 0, 0)
12+
class SvcDiff(
13+
svcscan.SvcScan,
14+
deprecation.PluginRenameClass,
15+
replacement_class=svcdiff.SvcDiff,
16+
removal_date="2026-06-07",
17+
):
18+
"""Compares services found through list walking versus scanning to find rootkits (deprecated)."""
2819

2920
def __init__(self, *args, **kwargs):
3021
super().__init__(*args, **kwargs)
3122
self._enumeration_method = self.service_diff
3223

33-
@classmethod
34-
def get_requirements(cls):
35-
# Since we're calling the plugin, make sure we have the plugin's requirements
36-
return [
37-
requirements.ModuleRequirement(
38-
name="kernel",
39-
description="Windows kernel",
40-
architectures=["Intel32", "Intel64"],
41-
),
42-
requirements.VersionRequirement(
43-
name="svclist", component=svclist.SvcList, version=(2, 0, 0)
44-
),
45-
requirements.VersionRequirement(
46-
name="svcscan", component=svcscan.SvcScan, version=(4, 0, 0)
47-
),
48-
]
49-
50-
@classmethod
51-
def service_diff(
52-
cls,
53-
context: interfaces.context.ContextInterface,
54-
kernel_module_name: str,
55-
service_table_name: str,
56-
service_binary_dll_map,
57-
filter_func,
58-
):
59-
"""
60-
On Windows 10 version 15063+ 64bit Windows memory samples, walk the services list
61-
and scan for services then report differences
62-
"""
63-
kernel = context.modules[kernel_module_name]
64-
65-
if not symbols.symbol_table_is_64bit(
66-
context=context, symbol_table_name=kernel.symbol_table_name
67-
) or not versions.is_win10_15063_or_later(
68-
context=context, symbol_table=kernel.symbol_table_name
69-
):
70-
vollog.warning(
71-
"This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples"
72-
)
73-
return
74-
75-
from_scan = set()
76-
from_list = set()
77-
records = {}
78-
79-
# collect unique service names from scanning
80-
for service in svcscan.SvcScan.service_scan(
81-
context,
82-
kernel_module_name,
83-
service_table_name,
84-
service_binary_dll_map,
85-
filter_func,
86-
):
87-
from_scan.add(service[6])
88-
records[service[6]] = service
89-
90-
# collect services from listing walking
91-
for service in svclist.SvcList.service_list(
92-
context,
93-
kernel_module_name,
94-
service_table_name,
95-
service_binary_dll_map,
96-
filter_func,
97-
):
98-
from_list.add(service[6])
24+
_required_framework_version = (2, 4, 0)
9925

100-
# report services found from scanning but not list walking
101-
for hidden_service in from_scan - from_list:
102-
yield records[hidden_service]
26+
_version = (2, 0, 0)

0 commit comments

Comments
 (0)