Obfuscation Algorithms

Rummage comprises a number of advanced obfuscation techniques. Most of these algorithms operate by irreversibly erasing information and structure which helps attackers. You don’t need to know what these algorithms are to use Rummage, but we thought you might be curious to know what’s under the hood.

Rename classes, methods, etc.

Rename classes, methods, etc.

Erases meaningful names of classes, methods and other types of entries. Since some renames are not possible due to reflection, picks plausble but completely random names to confuse the attacker.

BeforeAfter: traditionalAfter: Rummage
class Program

class Settings

class Reflected
    field Count
    method Update

class LicenseChecker
    field LicenseData
    method get_UserName
    method IsLicenseValid
    method ReadLicense
class a

class b

class Reflected
    field Count
    method Update

class c
    field a
    method b
    method c
    method d
class BrowseEntry

class Fixed

class Reflected
    field Count
    method Update

class InfoState
    field Binder
    method SetChanged
    method Remove
    method Service

Using the traditional approach, it is completely obvious which members could not be renamed for whatever reason. The Rummage approach makes them blend in and thus hard to spot.

Read more about renaming classes, methods, etc.

Remove unnecessary metadata

Remove unnecessary metadata

Removes several types of metadata which is not usually necessary at runtime, such as what makes a property or an event.

BeforeAfter
class MyClass
{
    public string DataPath
    {
        get { ... }
        set { ... }
    }

    public void Method()
    {
        DataPath += "abc";
    }
}
class Connections
{
    public string Split() ...
    public void Transition(string plaintext) ...

    public void Progress()
    {
        Transition(Split() + "abc");
    }
}

The above example combines this obfuscation with "Rename types/members". Among other things, this disassociates getters and setters.

Read more about removing unnecessary metadata

Inline methods

Inline methods

This obfuscation erases the encapsulation boundary between various layers of abstraction used in a program, merging independent components into one heavily-interlinked chunk.

BeforeAfter
static void Core()
{
    var x = new MyClass();
    try { DoIt(x); }
    catch { }
}

static void DoIt(MyClass x)
{
    var result = x.Grow();
    Console.WriteLine(result);
}

class MyClass
{
    private double _encapsulated = 42;

    public double Grow()
    {
        lock (this)
            _encapsulated *= 1.5;
        return _encapsulated;
    }
}
static void Core()
{
    var x = new MyClass();
    try
    {
        var x2 = x;
        var @this = x2;
        lock (@this)
            @this._encapsulated *= 1.5;
        var result = @this._encapsulated;
        Console.WriteLine(result);
    }
    catch { }
}

class MyClass
{
    public double _encapsulated = 42;
}
Read more about inlining methods

Obfuscate and optimize method IL code

Obfuscate and optimize method IL code

Performs advanced targeted transformations on method instructions which obfuscate control flow and make compiler constructs hard to infer and decompile. Rummage will never insert unnecessary branches (which can slow down code), but instead reorders code blocks and replaces instruction sequences with equivalents. In many cases Rummage eliminates redundancy and local variables.

BeforeAfter
for (int i = 0; i < count; i++)
    Console.WriteLine(i);
object arg_01_0 = 0;
while (true)
{
    object expr_01 = arg_01_0;
    if (expr_01 >= count)
        break;
    Console.WriteLine(expr_01);
    arg_01_0 = expr_01 + 1;
}

Normally the decompiler would have been able to deduce the for loop structure, but after Rummage it fails to do so, and also chooses the wrong type for the locals (so its output wouldn't compile). Moreover, the obfuscated IL has no locals at all (the i got eliminated completely):

Before (IL)After (IL)
.locals init (
         [0] int32 i
)
         ldc.i4.0
         stloc.0
         br.s IL_000e
IL_0004: ldloc.0
         call WriteLine(int32)
         ldloc.0
         ldc.i4.1
         add
         stloc.0
IL_000e: ldloc.0
         ldarg.0
         blt.s IL_0004
         ret
         ldc.i4.0
IL_0001: dup
         ldarg.0
         blt.s IL_0007
         pop
         ret
IL_0007: dup
         call WriteLine(int32)
         ldc.i4.1
         add
         br.s IL_0001

Observe that while the decompiled code looks longer than the original, the actual underlying IL code was significantly shortened by Rummage.

Read more about obfuscating and optimizing method IL code

Un-nest nested types

Un-nest nested types

Transforms nested types into non-nested types. Nested types are often generated by the compiler when you use a lambda expression, anonymous method, an iterator block (yield return), or an async method. Unnesting them removes the ability for decompilers to reconstruct these structures.

BeforeAfter
public class Item
{
    string Id;
}

...

var x = Items.Where(item => item.Id == id);
public class Item
{
    string Id;
}

// Rummage un-nested and renamed
// this compiler-generated type.
public sealed class Handle
{
    public string Neg;
    public bool Get(Item keys)
    {
        return keys.Id == this.Neg;
    }
}

...

var handle = new Handle();
handle.Neg = id;
var x = Items.Where(
  new Func<Item, bool>(handle.Get));

As with many other algorithms, the decompiled code looks longer, but the actual IL representation is almost exactly the same length as before obfuscation.

Read more about un-nesting nested types

Encrypt strings

Encrypt strings

This algorithm prevents a simple textual search through the binary locating interesting code, such as the code responsible for showing a specific warning.

BeforeAfter
static void CheckLicense()
{
    if (!LicenseValid())
    {
        MessageBox.Show(
            "License not valid.");
    }
}
private static void CheckLicense()
{
    if (!LicenseValid())
        MessageBox.Show(
            StateConsoleCollection.Site);
}

static class StateConsoleCollection
{
    public static string Site
        = UnregisterInternal.Type(24);
}

Rummage creates a field for each encrypted string, and picks a plausible-sounding name for it so that it does not stand out. The string is decrypted on first use and only once. The performance impact of this technique is negligible. Do keep in mind, however, that this transformation does not permanently erase information, and can, in theory, be undone.

Read more about string encryption

Obfuscate custom attribute data

Obfuscate custom attribute data

Moves all custom attribute data into the attribute's constructor, making automated extraction of attribute values significantly harder. The values are usually stored as metadata, but after the obfuscation they are mixed in with IL code.

BeforeAfter
[TestAttribute("log", Count = 42)]
static void SomeMethod() { ... }

[TestAttribute("license", Count = 10)]
static void OtherMethod() { ... }
[TestAttribute(0)]
static void SomeMethod() { ... }

[TestAttribute(1)]
static void OtherMethod() { ... }
Read more about obfuscating custom attribute data

Remove unused types and members

Remove unused types and members

This feature is designed for projects that make use of libraries. Use ILMerge to merge the library into your program and then let Rummage remove all the parts from the library that are not actually in use by your particular program. This reduces the size of your executable, thus improves run-time performance and memory consumption, and in conjunction with all the other obfuscations allows you to completely hide the very existence of any external library.

Read more about removing unused types and members

Mark types and members public

Mark types and members public

The structure of classes is inherently revealed by accessibility modifiers such as “private” and “protected”. Rummage sets all types and members to “public” in order to obfuscate which parts of a class are API and which are implementation detail.

Read more about marking types and members public