Created
July 22, 2026 01:14
-
-
Save dolfies/d442777ab8354e18c1a3827e27f85c3a to your computer and use it in GitHub Desktop.
Discord's executable fingerprint
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| 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