mirror of
https://github.com/obsproject/obs-studio.git
synced 2024-09-20 04:42:18 +02:00
132f0b85fc
This is actually a MainConcept redistributable and not related to Adobe. Unfortunately Elgato Game Capture HD software relies on this dependency when presenting the DirectShow device to OBS, so we unintentionally blocked it from loading. Instead of outright blocking, we now block only older versions than the version shipped by Elgato, which has hopefully been patched to fix the random crashes.
361 lines
11 KiB
C
361 lines
11 KiB
C
/******************************************************************************
|
|
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},
|
|
|
|
// 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},
|
|
};
|
|
|
|
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");
|
|
}
|
|
}
|
|
}
|