Skip to content

feature: randomize compressor key-derivation moduli (#69) - #106

Merged
mcpolo99 merged 1 commit into
developfrom
69-compressor-prime-moduli
Sep 21, 2026
Merged

mcpolo99 merged 1 commit into
developfrom
69-compressor-prime-moduli

Conversation

@mcpolo99

Copy link
Copy Markdown
Owner

Level 2 constant randomization — Compressor deriver (#69)

Removes the last fixed magic constants from the compressor packer: the three moduli of the key-derivation generator state = state * state % m, used identically on the obfuscator side (CompressorContext.Encrypt) and in the injected runtime Decrypt. de4dot/AV pattern-match these to fingerprint packed output.

Constant Role Was Now
State modulus main state advance → src[i] 0x143fc089 curated set → Mutation.KeyI1
Key-word modulus dst[i] derivation 0x444d56fb curated set → Mutation.KeyI2
Stream modulus data-XOR state advance 0x8a5cb7 curated set → Mutation.KeyI3

How sync is preserved

One value per set is chosen per pack, stored on CompressorContext, used by Encrypt for the main module and every embedded library, and injected into the runtime Decrypt via the verified InjectStubKeys helper (the mutation-placeholder + presence-check pattern from the feedback/LCG work). A runtime-source change that drops a placeholder throws ConfuserException instead of silently shipping a stub that can't decrypt. Both Compressor and CompressorCompat runtime variants are handled.

Curated set (scripts/curate_primes.py, seed 69)

The round-trip is correct for any modulus (both sides share it), so the curated values are chosen purely to preserve key-stream quality. Each is:

  • prime and ≡ 3 mod 4 — squaring permutes the quadratic residues, so the stream cannot collapse to a fixed point (the original constants were not even prime);
  • same bit-length as the original (no (uint) truncation, same value ranges);
  • health ≥ original, validated by simulating the exact ulong generator over thousands of random seeds.

32 values per set → ~15 bits of added per-pack identity. The script regenerates the baked CompressorPrimes.cs deterministically and is self-reproducible.

Validation

CompressorWithResx.Test — 12/12 pass (compat true/false × deriver normal/dynamic × resource modes). The test loads a de satellite assembly at runtime, resolved and decrypted through the exact Resolve → Decrypt path these moduli drive; a broken constant-sync fails immediately.

Remaining on #69 (follow-ups)

Part of #69.

The compressor derives its key stream with state = state * state % m on
both the obfuscator (CompressorContext.Encrypt) and the injected runtime
(Decrypt), using three fixed moduli (0x143fc089 / 0x444d56fb / 0x8a5cb7)
that de4dot/AV pattern-match to fingerprint packed output.

Each modulus is now chosen per pack from a curated set and injected into
the runtime Decrypt via mutation keys (KeyI1/KeyI2/KeyI3), reusing the
verified-injection helper from the feedback/LCG work so a runtime-source
change that drops a placeholder fails the build.

The round-trip is correct for any modulus (both sides share it), so the
curated values are selected purely to preserve key-stream quality: each
is prime and congruent to 3 mod 4 (squaring permutes the quadratic
residues, so the stream cannot collapse), matches the original's
bit-length, and mixes at least as well, validated by simulating the exact
generator. The originals were not even prime. scripts/curate_primes.py
regenerates the set deterministically (seed 69); ~15 bits of added
per-pack identity.

Validated by CompressorWithResx.Test (12/12).
@mcpolo99
mcpolo99 merged commit 473d8b0 into develop Sep 21, 2026
3 checks passed
@mcpolo99
mcpolo99 deleted the 69-compressor-prime-moduli branch September 29, 2026 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant