Skip to content

PerfGuy-dev/SugarGuardUnpacker

Repository files navigation

⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⠀⠀⠀⢠⣾⣧⣤⡖⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⠋⠀⠉⠀⢄⣸⣿⣿⣿⣿⣿⣥⡤⢶⣿⣦⣀⡀
⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⡆⠀⠀⠀⣙⣛⣿⣿⣿⣿⡏⠀⠀⣀⣿⣿⣿⡟
⠀⠀⠀⠀⠀⠀⠀⠀⠙⠻⠷⣦⣤⣤⣬⣽⣿⣿⣿⣿⣿⣿⣿⣟⠛⠿⠋⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⠋⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⡆⠀⠀
⠀⠀⠀⠀⣠⣶⣶⣶⣿⣦⡀⠘⣿⣿⣿⣿⣿⣿⣿⣿⠿⠋⠈⢹⡏⠁⠀⠀
⠀⠀⠀⢀⣿⡏⠉⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡆⠀⢀⣿⡇⠀⠀⠀
⠀⠀⠀⢸⣿⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣟⡘⣿⣿⣃⠀⠀⠀
⣴⣷⣀⣸⣿⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⠹⣿⣯⣤⣾⠏⠉⠉⠉⠙⠢⠀
⠈⠙⢿⣿⡟⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣄⠛⠉⢩⣷⣴⡆⠀⠀⠀⠀⠀
⠀⠀⠀⠋⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⣿⣿⣀⡠⠋⠈⢿⣇⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⠿⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀

SugarGuardUnpacker

Unpacker for .NET binaries protected with SugarGuard. Strips every obfuscation layer and outputs a clean assembly readable in dnSpy/ILSpy with zero residues.


Before / After

Program entry point

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());
    }
}

Form_Load with 38-case CFF + string encryption + import bridges

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();
}

Virtualized .cctor helper (IL view)

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

What it does

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

CFF Resolver (Phase 3)

The heaviest phase. Supports two SugarGuard CFF variants:

  • Variant A (InjectArray): transitions use actualKey / divisor - C, divisors emulated from UInt32 arrays, conditionals with ldc.i4/dup
  • Variant B (simple): transitions use actualKey - C (no divisor), conditionals with ldc.i4/ldc.i4 (no dup)

Both are detected and resolved automatically through the same BFS pipeline.


Usage

SugarUnpack.exe <path-to-obfuscated.exe>

Output: <name>-unpacked.exe in the same directory.

Build from source

cd Unpacker/SugarUnpack
dotnet publish -c Release -r win-x64 --self-contained false -o ../../release

Requires .NET 8.0 SDK. Dependencies (dnlib) are pulled automatically via NuGet.


Tested on

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.


Project Structure

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

Limitations

  • 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 using Decrypt(String, String, String) are not covered yet.
  • InjectArray: Only emulates add and sub in array cascades. If a version uses mul or xor in arrays, EmulateArrayDivisor needs extending.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages