|
@@ -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 <http://www.gnu.org/licenses/>.
|
|
|
+******************************************************************************/
|
|
|
+
|
|
|
+#include <Windows.h>
|
|
|
+#include <psapi.h>
|
|
|
+#include <stdint.h>
|
|
|
+#include <stdbool.h>
|
|
|
+#include <inttypes.h>
|
|
|
+#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");
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|