Skip to content

Commit ee3a56c

Browse files
mcpolo99RandomCrocodile
andauthored
feature: make watermark opt-in and configurable (#69) (#97)
Phase 1 of the unique-obfuscation-identity work (#69). The 'ConfusedByAttribute' watermark was force-inserted into every obfuscated assembly and carried the ConfuserEx version — an identifiable fingerprint that de4dot and AV heuristics match. It is now opt-in: - ConfuserEngine no longer force-inserts the watermark rule, so default output carries no fingerprint attribute unless <protection id="watermark" /> is set. - WatermarkingPhase reads two parameters: 'text' (attribute value, defaults to the version) and 'attributeName' (attribute type name, defaults to ConfusedByAttribute), so the watermark can be branded or disguised. - Documented the previously-undocumented protection in docs/protections.md. Behaviour change: assemblies are no longer watermarked by default. Re-enable per project with the watermark protection. Test: Watermark.Test verifies no fingerprint attribute when not requested, and the custom text/name are applied when enabled. Co-authored-by: RandomCrocodile <mawi@polosab.com>
1 parent 495ee4c commit ee3a56c

6 files changed

Lines changed: 124 additions & 7 deletions

File tree

Confuser.Core/ConfuserEngine.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,9 @@ static void RunInternal(ConfuserParameters parameters, CancellationToken token)
9090

9191
bool ok = false;
9292
try {
93-
// Enable watermarking by default
94-
context.Project.Rules.Insert(0, new Rule {
95-
new SettingItem<Protection>(WatermarkingProtection._Id)
96-
});
93+
// Watermarking is opt-in (issue #69): the "watermark" protection is no longer
94+
// force-enabled, so obfuscated output carries no ConfuserEx fingerprint attribute
95+
// unless a project explicitly requests it via <protection id="watermark" />.
9796

9897
var asmResolver = new ConfuserAssemblyResolver { EnableTypeDefCache = true };
9998
asmResolver.DefaultModuleContext = new ModuleContext(asmResolver);

Confuser.Core/WatermarkingProtection.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,17 @@ protected internal override void Execute(ConfuserContext context, ProtectionPara
4848

4949
context.Logger.LogDebug("Watermarking...");
5050
foreach (var module in parameters.Targets.OfType<ModuleDef>()) {
51+
// Both are configurable so users can brand (or disguise) the watermark instead of
52+
// carrying the identifiable default ConfuserEx fingerprint.
53+
var attributeName = parameters.GetParameter(context, module, "attributeName", "ConfusedByAttribute");
54+
if (string.IsNullOrEmpty(attributeName))
55+
attributeName = "ConfusedByAttribute";
56+
var text = parameters.GetParameter(context, module, "text", ConfuserEngine.Version);
57+
5158
var attrRef = module.CorLibTypes.GetTypeRef("System", "Attribute");
52-
var attrType = module.FindNormal("ConfusedByAttribute");
59+
var attrType = module.FindNormal(attributeName);
5360
if (attrType == null) {
54-
attrType = new TypeDefUser("", "ConfusedByAttribute", attrRef);
61+
attrType = new TypeDefUser("", attributeName, attrRef);
5562
module.Types.Add(attrType);
5663
marker.Mark(attrType, Parent);
5764
}
@@ -75,7 +82,7 @@ protected internal override void Execute(ConfuserContext context, ProtectionPara
7582
}
7683

7784
var attr = new CustomAttribute(ctor);
78-
attr.ConstructorArguments.Add(new CAArgument(module.CorLibTypes.String, ConfuserEngine.Version));
85+
attr.ConstructorArguments.Add(new CAArgument(module.CorLibTypes.String, text));
7986

8087
module.CustomAttributes.Add(attr);
8188
}

Confuser2.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AntiDebug.Test", "Tests\Ant
213213
EndProject
214214
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SymbolMapReuse.Test", "Tests\SymbolMapReuse.Test\SymbolMapReuse.Test.csproj", "{591069EF-617C-4284-A968-5DB55BE1E1EF}"
215215
EndProject
216+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Watermark.Test", "Tests\Watermark.Test\Watermark.Test.csproj", "{0C937593-1C74-4576-B79F-D99642612484}"
217+
EndProject
216218
Global
217219
GlobalSection(SolutionConfigurationPlatforms) = preSolution
218220
Debug|Any CPU = Debug|Any CPU
@@ -1399,6 +1401,18 @@ Global
13991401
{591069EF-617C-4284-A968-5DB55BE1E1EF}.Release|x64.Build.0 = Release|Any CPU
14001402
{591069EF-617C-4284-A968-5DB55BE1E1EF}.Release|x86.ActiveCfg = Release|Any CPU
14011403
{591069EF-617C-4284-A968-5DB55BE1E1EF}.Release|x86.Build.0 = Release|Any CPU
1404+
{0C937593-1C74-4576-B79F-D99642612484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1405+
{0C937593-1C74-4576-B79F-D99642612484}.Debug|Any CPU.Build.0 = Debug|Any CPU
1406+
{0C937593-1C74-4576-B79F-D99642612484}.Debug|x64.ActiveCfg = Debug|Any CPU
1407+
{0C937593-1C74-4576-B79F-D99642612484}.Debug|x64.Build.0 = Debug|Any CPU
1408+
{0C937593-1C74-4576-B79F-D99642612484}.Debug|x86.ActiveCfg = Debug|Any CPU
1409+
{0C937593-1C74-4576-B79F-D99642612484}.Debug|x86.Build.0 = Debug|Any CPU
1410+
{0C937593-1C74-4576-B79F-D99642612484}.Release|Any CPU.ActiveCfg = Release|Any CPU
1411+
{0C937593-1C74-4576-B79F-D99642612484}.Release|Any CPU.Build.0 = Release|Any CPU
1412+
{0C937593-1C74-4576-B79F-D99642612484}.Release|x64.ActiveCfg = Release|Any CPU
1413+
{0C937593-1C74-4576-B79F-D99642612484}.Release|x64.Build.0 = Release|Any CPU
1414+
{0C937593-1C74-4576-B79F-D99642612484}.Release|x86.ActiveCfg = Release|Any CPU
1415+
{0C937593-1C74-4576-B79F-D99642612484}.Release|x86.Build.0 = Release|Any CPU
14021416
EndGlobalSection
14031417
GlobalSection(SolutionProperties) = preSolution
14041418
HideSolutionNode = FALSE
@@ -1493,6 +1507,7 @@ Global
14931507
{1B22CAAE-FC4A-478D-BD68-D29A3081F938} = {356BDB31-853E-43BB-8F9A-D8AC08F69EBB}
14941508
{47197200-B8CB-400A-B1BD-84975FEC8C28} = {356BDB31-853E-43BB-8F9A-D8AC08F69EBB}
14951509
{591069EF-617C-4284-A968-5DB55BE1E1EF} = {356BDB31-853E-43BB-8F9A-D8AC08F69EBB}
1510+
{0C937593-1C74-4576-B79F-D99642612484} = {356BDB31-853E-43BB-8F9A-D8AC08F69EBB}
14961511
EndGlobalSection
14971512
GlobalSection(ExtensibilityGlobals) = postSolution
14981513
SolutionGuid = {0D937D9E-E04B-4A68-B639-D4260473A388}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net462</TargetFramework>
5+
<IsPackable>false</IsPackable>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<ProjectReference Include="..\Confuser.UnitTest\Confuser.UnitTest.csproj" />
10+
<ProjectReference Include="..\AntiTamper\AntiTamper.csproj" />
11+
</ItemGroup>
12+
13+
</Project>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System.IO;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using Confuser.Core;
5+
using Confuser.Core.Project;
6+
using Confuser.UnitTest;
7+
using dnlib.DotNet;
8+
using Xunit;
9+
using Xunit.Abstractions;
10+
11+
namespace Watermark.Test {
12+
public sealed class WatermarkTest : TestBase {
13+
public WatermarkTest(ITestOutputHelper outputHelper) : base(outputHelper) { }
14+
15+
// Issue #69: the watermark is opt-in. An obfuscation that does not request it must produce
16+
// output with no ConfusedByAttribute fingerprint.
17+
[Fact]
18+
[Trait("Category", "Protection")]
19+
[Trait("Issue", "https://github.com/mcpolo99/ConfuserExx/issues/69")]
20+
public Task Watermark_NotRequested_ProducesNoFingerprintAttribute() =>
21+
Run("AntiTamper.exe",
22+
new[] { "This is a test." },
23+
new SettingItem<Protection>("rename"),
24+
"_wm_off",
25+
postProcessAction: outputPath => {
26+
using (var module = ModuleDefMD.Load(Path.Combine(outputPath, "AntiTamper.exe"))) {
27+
Assert.DoesNotContain(module.CustomAttributes, a => a.TypeFullName == "ConfusedByAttribute");
28+
Assert.DoesNotContain(module.GetTypes(), t => t.Name == "ConfusedByAttribute");
29+
}
30+
return Task.CompletedTask;
31+
});
32+
33+
// When explicitly enabled with custom text and attribute name, the watermark must use them
34+
// (so it can be branded or disguised instead of the default ConfuserEx fingerprint).
35+
[Fact]
36+
[Trait("Category", "Protection")]
37+
[Trait("Issue", "https://github.com/mcpolo99/ConfuserExx/issues/69")]
38+
public Task Watermark_CustomTextAndName_AppliesConfiguredAttribute() =>
39+
Run("AntiTamper.exe",
40+
new[] { "This is a test." },
41+
new SettingItem<Protection>("watermark") {
42+
{ "text", "MyCorp Security" },
43+
{ "attributeName", "SecurityStampAttribute" }
44+
},
45+
"_wm_custom",
46+
postProcessAction: outputPath => {
47+
using (var module = ModuleDefMD.Load(Path.Combine(outputPath, "AntiTamper.exe"))) {
48+
var attr = module.CustomAttributes.FirstOrDefault(a => a.TypeFullName == "SecurityStampAttribute");
49+
Assert.NotNull(attr);
50+
Assert.Equal("MyCorp Security", attr.ConstructorArguments[0].Value?.ToString());
51+
52+
// The default fingerprint name must not appear.
53+
Assert.DoesNotContain(module.CustomAttributes, a => a.TypeFullName == "ConfusedByAttribute");
54+
}
55+
return Task.CompletedTask;
56+
});
57+
}
58+
}

docs/protections.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,31 @@ Compresses the entire output assembly and wraps it in a native stub that decompr
277277
</packer>
278278
```
279279

280+
---
281+
282+
### Watermark
283+
284+
**ID:** `watermark`
285+
286+
Adds a small custom attribute to the assembly marking that it was protected. It is **opt-in**
287+
obfuscated output carries no watermark unless this protection is explicitly enabled, so the
288+
default output has no identifiable ConfuserEx fingerprint.
289+
290+
**Options:**
291+
292+
| Name | Values | Default | Description |
293+
|------|--------|---------|-------------|
294+
| `text` | any string | ConfuserEx version | The value stored in the watermark attribute. |
295+
| `attributeName` | any identifier | `ConfusedByAttribute` | The name of the injected attribute type. |
296+
297+
```xml
298+
<!-- Branded watermark -->
299+
<protection id="watermark">
300+
<argument name="text" value="MyCompany Security" />
301+
<argument name="attributeName" value="SecurityStampAttribute" />
302+
</protection>
303+
```
304+
280305
## Combining Protections
281306

282307
Protections stack. You can start from a preset and add/remove individual protections:

0 commit comments

Comments
 (0)