Skip to content

Commit

Permalink
Change ReShade related detections (#1965)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
Soreepeong committed Jul 24, 2024
1 parent 4383a57 commit f1a1f17
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 41 deletions.
2 changes: 1 addition & 1 deletion Dalamud/Interface/Internal/InterfaceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,7 @@ private unsafe void ContinueConstruction(
0,
this.SetCursorDetour);

if (ReShadeAddonInterface.ReShadeHasSignature)
if (ReShadeAddonInterface.ReShadeIsSignedByReShade)
{
Log.Warning("Signed ReShade binary detected");
Service<NotificationManager>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -78,7 +70,98 @@ bool GetProcAddressInto(ProcessModule m, ReadOnlySpan<char> name, void* res)

/// <summary>Gets a value indicating whether the loaded ReShade has signatures.</summary>
/// <remarks>ReShade without addon support is signed, but may not pass signature verification.</remarks>
public static bool ReShadeHasSignature { get; private set; }
public static bool ReShadeIsSignedByReShade { get; private set; }

/// <summary>Gets the name of the signer of a file that has a certificate embedded within, without verifying if the
/// file has a valid signature.</summary>
/// <param name="path">Path to the file.</param>
/// <returns>Name of the signer.</returns>
// https://learn.microsoft.com/en-us/previous-versions/troubleshoot/windows/win32/get-information-authenticode-signed-executables
private static string GetSignatureSignerNameWithoutVerification(ReadOnlySpan<char> 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<byte>((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<char>((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
{
Expand Down
34 changes: 18 additions & 16 deletions Dalamud/Interface/Internal/SwapChainHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,26 +69,28 @@ public static IDXGISwapChain.Vtbl<IDXGISwapChain>* GameDeviceSwapChainVtbl
/// <returns><c>true</c> if the object is the game's swap chain.</returns>
public static bool IsGameDeviceSwapChain<T>(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<IDXGISwapChain>);
fixed (Guid* piid = &IID.IID_IDXGISwapChain)
{
using var u1 = default(ComPtr<IUnknown>);
if (gdsc->QueryInterface(iid, (void**)u1.GetAddressOf()).FAILED)
if (punk->QueryInterface(piid, (void**)psc.GetAddressOf()).FAILED)
return false;
}

using var u2 = default(ComPtr<IUnknown>);
if (punk->QueryInterface(iid, (void**)u2.GetAddressOf()).FAILED)
return false;
return IsGameDeviceSwapChain(psc.Get());
}

return u1.Get() == u2.Get();
}
/// <inheritdoc cref="IsGameDeviceSwapChain{T}"/>
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;
}

/// <summary>Wait for the game to have finished initializing the IDXGISwapChain.</summary>
Expand Down

0 comments on commit f1a1f17

Please sign in to comment.