mirror of
https://github.com/obsproject/obs-studio.git
synced 2024-09-20 13:08:50 +02:00
9a24b1ad06
- Add Bandicam - Add VirtualMotionCapture (unity capture) - Add HolisticMotionCapture (unity capture) Fixes https://github.com/obsproject/obs-studio/issues/8552
356 lines
10 KiB
C
356 lines
10 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},
|
|
|
|
// 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},
|
|
|
|
// Bandicam, doesn't unhook cleanly and freezes preview
|
|
// Reference: https://github.com/obsproject/obs-studio/issues/8552
|
|
{L"\\bdcam64.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");
|
|
}
|
|
}
|
|
}
|