From 110b0964e2acca91ff3c1bd2d15c930001864f83 Mon Sep 17 00:00:00 2001 From: RandomCrocodile Date: Sat, 4 Jul 2026 17:11:06 +0200 Subject: [PATCH 1/2] feature: randomize anti-tamper feedback constant per run (#69) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 2 of #69 (Level 2 identity). The anti-tamper method-body cipher used a hardcoded feedback constant 0x3dbb2819 in both the obfuscator (encrypt) and the injected runtime (decrypt) — a value de4dot signature-matches to identify ConfuserEx. Normal and Anti modes now generate a random per-run feedback value, use it in the encryption loop, and inject it into the runtime via a new Mutation key (KeyI5), exactly like the existing z/x/c/v hash constants (KeyI1-4). Obfuscator and runtime stay in sync automatically because the same generated value is both used and injected. JIT mode is intentionally left unchanged — its runtime component is already broken and its test is skipped, so a crypto change there cannot be validated. Validated by AntiTamper.Test: the obfuscated app decrypts its own method bodies at runtime and produces correct output (exit 42) for both Normal and Anti modes. --- .gitignore | 1 + Confuser.Protections/AntiTamper/AntiMode.cs | 8 +++++--- Confuser.Protections/AntiTamper/NormalMode.cs | 8 +++++--- Confuser.Runtime/AntiTamper.Anti.cs | 3 ++- Confuser.Runtime/AntiTamper.Normal.cs | 3 ++- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index beadc33d8..fbc51a807 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Visual Studio Cache files (starting with VS 2015) .vs/ +.temp # Launch Settings launchSettings.json diff --git a/Confuser.Protections/AntiTamper/AntiMode.cs b/Confuser.Protections/AntiTamper/AntiMode.cs index 5df4cdf65..4c28ea83b 100644 --- a/Confuser.Protections/AntiTamper/AntiMode.cs +++ b/Confuser.Protections/AntiTamper/AntiMode.cs @@ -16,6 +16,7 @@ namespace Confuser.Protections.AntiTamper { internal class AntiMode : IModeHandler { uint c; + uint feedback; IKeyDeriver deriver; List methods; @@ -31,6 +32,7 @@ public void HandleInject(AntiTamperProtection parent, ConfuserContext context, P x = random.NextUInt32(); c = random.NextUInt32(); v = random.NextUInt32(); + feedback = random.NextUInt32(); name1 = random.NextUInt32() & 0x7f7f7f7f; name2 = random.NextUInt32() & 0x7f7f7f7f; @@ -77,8 +79,8 @@ public void HandleInject(AntiTamperProtection parent, ConfuserContext context, P initMethod.Body.Instructions.Add(instr); MutationHelper.InjectKeys(initMethod, - new[] { 0, 1, 2, 3, 4 }, - new[] { (int)(name1 * name2), (int)z, (int)x, (int)c, (int)v }); + new[] { 0, 1, 2, 3, 4, 5 }, + new[] { (int)(name1 * name2), (int)z, (int)x, (int)c, (int)v, (int)feedback }); var name = context.Registry.GetService(); var marker = context.Registry.GetService(); @@ -219,7 +221,7 @@ void EncryptSection(ModuleWriterBase writer) { for (uint i = 0; i < encSize; i++) { uint data = reader.ReadUInt32(); result[i] = data ^ key[i & 0xf]; - key[i & 0xf] = (key[i & 0xf] ^ data) + 0x3dbb2819; + key[i & 0xf] = (key[i & 0xf] ^ data) + feedback; } var byteResult = new byte[encSize << 2]; Buffer.BlockCopy(result, 0, byteResult, 0, byteResult.Length); diff --git a/Confuser.Protections/AntiTamper/NormalMode.cs b/Confuser.Protections/AntiTamper/NormalMode.cs index 7818271aa..b73f57e2e 100644 --- a/Confuser.Protections/AntiTamper/NormalMode.cs +++ b/Confuser.Protections/AntiTamper/NormalMode.cs @@ -16,6 +16,7 @@ namespace Confuser.Protections.AntiTamper { internal class NormalMode : IModeHandler { uint c; + uint feedback; IKeyDeriver deriver; List methods; @@ -31,6 +32,7 @@ public void HandleInject(AntiTamperProtection parent, ConfuserContext context, P x = random.NextUInt32(); c = random.NextUInt32(); v = random.NextUInt32(); + feedback = random.NextUInt32(); name1 = random.NextUInt32() & 0x7f7f7f7f; name2 = random.NextUInt32() & 0x7f7f7f7f; @@ -77,8 +79,8 @@ public void HandleInject(AntiTamperProtection parent, ConfuserContext context, P initMethod.Body.Instructions.Add(instr); MutationHelper.InjectKeys(initMethod, - new[] { 0, 1, 2, 3, 4 }, - new[] { (int)(name1 * name2), (int)z, (int)x, (int)c, (int)v }); + new[] { 0, 1, 2, 3, 4, 5 }, + new[] { (int)(name1 * name2), (int)z, (int)x, (int)c, (int)v, (int)feedback }); var name = context.Registry.GetService(); var marker = context.Registry.GetService(); @@ -220,7 +222,7 @@ void EncryptSection(ModuleWriterBase writer) { for (uint i = 0; i < encSize; i++) { uint data = reader.ReadUInt32(); result[i] = data ^ key[i & 0xf]; - key[i & 0xf] = (key[i & 0xf] ^ data) + 0x3dbb2819; + key[i & 0xf] = (key[i & 0xf] ^ data) + feedback; } var byteResult = new byte[encSize << 2]; Buffer.BlockCopy(result, 0, byteResult, 0, byteResult.Length); diff --git a/Confuser.Runtime/AntiTamper.Anti.cs b/Confuser.Runtime/AntiTamper.Anti.cs index f1be244cd..7a0a88e7b 100644 --- a/Confuser.Runtime/AntiTamper.Anti.cs +++ b/Confuser.Runtime/AntiTamper.Anti.cs @@ -27,6 +27,7 @@ static unsafe void Initialize() { uint l = 0; var r = (uint*)(p + 0x18 + o); uint z = (uint)Mutation.KeyI1, x = (uint)Mutation.KeyI2, c = (uint)Mutation.KeyI3, v = (uint)Mutation.KeyI4; + uint fb = (uint)Mutation.KeyI5; CheckRemoteDebuggerPresent(Process.GetCurrentProcess().Handle, ref isDebuggerPresent); if (isDebuggerPresent) Environment.FailFast(null); @@ -80,7 +81,7 @@ static unsafe void Initialize() { uint h = 0; for (uint i = 0; i < l; i++) { *e ^= y[h & 0xf]; - y[h & 0xf] = (y[h & 0xf] ^ (*e++)) + 0x3dbb2819; + y[h & 0xf] = (y[h & 0xf] ^ (*e++)) + fb; CheckRemoteDebuggerPresent(Process.GetCurrentProcess().Handle, ref isDebuggerPresent); if (isDebuggerPresent) Environment.FailFast(null); diff --git a/Confuser.Runtime/AntiTamper.Normal.cs b/Confuser.Runtime/AntiTamper.Normal.cs index 6fc882ff9..1e09da850 100644 --- a/Confuser.Runtime/AntiTamper.Normal.cs +++ b/Confuser.Runtime/AntiTamper.Normal.cs @@ -20,6 +20,7 @@ static unsafe void Initialize() { uint l = 0; var r = (uint*)(p + 0x18 + o); uint z = (uint)Mutation.KeyI1, x = (uint)Mutation.KeyI2, c = (uint)Mutation.KeyI3, v = (uint)Mutation.KeyI4; + uint fb = (uint)Mutation.KeyI5; for (int i = 0; i < s; i++) { uint g = (*r++) * (*r++); if (g == (uint)Mutation.KeyI0) { @@ -60,7 +61,7 @@ static unsafe void Initialize() { uint h = 0; for (uint i = 0; i < l; i++) { *e ^= y[h & 0xf]; - y[h & 0xf] = (y[h & 0xf] ^ (*e++)) + 0x3dbb2819; + y[h & 0xf] = (y[h & 0xf] ^ (*e++)) + fb; h++; } } From 99977b6cda4ece7533a03a2990ba63375d4234d8 Mon Sep 17 00:00:00 2001 From: RandomCrocodile Date: Sat, 4 Jul 2026 17:23:29 +0200 Subject: [PATCH 2/2] feature: randomize CFG state multiplier constant per run (#69) The Constants protection's control-flow encoding baked the fixed multiplier 0x21412321 into both the obfuscator-side CFGState and the injected runtime CFGCtx constructor, giving de4dot a stable signature. Generate a random odd (invertible mod 2^32) multiplier per module when the CFG state type is injected, rewrite the literal in the runtime ctor IL, and thread the same value through the obfuscator CFGState so both sides stay in sync. Validated end-to-end by the constants protection tests (obfuscate -> run -> assert output). --- Confuser.Protections/Constants/CEContext.cs | 1 + .../Constants/ReferenceReplacer.cs | 25 +++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Confuser.Protections/Constants/CEContext.cs b/Confuser.Protections/Constants/CEContext.cs index c6feeb21d..3e9254a40 100644 --- a/Confuser.Protections/Constants/CEContext.cs +++ b/Confuser.Protections/Constants/CEContext.cs @@ -35,6 +35,7 @@ internal class CEContext { public TypeDef CfgCtxType; public MethodDef CfgCtxCtor; public MethodDef CfgCtxNext; + public uint CfgCtxMultiplier; public Dictionary>> ReferenceRepl; } diff --git a/Confuser.Protections/Constants/ReferenceReplacer.cs b/Confuser.Protections/Constants/ReferenceReplacer.cs index 901cd76cd..7083ea066 100644 --- a/Confuser.Protections/Constants/ReferenceReplacer.cs +++ b/Confuser.Protections/Constants/ReferenceReplacer.cs @@ -51,11 +51,11 @@ struct CFGState { public uint C; public uint D; - public CFGState(uint seed) { - A = seed *= 0x21412321; - B = seed *= 0x21412321; - C = seed *= 0x21412321; - D = seed *= 0x21412321; + public CFGState(uint seed, uint mult) { + A = seed *= mult; + B = seed *= mult; + C = seed *= mult; + D = seed *= mult; } public void UpdateExplicit(int id, uint value) { @@ -136,6 +136,15 @@ static void InjectStateType(CEContext ctx) { ctx.CfgCtxCtor = ctx.CfgCtxType.FindMethod(".ctor"); ctx.CfgCtxNext = ctx.CfgCtxType.FindMethod("Next"); + // Randomize the CFG state multiplier so the baked-in 0x21412321 literal + // no longer fingerprints the output. Must stay odd (invertible mod 2^32) and + // match the obfuscator-side CFGState computation (see CFGState.ctor). + ctx.CfgCtxMultiplier = ctx.Random.NextUInt32() | 1; + foreach (var instr in ctx.CfgCtxCtor.Body.Instructions) { + if (instr.OpCode == OpCodes.Ldc_I4 && (int)instr.Operand == 0x21412321) + instr.Operand = (int)ctx.CfgCtxMultiplier; + } + ctx.Name.MarkHelper(ctx.CfgCtxType, ctx.Marker, ctx.Protection); foreach (var def in ctx.CfgCtxType.Fields) ctx.Name.MarkHelper(def, ctx.Marker, ctx.Protection); @@ -212,7 +221,7 @@ static void InsertEmptyStateUpdate(CFGContext ctx, ControlFlowBlock block) { if (!ctx.StatesMap.TryGetValue(key.ExitState, out exit)) { // Create new exit state from random seed var seed = ctx.Random.NextUInt32(); - exit = new CFGState(seed); + exit = new CFGState(seed, ctx.Ctx.CfgCtxMultiplier); body.Instructions.Insert(targetIndex++, first = Instruction.Create(OpCodes.Ldloca, ctx.StateVariable)); body.Instructions.Insert(targetIndex++, Instruction.Create(OpCodes.Ldc_I4, (int)seed)); body.Instructions.Insert(targetIndex++, Instruction.Create(OpCodes.Call, ctx.Ctx.CfgCtxCtor)); @@ -301,7 +310,7 @@ static uint InsertStateGetAndUpdate(CFGContext ctx, ref int index, BlockKeyType if (targetState == null) { // Create new exit state from random seed var seed = ctx.Random.NextUInt32(); - currentState = new CFGState(seed); + currentState = new CFGState(seed, ctx.Ctx.CfgCtxMultiplier); body.Instructions.Insert(index++, Instruction.Create(OpCodes.Ldloca, ctx.StateVariable)); body.Instructions.Insert(index++, Instruction.Create(OpCodes.Dup)); body.Instructions.Insert(index++, Instruction.Create(OpCodes.Ldc_I4, (int)seed)); @@ -398,7 +407,7 @@ static void ReplaceCFG(MethodDef method, List> // Create new entry state uint blockSeed = ctx.Random.NextUInt32(); - currentState = new CFGState(blockSeed); + currentState = new CFGState(blockSeed, ctx.CfgCtxMultiplier); cfgCtx.StatesMap[key.EntryState] = currentState; var index = graph.Body.Instructions.IndexOf(graph[blockRef.Key].Header);