diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt
index 211db3a16..b1558c0e8 100644
--- a/UI/CMakeLists.txt
+++ b/UI/CMakeLists.txt
@@ -345,10 +345,13 @@ if(OS_WINDOWS)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/obs.rc.in
${CMAKE_BINARY_DIR}/obs.rc)
+ find_package(Detours REQUIRED)
+
target_sources(
obs
PRIVATE obs.manifest
platform-windows.cpp
+ win-dll-blocklist.c
update/update-window.cpp
update/update-window.hpp
update/win-update.cpp
@@ -367,13 +370,14 @@ if(OS_WINDOWS)
endif()
find_package(MbedTLS)
- target_link_libraries(obs PRIVATE Mbedtls::Mbedtls OBS::blake2)
+ target_link_libraries(obs PRIVATE Mbedtls::Mbedtls OBS::blake2
+ Detours::Detours)
target_compile_features(obs PRIVATE cxx_std_17)
target_compile_definitions(
obs PRIVATE UNICODE _UNICODE _CRT_SECURE_NO_WARNINGS
- _CRT_NONSTDC_NO_WARNINGS)
+ _CRT_NONSTDC_NO_WARNINGS PSAPI_VERSION=2)
if(MSVC)
target_link_options(obs PRIVATE "LINKER:/IGNORE:4098" "LINKER:/IGNORE:4099")
diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp
index 900abc950..cf293caf7 100644
--- a/UI/obs-app.cpp
+++ b/UI/obs-app.cpp
@@ -3210,6 +3210,9 @@ int main(int argc, char *argv[])
#endif
#ifdef _WIN32
+ // Try to keep this as early as possible
+ install_dll_blocklist_hook();
+
obs_init_win32_crash_handler();
SetErrorMode(SEM_FAILCRITICALERRORS);
load_debug_privilege();
@@ -3380,6 +3383,8 @@ int main(int argc, char *argv[])
func();
FreeLibrary(hRtwq);
}
+
+ log_blocked_dlls();
#endif
blog(LOG_INFO, "Number of memory leaks: %ld", bnum_allocs());
diff --git a/UI/obs-app.hpp b/UI/obs-app.hpp
index 904dd6c7d..b530796e4 100644
--- a/UI/obs-app.hpp
+++ b/UI/obs-app.hpp
@@ -268,3 +268,8 @@ extern bool opt_disable_high_dpi_scaling;
#endif
extern std::string opt_starting_scene;
extern bool restart;
+
+#ifdef _WIN32
+extern "C" void install_dll_blocklist_hook(void);
+extern "C" void log_blocked_dlls(void);
+#endif
diff --git a/UI/win-dll-blocklist.c b/UI/win-dll-blocklist.c
new file mode 100644
index 000000000..9baaef7ab
--- /dev/null
+++ b/UI/win-dll-blocklist.c
@@ -0,0 +1,334 @@
+/******************************************************************************
+ Copyright (C) 2023 by Richard Stanway
+
+ 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 "detours.h"
+#include "obs.h"
+
+// 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)
+
+// 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[] = {
+ // Dell / Alienware Backup & Recovery, crashes during "Browse" dialogs
+ {L"\\dbroverlayiconbackuped.dll", 0, 0, TS_IGNORE},
+
+ // RTSS, no good reason for this to be in OBS
+ {L"\\rtsshooks.dll", 0, 0, TS_IGNORE},
+
+ // Dolby Axon overlay
+ {L"\\axonoverlay.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},
+
+ // Malware
+ {L"\\sendori.dll", 0, 0, TS_IGNORE},
+
+ // Astril VPN Proxy, hooks stuff and crashes
+ {L"\\asproxy64.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},
+
+ // Adobe Dynamic Link (Adobe CC), crashes in its own thread
+ {L"\\mc_trans_video_imagescaler.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 OBS
+ {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},
+
+ // Fasso DRM, crashes D3D
+ {L"\\f_sps.dll", 0, 0, TS_IGNORE},
+
+ // Korean banking "security" software, crashes randomly
+ {L"\\t_prevent64.dll", 0, 0, TS_IGNORE},
+
+ // Generic named unity capture filter. Unfortunately only a forked version
+ // has a critical fix to prevent deadlocks during enumeration. We block
+ // all versions since if someone didn't change the DLL name they likely
+ // didn't implement the deadlock fix.
+ // Reference: https://github.com/schellingb/UnityCapture/commit/2eabf0f
+ {L"\\unitycapturefilter64bit.dll", 0, 0, TS_IGNORE},
+
+ // VSeeFace capture filter < v1.13.38b3 without above fix implemented
+ {L"\\vseefacecamera64bit.dll", 0, 1666993098, TS_LESS_THAN},
+};
+
+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 < _countof(blocked_modules); 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;
+}
+
+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,
+ _countof(fileName)) == 0)
+ return ret;
+
+ if (is_module_blocked(fileName, timestamp)) {
+ ntUnmap(ProcessHandle, BaseAddress);
+ ret = STATUS_UNSUCCESSFUL;
+ }
+
+ return ret;
+}
+
+void install_dll_blocklist_hook(void)
+{
+ 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 < _countof(blocked_modules); 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();
+}
+
+void log_blocked_dlls(void)
+{
+ for (int i = 0; i < _countof(blocked_modules); i++) {
+ blocked_module_t *b = &blocked_modules[i];
+ if (b->blocked_count) {
+ blog(LOG_WARNING,
+ "Blocked loading of '%S' %" PRIu64 " time%S.",
+ b->name, b->blocked_count,
+ b->blocked_count == 1 ? L"" : L"s");
+ }
+ }
+}