Skip to content

Instantly share code, notes, and snippets.

@dolfies
Created July 22, 2026 01:14
Show Gist options
  • Select an option

  • Save dolfies/d442777ab8354e18c1a3827e27f85c3a to your computer and use it in GitHub Desktop.

Select an option

Save dolfies/d442777ab8354e18c1a3827e27f85c3a to your computer and use it in GitHub Desktop.
Discord's executable fingerprint
<#
In case you're wondering what exactly Discord's executable fingerprint contains, the following is collected:
- The executable file size
- The PE TimeDateStamp (compile timestamp)
- The PE SizeOfImage
- SHA256 of the PE headers before the section table
- SHA256 of the PE section table
- Whether the executable is signed
- For signed executables: SHA256 of the certificate subject name plus the certificate public key hash
A hash of your Windows MachineGuid plus your Discord user ID is used to tie the fingerprint to you,
and an XOR wrapper keyed by a salted hash of your Discord user ID is used to obfuscate it.
#>
param(
[int]$ProcessId = 0,
[string]$Path,
[Parameter(Mandatory = $true)][string]$UserId
)
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public static class CertPubKeyHash {
const uint X509_ASN_ENCODING = 0x00000001;
const uint PKCS_7_ASN_ENCODING = 0x00010000;
const uint CALG_SHA_256 = 0x0000800C;
[StructLayout(LayoutKind.Sequential)] struct Blob { public uint cbData; public IntPtr pbData; }
[StructLayout(LayoutKind.Sequential)] struct AlgId { public IntPtr pszObjId; public Blob Parameters; }
[StructLayout(LayoutKind.Sequential)] struct BitBlob { public uint cbData; public IntPtr pbData; public uint cUnusedBits; }
[StructLayout(LayoutKind.Sequential)] struct PubKeyInfo { public AlgId Algorithm; public BitBlob PublicKey; }
[StructLayout(LayoutKind.Sequential)] struct FileTime { public uint Low; public uint High; }
[StructLayout(LayoutKind.Sequential)]
struct CertInfo {
public uint Version; public Blob SerialNumber; public AlgId SignatureAlgorithm; public Blob Issuer;
public FileTime NotBefore; public FileTime NotAfter; public Blob Subject;
public PubKeyInfo SubjectPublicKeyInfo; public BitBlob IssuerUniqueId; public BitBlob SubjectUniqueId;
public uint ExtensionCount; public IntPtr Extensions;
}
[StructLayout(LayoutKind.Sequential)]
struct CertContext {
public uint EncodingType; public IntPtr Encoded; public uint EncodedSize;
public IntPtr CertInfo; public IntPtr Store;
}
[DllImport("crypt32.dll", SetLastError = true)]
static extern bool CryptHashPublicKeyInfo(
IntPtr provider, uint algid, uint flags, uint encoding, ref PubKeyInfo info, byte[] hash, ref uint hashSize);
public static byte[] Hash(IntPtr certContext) {
var ctx = Marshal.PtrToStructure<CertContext>(certContext);
var info = Marshal.PtrToStructure<CertInfo>(ctx.CertInfo);
var hash = new byte[32];
uint hashSize = 32;
if (!CryptHashPublicKeyInfo(IntPtr.Zero, CALG_SHA_256, 0, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, ref info.SubjectPublicKeyInfo, hash, ref hashSize) || hashSize != 32)
throw new InvalidOperationException("CryptHashPublicKeyInfo failed");
return hash;
}
}
"@
if (!$Path) {
if (!$ProcessId) { throw "Pass -Path <exe> or -ProcessId <pid>." }
$Path = (Get-Process -Id $ProcessId).Path
}
if (!$Path) { throw "Could not resolve executable path." }
$fs = [IO.File]::Open($Path, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite -bor [IO.FileShare]::Delete)
try {
$fileSize = [UInt64]$fs.Length
$readSize = [int][Math]::Min($fs.Length, 0x10000)
$buf = [byte[]]::new($readSize)
$read = $fs.Read($buf, 0, $readSize)
if ($read -lt 0x40) { throw "File prefix is too small." }
if ($read -ne $readSize) { [Array]::Resize([ref]$buf, $read) }
} finally {
$fs.Dispose()
}
if ([BitConverter]::ToUInt16($buf, 0) -ne 0x5A4D) { throw "Not an MZ executable." }
$pe = [BitConverter]::ToInt32($buf, 0x3C)
if ($pe -lt 0 -or [BitConverter]::ToUInt32($buf, $pe) -ne 0x4550) { throw "Not a PE executable." }
$fh = $pe + 4
$sections = [BitConverter]::ToUInt16($buf, $fh + 2)
$timestamp = [BitConverter]::ToUInt32($buf, $fh + 4)
$optSize = [BitConverter]::ToUInt16($buf, $fh + 16)
$opt = $fh + 20
$isPe32Plus = [BitConverter]::ToUInt16($buf, $opt) -eq 0x20B
if (!$isPe32Plus) { throw "Discord only fingerprints PE32+." }
$sizeOfImage = [BitConverter]::ToUInt32($buf, $opt + 56)
$sectionTable = $opt + $optSize
$sha256 = [Security.Cryptography.SHA256]::Create()
$headerHash = $sha256.ComputeHash([byte[]]$buf[0..($sectionTable - 1)])
$sectionHash = $sha256.ComputeHash([byte[]]$buf[$sectionTable..($sectionTable + $sections * 40 - 1)])
$machineGuid = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Cryptography" -Name MachineGuid).MachineGuid
$machineUserHash = $sha256.ComputeHash([Text.Encoding]::UTF8.GetBytes($machineGuid + $UserId))
$sig = Get-AuthenticodeSignature -LiteralPath $Path
$signed = $sig.Status -eq "Valid" -and $sig.SignerCertificate
if ($signed) {
$name = $sig.SignerCertificate.GetNameInfo([Security.Cryptography.X509Certificates.X509NameType]::SimpleName, $false)
$certHash = $sha256.ComputeHash([Text.Encoding]::UTF8.GetBytes($name) + [CertPubKeyHash]::Hash($sig.SignerCertificate.Handle))
} else {
$certHash = [byte[]]::new(32)
}
$flags = 0
if ($signed) { $flags += 1 }
if ($isPe32Plus) { $flags += 2 }
$out = [byte[]](@(1, [byte]$flags) +
[BitConverter]::GetBytes($fileSize) +
[BitConverter]::GetBytes($timestamp) +
[BitConverter]::GetBytes($sizeOfImage) +
$headerHash + $machineUserHash + $sectionHash + $certHash)
$key = $sha256.ComputeHash([Text.Encoding]::UTF8.GetBytes($UserId + "https://discord.com/careers"))
for ($i = 1; $i -lt $out.Length; $i++) {
$out[$i] = $out[$i] -bxor $key[($i - 1) -band 31]
}
[Convert]::ToBase64String($out)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment