diff --git a/build.rs b/build.rs
index 92fb1f4b452..afa4bfd711a 100644
--- a/build.rs
+++ b/build.rs
@@ -2,10 +2,16 @@
fn build_windows() {
let file = "src/platform/windows.cc";
let file2 = "src/platform/windows_delete_test_cert.cc";
- cc::Build::new().file(file).file(file2).compile("windows");
+ let file3 = "src/platform/win_dll_blocklist.c";
+ cc::Build::new()
+ .file(file)
+ .file(file2)
+ .file(file3)
+ .compile("windows");
println!("cargo:rustc-link-lib=WtsApi32");
println!("cargo:rerun-if-changed={}", file);
println!("cargo:rerun-if-changed={}", file2);
+ println!("cargo:rerun-if-changed={}", file3);
}
#[cfg(target_os = "macos")]
diff --git a/res/vcpkg/detours/vcpkg.json b/res/vcpkg/detours/vcpkg.json
new file mode 100644
index 00000000000..c905b587440
--- /dev/null
+++ b/res/vcpkg/detours/vcpkg.json
@@ -0,0 +1,7 @@
+{
+ "name": "detours",
+ "version": "4.0.1",
+ "description": "Detours is a software package for monitoring and instrumenting API calls on Windows.",
+ "homepage": "https://github.com/microsoft/Detours",
+ "license": "MIT"
+}
diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs
index a46cfd8b6e7..60fadf2e38b 100644
--- a/src/flutter_ffi.rs
+++ b/src/flutter_ffi.rs
@@ -38,6 +38,9 @@ lazy_static::lazy_static! {
}
fn initialize(app_dir: &str, custom_client_config: &str) {
+ #[cfg(windows)]
+ crate::platform::init_dll_blocklist();
+
flutter::async_tasks::start_flutter_async_runner();
// `APP_DIR` is set in `main_get_data_dir_ios()` on iOS.
#[cfg(not(target_os = "ios"))]
diff --git a/src/main.rs b/src/main.rs
index 9bc90a8fab2..0e4ca96a48b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,6 +7,9 @@ use librustdesk::*;
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
fn main() {
+ #[cfg(windows)]
+ crate::platform::init_dll_blocklist();
+
if !common::global_init() {
eprintln!("Global initialization failed.");
return;
@@ -14,6 +17,9 @@ fn main() {
common::test_rendezvous_server();
common::test_nat_type();
common::global_clean();
+
+ #[cfg(windows)]
+ crate::platform::cleanup_dll_blocklist();
}
#[cfg(not(any(
@@ -23,6 +29,9 @@ fn main() {
feature = "flutter"
)))]
fn main() {
+ #[cfg(windows)]
+ crate::platform::init_dll_blocklist();
+
#[cfg(all(windows, not(feature = "inline")))]
unsafe {
winapi::um::shellscalingapi::SetProcessDpiAwareness(2);
@@ -31,10 +40,16 @@ fn main() {
ui::start(args);
}
common::global_clean();
+
+ #[cfg(windows)]
+ crate::platform::cleanup_dll_blocklist();
}
#[cfg(feature = "cli")]
fn main() {
+ #[cfg(windows)]
+ crate::platform::init_dll_blocklist();
+
if !common::global_init() {
return;
}
@@ -101,4 +116,7 @@ fn main() {
crate::start_server(true, false);
}
common::global_clean();
+
+ #[cfg(windows)]
+ crate::platform::cleanup_dll_blocklist();
}
diff --git a/src/platform/win_dll_blocklist.c b/src/platform/win_dll_blocklist.c
new file mode 100644
index 00000000000..7ac98804ca5
--- /dev/null
+++ b/src/platform/win_dll_blocklist.c
@@ -0,0 +1,373 @@
+/******************************************************************************
+ Copyright (C) 2023 by Richard Stanway
+ Copyright (C) 2026 by RustDesk
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+******************************************************************************/
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+// Check if Detours is available
+#ifdef HAVE_DETOURS
+#include "detours.h"
+#define USE_DETOURS 1
+#else
+#define USE_DETOURS 0
+#endif
+
+// Undocumented NT structs / function definitions
+typedef enum _SECTION_INHERIT { ViewShare = 1, ViewUnmap = 2 } SECTION_INHERIT;
+
+typedef enum _SECTION_INFORMATION_CLASS {
+ SectionBasicInformation = 0,
+ SectionImageInformation
+} SECTION_INFORMATION_CLASS;
+
+typedef struct _SECTION_BASIC_INFORMATION {
+ PVOID BaseAddress;
+ ULONG Attributes;
+ LARGE_INTEGER Size;
+} SECTION_BASIC_INFORMATION;
+
+typedef NTSTATUS(STDMETHODCALLTYPE *fn_NtMapViewOfSection)(
+ HANDLE, HANDLE, PVOID, ULONG_PTR, SIZE_T, PLARGE_INTEGER, PSIZE_T,
+ SECTION_INHERIT, ULONG, ULONG);
+
+typedef NTSTATUS(STDMETHODCALLTYPE *fn_NtUnmapViewOfSection)(HANDLE, PVOID);
+
+typedef NTSTATUS(STDMETHODCALLTYPE *fn_NtQuerySection)(
+ HANDLE, SECTION_INFORMATION_CLASS, PVOID, SIZE_T, PSIZE_T);
+
+static fn_NtMapViewOfSection ntMap;
+static fn_NtUnmapViewOfSection ntUnmap;
+static fn_NtQuerySection ntQuery;
+
+#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
+#ifndef SEC_IMAGE
+#define SEC_IMAGE 0x1000000
+#endif
+
+// Method of matching timestamp of DLL in PE header
+typedef enum {
+ TS_IGNORE = 0, // Ignore timestamp; block all DLLs with this name
+ TS_EQUAL, // Block only DLL with this exact timestamp
+ TS_LESS_THAN, // Block all DLLs with an earlier timestamp
+ TS_GREATER_THAN, // Block all DLLs with a later timestamp
+ TS_ALLOW_ONLY_THIS, // Invert behavior: only allow this specific timestamp
+} ts_compare_t;
+
+typedef struct {
+ // DLL name, lower case
+ const wchar_t *name;
+
+ // Length of name, calculated at startup - leave as zero
+ size_t name_len;
+
+ // PE timestamp
+ const uint32_t timestamp;
+
+ // How to treat the timestamp field
+ const ts_compare_t method;
+
+ // Number of times we've blocked this DLL, for logging purposes
+ uint64_t blocked_count;
+} blocked_module_t;
+
+/*
+ * Note: The name matches at the end of the string based on its length, this allows
+ * for matching DLLs that may have generic names but a problematic version only
+ * exists in a certain directory. A name should always include a path component
+ * so that e.g. fraps.dll doesn't match notfraps.dll.
+ */
+
+static blocked_module_t blocked_modules[] = {
+ // Astrill VPN Proxy, hooks stuff and crashes RustDesk
+ // Reference: https://github.com/rustdesk/rustdesk/discussions/7010#discussioncomment-15739560
+ {L"\\asproxy64.dll", 0, 0, TS_IGNORE},
+
+ // Dell / Alienware Backup & Recovery, crashes during "Browse" dialogs
+ {L"\\dbroverlayiconbackuped.dll", 0, 0, TS_IGNORE},
+
+ // RTSS, no good reason for this to be in RustDesk
+ {L"\\rtsshooks.dll", 0, 0, TS_IGNORE},
+
+ // Action! Recorder Software
+ {L"\\action_x64.dll", 0, 0, TS_IGNORE},
+
+ // ASUS GamerOSD, breaks DX11 things
+ {L"\\atkdx11disp.dll", 0, 0, TS_IGNORE},
+
+ // Nahimic Audio
+ {L"\\nahimicmsidevprops.dll", 0, 0, TS_IGNORE},
+ {L"\\nahimicmsiosd.dll", 0, 0, TS_IGNORE},
+
+ // FRAPS hook
+ {L"\\fraps64.dll", 0, 0, TS_IGNORE},
+
+ // ASUS GPU TWEAK II OSD
+ {L"\\gtii-osd64.dll", 0, 0, TS_IGNORE},
+ {L"\\gtii-osd64-vk.dll", 0, 0, TS_IGNORE},
+
+ // EVGA Precision, D3D crashes
+ {L"\\pxshw10_x64.dll", 0, 0, TS_IGNORE},
+
+ // Wacom / Other tablet driver, locks up UI
+ {L"\\wintab32.dll", 0, 0, TS_IGNORE},
+
+ // Weird Polish banking "security" software, breaks UI
+ {L"\\wslbscr64.dll", 0, 0, TS_IGNORE},
+
+ // Various things hooking with EasyHook that probably shouldn't touch RustDesk
+ {L"\\easyhook64.dll", 0, 0, TS_IGNORE},
+
+ // Ultramon
+ {L"\\rtsultramonhook.dll", 0, 0, TS_IGNORE},
+
+ // HiAlgo Boost, locks up UI
+ {L"\\hookdll.dll", 0, 0, TS_IGNORE},
+
+ // Adobe Core Sync? Crashes NDI.
+ {L"\\coresync_x64.dll", 0, 0, TS_IGNORE},
+
+ // Korean banking "security" software, crashes randomly
+ {L"\\t_prevent64.dll", 0, 0, TS_IGNORE},
+
+ // Bandicam, doesn't unhook cleanly and freezes preview
+ {L"\\bdcam64.dll", 0, 0, TS_IGNORE},
+};
+
+static bool is_module_blocked(wchar_t *dll, uint32_t timestamp)
+{
+ blocked_module_t *first_allowed = NULL;
+ size_t len;
+
+ len = wcslen(dll);
+
+ _wcslwr(dll);
+
+ // Default behavior is to not block
+ bool should_block = false;
+
+ for (int i = 0; i < sizeof(blocked_modules) / sizeof(blocked_modules[0]); i++) {
+ blocked_module_t *b = &blocked_modules[i];
+ wchar_t *dll_ptr;
+
+ if (len >= b->name_len)
+ dll_ptr = dll + len - b->name_len;
+ else
+ dll_ptr = dll;
+
+ if (!wcscmp(dll_ptr, b->name)) {
+ if (b->method == TS_IGNORE) {
+ b->blocked_count++;
+ return true;
+ } else if (b->method == TS_EQUAL &&
+ timestamp == b->timestamp) {
+ b->blocked_count++;
+ return true;
+ } else if (b->method == TS_LESS_THAN &&
+ timestamp < b->timestamp) {
+ b->blocked_count++;
+ return true;
+ } else if (b->method == TS_GREATER_THAN &&
+ timestamp > b->timestamp) {
+ b->blocked_count++;
+ return true;
+ } else if (b->method == TS_ALLOW_ONLY_THIS) {
+ // Invert default behavior to block if
+ // we don't find any matching timestamps
+ // for this DLL.
+ should_block = true;
+
+ if (timestamp == b->timestamp)
+ return false;
+
+ // Bit of a hack to support counting of
+ // TS_ALLOW_ONLY_THIS blocks as there may
+ // be multiple entries for the same DLL.
+ if (!first_allowed)
+ first_allowed = b;
+ }
+ }
+ }
+
+ if (first_allowed)
+ first_allowed->blocked_count++;
+
+ return should_block;
+}
+
+#if USE_DETOURS
+static NTSTATUS
+NtMapViewOfSection_hook(HANDLE SectionHandle, HANDLE ProcessHandle,
+ PVOID *BaseAddress, ULONG_PTR ZeroBits,
+ SIZE_T CommitSize, PLARGE_INTEGER SectionOffset,
+ PSIZE_T ViewSize, SECTION_INHERIT InheritDisposition,
+ ULONG AllocationType, ULONG Win32Protect)
+{
+ SECTION_BASIC_INFORMATION section_information;
+ wchar_t fileName[MAX_PATH];
+ SIZE_T wrote = 0;
+ NTSTATUS ret;
+ uint32_t timestamp = 0;
+
+ ret = ntMap(SectionHandle, ProcessHandle, BaseAddress, ZeroBits,
+ CommitSize, SectionOffset, ViewSize, InheritDisposition,
+ AllocationType, Win32Protect);
+
+ // Verify map and process
+ if (ret < 0 || ProcessHandle != GetCurrentProcess())
+ return ret;
+
+ // Fetch section information
+ if (ntQuery(SectionHandle, SectionBasicInformation,
+ §ion_information, sizeof(section_information),
+ &wrote) < 0)
+ return ret;
+
+ // Verify fetch was successful
+ if (wrote != sizeof(section_information))
+ return ret;
+
+ // We're not interested in non-image maps
+ if (!(section_information.Attributes & SEC_IMAGE))
+ return ret;
+
+ // Examine the PE header. Perhaps the map is small
+ // so wrap it in an exception handler in case we
+ // read past the end of the buffer.
+ __try {
+ BYTE *p = (BYTE *)*BaseAddress;
+ IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)p;
+ if (dos->e_magic != IMAGE_DOS_SIGNATURE)
+ return ret;
+
+ IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)(p + dos->e_lfanew);
+ if (nt->Signature != IMAGE_NT_SIGNATURE)
+ return ret;
+
+ timestamp = nt->FileHeader.TimeDateStamp;
+
+ } __except (EXCEPTION_EXECUTE_HANDLER) {
+ return ret;
+ }
+
+ // Get the actual filename if possible
+ if (K32GetMappedFileNameW(ProcessHandle, *BaseAddress, fileName,
+ sizeof(fileName) / sizeof(fileName[0])) == 0)
+ return ret;
+
+ if (is_module_blocked(fileName, timestamp)) {
+ ntUnmap(ProcessHandle, BaseAddress);
+ ret = STATUS_UNSUCCESSFUL;
+ }
+
+ return ret;
+}
+#endif
+
+void install_dll_blocklist_hook(void)
+{
+#if USE_DETOURS
+ HMODULE nt = GetModuleHandle(L"NTDLL");
+ if (!nt)
+ return;
+
+ ntMap = (fn_NtMapViewOfSection)GetProcAddress(nt, "NtMapViewOfSection");
+ if (!ntMap)
+ return;
+
+ ntUnmap = (fn_NtUnmapViewOfSection)GetProcAddress(
+ nt, "NtUnmapViewOfSection");
+ if (!ntUnmap)
+ return;
+
+ ntQuery = (fn_NtQuerySection)GetProcAddress(nt, "NtQuerySection");
+ if (!ntQuery)
+ return;
+
+ // Pre-compute length of all DLL names for exact matching
+ for (int i = 0; i < sizeof(blocked_modules) / sizeof(blocked_modules[0]); i++) {
+ blocked_module_t *b = &blocked_modules[i];
+ b->name_len = wcslen(b->name);
+ }
+
+ DetourTransactionBegin();
+
+ if (DetourAttach((PVOID *)&ntMap, NtMapViewOfSection_hook) != NO_ERROR)
+ DetourTransactionAbort();
+ else
+ DetourTransactionCommit();
+#else
+ // Detours not available - use alternative DLL loading protection
+ // SetDefaultDllDirectories restricts DLL search paths to system directories
+ // and the application directory, preventing DLL hijacking attacks
+ typedef BOOL (WINAPI *SetDefaultDllDirectoriesFunc)(DWORD);
+
+ HMODULE kernel32 = GetModuleHandle(L"kernel32.dll");
+ if (kernel32) {
+ SetDefaultDllDirectoriesFunc setDefaultDllDirectories =
+ (SetDefaultDllDirectoriesFunc)GetProcAddress(kernel32, "SetDefaultDllDirectories");
+
+ if (setDefaultDllDirectories) {
+ // LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_APPLICATION_DIR
+ setDefaultDllDirectories(0x00000800 | 0x00000400 | 0x00000200);
+ }
+ }
+
+ // Log that we're using fallback protection
+ wchar_t temp_path[MAX_PATH];
+ wchar_t log_path[MAX_PATH];
+ if (GetTempPathW(MAX_PATH, temp_path) > 0) {
+ swprintf(log_path, MAX_PATH, L"%srustdesk_dll_blocklist.log", temp_path);
+ FILE *log = _wfopen(log_path, L"w");
+ if (log) {
+ fprintf(log, "DLL blocklist initialized without Detours - using SetDefaultDllDirectories\n");
+ fclose(log);
+ }
+ }
+#endif
+}
+
+void log_blocked_dlls(void)
+{
+#if USE_DETOURS
+ wchar_t temp_path[MAX_PATH];
+ wchar_t log_path[MAX_PATH];
+
+ if (GetTempPathW(MAX_PATH, temp_path) == 0)
+ return;
+
+ swprintf(log_path, MAX_PATH, L"%srustdesk_dll_blocklist.log", temp_path);
+ FILE *log = _wfopen(log_path, L"a");
+ if (!log)
+ return;
+
+ for (int i = 0; i < sizeof(blocked_modules) / sizeof(blocked_modules[0]); i++) {
+ blocked_module_t *b = &blocked_modules[i];
+ if (b->blocked_count) {
+ fprintf(log, "Blocked loading of '%S' %" PRIu64 " time%s.\n",
+ b->name, b->blocked_count,
+ b->blocked_count == 1 ? "" : "s");
+ }
+ }
+
+ fclose(log);
+#endif
+}
diff --git a/src/platform/windows.rs b/src/platform/windows.rs
index c40e87441e1..751cf54e34d 100644
--- a/src/platform/windows.rs
+++ b/src/platform/windows.rs
@@ -3416,6 +3416,12 @@ extern "C" {
fn PrintXPSRawData(printer_name: *const u16, raw_data: *const u8, data_size: c_ulong) -> DWORD;
}
+// DLL blocklist functions
+extern "C" {
+ fn install_dll_blocklist_hook();
+ fn log_blocked_dlls();
+}
+
pub fn send_raw_data_to_printer(printer_name: Option, data: Vec) -> ResultType<()> {
let mut printer_name = printer_name.unwrap_or_default();
if printer_name.is_empty() {
@@ -3465,6 +3471,20 @@ pub fn send_raw_data_to_printer(printer_name: Option, data: Vec) ->
Ok(())
}
+/// Initialize DLL blocklist to prevent problematic DLL injections
+pub fn init_dll_blocklist() {
+ unsafe {
+ install_dll_blocklist_hook();
+ }
+}
+
+/// Log any DLLs that were blocked during execution
+pub fn cleanup_dll_blocklist() {
+ unsafe {
+ log_blocked_dlls();
+ }
+}
+
fn get_pids>(name: S) -> ResultType> {
let name = name.as_ref().to_lowercase();
let mut pids = Vec::new();
diff --git a/src/service.rs b/src/service.rs
index ce1855bdb8b..f2b96e5c6c7 100644
--- a/src/service.rs
+++ b/src/service.rs
@@ -1,7 +1,10 @@
use librustdesk::*;
#[cfg(not(target_os = "macos"))]
-fn main() {}
+fn main() {
+ #[cfg(windows)]
+ crate::platform::init_dll_blocklist();
+}
#[cfg(target_os = "macos")]
fn main() {