mirror of
https://github.com/obsproject/obs-studio.git
synced 2024-07-15 07:44:10 +00:00
UI: Add DLL blocking functionality for Windows
Adds a NtMapViewOfSection hook which we can use before DLLs are loaded to decide whether to abort the process. This is useful for blocking known problematic DLLs that cause crashes. In addition, the PE timestamp can be checked to block only older DLLs such as older versions of Vtuber virtual cameras with known deadlock bugs. A simple counter of each DLL is used for logging purposes. Since DLLs can be blocked at any time, the output is only logged on exit of OBS.
This commit is contained in:
parent
970c284a65
commit
9d4a7f01dd
|
@ -345,10 +345,13 @@ if(OS_WINDOWS)
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/obs.rc.in
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/obs.rc.in
|
||||||
${CMAKE_BINARY_DIR}/obs.rc)
|
${CMAKE_BINARY_DIR}/obs.rc)
|
||||||
|
|
||||||
|
find_package(Detours REQUIRED)
|
||||||
|
|
||||||
target_sources(
|
target_sources(
|
||||||
obs
|
obs
|
||||||
PRIVATE obs.manifest
|
PRIVATE obs.manifest
|
||||||
platform-windows.cpp
|
platform-windows.cpp
|
||||||
|
win-dll-blocklist.c
|
||||||
update/update-window.cpp
|
update/update-window.cpp
|
||||||
update/update-window.hpp
|
update/update-window.hpp
|
||||||
update/win-update.cpp
|
update/win-update.cpp
|
||||||
|
@ -367,13 +370,14 @@ if(OS_WINDOWS)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(MbedTLS)
|
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_features(obs PRIVATE cxx_std_17)
|
||||||
|
|
||||||
target_compile_definitions(
|
target_compile_definitions(
|
||||||
obs PRIVATE UNICODE _UNICODE _CRT_SECURE_NO_WARNINGS
|
obs PRIVATE UNICODE _UNICODE _CRT_SECURE_NO_WARNINGS
|
||||||
_CRT_NONSTDC_NO_WARNINGS)
|
_CRT_NONSTDC_NO_WARNINGS PSAPI_VERSION=2)
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
target_link_options(obs PRIVATE "LINKER:/IGNORE:4098" "LINKER:/IGNORE:4099")
|
target_link_options(obs PRIVATE "LINKER:/IGNORE:4098" "LINKER:/IGNORE:4099")
|
||||||
|
|
|
@ -3210,6 +3210,9 @@ int main(int argc, char *argv[])
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
// Try to keep this as early as possible
|
||||||
|
install_dll_blocklist_hook();
|
||||||
|
|
||||||
obs_init_win32_crash_handler();
|
obs_init_win32_crash_handler();
|
||||||
SetErrorMode(SEM_FAILCRITICALERRORS);
|
SetErrorMode(SEM_FAILCRITICALERRORS);
|
||||||
load_debug_privilege();
|
load_debug_privilege();
|
||||||
|
@ -3380,6 +3383,8 @@ int main(int argc, char *argv[])
|
||||||
func();
|
func();
|
||||||
FreeLibrary(hRtwq);
|
FreeLibrary(hRtwq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log_blocked_dlls();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
blog(LOG_INFO, "Number of memory leaks: %ld", bnum_allocs());
|
blog(LOG_INFO, "Number of memory leaks: %ld", bnum_allocs());
|
||||||
|
|
|
@ -268,3 +268,8 @@ extern bool opt_disable_high_dpi_scaling;
|
||||||
#endif
|
#endif
|
||||||
extern std::string opt_starting_scene;
|
extern std::string opt_starting_scene;
|
||||||
extern bool restart;
|
extern bool restart;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
extern "C" void install_dll_blocklist_hook(void);
|
||||||
|
extern "C" void log_blocked_dlls(void);
|
||||||
|
#endif
|
||||||
|
|
334
UI/win-dll-blocklist.c
Normal file
334
UI/win-dll-blocklist.c
Normal file
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue