Skip to content

Commit

Permalink
Merge pull request volatilityfoundation#1168 from volatilityfoundatio…
Browse files Browse the repository at this point in the history
…n/svcdiff_malware_detection

Add svclist and svcdiff plugins. Make svcscan more modular to support…
  • Loading branch information
ikelos authored Jul 21, 2024
2 parents fba7255 + 111873e commit 7219716
Show file tree
Hide file tree
Showing 4 changed files with 340 additions and 58 deletions.
103 changes: 103 additions & 0 deletions volatility3/framework/plugins/windows/svcdiff.py
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]
115 changes: 115 additions & 0 deletions volatility3/framework/plugins/windows/svclist.py
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
Loading

0 comments on commit 7219716

Please sign in to comment.