/****************************************************************************** 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}, // MainConcept Image Scaler, crashes in its own thread. Block versions // older than the one Elgato uses (2016-02-15). {L"\\mc_trans_video_imagescaler.dll", 0, 1455495131, TS_LESS_THAN}, // 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}, // Bandicam, doesn't unhook cleanly and freezes preview // Reference: https://github.com/obsproject/obs-studio/issues/8552 {L"\\bdcam64.dll", 0, 0, TS_IGNORE}, // "Citrix ICAService" that crashes during DShow enumeration // Reference: https://obsproject.com/forum/threads/165863/ {L"\\ctxdsendpoints64.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}, // VTuber Maker capture filter < 2023-03-13 without above fix implemented {L"\\live3dvirtualcam\\lib64_new2.dll", 0, 1678695956, TS_LESS_THAN}, // Obsolete unfixed versions of VTuber Maker capture filter {L"\\live3dvirtualcam\\lib64_new.dll", 0, 0, TS_IGNORE}, {L"\\live3dvirtualcam\\lib64.dll", 0, 0, TS_IGNORE}, // VirtualMotionCapture capture filter < 2022-12-18 without above fix // Reference: https://github.com/obsproject/obs-studio/issues/8552 {L"\\vmc_camerafilter64bit.dll", 0, 1671349891, TS_LESS_THAN}, // HolisticMotionCapture capture filter, not yet patched. Blocking // all previous versions in case an update is released. // Reference: https://github.com/obsproject/obs-studio/issues/8552 {L"\\holisticmotioncapturefilter64bit.dll", 0, 1680044549, TS_LESS_THAN}, // Elgato Stream Deck plugin < 2024-02-01 // Blocking all previous versions because they have undefined behavior // that results in crashes. // Reference: https://github.com/obsproject/obs-studio/issues/10245 {L"\\streamdeckplugin.dll", 0, 1706745600, 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"); } } }