Post-Build String Encryption for Windows x64 Binaries
Encrypts eligible string literals inside compiled executables and embeds authenticated metadata in a dedicated .ccgtr PE section.
Post-build patcher · Header-only runtime shim · AEAD-backed region protection · Static analysis friction layer
CCGT is a two-part system:
-
CCGT CLI (post-build patcher) A command-line tool that:
- scans an EXE for candidate strings
- encrypts those bytes directly inside the file
- writes a metadata table (regions + key material) into a dedicated section
-
CCGT Runtime (in-binary decrypt shim) A header-only runtime (
ccgt_runtime.h) that:- defines the metadata section (
.ccgtr) - runs automatically during process initialization
- decrypts encrypted regions in-memory using the metadata
- defines the metadata section (
-
Protecting sensitive identifiers embedded as string literals:
- API endpoints, URLs, tokens, keys, auth strings
- internal routes, feature flags, debug markers
- error messages revealing architecture or detection logic
-
Making static triage harder:
strings.exe,floss, naive regex harvesting- simple signature rules targeting plain
.rdatastrings
-
Scan the PE file
- locate candidate ASCII strings and their file offsets
- validate length and null termination
-
Verify candidates are safe to patch
- must belong to
.rdataor.data - must not overlap PE directories (imports, resources, IAT, relocations, etc.)
- must not be inside the
.ccgtrmeta section - must be within file bounds and outside headers
- must belong to
-
Encrypt in-place
-
algorithm: ChaCha20-Poly1305 (AEAD)
-
key: per-binary master key (32 bytes)
-
nonce: derived per region from
(seed, RVA) -
AAD: region metadata
(RVA, length, seed) -
result:
- the original bytes at each string location are replaced with ciphertext bytes
- a per-region Poly1305 authentication tag is generated
-
-
Write metadata
- regions list:
{ RVA, length, seed, tag }for each encrypted block - key material: stored as
frag ^ maskcomponents in.ccgtr - a signed SHA-256 hash of the on-disk file (signature field zeroed, Authenticode certificate table excluded) is generated and stored in
.ccgtr - metadata is written into the
.ccgtrsection embedded into the target binary
- regions list:
On process start (before main()), the runtime:
-
Locates its own module base.
-
Reads
.ccgtrmetadata (g_meta). -
Reconstructs the master key from
frag ^ mask. -
For each region:
-
computes address from
base + RVA -
flips protection to RW via
VirtualProtect -
verifies and decrypts using ChaCha20-Poly1305
- authentication covers ciphertext and region metadata
-
verifies a signed SHA-256 hash of the on-disk file before decryption
-
if authentication fails, the region is left encrypted
-
restores original protection
-
Result: string call sites remain valid and the program runs normally, but the on-disk binary no longer contains plaintext strings. Unauthorized file patching (outside the Authenticode certificate table) is detected at runtime.
- Toolchain: MSVC
- Language: C++20
- Platform: x64
Build ccgt as a normal console app.
In the project you want to protect:
- Add
/include/*hto your include path (all header files) - Include
/include/ccgt_runtime.hit in one translation unit (recommended: your main file)
Example:
#include "ccgt_runtime.h"
#include <iostream>
int main() {
std::cout << "Hello from protected binary\n";
return 0;
}This causes the protected binary to contain a .ccgtr section with g_meta.
From the output directory containing your built executable:
.\ccgt.exe .\<ExeToProtect>.exeIf you see signing key not configured, generate keys once and rebuild:
.\ccgt.exe --gen-keysCheck that .ccgtr exists:
dumpbin /headers .\prototype.exe
dumpbin /section:.ccgtr .\prototype.exeConfirm plaintext strings are reduced:
strings.exe .\prototype.exe | findstr /i "example.com"usage:
ccgt <binary.exe> [--scan] [--dry-run] [-v] [--min N] [--max N] [--no-backup]
notes:
- default action is to PATCH (encrypt strings + write meta)
- --scan prints findings only
-
--scanScan only, print candidate strings and offsets. No patching occurs. -
--dry-runPerform all checks and plan encryption, but do not write changes. -
-v/--verbosePrint detailed logs (skips, decisions, region selections). -
--min N/--max NClamp eligible string lengths. -
--no-backupDisable.bakcreation.
Your target binary does no contain the .ccgtr section.
Fix:
- Ensure all header files from
/include/are included in your project - Ensure
/include/ccgt_runtime.hin at least one translation unit that is linked - Ensure the compiler/linker didn’t discard it
The .ccgtr section exists but is not large enough.
Fix:
- Do not shrink meta capacity
- Ensure
Metais allocated exactly as provided in the runtime header
Likely causes:
- encrypting bytes that are not safe to modify
- accidental overlap with structured PE regions
- runtime decrypt not running early enough
Fix:
- run with
--verboseand inspect skip reasons - tighten selection rules: require null termination, raise
min_len - validate runtime auto-init is linked (CRT initialization section present)
CCGT is intended for legitimate protection and defensive hardening. It is not a guarantee against reverse engineering or runtime analysis. It is designed to raise the cost of static extraction and reduce accidental exposure of sensitive strings in deployed binaries.