Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions Confuser.Protections/Compress/Compressor.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -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<MethodDef>().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<string>(
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) {
Expand Down
5 changes: 4 additions & 1 deletion Confuser.Protections/Compress/CompressorContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<double> progressFunc) {
data = (byte[])data.Clone();
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions Confuser.Runtime/Compressor.Compat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions Confuser.Runtime/Compressor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down