Files
2026-06-18 09:05:00 -07:00

329 lines
12 KiB
C#

using System;
using System.IO;
using System.Diagnostics;
using dnlib.DotNet;
using dnlib.DotNet.Writer;
using System.Collections.Generic;
using UnConfuserEx.Protections;
using log4net;
using log4net.Config;
using UnConfuserEx.Protections.Delegates;
using UnConfuserEx.Protections.AntiDebug;
using UnConfuserEx.Protections.AntiDump;
using UnConfuserEx.Protections.AntiTamper;
using UnConfuserEx.Protections.Compressor;
using dnlib.DotNet.Emit;
namespace UnConfuserEx
{
internal class UnConfuserEx
{
static ILog Logger = LogManager.GetLogger("UnConfuserEx");
internal static void NormalizeMethodBodiesForWrite(ModuleDef module)
{
foreach (var type in module.GetTypes())
{
foreach (var method in type.Methods)
{
var body = method.Body;
if (body == null)
continue;
body.SimplifyBranches();
body.OptimizeBranches();
}
}
}
internal static void PrepareModuleForWrite(ModuleDef module)
{
NormalizeMethodBodiesForWrite(module);
}
sealed class FailureInfo
{
public required string ProtectionName { get; init; }
public required string Stage { get; init; }
public required string Message { get; init; }
public string? ExceptionType { get; init; }
}
static void LogFailureSummary(string inputPath, string outputPath, ModuleDefMD module, IList<string> detectedProtections, IList<FailureInfo> failures)
{
Logger.Warn("----- FAILURE SUMMARY BEGIN -----");
Logger.Warn($"Input path: {inputPath}");
Logger.Warn($"Output path: {outputPath}");
Logger.Warn($"Module kind: {module.Kind}");
Logger.Warn($"IL only: {module.IsILOnly}");
Logger.Warn($"Entry point: {(module.EntryPoint?.FullName ?? "<null>")}");
Logger.Warn($"Detected protections: {(detectedProtections.Count == 0 ? "<none>" : string.Join(", ", detectedProtections))}");
Logger.Warn($"Failure count: {failures.Count}");
for (int i = 0; i < failures.Count; i++)
{
var failure = failures[i];
Logger.Warn($"Failure {i + 1}:");
Logger.Warn($" Protection: {failure.ProtectionName}");
Logger.Warn($" Stage: {failure.Stage}");
Logger.Warn($" Message: {failure.Message}");
if (!string.IsNullOrWhiteSpace(failure.ExceptionType))
Logger.Warn($" Exception type: {failure.ExceptionType}");
}
Logger.Warn("If you open an issue, include the full console output and say whether the failure happened during detection, removal, writing output, or runtime after deobfuscation.");
Logger.Warn("----- FAILURE SUMMARY END -----");
}
static int Main(string[] args)
{
XmlConfigurator.Configure(typeof(UnConfuserEx).Assembly.GetManifestResourceStream("UnConfuserEx.log4net.xml"));
string? pathArg = null;
string? outputArg = null;
RuntimeOptions.RebuildEmbeddedPe = false;
RuntimeOptions.EnableSerializedResourceDeserialization = false;
foreach (var arg in args)
{
if (arg.Equals("--rebuild-embedded-pe", StringComparison.OrdinalIgnoreCase))
{
RuntimeOptions.RebuildEmbeddedPe = true;
continue;
}
if (arg.Equals("--enable-serialized-resources", StringComparison.OrdinalIgnoreCase))
{
RuntimeOptions.EnableSerializedResourceDeserialization = true;
continue;
}
if (arg.StartsWith("-", StringComparison.Ordinal))
{
Logger.Error($"Unknown option: {arg}");
Logger.Error("Usage: unconfuser.exe <module path> <output path> [--rebuild-embedded-pe] [--enable-serialized-resources]");
return 1;
}
if (pathArg is null)
{
pathArg = arg;
}
else if (outputArg is null)
{
outputArg = arg;
}
else
{
Logger.Error("Usage: unconfuser.exe <module path> <output path> [--rebuild-embedded-pe] [--enable-serialized-resources]");
return 1;
}
}
if (pathArg is null)
{
Logger.Error("Usage: unconfuser.exe <module path> <output path> [--rebuild-embedded-pe] [--enable-serialized-resources]");
return 1;
}
if (RuntimeOptions.EnableSerializedResourceDeserialization)
AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true);
var path = Path.GetFullPath(pathArg);
if (!File.Exists(path))
{
Logger.Error($"File {path} does not exist");
return 1;
}
// Load the module
ModuleContext context = new();
ModuleDefMD module = ModuleDefMD.Load(path, context);
var pipeline = new List<IProtection>
{
new CompressorRemover(),
new JITAntiTamperRemover(),
new AntiTamperRemover(),
new ControlFlowRemover(),
new ResourcesRemover(),
new ConstantsRemover(),
new RefProxyRemover(),
new AntiDumpRemover(),
new UnicodeRemover(),
new AntiDebugRemover(),
new StaticCleanupRemover(),
};
List<string> detectedProtections = new();
List<FailureInfo> failures = new();
foreach (var p in pipeline)
{
var stopwatch = Stopwatch.StartNew();
bool present;
try
{
present = p.IsPresent(ref module);
}
catch (Exception ex)
{
stopwatch.Stop();
Logger.Error($"Detection for {p.Name} threw — skipping ({ex.Message})");
Logger.Error(ex.ToString());
failures.Add(new FailureInfo
{
ProtectionName = p.Name,
Stage = "detection",
Message = ex.Message,
ExceptionType = ex.GetType().FullName
});
continue;
}
stopwatch.Stop();
Logger.Debug($"Detection for {p.Name}: present={present} elapsed={stopwatch.ElapsedMilliseconds}ms");
if (!present) continue;
detectedProtections.Add(p.Name);
Logger.Info($"{p.Name} detected, attempting to remove");
stopwatch.Restart();
try
{
if (p.Remove(ref module))
{
stopwatch.Stop();
Logger.Debug($"Removal for {p.Name} completed in {stopwatch.ElapsedMilliseconds}ms");
Logger.Info($"Successfully removed {p.Name} protection");
}
else
{
stopwatch.Stop();
Logger.Warn($"Removal for {p.Name} returned false after {stopwatch.ElapsedMilliseconds}ms");
Logger.Error($"Failed to remove {p.Name} protection — continuing with remaining protections");
failures.Add(new FailureInfo
{
ProtectionName = p.Name,
Stage = "removal",
Message = "Remove() returned false"
});
}
}
catch (Exception ex)
{
stopwatch.Stop();
Logger.Fatal($"Caught exception when trying to remove {p.Name} protection");
Logger.Error(ex.ToString());
failures.Add(new FailureInfo
{
ProtectionName = p.Name,
Stage = "removal",
Message = ex.Message,
ExceptionType = ex.GetType().FullName
});
}
}
if (failures.Count > 0)
{
Logger.Warn($"{failures.Count} protection(s) could not be removed cleanly. Writing best-effort output anyway");
}
if ((module.Kind == ModuleKind.Console || module.Kind == ModuleKind.Windows)
&& module.EntryPoint == null)
{
MethodDef? best = null;
int bestScore = -1;
foreach (var t in module.GetTypes())
{
foreach (var m in t.Methods)
{
if (!m.IsStatic || !m.HasBody || m.IsSpecialName || m.MethodSig == null)
continue;
var sig = m.MethodSig.ToString();
int score = sig switch
{
"System.Int32 (System.String[])" => 4,
"System.Void (System.String[])" => 3,
"System.Int32 ()" => 2,
"System.Void ()" => 1,
_ => 0
};
if (score == 0) continue;
if (m.Name == "Main" || m.Name == "<Main>$") score += 10;
if (score > bestScore)
{
bestScore = score;
best = m;
}
}
}
if (best != null)
{
module.EntryPoint = best;
Logger.Info($"Recovered entry point via heuristic: {best.FullName}");
}
}
var newPath = $"{Path.GetDirectoryName(path)}\\{Path.GetFileNameWithoutExtension(path)}-deobfuscated{Path.GetExtension(path)}";
if (!string.IsNullOrWhiteSpace(outputArg))
{
newPath = outputArg;
}
if (failures.Count > 0)
{
LogFailureSummary(
path,
newPath,
module,
detectedProtections,
failures
);
}
Logger.Info($"All detected protections removed. Writing new module to {newPath}");
try
{
PrepareModuleForWrite(module);
if (module.IsILOnly)
{
ModuleWriterOptions writerOptions = new ModuleWriterOptions(module);
writerOptions.MetadataOptions.Flags |= MetadataFlags.KeepOldMaxStack;
module.Write(newPath, writerOptions);
}
else
{
NativeModuleWriterOptions writerOptions = new NativeModuleWriterOptions(module, true);
writerOptions.MetadataOptions.Flags |= MetadataFlags.KeepOldMaxStack;
//writerOptions.Logger = DummyLogger.NoThrowInstance;
module.NativeWrite(newPath, writerOptions);
}
}
catch (Exception ex)
{
Logger.Error("Failed to write module");
Logger.Error(ex.ToString());
failures.Add(new FailureInfo
{
ProtectionName = "module writer",
Stage = "writing output",
Message = ex.Message,
ExceptionType = ex.GetType().FullName
});
LogFailureSummary(path, newPath, module, detectedProtections, failures);
return 1;
}
Logger.Info("Deobfuscated module successfully written");
return 0;
}
}
}