Source code for volatility3.plugins.windows.malware.indirect_system_calls

# 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
#

import struct
import logging
from typing import List, Optional

from volatility3.framework import interfaces, exceptions
from volatility3.framework.configuration import requirements
from volatility3.plugins import yarascan
from volatility3.plugins.windows.malware import direct_system_calls

vollog = logging.getLogger(__name__)


[docs] class IndirectSystemCalls(direct_system_calls.DirectSystemCalls): """Detects the Indirect System Call technique used to bypass EDRs.""" _required_framework_version = (2, 4, 0) _version = (1, 0, 0) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.syscall_finder = direct_system_calls.syscall_finder_type( # gets the target address of a indirect jmp self._indirect_syscall_block_target, # we are looking for indirect system calls, so we don't want 'syscall' instructions in our code block False, # jmp [address]; ret "/\\xff\\x25[^\\xc3]{,24}\\xc3/", # any of these mean we aren't in a malicious indirect call ["call", "leave", "int3", "ret"], # stop at jmp, this should reference the system call instruction ["jmp"], )
[docs] @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: # create a list of requirements for vadyarascan vadyarascan_requirements = [ requirements.ModuleRequirement( name="kernel", description="Windows kernel", architectures=["Intel32", "Intel64"], ), requirements.VersionRequirement( name="yarascanner", component=yarascan.YaraScanner, version=(2, 1, 0) ), requirements.VersionRequirement( name="yarascan", component=yarascan.YaraScan, version=(2, 0, 0) ), requirements.VersionRequirement( name="direct_system_calls", component=direct_system_calls.DirectSystemCalls, version=(2, 0, 0), ), ] # get base yarascan requirements for command line options yarascan_requirements = yarascan.YaraScan.get_yarascan_option_requirements() # return the combined requirements return yarascan_requirements + vadyarascan_requirements
@staticmethod def _indirect_syscall_block_target( proc_layer: interfaces.layers.DataLayerInterface, inst ) -> Optional[int]: """ This function determines the address of a jmp in the following form: jmp [address] To determine this, we must: 1) Pull the 4 byte relative offset of 'address' inside the instruction 2) Compute the full address of this relative offset 3) Read from the address as it is being dereferenced 4) Ensure the target address points to a 'syscall' instruction Args: proc_layer: the layer of the potential syscall block inst: the terminating instruction of the syscall block check Returns: The target address of the jump if it can be computed """ try: jmp_address_str = proc_layer.read(inst.address, 6) except exceptions.InvalidAddressException: return None # Should be an jmp... if jmp_address_str[0:2] != b"\xff\x25": return None # get the address of the 'jmp [address]' instruction relative_offset = struct.unpack("<I", jmp_address_str[2:])[0] if not relative_offset or relative_offset == -1: return None # compute the target address of the jmp (dereference) jmp_address = inst.address + relative_offset + 6 try: jmp_target_str = proc_layer.read(jmp_address, 8) except exceptions.InvalidAddressException: return None # compute from the target address then read from it jmp_target_address = struct.unpack("<Q", jmp_target_str)[0] try: jmp_target = proc_layer.read(jmp_target_address, 2) except exceptions.InvalidAddressException: return None # check that the address points to a 'syscall' instruction if jmp_target == b"\x0f\x05": return jmp_target_address return None