diff --git a/Confuser.Protections/Compress/Compressor.cs b/Confuser.Protections/Compress/Compressor.cs index ef38b99f8..40800f27c 100644 --- a/Confuser.Protections/Compress/Compressor.cs +++ b/Confuser.Protections/Compress/Compressor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -160,9 +161,9 @@ void PackModules(ConfuserContext context, CompressorContext compCtx, ModuleDef s for (int i = 0; i < name.Length; i++) name[i] *= key[i + 4]; - uint state = 0x6fff61; + uint state = compCtx.LcgInit; foreach (byte chr in name) - state = state * 0x5e3f1f + chr; + state = state * compCtx.LcgMultiplier + chr; byte[] encrypted = compCtx.Encrypt(comp, entry.Value, state, progress => { progress = (progress + moduleIndex) / modules.Count; context.ProgressReporter.Progress((int)(progress * 10000), 10000); @@ -242,6 +243,17 @@ void InjectStub(ConfuserContext context, CompressorContext compCtx, ProtectionPa new MemberRefUser(stubModule, ".ctor", ctorSig, attrType))); } + // Randomize the encryption feedback constant (a fixed value in the stub is a + // fingerprint). Any value works since it is purely additive; the same value is injected + // into the runtime Decrypt method below (Mutation.KeyI0) so encrypt/decrypt stay in sync. + compCtx.Feedback = random.NextUInt32(); + + // Randomize the rolling-hash used to derive each library's seed from its name. The + // multiplier is kept odd for good distribution. The same values are injected into the + // runtime Resolve method below (Mutation.KeyI0 = init, Mutation.KeyI1 = multiplier). + compCtx.LcgInit = random.NextUInt32(); + compCtx.LcgMultiplier = random.NextUInt32() | 1; + uint seed = random.NextUInt32(); compCtx.OriginModule = context.OutputModules[compCtx.ModuleIndex]; @@ -286,10 +298,45 @@ void InjectStub(ConfuserContext context, CompressorContext compCtx, ProtectionPa foreach (Instruction instr in instrs) decrypter.Body.Instructions.Add(instr); + // Sync the runtime feedback constant with the value used during encryption. The + // runtime Decrypt exposes it as the Mutation.KeyI0 placeholder; injection is verified + // so a runtime-source change that drops the placeholder fails the build loudly instead + // of silently shipping a stub that can no longer decrypt what we encrypted. + InjectStubKeys(context, decrypter, new[] { 0 }, new[] { (int)compCtx.Feedback }); + + // Sync the runtime rolling-hash constants (init + odd multiplier) used to derive each + // library's seed from its name (see PackModules) with the Mutation.KeyI0 / KeyI1 + // placeholders the runtime Resolve exposes. + MethodDef resolver = defs.OfType().Single(method => method.Name == "Resolve"); + InjectStubKeys(context, resolver, new[] { 0, 1 }, + new[] { (int)compCtx.LcgInit, (int)compCtx.LcgMultiplier }); + // Pack modules PackModules(context, compCtx, stubModule, comp, random); } + // Injects mutation key literals into an injected runtime stub method and first verifies + // every expected Mutation.KeyI* placeholder is actually present. If the runtime source is + // changed so a placeholder is removed or renamed, this throws instead of silently emitting + // a stub whose baked-in constants no longer match the obfuscator side. + static void InjectStubKeys(ConfuserContext context, MethodDef method, int[] keyIds, int[] values) { + var missing = new HashSet( + keyIds.Select(id => "KeyI" + id.ToString(CultureInfo.InvariantCulture))); + foreach (Instruction instr in method.Body.Instructions) { + if (instr.OpCode == OpCodes.Ldsfld && instr.Operand is IField field && + field.DeclaringType?.FullName == "Mutation") + missing.Remove(field.Name); + } + if (missing.Count != 0) { + context.Logger.LogError( + "Compressor stub is out of sync: runtime method '{Method}' is missing expected mutation placeholder(s) {Missing}. The runtime source and the injector must be updated together.", + method.Name, string.Join(", ", missing.OrderBy(name => name, StringComparer.Ordinal))); + throw new ConfuserException(null); + } + + MutationHelper.InjectKeys(method, keyIds, values); + } + void ImportAssemblyTypeReferences(ModuleDef originModule, ModuleDef stubModule) { var assembly = stubModule.Assembly; foreach (var ca in assembly.CustomAttributes) { diff --git a/Confuser.Protections/Compress/CompressorContext.cs b/Confuser.Protections/Compress/CompressorContext.cs index 0e0b11ae9..4500d0dfa 100644 --- a/Confuser.Protections/Compress/CompressorContext.cs +++ b/Confuser.Protections/Compress/CompressorContext.cs @@ -19,6 +19,9 @@ internal class CompressorContext { public byte[] OriginModule; public ModuleDef OriginModuleDef; public bool CompatMode; + public uint Feedback; + public uint LcgInit; + public uint LcgMultiplier; public byte[] Encrypt(ICompressionService compress, byte[] data, uint seed, Action progressFunc) { data = (byte[])data.Clone(); @@ -46,7 +49,7 @@ public byte[] Encrypt(ICompressionService compress, byte[] data, uint seed, Acti for (int i = 0; i < data.Length; i += 4) { var datum = (uint)(data[i + 0] | (data[i + 1] << 8) | (data[i + 2] << 16) | (data[i + 3] << 24)); uint encrypted = datum ^ key[keyIndex & 0xf]; - key[keyIndex & 0xf] = (key[keyIndex & 0xf] ^ datum) + 0x3ddb2819; + key[keyIndex & 0xf] = (key[keyIndex & 0xf] ^ datum) + Feedback; encryptedData[i + 0] = (byte)(encrypted >> 0); encryptedData[i + 1] = (byte)(encrypted >> 8); encryptedData[i + 2] = (byte)(encrypted >> 16); diff --git a/Confuser.Runtime/Compressor.Compat.cs b/Confuser.Runtime/Compressor.Compat.cs index 3dbe11874..ec2225939 100644 --- a/Confuser.Runtime/Compressor.Compat.cs +++ b/Confuser.Runtime/Compressor.Compat.cs @@ -23,7 +23,7 @@ static byte[] Decrypt(uint[] data, uint seed) { uint h = 0; for (int i = 0; i < data.Length; i++) { uint d = data[i] ^ w[i & 0xf]; - w[i & 0xf] = (w[i & 0xf] ^ d) + 0x3ddb2819; + w[i & 0xf] = (w[i & 0xf] ^ d) + (uint)Mutation.KeyI0; b[h + 0] = (byte)(d >> 0); b[h + 1] = (byte)(d >> 8); b[h + 2] = (byte)(d >> 16); @@ -86,9 +86,9 @@ static Assembly Resolve(object sender, ResolveEventArgs e) { Buffer.BlockCopy(t, 0, d, o, r); o += r; } - uint s = 0x6fff61; + uint s = (uint)Mutation.KeyI0; foreach (byte c in b) - s = s * 0x5e3f1f + c; + s = s * (uint)Mutation.KeyI1 + c; byte[] f = Decrypt(d, s); Assembly a = Assembly.Load(f); Array.Clear(f, 0, f.Length); diff --git a/Confuser.Runtime/Compressor.cs b/Confuser.Runtime/Compressor.cs index 5e3d3b09b..b69e0fe12 100644 --- a/Confuser.Runtime/Compressor.cs +++ b/Confuser.Runtime/Compressor.cs @@ -23,7 +23,7 @@ static byte[] Decrypt(uint[] data, uint seed) { uint h = 0; for (int i = 0; i < data.Length; i++) { uint d = data[i] ^ w[i & 0xf]; - w[i & 0xf] = (w[i & 0xf] ^ d) + 0x3ddb2819; + w[i & 0xf] = (w[i & 0xf] ^ d) + (uint)Mutation.KeyI0; b[h + 0] = (byte)(d >> 0); b[h + 1] = (byte)(d >> 8); b[h + 2] = (byte)(d >> 16); @@ -92,9 +92,9 @@ static Assembly Resolve(object sender, ResolveEventArgs e) { Buffer.BlockCopy(t, 0, d, o, r); o += r; } - uint s = 0x6fff61; + uint s = (uint)Mutation.KeyI0; foreach (byte c in b) - s = s * 0x5e3f1f + c; + s = s * (uint)Mutation.KeyI1 + c; byte[] f = Decrypt(d, s); Assembly a = Assembly.Load(f); Array.Clear(f, 0, f.Length);