Skip to content

Commit 2d7e004

Browse files
authored
Sideloading detection & monitor sideloader countermeasure
2 parents 3bc131d + e5c000d commit 2d7e004

File tree

4 files changed

+129
-2
lines changed

4 files changed

+129
-2
lines changed

analyzer/windows/dll/version.dll

10.5 KB
Binary file not shown.

analyzer/windows/dll/version_x64.dll

12 KB
Binary file not shown.

analyzer/windows/lib/api/process.py

+127-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import urllib.error
1414
import urllib.parse
1515
import urllib.request
16-
from ctypes import byref, c_buffer, c_int, c_ulong, create_string_buffer, sizeof
16+
from ctypes import byref, c_buffer, c_int, c_ulong, create_string_buffer, sizeof, windll, ArgumentError
1717
from pathlib import Path
1818
from shutil import copy
1919

@@ -50,6 +50,8 @@
5050
TERMINATE_EVENT,
5151
TTD32_NAME,
5252
TTD64_NAME,
53+
SIDELOADER32_NAME,
54+
SIDELOADER64_NAME,
5355
)
5456
from lib.common.defines import (
5557
KERNEL32,
@@ -65,6 +67,13 @@
6567
from lib.core.compound import create_custom_folders
6668
from lib.core.config import Config
6769

70+
# CSIDL constants
71+
CSIDL_WINDOWS = 0x0024
72+
CSIDL_SYSTEM = 0x0025
73+
CSIDL_SYSTEMX86 = 0x0029
74+
CSIDL_PROGRAM_FILES = 0x0026
75+
CSIDL_PROGRAM_FILESX86 = 0x002a
76+
6877
IOCTL_PID = 0x222008
6978
IOCTL_CUCKOO_PATH = 0x22200C
7079
PATH_KERNEL_DRIVER = "\\\\.\\DriverSSDT"
@@ -94,6 +103,20 @@ def get_referrer_url(interest):
94103
return f"http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd={itemidx}&ved={vedstr}&url={escapedurl}&ei={eistr}&usg={usgstr}"
95104

96105

106+
def nt_path_to_dos_path_ansi(nt_path: str) -> str:
107+
drive_letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
108+
nt_path_bytes = nt_path.encode("utf-8", errors="ignore")
109+
for letter in drive_letters:
110+
drive = f"{letter}:"
111+
target = create_string_buffer(1024)
112+
res = KERNEL32.QueryDosDeviceA(drive.encode("ascii"), target, 1024)
113+
if res != 0:
114+
device_path = target.value
115+
if nt_path_bytes.startswith(device_path):
116+
converted = nt_path_bytes.replace(device_path, drive.encode("ascii"), 1)
117+
return converted.decode("utf-8", errors="ignore")
118+
return nt_path
119+
97120
def NT_SUCCESS(val):
98121
return val >= 0
99122

@@ -228,6 +251,12 @@ def get_filepath(self):
228251

229252
return ""
230253

254+
def get_folder_path(self, csidl):
255+
"""Use SHGetFolderPathW to get the system folder path for a given CSIDL."""
256+
buf = create_string_buffer(MAX_PATH)
257+
windll.shell32.SHGetFolderPathA(None, csidl, None, 0, buf)
258+
return buf.value.decode('utf-8', errors='ignore')
259+
231260
def get_image_name(self):
232261
"""Get the image name; returns an empty string on error."""
233262
if not self.h_process:
@@ -278,6 +307,58 @@ def get_parent_pid(self):
278307

279308
return None
280309

310+
def detect_dll_sideloading(self, directory_path: str) -> bool:
311+
"""Detect potential DLL sideloading in the provided directory."""
312+
try:
313+
directory = Path(directory_path)
314+
if not directory.is_dir():
315+
return False
316+
317+
if (directory/"capemon.dll").exists():
318+
return False
319+
320+
# Early exit if directory is a known system location
321+
try:
322+
system_dirs = {
323+
Path(self.get_folder_path(CSIDL_WINDOWS)).resolve(),
324+
Path(self.get_folder_path(CSIDL_SYSTEM)).resolve(),
325+
Path(self.get_folder_path(CSIDL_SYSTEMX86)).resolve(),
326+
Path(self.get_folder_path(CSIDL_PROGRAM_FILES)).resolve(),
327+
Path(self.get_folder_path(CSIDL_PROGRAM_FILESX86)).resolve()
328+
}
329+
if directory.resolve() in system_dirs:
330+
return False
331+
except (OSError, ArgumentError, ValueError) as e:
332+
log.warning("detect_dll_sideloading: failed to retrieve system paths: %s", e)
333+
return False
334+
335+
try:
336+
local_dlls = {f.name.lower() for f in directory.glob("*.dll") if f.is_file()}
337+
if not local_dlls:
338+
return False
339+
except (OSError, PermissionError) as e:
340+
log.warning("detect_dll_sideloading: could not list DLLs in %s: %s", directory_path, e)
341+
return False
342+
343+
# Build set of known system DLLs
344+
known_dlls = set()
345+
for sys_dir in system_dirs:
346+
try:
347+
if sys_dir.exists():
348+
known_dlls.update(f.name.lower() for f in sys_dir.glob("*.dll") if f.is_file())
349+
except (OSError, PermissionError) as e:
350+
log.debug("detect_dll_sideloading: skipping system dir %s: %s", sys_dir, e)
351+
352+
suspicious = local_dlls & known_dlls
353+
if suspicious:
354+
for dll in suspicious:
355+
log.info("Potential dll side-loading detected in local directory: %s", dll)
356+
return bool(suspicious)
357+
358+
except Exception as e:
359+
log.error("detect_dll_sideloading: unexpected error with path %s: %s", directory_path, e)
360+
return False
361+
281362
def kernel_analyze(self):
282363
"""zer0m0n kernel analysis"""
283364
log.info("Starting kernel analysis")
@@ -535,7 +616,7 @@ def ttd_stop(self):
535616
if result.stderr:
536617
log.error(" ".join(result.stderr.split()))
537618

538-
log.info("Stopped TTD for %s process with pid %d: %s", bit_str, self.pid)
619+
log.info("Stopped TTD for %s process with pid %d", bit_str, self.pid)
539620

540621
return True
541622

@@ -705,6 +786,12 @@ def inject(self, interest=None, nosleepskip=False):
705786

706787
self.write_monitor_config(interest, nosleepskip)
707788

789+
path = os.path.dirname(nt_path_to_dos_path_ansi(self.get_filepath()))
790+
791+
if self.detect_dll_sideloading(path) and self.has_msimg32(path):
792+
self.deploy_version_proxy(path)
793+
return True
794+
708795
log.info("%s DLL to inject is %s, loader %s", bit_str, dll, bin_name)
709796

710797
try:
@@ -769,3 +856,41 @@ def __str__(self):
769856
"""Get a string representation of this process."""
770857
image_name = self.get_image_name() or "???"
771858
return f"<{self.__class__.__name__} {self.pid} {image_name}>"
859+
860+
def has_msimg32(self, directory_path: str) -> bool:
861+
"""Check if msimg32.dll exists in directory"""
862+
try:
863+
return any(
864+
f.name.lower() == "msimg32.dll"
865+
for f in Path(directory_path).glob("*")
866+
if f.is_file()
867+
)
868+
except (OSError, PermissionError):
869+
return False
870+
871+
def deploy_version_proxy(self, directory_path: str):
872+
"""Deploy version.dll proxy loader"""
873+
if self.is_64bit():
874+
dll = CAPEMON64_NAME
875+
side_dll = SIDELOADER64_NAME
876+
bit_str = "64-bit"
877+
else:
878+
dll = CAPEMON32_NAME
879+
side_dll = SIDELOADER32_NAME
880+
bit_str = "32-bit"
881+
882+
dll = os.path.join(Path.cwd(), dll)
883+
884+
if not os.path.exists(dll):
885+
log.warning("invalid path %s for monitor DLL to be sideloaded in %s, sideloading aborted", dll, self)
886+
return
887+
888+
try:
889+
copy(dll, os.path.join(directory_path, "capemon.dll"))
890+
copy(side_dll, os.path.join(directory_path, "version.dll"))
891+
copy(os.path.join(Path.cwd(), "dll", f"{self.pid}.ini"), os.path.join(directory_path, "config.ini"))
892+
except OSError as e:
893+
log.error("Failed to copy DLL: %s", e)
894+
return
895+
log.info("%s DLL to sideload is %s, sideloader %s", bit_str, os.path.join(directory_path, "capemon.dll"), os.path.join(directory_path, "version.dll"))
896+
return

analyzer/windows/lib/common/constants.py

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
LOADER64_NAME = f"bin\\{random_string(8)}.exe"
2929
TTD32_NAME = "bin\\wow64\\TTD.exe"
3030
TTD64_NAME = "bin\\TTD.exe"
31+
SIDELOADER32_NAME = "dll\\version.dll"
32+
SIDELOADER64_NAME = "dll\\version_x64.dll"
3133

3234
# Options
3335
OPT_APPDATA = "appdata"

0 commit comments

Comments
 (0)