Skip to content

Sideloading detection & monitor sideloader countermeasure #2551

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 14, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added analyzer/windows/dll/version.dll
Binary file not shown.
Binary file added analyzer/windows/dll/version_x64.dll
Binary file not shown.
69 changes: 67 additions & 2 deletions analyzer/windows/lib/api/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import urllib.error
import urllib.parse
import urllib.request
from ctypes import byref, c_buffer, c_int, c_ulong, create_string_buffer, sizeof
from ctypes import byref, c_buffer, c_int, c_ulong, create_string_buffer, sizeof, create_unicode_buffer, windll
from pathlib import Path
from shutil import copy

Expand Down Expand Up @@ -50,6 +50,8 @@
TERMINATE_EVENT,
TTD32_NAME,
TTD64_NAME,
SIDELOADER32_NAME,
SIDELOADER64_NAME,
)
from lib.common.defines import (
KERNEL32,
Expand All @@ -65,6 +67,13 @@
from lib.core.compound import create_custom_folders
from lib.core.config import Config

# CSIDL constants
CSIDL_WINDOWS = 0x0024
CSIDL_SYSTEM = 0x0025
CSIDL_SYSTEMX86 = 0x0029
CSIDL_PROGRAM_FILES = 0x0026
CSIDL_PROGRAM_FILESX86 = 0x002a

IOCTL_PID = 0x222008
IOCTL_CUCKOO_PATH = 0x22200C
PATH_KERNEL_DRIVER = "\\\\.\\DriverSSDT"
Expand Down Expand Up @@ -124,6 +133,7 @@ def __init__(self, options=None, config=None, pid=0, h_process=0, thread_id=0, h
self.suspended = suspended
self.system_info = SYSTEM_INFO()
self.critical = False
self.path = None

def __del__(self):
"""Close open handles."""
Expand Down Expand Up @@ -228,6 +238,12 @@ def get_filepath(self):

return ""

def get_folder_path(self, csidl):
"""Use SHGetFolderPathW to get the system folder path for a given CSIDL."""
buf = create_unicode_buffer(MAX_PATH)
windll.shell32.SHGetFolderPathW(None, csidl, None, 0, buf)
return buf.value

def get_image_name(self):
"""Get the image name; returns an empty string on error."""
if not self.h_process:
Expand Down Expand Up @@ -278,6 +294,42 @@ def get_parent_pid(self):

return None

def detect_dll_sideloading(self, directory_path: str) -> bool:
"""Detect potential DLL sideloading in the provided directory."""
try:
directory = Path(directory_path)
if not directory.is_dir():
return False

local_dlls = {f.name.lower() for f in directory.glob("*.dll") if f.is_file()}
if not local_dlls:
return False

system_dirs = [
self.get_folder_path(CSIDL_WINDOWS),
self.get_folder_path(CSIDL_SYSTEM),
self.get_folder_path(CSIDL_SYSTEMX86),
self.get_folder_path(CSIDL_PROGRAM_FILES),
self.get_folder_path(CSIDL_PROGRAM_FILESX86),
]

# Build set of known system DLLs (names only, lowercased)
known_dlls = set()
for sys_dir in system_dirs:
sys_path = Path(sys_dir)
if sys_path.exists():
known_dlls.update(f.name.lower() for f in sys_path.glob("*.dll") if f.is_file())

suspicious = local_dlls & known_dlls
if suspicious:
for dll in suspicious:
log.info("detect_dll_sideloading: suspicious DLL found: %s", dll)
return bool(suspicious)

except Exception as e:
log.error("detect_dll_sideloading: exception %s with path %s", e, directory_path)
return False

def kernel_analyze(self):
"""zer0m0n kernel analysis"""
log.info("Starting kernel analysis")
Expand Down Expand Up @@ -441,6 +493,8 @@ def execute(self, path, args=None, suspended=False, kernel_analysis=False):
if args:
arguments += args

self.path = path

creation_flags = CREATE_NEW_CONSOLE
if suspended:
self.suspended = True
Expand Down Expand Up @@ -535,7 +589,7 @@ def ttd_stop(self):
if result.stderr:
log.error(" ".join(result.stderr.split()))

log.info("Stopped TTD for %s process with pid %d: %s", bit_str, self.pid)
log.info("Stopped TTD for %s process with pid %d", bit_str, self.pid)

return True

Expand Down Expand Up @@ -678,11 +732,13 @@ def inject(self, interest=None, nosleepskip=False):
bin_name = LOADER64_NAME
dll = CAPEMON64_NAME
bit_str = "64-bit"
side_dll = SIDELOADER64_NAME
else:
ttd_name = TTD32_NAME
bin_name = LOADER32_NAME
dll = CAPEMON32_NAME
bit_str = "32-bit"
side_dll = SIDELOADER32_NAME

bin_name = os.path.join(Path.cwd(), bin_name)
dll = os.path.join(Path.cwd(), dll)
Expand All @@ -705,6 +761,15 @@ def inject(self, interest=None, nosleepskip=False):

self.write_monitor_config(interest, nosleepskip)

path = os.path.dirname(self.path)

if self.detect_dll_sideloading(path):
copy(dll, os.path.join(path, "capemon.dll"))
copy(side_dll, os.path.join(path, "version.dll"))
copy(os.path.join(Path.cwd(), "dll", f"{self.pid}.ini"), os.path.join(path, "config.ini"))
log.info("%s DLL to sideload is %s, sideloader %s", bit_str, os.path.join(path, "capemon.dll"), os.path.join(path, "version.dll"))
return True

log.info("%s DLL to inject is %s, loader %s", bit_str, dll, bin_name)

try:
Expand Down
2 changes: 2 additions & 0 deletions analyzer/windows/lib/common/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
LOADER64_NAME = f"bin\\{random_string(8)}.exe"
TTD32_NAME = "bin\\wow64\\TTD.exe"
TTD64_NAME = "bin\\TTD.exe"
SIDELOADER32_NAME = "dll\\version.dll"
SIDELOADER64_NAME = "dll\\version_x64.dll"

# Options
OPT_APPDATA = "appdata"
Expand Down