forked from volatilityfoundation/volatility3
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request volatilityfoundation#1168 from volatilityfoundatio…
…n/svcdiff_malware_detection Add svclist and svcdiff plugins. Make svcscan more modular to support…
- Loading branch information
Showing
4 changed files
with
340 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0 | ||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 | ||
# | ||
|
||
# This module attempts to locate skeleton-key like function hooks. | ||
# It does this by locating the CSystems array through a variety of methods, | ||
# and then validating the entry for RC4 HMAC (0x17 / 23) | ||
# | ||
# For a thorough walkthrough on how the R&D was performed to develop this plugin, | ||
# please see our blogpost here: | ||
# | ||
# https://volatility-labs.blogspot.com/2021/10/memory-forensics-r-illustrated.html | ||
|
||
import logging | ||
|
||
from volatility3.framework import symbols, interfaces | ||
from volatility3.framework.configuration import requirements | ||
from volatility3.plugins.windows import svclist, svcscan | ||
from volatility3.framework.symbols.windows import versions | ||
|
||
vollog = logging.getLogger(__name__) | ||
|
||
|
||
class SvcDiff(svcscan.SvcScan): | ||
"""Compares services found through list walking versus scanning to find rootkits""" | ||
|
||
_required_framework_version = (2, 4, 0) | ||
|
||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self._enumeration_method = self.service_diff | ||
|
||
@classmethod | ||
def get_requirements(cls): | ||
# Since we're calling the plugin, make sure we have the plugin's requirements | ||
return [ | ||
requirements.ModuleRequirement( | ||
name="kernel", | ||
description="Windows kernel", | ||
architectures=["Intel32", "Intel64"], | ||
), | ||
requirements.VersionRequirement( | ||
name="svclist", component=svclist.SvcList, version=(1, 0, 0) | ||
), | ||
requirements.VersionRequirement( | ||
name="svcscan", component=svcscan.SvcScan, version=(3, 0, 0) | ||
), | ||
] | ||
|
||
@classmethod | ||
def service_diff( | ||
cls, | ||
context: interfaces.context.ContextInterface, | ||
layer_name: str, | ||
symbol_table: str, | ||
service_table_name: str, | ||
service_binary_dll_map, | ||
filter_func, | ||
): | ||
""" | ||
On Windows 10 version 15063+ 64bit Windows memory samples, walk the services list | ||
and scan for services then report differences | ||
""" | ||
if not symbols.symbol_table_is_64bit( | ||
context, symbol_table | ||
) or not versions.is_win10_15063_or_later( | ||
context=context, symbol_table=symbol_table | ||
): | ||
vollog.warning( | ||
"This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples" | ||
) | ||
return | ||
|
||
from_scan = set() | ||
from_list = set() | ||
records = {} | ||
|
||
# collect unique service names from scanning | ||
for service in svcscan.SvcScan.service_scan( | ||
context, | ||
layer_name, | ||
symbol_table, | ||
service_table_name, | ||
service_binary_dll_map, | ||
filter_func, | ||
): | ||
from_scan.add(service[6]) | ||
records[service[6]] = service | ||
|
||
# collect services from listing walking | ||
for service in svclist.SvcList.service_list( | ||
context, | ||
layer_name, | ||
symbol_table, | ||
service_table_name, | ||
service_binary_dll_map, | ||
filter_func, | ||
): | ||
from_list.add(service[6]) | ||
|
||
# report services found from scanning but not list walking | ||
for hidden_service in from_scan - from_list: | ||
yield records[hidden_service] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 | ||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 | ||
# | ||
|
||
import logging | ||
|
||
from typing import List, Optional, Tuple | ||
|
||
from volatility3.framework import interfaces, exceptions, symbols | ||
from volatility3.framework.configuration import requirements | ||
from volatility3.framework.symbols.windows import versions | ||
from volatility3.plugins.windows import svcscan, pslist | ||
from volatility3.framework.layers import scanners | ||
|
||
vollog = logging.getLogger(__name__) | ||
|
||
|
||
class SvcList(svcscan.SvcScan): | ||
"""Lists services contained with the services.exe doubly linked list of services""" | ||
|
||
_version = (1, 0, 0) | ||
|
||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self._enumeration_method = self.service_list | ||
|
||
@classmethod | ||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: | ||
# Since we're calling the plugin, make sure we have the plugin's requirements | ||
return [ | ||
requirements.PluginRequirement( | ||
name="svcscan", plugin=svcscan.SvcScan, version=(3, 0, 0) | ||
), | ||
requirements.ModuleRequirement( | ||
name="kernel", | ||
description="Windows kernel", | ||
architectures=["Intel32", "Intel64"], | ||
), | ||
] | ||
|
||
@classmethod | ||
def _get_exe_range(cls, proc) -> Optional[Tuple[int, int]]: | ||
""" | ||
Returns a tuple of starting,ending address for | ||
the VAD containing services.exe | ||
""" | ||
|
||
vad_root = proc.get_vad_root() | ||
for vad in vad_root.traverse(): | ||
filename = vad.get_file_name() | ||
if isinstance(filename, str) and filename.lower().endswith( | ||
"\\services.exe" | ||
): | ||
return [(vad.get_start(), vad.get_size())] | ||
|
||
return None | ||
|
||
@classmethod | ||
def service_list( | ||
cls, | ||
context: interfaces.context.ContextInterface, | ||
layer_name: str, | ||
symbol_table: str, | ||
service_table_name: str, | ||
service_binary_dll_map, | ||
filter_func, | ||
): | ||
if not symbols.symbol_table_is_64bit( | ||
context, symbol_table | ||
) or not versions.is_win10_15063_or_later( | ||
context=context, symbol_table=symbol_table | ||
): | ||
vollog.warning( | ||
"This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples" | ||
) | ||
return | ||
|
||
for proc in pslist.PsList.list_processes( | ||
context=context, | ||
layer_name=layer_name, | ||
symbol_table=symbol_table, | ||
filter_func=filter_func, | ||
): | ||
try: | ||
layer_name = proc.add_process_layer() | ||
except exceptions.InvalidAddressException: | ||
vollog.warning( | ||
"Unable to access memory of services.exe running with PID: {}".format( | ||
proc.UniqueProcessId | ||
) | ||
) | ||
continue | ||
|
||
layer = context.layers[layer_name] | ||
|
||
exe_range = cls._get_exe_range(proc) | ||
if not exe_range: | ||
vollog.warning( | ||
"Could not find the application executable VAD for services.exe. Unable to proceed." | ||
) | ||
continue | ||
|
||
for offset in layer.scan( | ||
context=context, | ||
scanner=scanners.BytesScanner(needle=b"Sc27"), | ||
sections=exe_range, | ||
): | ||
for record in cls.enumerate_vista_or_later_header( | ||
context, | ||
service_table_name, | ||
service_binary_dll_map, | ||
layer_name, | ||
offset, | ||
): | ||
yield record |
Oops, something went wrong.