Skip to content

Commit 495ee4c

Browse files
mcpolo99RandomCrocodile
andauthored
feature: symbol map reuse for consistent re-obfuscation (#26) (#96)
* feature: symbol map reuse for consistent re-obfuscation (#26) Implements InputSymbolMap (upstream mkaring#168 / yck1509#680) so re-obfuscation produces the same obfuscated names across builds — needed to patch already-deployed obfuscated assemblies without replacing every module. - ConfuserProject gains an inputSymbolMap attribute (Save/Load/Clone + XSD). - NameService loads that map on construction and pre-populates the name maps, so the existing reuse path in ObfuscateName returns the previous obfuscated name for any original that appears in the map. Missing files / parse errors log a warning and fall back to fresh names. - When an input map is in use, names for all rename modes are recorded (not just Decodable/Sequential), so the exported symbols.map stays complete and chainable. - CLI --map flag sets InputSymbolMap for non-project runs. Behaviour is unchanged when no map is supplied (hasInputMap gates all new paths). Test: obfuscate twice with DIFFERENT seeds — with the input map the names match, and a no-map control with the same second seed produces different names, proving reuse is what causes the consistency. Existing renamer tests unaffected. * feature: add Input Symbol Map field to GUI advanced settings (#26) Completes the #26 GUI follow-up. The project's Advanced Settings dialog now has an 'Input Symbol Map' field (text box with file drag-drop + a browse button) bound to ProjectVM.InputSymbolMap, so users can select a previous symbols.map from the GUI for consistent re-obfuscation. Round-trips through the existing project save/load. --------- Co-authored-by: RandomCrocodile <mawi@polosab.com>
1 parent 44e6f29 commit 495ee4c

10 files changed

Lines changed: 232 additions & 7 deletions

File tree

Confuser.CLI/Program.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ static int Main(string[] args) {
2828
bool quiet = false;
2929
bool dumpRequested = false;
3030
string dumpPath = null;
31+
string inputMap = null;
3132
int verbosity = 0;
3233
string outDir = null;
3334
string snKeyPath = null;
@@ -65,6 +66,9 @@ static int Main(string[] args) {
6566
}, {
6667
"dump:", "write a diagnostic report (optionally to the given file).",
6768
value => { dumpRequested = true; if (!string.IsNullOrEmpty(value)) dumpPath = value; }
69+
}, {
70+
"map=", "reuse names from a previous symbol map for consistent re-obfuscation.",
71+
value => { inputMap = value; }
6872
}
6973
};
7074

@@ -147,6 +151,9 @@ static int Main(string[] args) {
147151
parameters.Project = proj;
148152
}
149153

154+
if (inputMap != null && parameters.Project != null)
155+
parameters.Project.InputSymbolMap = inputMap;
156+
150157
int retVal = RunProject(parameters, quiet, verbosity, dumpRequested, dumpPath);
151158

152159
if (NeedPause() && !noPause) {
@@ -284,6 +291,7 @@ static void PrintUsage() {
284291
WriteLine(" -v|verbose : increase verbosity (-v debug, -vv trace).");
285292
WriteLine(" -q|quiet : only show warnings and errors.");
286293
WriteLine(" -dump : write a diagnostic report (-dump=<file> for a custom path).");
294+
WriteLine(" -map : reuse names from a previous symbol map for consistent re-obfuscation.");
287295
}
288296

289297
static void WriteLineWithColor(ConsoleColor color, string txt) {

Confuser.Core/Project/ConfuserPrj.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
<xs:attribute name="baseDir" type="xs:string" use="required" />
7676
<xs:attribute name="seed" type="xs:string" use="optional" />
7777
<xs:attribute name="debug" type="xs:boolean" default="false" />
78+
<xs:attribute name="inputSymbolMap" type="xs:string" use="optional" />
7879
</xs:complexType>
7980
</xs:element>
8081
</xs:schema>

Confuser.Core/Project/ConfuserProject.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,14 @@ public ConfuserProject() {
528528
/// <value><c>true</c> if debug symbols are generated; otherwise, <c>false</c>.</value>
529529
public bool Debug { get; set; }
530530

531+
/// <summary>
532+
/// Gets or sets the path to a symbol map from a previous obfuscation run. When set, the
533+
/// renamer reuses the obfuscated names recorded in that map, so re-obfuscation produces
534+
/// consistent names across builds (e.g. to patch already-deployed obfuscated assemblies).
535+
/// </summary>
536+
/// <value>The path to the input symbol map, or <c>null</c> to generate fresh names.</value>
537+
public string InputSymbolMap { get; set; }
538+
531539
/// <summary>
532540
/// Gets or sets the output directory.
533541
/// </summary>
@@ -594,6 +602,12 @@ public XmlDocument Save() {
594602
elem.Attributes.Append(debugAttr);
595603
}
596604

605+
if (InputSymbolMap != null) {
606+
XmlAttribute mapAttr = xmlDoc.CreateAttribute("inputSymbolMap");
607+
mapAttr.Value = InputSymbolMap;
608+
elem.Attributes.Append(mapAttr);
609+
}
610+
597611
foreach (Rule i in Rules)
598612
elem.AppendChild(i.Save(xmlDoc));
599613

@@ -655,6 +669,11 @@ public void Load(XmlDocument doc, string baseDirRoot = null) {
655669
else
656670
Debug = false;
657671

672+
if (docElem.Attributes["inputSymbolMap"] != null)
673+
InputSymbolMap = docElem.Attributes["inputSymbolMap"].Value.NullIfEmpty();
674+
else
675+
InputSymbolMap = null;
676+
658677
Packer = null;
659678
Clear();
660679
ProbePaths.Clear();
@@ -724,6 +743,7 @@ public ConfuserProject Clone() {
724743
var ret = new ConfuserProject();
725744
ret.Seed = Seed;
726745
ret.Debug = Debug;
746+
ret.InputSymbolMap = InputSymbolMap;
727747
ret.OutputDirectory = OutputDirectory;
728748
ret.BaseDirectory = BaseDirectory;
729749
ret.Packer = Packer == null ? null : Packer.Clone();

Confuser.Renamer/NameService.cs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Globalization;
4+
using System.IO;
45
using System.Linq;
56
using System.Text;
67
using Confuser.Core;
78
using Confuser.Core.Services;
89
using Confuser.Renamer.Analyzers;
910
using Confuser.Renamer.Properties;
1011
using dnlib.DotNet;
12+
using Microsoft.Extensions.Logging;
1113

1214
namespace Confuser.Renamer {
1315
public interface INameService {
@@ -67,13 +69,15 @@ internal class NameService : INameService {
6769
readonly Dictionary<string, string> _originalToObfuscatedNameMap = new Dictionary<string, string>();
6870
readonly Dictionary<string, string> _obfuscatedToOriginalNameMap = new Dictionary<string, string>();
6971
readonly Dictionary<string, string> _prefixesMap = new Dictionary<string, string>();
72+
readonly bool hasInputMap;
7073
internal ReversibleRenamer reversibleRenamer;
7174

7275
public NameService(ConfuserContext context) {
7376
this.context = context;
7477
storage = new VTableStorage(context.Logger);
7578
random = context.Registry.GetService<IRandomService>().GetRandomGenerator(NameProtection._FullId);
7679
nameSeed = random.NextBytes(20);
80+
hasInputMap = LoadInputSymbolMap();
7781

7882
Renamers = new List<IRenamer> {
7983
new InterReferenceAnalyzer(),
@@ -89,6 +93,57 @@ public NameService(ConfuserContext context) {
8993

9094
public IList<IRenamer> Renamers { get; private set; }
9195

96+
/// <summary>
97+
/// Loads the project's input symbol map (if any) so previously assigned obfuscated names
98+
/// are reused, giving consistent names across re-obfuscation runs. The file uses the same
99+
/// "{obfuscated}\t{original}" format as the exported <c>symbols.map</c>.
100+
/// </summary>
101+
/// <returns><c>true</c> if a non-empty map was loaded; otherwise <c>false</c>.</returns>
102+
bool LoadInputSymbolMap() {
103+
var mapPath = context.Project.InputSymbolMap;
104+
if (string.IsNullOrEmpty(mapPath))
105+
return false;
106+
107+
try {
108+
if (!Path.IsPathRooted(mapPath))
109+
mapPath = Path.Combine(context.BaseDirectory, mapPath);
110+
111+
if (!File.Exists(mapPath)) {
112+
context.Logger.LogWarning("Input symbol map not found: '{0}'. Names will be generated fresh.", mapPath);
113+
return false;
114+
}
115+
116+
int count = 0;
117+
foreach (var line in File.ReadAllLines(mapPath)) {
118+
if (string.IsNullOrWhiteSpace(line))
119+
continue;
120+
121+
int tab = line.IndexOf('\t');
122+
if (tab <= 0)
123+
continue;
124+
125+
var obfuscated = line.Substring(0, tab);
126+
var original = line.Substring(tab + 1);
127+
128+
// The map is "{obfuscated}\t{original}"; reuse looks up original -> obfuscated.
129+
if (!_obfuscatedToOriginalNameMap.ContainsKey(obfuscated))
130+
_obfuscatedToOriginalNameMap[obfuscated] = original;
131+
if (!_originalToObfuscatedNameMap.ContainsKey(original)) {
132+
_originalToObfuscatedNameMap[original] = obfuscated;
133+
count++;
134+
}
135+
}
136+
137+
context.Logger.LogInformation(
138+
"Loaded {0} name mappings from input symbol map for consistent re-obfuscation.", count);
139+
return count > 0;
140+
}
141+
catch (Exception ex) {
142+
context.Logger.LogWarning(ex, "Failed to load input symbol map. Names will be generated fresh.");
143+
return false;
144+
}
145+
}
146+
92147
public VTableStorage GetVTables() {
93148
return storage;
94149
}
@@ -255,7 +310,10 @@ public string ObfuscateName(string format, string name, RenameMode mode, bool pr
255310
hash = Utils.SHA1(hash);
256311
}
257312

258-
if (mode == RenameMode.Decodable || mode == RenameMode.Sequential) {
313+
// Decodable/Sequential always record the mapping (they need it to be reversible).
314+
// When an input symbol map is in use, record every mode so newly-generated names are
315+
// reused on the next run and the exported map stays complete and chainable.
316+
if (mode == RenameMode.Decodable || mode == RenameMode.Sequential || hasInputMap) {
259317
_obfuscatedToOriginalNameMap.Add(newName, name);
260318
_originalToObfuscatedNameMap.Add(name, newName);
261319
}

Confuser2.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Confuser.Analyzers.Test", "
211211
EndProject
212212
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AntiDebug.Test", "Tests\AntiDebug.Test\AntiDebug.Test.csproj", "{47197200-B8CB-400A-B1BD-84975FEC8C28}"
213213
EndProject
214+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SymbolMapReuse.Test", "Tests\SymbolMapReuse.Test\SymbolMapReuse.Test.csproj", "{591069EF-617C-4284-A968-5DB55BE1E1EF}"
215+
EndProject
214216
Global
215217
GlobalSection(SolutionConfigurationPlatforms) = preSolution
216218
Debug|Any CPU = Debug|Any CPU
@@ -1385,6 +1387,18 @@ Global
13851387
{47197200-B8CB-400A-B1BD-84975FEC8C28}.Release|x64.Build.0 = Release|Any CPU
13861388
{47197200-B8CB-400A-B1BD-84975FEC8C28}.Release|x86.ActiveCfg = Release|Any CPU
13871389
{47197200-B8CB-400A-B1BD-84975FEC8C28}.Release|x86.Build.0 = Release|Any CPU
1390+
{591069EF-617C-4284-A968-5DB55BE1E1EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1391+
{591069EF-617C-4284-A968-5DB55BE1E1EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
1392+
{591069EF-617C-4284-A968-5DB55BE1E1EF}.Debug|x64.ActiveCfg = Debug|Any CPU
1393+
{591069EF-617C-4284-A968-5DB55BE1E1EF}.Debug|x64.Build.0 = Debug|Any CPU
1394+
{591069EF-617C-4284-A968-5DB55BE1E1EF}.Debug|x86.ActiveCfg = Debug|Any CPU
1395+
{591069EF-617C-4284-A968-5DB55BE1E1EF}.Debug|x86.Build.0 = Debug|Any CPU
1396+
{591069EF-617C-4284-A968-5DB55BE1E1EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
1397+
{591069EF-617C-4284-A968-5DB55BE1E1EF}.Release|Any CPU.Build.0 = Release|Any CPU
1398+
{591069EF-617C-4284-A968-5DB55BE1E1EF}.Release|x64.ActiveCfg = Release|Any CPU
1399+
{591069EF-617C-4284-A968-5DB55BE1E1EF}.Release|x64.Build.0 = Release|Any CPU
1400+
{591069EF-617C-4284-A968-5DB55BE1E1EF}.Release|x86.ActiveCfg = Release|Any CPU
1401+
{591069EF-617C-4284-A968-5DB55BE1E1EF}.Release|x86.Build.0 = Release|Any CPU
13881402
EndGlobalSection
13891403
GlobalSection(SolutionProperties) = preSolution
13901404
HideSolutionNode = FALSE
@@ -1478,6 +1492,7 @@ Global
14781492
{4458415A-0F5E-4136-B723-7A67955D6047} = {356BDB31-853E-43BB-8F9A-D8AC08F69EBB}
14791493
{1B22CAAE-FC4A-478D-BD68-D29A3081F938} = {356BDB31-853E-43BB-8F9A-D8AC08F69EBB}
14801494
{47197200-B8CB-400A-B1BD-84975FEC8C28} = {356BDB31-853E-43BB-8F9A-D8AC08F69EBB}
1495+
{591069EF-617C-4284-A968-5DB55BE1E1EF} = {356BDB31-853E-43BB-8F9A-D8AC08F69EBB}
14811496
EndGlobalSection
14821497
GlobalSection(ExtensibilityGlobals) = postSolution
14831498
SolutionGuid = {0D937D9E-E04B-4A68-B639-D4260473A388}

ConfuserEx/ViewModel/Project/ProjectVM.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ public bool Debug {
5757
set { SetProperty(proj.Debug != value, val => proj.Debug = val, value, "Debug"); }
5858
}
5959

60+
public string InputSymbolMap {
61+
get { return proj.InputSymbolMap; }
62+
set { SetProperty(proj.InputSymbolMap != value, val => proj.InputSymbolMap = val, value, "InputSymbolMap"); }
63+
}
64+
6065
public string BaseDirectory {
6166
get { return proj.BaseDirectory; }
6267
set { SetProperty(proj.BaseDirectory != value, val => proj.BaseDirectory = val, value, "BaseDirectory"); }

ConfuserEx/Views/ProjectTabAdvancedView.xaml

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,34 @@
1212
<ColumnDefinition Width="36px" />
1313
</Grid.ColumnDefinitions>
1414
<Grid.RowDefinitions>
15+
<RowDefinition Height="36px" />
1516
<RowDefinition Height="36px" />
1617
<RowDefinition Height="*" />
1718
</Grid.RowDefinitions>
1819

19-
<Label Grid.Row="0" Grid.Column="0" Content="Probe Paths :" Margin="5" VerticalAlignment="Center" />
20-
<ListBox Grid.Row="1" Grid.Column="0" Margin="5" x:Name="ProbePaths" ItemsSource="{Binding ProbePaths}"
20+
<Grid Grid.Row="0" Grid.ColumnSpan="4">
21+
<Grid.ColumnDefinitions>
22+
<ColumnDefinition Width="120px" />
23+
<ColumnDefinition Width="*" />
24+
<ColumnDefinition Width="36px" />
25+
</Grid.ColumnDefinitions>
26+
<Label Grid.Column="0" Content="Input Symbol Map :" Margin="5" HorizontalContentAlignment="Right"
27+
VerticalContentAlignment="Center" />
28+
<TextBox Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
29+
Text="{Binding InputSymbolMap, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
30+
local:Skin.EmptyPrompt="Reuse a previous symbols.map for consistent re-obfuscation"
31+
local:FileDragDrop.Command="{x:Static local:FileDragDrop.FileCmd}" />
32+
<Button Grid.Column="2" Margin="5" VerticalAlignment="Center" Height="26" x:Name="ChooseSymbolMap">
33+
<TextBlock FontSize="14px" FontFamily="{DynamicResource FontAwesome}" Text="&#xf141;" Height="10px"
34+
TextOptions.TextRenderingMode="GrayScale" />
35+
</Button>
36+
</Grid>
37+
38+
<Label Grid.Row="1" Grid.Column="0" Content="Probe Paths :" Margin="5" VerticalAlignment="Center" />
39+
<ListBox Grid.Row="2" Grid.Column="0" Margin="5" x:Name="ProbePaths" ItemsSource="{Binding ProbePaths}"
2140
local:FileDragDrop.Command="{x:Static local:FileDragDrop.DirectoryCmd}"
2241
ScrollViewer.CanContentScroll="False" />
23-
<StackPanel Grid.Row="1" Grid.Column="1">
42+
<StackPanel Grid.Row="2" Grid.Column="1">
2443
<Button Height="26" Margin="5" DockPanel.Dock="Top" x:Name="AddProbe">
2544
<TextBlock FontSize="14px" FontFamily="{DynamicResource FontAwesome}" Text="&#xf067;" Height="12px"
2645
TextOptions.TextRenderingMode="GrayScale" />
@@ -31,11 +50,11 @@
3150
</Button>
3251
</StackPanel>
3352

34-
<Label Grid.Row="0" Grid.Column="2" Content="Plugins :" Margin="5" VerticalAlignment="Center" />
35-
<ListBox Grid.Row="1" Grid.Column="2" Margin="5" x:Name="PluginPaths" ItemsSource="{Binding Plugins}"
53+
<Label Grid.Row="1" Grid.Column="2" Content="Plugins :" Margin="5" VerticalAlignment="Center" />
54+
<ListBox Grid.Row="2" Grid.Column="2" Margin="5" x:Name="PluginPaths" ItemsSource="{Binding Plugins}"
3655
local:FileDragDrop.Command="{x:Static local:FileDragDrop.FileCmd}"
3756
ScrollViewer.CanContentScroll="False" />
38-
<StackPanel Grid.Row="1" Grid.Column="3">
57+
<StackPanel Grid.Row="2" Grid.Column="3">
3958
<Button Height="26" Margin="5" DockPanel.Dock="Top" x:Name="AddPlugin">
4059
<TextBlock FontSize="14px" FontFamily="{DynamicResource FontAwesome}" Text="&#xf067;" Height="12px"
4160
TextOptions.TextRenderingMode="GrayScale" />

ConfuserEx/Views/ProjectTabAdvancedView.xaml.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ public ProjectTabAdvancedView(ProjectVM project) {
1818
public override void OnApplyTemplate() {
1919
base.OnApplyTemplate();
2020

21+
ChooseSymbolMap.Command = new RelayCommand(() => {
22+
var ofd = new VistaOpenFileDialog();
23+
ofd.Filter = "Symbol map (*.map)|*.map|All Files (*.*)|*.*";
24+
if (ofd.ShowDialog() ?? false)
25+
project.InputSymbolMap = ofd.FileName;
26+
});
27+
2128
AddPlugin.Command = new RelayCommand(() => {
2229
var ofd = new VistaOpenFileDialog();
2330
ofd.Filter = ".NET assemblies (*.exe, *.dll)|*.exe;*.dll|All Files (*.*)|*.*";
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>

0 commit comments

Comments
 (0)