Chapter 57

Code Signing and Certificate Abuse

A valid Authenticode signature on your binary changes everything in the detection landscape. Windows SmartScreen skips its warning. Many AV products reduce their scan aggressiveness for signed binaries. EDR solutions apply lower-confidence rules to signed executables. The Microsoft-signed DLL injection blocking from Chapter 49 relies on signature checking — and attackers have learned to exploit the same mechanism. This chapter covers the legitimate and illegitimate paths to getting your implant signed, stolen certificate abuse observed in real APT campaigns, and the cat-and-mouse of certificate revocation vs offline CRL checking.

Why Code Signing Changes Detection

Signing verification flow and where it affects detection
  When Windows loads a PE with Authenticode signature:
  ─────────────────────────────────────────────────────────────────────────
  WinVerifyTrust() → verifies signature against certificate chain
    → checks certificate against trusted root CAs
    → (if online) checks CRL or OCSP for revocation
    → returns: valid, invalid, or revoked
  
  What a valid signature unlocks:
  ─────────────────────────────────────────────────────────────────────────
  SmartScreen:      No "This app may harm your computer" warning
  UAC:              Publisher name shows in UAC dialog (looks legitimate)
  WDAC/AppLocker:   If policy is "publisher-based": passes if publisher is trusted
  AMSI:             Microsoft-signed binaries get reduced scan aggressiveness
  AV products:      Most reduce scan depth for signed binaries (performance tradeoff)
  EDR:              Lower confidence alerts for signed executables
  WHQL:             Required for kernel drivers (separate process)
  
  The mitigation policy from Ch49:
  ─────────────────────────────────────────────────────────────────────────
  BLOCK_NON_MICROSOFT_BINARIES works by checking:
    - Is the binary signed?
    - Is the signature chain rooted in Microsoft's cert?
  If yes: allowed to load. If no: blocked.
  
  A Microsoft-signed binary (or one with a stolen MS cert): passes the block.
  A legitimately-obtained third-party code signing cert: does NOT pass the block.
  
  Routes to signed malware:
  ─────────────────────────────────────────────────────────────────────────
  1. Buy a legitimate code signing certificate (Extended Validation or OV)
     from a CA — this gives you a real, valid chain. Expensive, requires
     identity verification, but produces the strongest trust signal.
  
  2. Steal a private key from a compromised vendor (supply chain attack)
     and sign with their certificate. Seen in: SolarWinds (SUNBURST used
     SolarWinds cert), NotPetya (M.E.Doc supply chain), 3CX supply chain.
  
  3. Backdoor a legitimate binary (replace/patch code while keeping the
     signature by exploiting SHA-1 collision or appending data after the
     signed hash area). Highly advanced, rarely seen in practice.
  
  4. Use already-signed legitimate binaries (LOLBins) to load your code —
     the BINARY is signed and trusted, even though it loads your unsigned DLL.

Obtaining a Code Signing Certificate

Code signing certificate tiers:
─────────────────────────────────────────────────────────────────────────

Organization Validated (OV) Code Signing:
  Cost:     $100-300/year
  Process:  CA verifies your organization exists (phone call, business registry)
  Time:     1-3 days
  SmartScreen: Limited trust — new certificates start with no SmartScreen
               reputation. Unsigned warning may still appear for new certs
               until the cert accumulates "reputation" (file count, age).
  Used for: Legitimate software. For red team: provides a valid chain,
            AV reduction, WDAC publisher trust if the cert org is allow-listed.

Extended Validation (EV) Code Signing:
  Cost:     $300-800/year
  Process:  Stricter vetting — CA physically verifies the organization's
            existence, calls registered business number, requires incorporation documents.
  Time:     5-14 days
  SmartScreen: Immediate trust — EV certificates bypass SmartScreen reputation
               requirements. A binary signed with a fresh EV cert shows NO
               SmartScreen warning even on first execution anywhere in the world.
  Note:     EV certs require a hardware token (USB HSM like Yubikey or DigiCert HSM).
            The private key is generated on the token and cannot be exported.
            For red team ops: purchase under a shell company.

Stolen/leaked OV certificates (used in wild):
─────────────────────────────────────────────────────────────────────────
  Source:   Breached software vendors, leaked from CI/CD systems, dark web markets.
  Risk:     Certificate will be revoked as soon as theft is discovered.
  Defense:  CRL/OCSP check at verification time catches revoked certs.
            BUT: CRL checks are often skipped when offline or CRL endpoint is down.
            Many enterprise environments have CRL caching (24-72 hours).
            Offline signing attacks: sign binary before CA revokes cert → within the
            CRL cache window, the cert appears valid.

Timestamp counter-signing (crucial for persistence):
─────────────────────────────────────────────────────────────────────────
  Authenticode supports RFC 3161 trusted timestamp servers.
  When you sign a binary AND counter-sign with a timestamp:
    The binary remains valid even AFTER the signing cert expires.
    The counter-signature proves the binary was signed BEFORE the cert expired.
    
  Without timestamp: cert expiry = binary validation fails.
  With timestamp:    cert revocation still causes failure (revocation != expiry).
  
  Key: a stolen cert + timestamp counter-signature + sign before revocation =
       binary that remains "valid" for years after the cert is revoked IF the
       enterprise doesn't do online OCSP checking at load time.

Signing and Verifying Your Binary

# ── Signing a binary with signtool.exe (Windows SDK) ─────────────────

# Step 1: Convert your PFX (cert + private key) to the signing format
# (PFX is the standard export format from CAs and certificate stores)

# Sign with SHA-256 and add a trusted timestamp (crucial for persistence):
signtool.exe sign `
  /fd sha256 `                    # file digest algorithm: SHA-256
  /td sha256 `                    # timestamp digest algorithm
  /tr http://timestamp.digicert.com `  # DigiCert timestamp server (free)
  /f your_cert.pfx `              # PFX file containing cert + private key
  /p YourPfxPassword `            # PFX password
  implant.exe                     # target binary

# Verify the signature:
signtool.exe verify /pa /v implant.exe

# Check certificate details (from PowerShell):
$sig = Get-AuthenticodeSignature .\implant.exe
$sig.SignerCertificate | Select-Object Subject, NotAfter, Thumbprint
$sig.TimeStamperCertificate  # check if timestamped

# ── Verifying certificate chain validity ──────────────────────────────
# PowerShell: check if cert is currently revoked
$cert = (Get-AuthenticodeSignature .\implant.exe).SignerCertificate
$chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
$chain.ChainPolicy.RevocationMode = `
    [System.Security.Cryptography.X509Certificates.X509RevocationMode]::Online
$chain.Build($cert)
$chain.ChainStatus  # empty = valid, populated = error including revocation
/* ── Checking signature validity from C ──────────────────────────────── */
/*
 * WinVerifyTrust with WINTRUST_ACTION_GENERIC_VERIFY_V2 checks Authenticode.
 * This is what Windows uses internally when loading executables.
 * Call this in your loader to verify your embedded payload is still intact
 * (anti-tamper), or in your implant to check if it's running from a signed copy.
 */
#include <windows.h>
#include <wintrust.h>
#include <softpub.h>
#pragma comment(lib, "wintrust.lib")

BOOL verify_authenticode(const wchar_t *file_path) {
    WINTRUST_FILE_INFO file_info = {0};
    file_info.cbStruct    = sizeof(file_info);
    file_info.pcwszFilePath = file_path;

    WINTRUST_DATA trust_data = {0};
    trust_data.cbStruct     = sizeof(trust_data);
    trust_data.dwUIChoice   = WTD_UI_NONE;       /* no UI */
    trust_data.fdwRevocationChecks = WTD_REVOKE_WHOLECHAIN; /* online OCSP */
    trust_data.dwUnionChoice = WTD_CHOICE_FILE;
    trust_data.pFile        = &file_info;
    trust_data.dwStateAction = WTD_STATEACTION_VERIFY;
    trust_data.dwProvFlags  = WTD_CACHE_ONLY_URL_RETRIEVAL; /* offline CRL */

    GUID action = WINTRUST_ACTION_GENERIC_VERIFY_V2;
    LONG result = WinVerifyTrust(NULL, &action, &trust_data);

    /* Close the trust state */
    trust_data.dwStateAction = WTD_STATEACTION_CLOSE;
    WinVerifyTrust(NULL, &action, &trust_data);

    return result == ERROR_SUCCESS;
}

Questions & Answers

How do defenders detect malware that uses legitimately obtained code signing certificates?

Several approaches: First, certificate reputation — a code signing cert that has never been seen signing any binary before the incident is suspicious ("new" certificate with no history). Threat intelligence feeds track certificate thumbprints seen in malware; VirusTotal aggregates this data. Second, signer identity vs binary behavior — a cert from "Acme Widgets LLC" signing a binary that does process injection doesn't match expected behavior; automated ML models correlate signer identity with behavioral patterns at scale (MDE does this). Third, CRL/revocation checking — as soon as the issuing CA is notified (often by an industry tip, a law enforcement request, or the breached vendor's own discovery), they revoke the cert. Online OCSP checking then fails immediately. Fourth, certificate CT (Certificate Transparency) logs — all publicly-issued certs are logged; defenders can subscribe to notifications for new certs matching certain criteria (e.g., "new cert issued to a company I've never heard of").

What is the "catalog signing" mechanism and can it be abused?

Windows allows signing by catalog: instead of embedding a signature in the PE file, you create a security catalog (.cat file) that contains hash entries for multiple files. The catalog itself is signed and installed in the Windows catalog database. When Windows verifies a file, it checks if the file's hash appears in any installed trusted catalog — if so, the file is considered signed even without an embedded signature. This is used by Microsoft to sign system files without modifying them. Catalog abuse: if you can install a catalog entry (requires admin), you can make an unsigned binary appear signed to WinVerifyTrust. Attackers with SYSTEM access can install malicious catalog entries. Defenders: catalog modifications are auditable via the catalog database, and any modification requires administrative privilege — so if you have admin to install a catalog, you already have other options that don't require this complexity.

Can you sign a binary after adding a backdoor without invalidating the original signature?

No — the Authenticode signature covers the entire PE file's hash. Modifying any byte of the signed content (code sections, data sections, imports) changes the hash and invalidates the signature. The exception is the certificate area itself: PE files have reserved space for the Authenticode signature in the "Security Directory" — you can replace the signature in this area without affecting the signed hash (because the security directory is excluded from the hash computation). This is how you'd add your own signature after stripping the original. But you cannot ADD code to a binary without breaking the original signature. The SolarWinds/SUNBURST attack didn't modify SolarWinds' binaries after signing — SolarWinds' own build pipeline was compromised, so the backdoored binary was signed during the legitimate build process.