⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⠀⠀⠀⢠⣾⣧⣤⡖⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⠋⠀⠉⠀⢄⣸⣿⣿⣿⣿⣿⣥⡤⢶⣿⣦⣀⡀
⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⡆⠀⠀⠀⣙⣛⣿⣿⣿⣿⡏⠀⠀⣀⣿⣿⣿⡟
⠀⠀⠀⠀⠀⠀⠀⠀⠙⠻⠷⣦⣤⣤⣬⣽⣿⣿⣿⣿⣿⣿⣿⣟⠛⠿⠋⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⠋⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⡆⠀⠀
⠀⠀⠀⠀⣠⣶⣶⣶⣿⣦⡀⠘⣿⣿⣿⣿⣿⣿⣿⣿⠿⠋⠈⢹⡏⠁⠀⠀
⠀⠀⠀⢀⣿⡏⠉⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡆⠀⢀⣿⡇⠀⠀⠀
⠀⠀⠀⢸⣿⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣟⡘⣿⣿⣃⠀⠀⠀
⣴⣷⣀⣸⣿⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⠹⣿⣯⣤⣾⠏⠉⠉⠉⠙⠢⠀
⠈⠙⢿⣿⡟⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣄⠛⠉⢩⣷⣴⡆⠀⠀⠀⠀⠀
⠀⠀⠀⠋⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⣿⣿⣀⡠⠋⠈⢿⣇⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⠿⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀
Unpacker for .NET binaries protected with SugarGuard. Strips every obfuscation layer and outputs a clean assembly readable in dnSpy/ILSpy with zero residues.
Before - CFF + L2F + InjectArray turns 3 lines into an unreadable state machine:
internal static class Program
{
[STAThread]
private static void Main()
{
Application.EnableVisualStyles();
for (;;)
{
IL_0005:
uint num = (uint)<Module>.CA90A651;
for (;;)
{
uint num2;
switch ((num2 = num ^ 2100042526U) % 4U)
{
case 0U:
goto IL_0005;
case 1U:
{
Application.SetCompatibleTextRenderingDefault(false);
uint num3 = num2;
uint[] array = new uint[3];
array[0] = 195U;
array[1] = 647U - array[0];
array[2] = 4294967009U + array[1] + array[0];
uint num4 = num3 / array[2];
num = num4 - 5833451U;
continue;
}
case 2U:
{
Application.Run(new MainForm());
uint num5 = num2;
uint[] array = new uint[4];
array[0] = 343U;
array[1] = 749U - array[0];
array[2] = 4294966689U + array[1] + array[0];
array[3] = 355U - array[2] - array[1] + array[0];
uint num6 = num5 / array[3];
num = num6 - 14000282U;
continue;
}
}
return;
}
}
}
}After - the actual 3 lines of code:
internal static class Program
{
[STAThread]
private static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}Before - 785 instructions, 38 switch cases, encrypted strings, delegate calls:
private void SettingsForm_Load(object sender, EventArgs e)
{
C378DFB7.A1E9B3C2(this); // delegate bridge - what does this even call?
uint num = 55U;
uint num2;
switch ((num2 = num ^ 146273173U) % 38U)
{
case 0u:
goto IL_04D4;
case 1u:
goto IL_0965;
case 2u:
{
if (this.appSettings.GetValue(<Module>.CE3AAF6A) != null) // encrypted string
{
// ... more CFF transitions with XOR key computation
}
uint num3 = num2;
uint[] array = new uint[3];
array[0] = 195U;
array[1] = 647U - array[0];
array[2] = 4294967009U + array[1] + array[0];
num = (num3 / array[2]) ^ (uint)(<Module>.CA90A651); // L2F field
continue;
}
// ... 35 more cases
}
}After - clean linear flow, strings readable, direct calls:
private void SettingsForm_Load(object sender, EventArgs e)
{
MainForm.Main.Hide();
if (this.appSettings.GetValue("Theme") != null)
{
this.comboTheme.SelectedIndex = 1;
}
if (this.appSettings.GetValue("Language") != null)
{
this.comboLanguage.Text = this.appSettings.GetValue("Language").ToString();
}
if (this.appSettings.GetValue("AutoUpdate") != null)
{
this.checkAutoUpdate.Checked = true;
}
this.Show();
}Before - 78,000+ real instructions hidden inside a binary resource, method body is just a VM call:
IL_0000: ldc.i4 0
IL_0005: ldnull
IL_0006: call object <Module>::231C5E6C(int32, object[])
IL_000B: pop
IL_000C: ret
After - original IL fully restored:
IL_0000: ldstr "HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"
IL_0005: ldstr "DefaultTTL"
IL_000A: ldc.i4 64
IL_000F: call void Microsoft.Win32.Registry::SetValue(string, string, object)
IL_0014: ldstr "HKLM\\SYSTEM\\CurrentControlSet\\Services\\AFD\\Parameters"
IL_0019: ldstr "FastSendDatagramThreshold"
IL_001E: ldc.i4 1024
IL_0023: call void Microsoft.Win32.Registry::SetValue(string, string, object)
// ... 78,000+ instructions restored from VM resource
Each phase attacks a specific SugarGuard protection layer:
| Phase | Class | What it resolves |
|---|---|---|
| 0 | InvalidMethodFixer |
Strips box System.Math injected as first IL to crash decompilers |
| 1 | Devirtualizer |
Parses VM binary resource, restores original IL from virtualized methods |
| 2 | LocalToFieldReverser |
Reverts LocalToField - inlines static <Module> fields back as constants |
| 3 | ControlFlowDeobfuscator |
Resolves CFF (Control Flow Flattening) switch dispatch via BFS |
| 4 | ImportDeobfuscator |
Resolves delegate bridges, replaces indirect calls with direct ones |
| 6 | StringDecryptor |
Decrypts strings from embedded resource, inlines as ldstr |
| 7 | Cleaner |
Removes orphan VM methods/fields, unused locals, dead code |
The heaviest phase. Supports two SugarGuard CFF variants:
- Variant A (InjectArray): transitions use
actualKey / divisor - C, divisors emulated from UInt32 arrays, conditionals withldc.i4/dup - Variant B (simple): transitions use
actualKey - C(no divisor), conditionals withldc.i4/ldc.i4(no dup)
Both are detected and resolved automatically through the same BFS pipeline.
SugarUnpack.exe <path-to-obfuscated.exe>Output: <name>-unpacked.exe in the same directory.
cd Unpacker/SugarUnpack
dotnet publish -c Release -r win-x64 --self-contained false -o ../../releaseRequires .NET 8.0 SDK. Dependencies (dnlib) are pulled automatically via NuGet.
Tested across 8 SugarGuard-protected binaries (.exe and .dll), covering both CFF variants (InjectArray and simple), with and without VM virtualization, import protection, and string encryption.
All targets produce zero residues after unpacking: 0 switch, 0 newarr UInt32, 0 rem.un, 0 box System.Math.
Unpacker/SugarUnpack/
Program.cs -- Entry point, pipeline orchestration
Logger.cs -- Colored console output + ASCII art banner
ILHelpers.cs -- Shared IL instruction utilities
InvalidMethodFixer.cs -- Phase 0
Analyzer.cs -- Assembly structure diagnostic
Devirtualizer.cs -- Phase 1
LocalToFieldReverser.cs -- Phase 2
ControlFlowDeobfuscator.cs -- Phase 3
ImportDeobfuscator.cs -- Phase 4
StringDecryptor.cs -- Phase 6
Cleaner.cs -- Phase 7
- VM bytecode: Phase 1 depends on SugarGuard's specific binary resource layout. Different versions may need parser adjustments.
- String decryption: Handles
String(Int32)pattern with byte array. Versions usingDecrypt(String, String, String)are not covered yet. - InjectArray: Only emulates
addandsubin array cascades. If a version usesmulorxorin arrays,EmulateArrayDivisorneeds extending.