diff --git a/Confuser.Core/ConfuserEngine.cs b/Confuser.Core/ConfuserEngine.cs index 68ac9c48..91594f9c 100644 --- a/Confuser.Core/ConfuserEngine.cs +++ b/Confuser.Core/ConfuserEngine.cs @@ -90,10 +90,9 @@ static void RunInternal(ConfuserParameters parameters, CancellationToken token) bool ok = false; try { - // Enable watermarking by default - context.Project.Rules.Insert(0, new Rule { - new SettingItem(WatermarkingProtection._Id) - }); + // Watermarking is opt-in (issue #69): the "watermark" protection is no longer + // force-enabled, so obfuscated output carries no ConfuserEx fingerprint attribute + // unless a project explicitly requests it via . var asmResolver = new ConfuserAssemblyResolver { EnableTypeDefCache = true }; asmResolver.DefaultModuleContext = new ModuleContext(asmResolver); diff --git a/Confuser.Core/WatermarkingProtection.cs b/Confuser.Core/WatermarkingProtection.cs index 7fff3220..b1d1aedd 100644 --- a/Confuser.Core/WatermarkingProtection.cs +++ b/Confuser.Core/WatermarkingProtection.cs @@ -48,10 +48,17 @@ protected internal override void Execute(ConfuserContext context, ProtectionPara context.Logger.LogDebug("Watermarking..."); foreach (var module in parameters.Targets.OfType()) { + // Both are configurable so users can brand (or disguise) the watermark instead of + // carrying the identifiable default ConfuserEx fingerprint. + var attributeName = parameters.GetParameter(context, module, "attributeName", "ConfusedByAttribute"); + if (string.IsNullOrEmpty(attributeName)) + attributeName = "ConfusedByAttribute"; + var text = parameters.GetParameter(context, module, "text", ConfuserEngine.Version); + var attrRef = module.CorLibTypes.GetTypeRef("System", "Attribute"); - var attrType = module.FindNormal("ConfusedByAttribute"); + var attrType = module.FindNormal(attributeName); if (attrType == null) { - attrType = new TypeDefUser("", "ConfusedByAttribute", attrRef); + attrType = new TypeDefUser("", attributeName, attrRef); module.Types.Add(attrType); marker.Mark(attrType, Parent); } @@ -75,7 +82,7 @@ protected internal override void Execute(ConfuserContext context, ProtectionPara } var attr = new CustomAttribute(ctor); - attr.ConstructorArguments.Add(new CAArgument(module.CorLibTypes.String, ConfuserEngine.Version)); + attr.ConstructorArguments.Add(new CAArgument(module.CorLibTypes.String, text)); module.CustomAttributes.Add(attr); } diff --git a/Confuser2.sln b/Confuser2.sln index bec181e0..9dc5bf91 100644 --- a/Confuser2.sln +++ b/Confuser2.sln @@ -213,6 +213,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AntiDebug.Test", "Tests\Ant EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SymbolMapReuse.Test", "Tests\SymbolMapReuse.Test\SymbolMapReuse.Test.csproj", "{591069EF-617C-4284-A968-5DB55BE1E1EF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Watermark.Test", "Tests\Watermark.Test\Watermark.Test.csproj", "{0C937593-1C74-4576-B79F-D99642612484}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1399,6 +1401,18 @@ Global {591069EF-617C-4284-A968-5DB55BE1E1EF}.Release|x64.Build.0 = Release|Any CPU {591069EF-617C-4284-A968-5DB55BE1E1EF}.Release|x86.ActiveCfg = Release|Any CPU {591069EF-617C-4284-A968-5DB55BE1E1EF}.Release|x86.Build.0 = Release|Any CPU + {0C937593-1C74-4576-B79F-D99642612484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C937593-1C74-4576-B79F-D99642612484}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C937593-1C74-4576-B79F-D99642612484}.Debug|x64.ActiveCfg = Debug|Any CPU + {0C937593-1C74-4576-B79F-D99642612484}.Debug|x64.Build.0 = Debug|Any CPU + {0C937593-1C74-4576-B79F-D99642612484}.Debug|x86.ActiveCfg = Debug|Any CPU + {0C937593-1C74-4576-B79F-D99642612484}.Debug|x86.Build.0 = Debug|Any CPU + {0C937593-1C74-4576-B79F-D99642612484}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C937593-1C74-4576-B79F-D99642612484}.Release|Any CPU.Build.0 = Release|Any CPU + {0C937593-1C74-4576-B79F-D99642612484}.Release|x64.ActiveCfg = Release|Any CPU + {0C937593-1C74-4576-B79F-D99642612484}.Release|x64.Build.0 = Release|Any CPU + {0C937593-1C74-4576-B79F-D99642612484}.Release|x86.ActiveCfg = Release|Any CPU + {0C937593-1C74-4576-B79F-D99642612484}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1493,6 +1507,7 @@ Global {1B22CAAE-FC4A-478D-BD68-D29A3081F938} = {356BDB31-853E-43BB-8F9A-D8AC08F69EBB} {47197200-B8CB-400A-B1BD-84975FEC8C28} = {356BDB31-853E-43BB-8F9A-D8AC08F69EBB} {591069EF-617C-4284-A968-5DB55BE1E1EF} = {356BDB31-853E-43BB-8F9A-D8AC08F69EBB} + {0C937593-1C74-4576-B79F-D99642612484} = {356BDB31-853E-43BB-8F9A-D8AC08F69EBB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0D937D9E-E04B-4A68-B639-D4260473A388} diff --git a/Tests/Watermark.Test/Watermark.Test.csproj b/Tests/Watermark.Test/Watermark.Test.csproj new file mode 100644 index 00000000..48ef32c6 --- /dev/null +++ b/Tests/Watermark.Test/Watermark.Test.csproj @@ -0,0 +1,13 @@ + + + + net462 + false + + + + + + + + diff --git a/Tests/Watermark.Test/WatermarkTest.cs b/Tests/Watermark.Test/WatermarkTest.cs new file mode 100644 index 00000000..f83f5f44 --- /dev/null +++ b/Tests/Watermark.Test/WatermarkTest.cs @@ -0,0 +1,58 @@ +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Confuser.Core; +using Confuser.Core.Project; +using Confuser.UnitTest; +using dnlib.DotNet; +using Xunit; +using Xunit.Abstractions; + +namespace Watermark.Test { + public sealed class WatermarkTest : TestBase { + public WatermarkTest(ITestOutputHelper outputHelper) : base(outputHelper) { } + + // Issue #69: the watermark is opt-in. An obfuscation that does not request it must produce + // output with no ConfusedByAttribute fingerprint. + [Fact] + [Trait("Category", "Protection")] + [Trait("Issue", "https://github.com/mcpolo99/ConfuserExx/issues/69")] + public Task Watermark_NotRequested_ProducesNoFingerprintAttribute() => + Run("AntiTamper.exe", + new[] { "This is a test." }, + new SettingItem("rename"), + "_wm_off", + postProcessAction: outputPath => { + using (var module = ModuleDefMD.Load(Path.Combine(outputPath, "AntiTamper.exe"))) { + Assert.DoesNotContain(module.CustomAttributes, a => a.TypeFullName == "ConfusedByAttribute"); + Assert.DoesNotContain(module.GetTypes(), t => t.Name == "ConfusedByAttribute"); + } + return Task.CompletedTask; + }); + + // When explicitly enabled with custom text and attribute name, the watermark must use them + // (so it can be branded or disguised instead of the default ConfuserEx fingerprint). + [Fact] + [Trait("Category", "Protection")] + [Trait("Issue", "https://github.com/mcpolo99/ConfuserExx/issues/69")] + public Task Watermark_CustomTextAndName_AppliesConfiguredAttribute() => + Run("AntiTamper.exe", + new[] { "This is a test." }, + new SettingItem("watermark") { + { "text", "MyCorp Security" }, + { "attributeName", "SecurityStampAttribute" } + }, + "_wm_custom", + postProcessAction: outputPath => { + using (var module = ModuleDefMD.Load(Path.Combine(outputPath, "AntiTamper.exe"))) { + var attr = module.CustomAttributes.FirstOrDefault(a => a.TypeFullName == "SecurityStampAttribute"); + Assert.NotNull(attr); + Assert.Equal("MyCorp Security", attr.ConstructorArguments[0].Value?.ToString()); + + // The default fingerprint name must not appear. + Assert.DoesNotContain(module.CustomAttributes, a => a.TypeFullName == "ConfusedByAttribute"); + } + return Task.CompletedTask; + }); + } +} diff --git a/docs/protections.md b/docs/protections.md index 3851215c..48665fd9 100644 --- a/docs/protections.md +++ b/docs/protections.md @@ -277,6 +277,31 @@ Compresses the entire output assembly and wraps it in a native stub that decompr ``` +--- + +### Watermark + +**ID:** `watermark` + +Adds a small custom attribute to the assembly marking that it was protected. It is **opt-in** — +obfuscated output carries no watermark unless this protection is explicitly enabled, so the +default output has no identifiable ConfuserEx fingerprint. + +**Options:** + +| Name | Values | Default | Description | +|------|--------|---------|-------------| +| `text` | any string | ConfuserEx version | The value stored in the watermark attribute. | +| `attributeName` | any identifier | `ConfusedByAttribute` | The name of the injected attribute type. | + +```xml + + + + + +``` + ## Combining Protections Protections stack. You can start from a preset and add/remove individual protections: