From f1a1f176c3c4e12a5ecb3d24ca6c6ec5a25e31ec Mon Sep 17 00:00:00 2001 From: srkizer Date: Thu, 25 Jul 2024 02:52:57 +0900 Subject: [PATCH] Change ReShade related detections (#1965) * Changed ReShade w/o addon support detection to compare the name of the signer to the string "ReShade", so that any false positives stemming from use of other injector do not trigger warnings. * Changed main SwapChain detection to be done by comparing the HWND of window that the SwapChain is attached to. --- .../Interface/Internal/InterfaceManager.cs | 2 +- .../ReShadeAddonInterface.Exports.cs | 131 ++++++++++++++---- Dalamud/Interface/Internal/SwapChainHelper.cs | 34 ++--- 3 files changed, 126 insertions(+), 41 deletions(-) diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index b7267ea94..eeddb8334 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -772,7 +772,7 @@ private unsafe void ContinueConstruction( 0, this.SetCursorDetour); - if (ReShadeAddonInterface.ReShadeHasSignature) + if (ReShadeAddonInterface.ReShadeIsSignedByReShade) { Log.Warning("Signed ReShade binary detected"); Service diff --git a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs index 60bbc37cd..b3add07e7 100644 --- a/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs +++ b/Dalamud/Interface/Internal/ReShadeHandling/ReShadeAddonInterface.Exports.cs @@ -1,9 +1,12 @@ +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using System.Text; +using Serilog; + using TerraFX.Interop.Windows; using static TerraFX.Interop.Windows.Windows; @@ -30,30 +33,19 @@ static ReShadeAddonInterface() !GetProcAddressInto(m, nameof(e.ReShadeUnregisterEvent), &e.ReShadeUnregisterEvent)) continue; - fixed (void* pwszFile = m.FileName) - fixed (Guid* pguid = &WINTRUST_ACTION_GENERIC_VERIFY_V2) + try { - var wtfi = new WINTRUST_FILE_INFO - { - cbStruct = (uint)sizeof(WINTRUST_FILE_INFO), - pcwszFilePath = (ushort*)pwszFile, - hFile = default, - pgKnownSubject = null, - }; - var wtd = new WINTRUST_DATA - { - cbStruct = (uint)sizeof(WINTRUST_DATA), - pPolicyCallbackData = null, - pSIPClientData = null, - dwUIChoice = WTD.WTD_UI_NONE, - fdwRevocationChecks = WTD.WTD_REVOKE_NONE, - dwUnionChoice = WTD.WTD_STATEACTION_VERIFY, - hWVTStateData = default, - pwszURLReference = null, - dwUIContext = 0, - pFile = &wtfi, - }; - ReShadeHasSignature = WinVerifyTrust(default, pguid, &wtd) != TRUST.TRUST_E_NOSIGNATURE; + var signerName = GetSignatureSignerNameWithoutVerification(m.FileName); + ReShadeIsSignedByReShade = signerName == "ReShade"; + Log.Information( + "ReShade DLL is signed by {signerName}. {vn}={v}", + signerName, + nameof(ReShadeIsSignedByReShade), + ReShadeIsSignedByReShade); + } + catch (Exception ex) + { + Log.Information(ex, "ReShade DLL did not had a valid signature."); } ReShadeModule = m; @@ -78,7 +70,98 @@ bool GetProcAddressInto(ProcessModule m, ReadOnlySpan name, void* res) /// Gets a value indicating whether the loaded ReShade has signatures. /// ReShade without addon support is signed, but may not pass signature verification. - public static bool ReShadeHasSignature { get; private set; } + public static bool ReShadeIsSignedByReShade { get; private set; } + + /// Gets the name of the signer of a file that has a certificate embedded within, without verifying if the + /// file has a valid signature. + /// Path to the file. + /// Name of the signer. + // https://learn.microsoft.com/en-us/previous-versions/troubleshoot/windows/win32/get-information-authenticode-signed-executables + private static string GetSignatureSignerNameWithoutVerification(ReadOnlySpan path) + { + var hCertStore = default(HCERTSTORE); + var hMsg = default(HCRYPTMSG); + var pCertContext = default(CERT_CONTEXT*); + try + { + fixed (void* pwszFile = path) + { + uint dwMsgAndCertEncodingType; + uint dwContentType; + uint dwFormatType; + void* pvContext; + if (!CryptQueryObject( + CERT.CERT_QUERY_OBJECT_FILE, + pwszFile, + CERT.CERT_QUERY_CONTENT_FLAG_ALL, + CERT.CERT_QUERY_FORMAT_FLAG_ALL, + 0, + &dwMsgAndCertEncodingType, + &dwContentType, + &dwFormatType, + &hCertStore, + &hMsg, + &pvContext)) + { + throw new Win32Exception("CryptQueryObject"); + } + } + + var pcb = 0u; + if (!CryptMsgGetParam(hMsg, CMSG.CMSG_SIGNER_INFO_PARAM, 0, null, &pcb)) + throw new Win32Exception("CryptMsgGetParam(1)"); + + var signerInfo = GC.AllocateArray((int)pcb, true); + var pSignerInfo = (CMSG_SIGNER_INFO*)Unsafe.AsPointer(ref signerInfo[0]); + if (!CryptMsgGetParam(hMsg, CMSG.CMSG_SIGNER_INFO_PARAM, 0, pSignerInfo, &pcb)) + throw new Win32Exception("CryptMsgGetParam(2)"); + + var certInfo = new CERT_INFO + { + Issuer = pSignerInfo->Issuer, + SerialNumber = pSignerInfo->SerialNumber, + }; + pCertContext = CertFindCertificateInStore( + hCertStore, + X509.X509_ASN_ENCODING | PKCS.PKCS_7_ASN_ENCODING, + 0, + CERT.CERT_FIND_SUBJECT_CERT, + &certInfo, + null); + if (pCertContext == default) + throw new Win32Exception("CertFindCertificateInStore"); + + pcb = CertGetNameStringW( + pCertContext, + CERT.CERT_NAME_SIMPLE_DISPLAY_TYPE, + CERT.CERT_NAME_ISSUER_FLAG, + null, + null, + pcb); + if (pcb == 0) + throw new Win32Exception("CertGetNameStringW(1)"); + + var issuerName = GC.AllocateArray((int)pcb, true); + pcb = CertGetNameStringW( + pCertContext, + CERT.CERT_NAME_SIMPLE_DISPLAY_TYPE, + CERT.CERT_NAME_ISSUER_FLAG, + null, + (ushort*)Unsafe.AsPointer(ref issuerName[0]), + pcb); + if (pcb == 0) + throw new Win32Exception("CertGetNameStringW(2)"); + + // The string is null-terminated. + return new(issuerName.AsSpan()[..^1]); + } + finally + { + if (pCertContext != default) CertFreeCertificateContext(pCertContext); + if (hCertStore != default) CertCloseStore(hCertStore, 0); + if (hMsg != default) CryptMsgClose(hMsg); + } + } private struct ExportsStruct { diff --git a/Dalamud/Interface/Internal/SwapChainHelper.cs b/Dalamud/Interface/Internal/SwapChainHelper.cs index 4a336ee9f..6e9254680 100644 --- a/Dalamud/Interface/Internal/SwapChainHelper.cs +++ b/Dalamud/Interface/Internal/SwapChainHelper.cs @@ -69,26 +69,28 @@ public static IDXGISwapChain.Vtbl* GameDeviceSwapChainVtbl /// true if the object is the game's swap chain. public static bool IsGameDeviceSwapChain(T* punk) where T : unmanaged, IUnknown.Interface { - // https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void) - // For any given COM object (also known as a COM component), a specific query for the IUnknown interface on any - // of the object's interfaces must always return the same pointer value. - - var gdsc = GameDeviceSwapChain; - if (gdsc is null || punk is null) - return false; - - fixed (Guid* iid = &IID.IID_IUnknown) + using var psc = default(ComPtr); + fixed (Guid* piid = &IID.IID_IDXGISwapChain) { - using var u1 = default(ComPtr); - if (gdsc->QueryInterface(iid, (void**)u1.GetAddressOf()).FAILED) + if (punk->QueryInterface(piid, (void**)psc.GetAddressOf()).FAILED) return false; + } - using var u2 = default(ComPtr); - if (punk->QueryInterface(iid, (void**)u2.GetAddressOf()).FAILED) - return false; + return IsGameDeviceSwapChain(psc.Get()); + } - return u1.Get() == u2.Get(); - } + /// + public static bool IsGameDeviceSwapChain(IDXGISwapChain* punk) + { + DXGI_SWAP_CHAIN_DESC desc1; + if (punk->GetDesc(&desc1).FAILED) + return false; + + DXGI_SWAP_CHAIN_DESC desc2; + if (GameDeviceSwapChain->GetDesc(&desc2).FAILED) + return false; + + return desc1.OutputWindow == desc2.OutputWindow; } /// Wait for the game to have finished initializing the IDXGISwapChain.