Obfuscators (and deobfuscators) for .NET §1
1. Obfuscators.
1.1. Methodologies
Combining assemblies and namespaces (Assembly Merge, Namespace Flatten)
This technique in itself does not delay an attacker for even a minute, but is very useful for further confusing it. Because the more classes the resulting assembly contains, the more difficult it will be to find what you need without a detailed analysis.
Again, when you try to steal your code, the attacker will receive instead of several library projects and one program, only one project in which all classes will lie in one folder (and in one namespace).
To merge assemblies, you can use the ilmerge utility, or functionality built into the obfuscator. Namespaces are usually combined during the obfuscation of class names (so that there are no collisions with equally named classes from different namespaces).
Renaming classes, methods, etc.
This approach is implemented in almost all respectful obfuscators. All “hints” that an attacker can use to quickly search for classes responsible for licensing are deleted, or if the code is “stolen” it will be very difficult to understand the application logic, why and how classes are created, and methods are called.

The most popular option at the moment is renaming into unprintable characters (or some kind of "Chinese" like 儽. 凍 :: 儽). This complicates the viewing of the assembly in the reflector a little, but does not affect the de-obfuscator in any way.
In addition, from the shortcomings, we receive hard-to-read messages about exceptions that could happen to the end user, and if he sends us the text in a non-Unicode encoding, then it will be almost impossible to parse.
A similar option is to use short but printed identifiers (a, b, c, .... aa, ab, ac ...). For the de-obfuscator, this option is completely similar to the previous one, but it is devoid of this drawback.
The third variant of naming - the use of high-level language keywords (C # or VB.net), or invalid identifiers for this language (for example? 123?) - is no better than the previous two, but for some reason it is believed that when “stealing” the code, will be used by the deobfuscator, and the output will result in uncompiled text ...There is still a bunch of “stupid” options that certainly hide the meaning of the original names, but why make them so long?


An interesting and even more confusing approach is to create a large number of overload methods with the same name, which had different names before obfuscation, and were not connected in any way.
Also .net allows you to create override methods whose names differ from the names of the methods that they overlapped. This confuses not only attackers, but also adds unnecessary requirements to the deobfuscator.

Changing the contents of classes
Some obfuscators can combine several classes into one, or make a nested class from a regular class. But such obfuscation often leads to errors in the resulting program, and is used very rarely.
Obfuscation control flow
At this point, the order of the instructions in the code changes, and even the instructions themselves change. Perhaps the most interesting and most controversial stage.
This technique allows you to mislead (and sometimes to a complete stupor) most high-level language decompilers. Which counteracts code theft very well. It also confuses crackers and keygen authors.
The flip side is sometimes reduced performance. It is logical that the more we confuse the course of the program, the longer it takes. This especially applies to the use of exceptions.
In most cases, the method code breaks into blocks, these blocks are randomly mixed and “glued” using unconditional jumps (br and br.s instructions). As an example:
L_0034: br.s L_003a
L_0036: nop
L_0037: br.s L_0041
L_0039: nop
L_003a: callvirt instance void [Aaa]Xxx.Yyy::Zzz()
L_003f: br.s L_0036
L_0041: nop There are cases when the method is very short, and it doesn’t work well to “mix”, in this case some obfuscators give the transition to the following instruction:
L_0008: br.s L_000a
L_000a: ldarg.0 Between the transition instruction, and its purpose very often any “bullshit” is inserted, such as falling out into a debugger, or simply invalid instructions:
L_0000: br.s L_0003
L_0002: break
L_0003: ldarg.0Some obfuscators replace transition instructions (both original and inserted) with constant loading and switching to switch:
L_0000: br.s L_0023
L_0002: ldloc num3
L_0006: switch (L_005b, L_0068, L_00ce, L_00af, L_0047, L_007b)
...
...
...
L_003c: ldc.i4 4
L_0041: stloc num3
L_0045: br.s L_0002it is obvious that in this example, the instruction with the offset L_0045 “in girlhood” was br L_0047, and if you take into account the previous methods, then this is generally nop;)
Sometimes you can see a “transition to transition”:

in one of the programs I saw a chain of 6 (six ) such transitions;)
An interesting approach is the use of conditional transitions for expressions that are always true (or incorrect).
The simplest example:
L_0014: ldc.i4.1
L_0015: brtrue.s L_002eSame thing, but slightly more confusing:
L_0014: ldc.i4.1
L_0015: stloc.0
L_0016: br.s L_001c
L_0018: nop
L_0019: ldarg.1
L_001a: br.s L_002e
L_001c: ldloc.0
L_001d: brtrue.s L_0018Another option:
if (5 < (5 - 6)) {
// IL-мусор, или неверный код
}in the form of IL it will look something like this: L_0000: ldc.i4.5
L_0001: dup
L_0002: dup
L_0003: ldc.i4.6
L_0004: sub
L_0005: blt L_0001Simple mixing of some instructions, for example:
L_0000: ldc.i4 4
L_0005: stloc num
L_0009: ldstr "\u5f03"
L_000e: ldloc numthe compiler can sometimes produce code of the form stloc X, ldloc X when it is necessary to write a value to a local variable, but not remove it from the stack. In the case of obfuscators, this variable (num) was added artificially, and is nowhere used except for these two instructions. One of the hardest methods is always throwing an exception inside the try — catch block. This approach is used very rarely, because dramatically reduces performance and can disrupt application logic if used improperly. I don’t give a screenshot because it takes up a lot of space.
It seems that I have listed the most popular methods, if you know something else, please let me know in the comments.
Invalid IL
Everything is very simple here. In code sections that will never be executed, opcodes not described in the standard are inserted (i.e. invalid instructions).
In the reflector, you will see something like this:

or, if you switch to IL:

This technique discourages beginner “haxors”. But it is not difficult to circumvent (these opcodes are simply replaced by nop).
Hiding strings
With deobfuscators this is called “string encryption”, but I don’t dare call it encryption.
Usually this is done by some kind of "child" encryption algorithm of the XOR type for a constant:
public static string Decode(string str, int num)
{
int length = str.Length;
char[] chArray = str.ToCharArray();
while (--length >= 0)
chArray[length] = (char)(chArray[length] ^ num);
return new string(chArray);
}
Sometimes the strings are combined into one, and then the Substring method is called; sometimes strings are hidden in resources.
In any case, “encryption” is presented as a static method with several arguments, usually a string and / or a number. No cryptographic algorithms are used, which is quite logical: if you use real encryption here, the program will be shamelessly slow down.
This method saves from novice crackers who will search by code for a string like “Invalid serial number” or other message texts.
Specific Attributes and Decompiler Bugs
The most common attribute is [ SuppressIldasm], which "politely asks" not to work on this assembly, the official Microsoft decompiler - ildasm. There are also specific attributes for the reflector and for commercial decompilers.
As bugs, you can find purely technical flaws in decompilers (for example, the reflector falls on the ldfld string 儽. 凍 :: 儽 instructions, and most deobfuscators based on Mono.Cecil on the wrong RVA), as well as algorithmic assumptions: many high-level decompilers track the state of the stack, but they go according to the method not as on a graph, but linearly, and happily fall on methods in which an infinite loop is inserted after the last ret statement. Against the Reflexil plugin, an instruction that switches to itself "helps" well.
Other methods
Sometimes you can come up with a very similar approach to hiding strings, but for resources.
One of the obfuscators also offers “V-Spot Elimination” (which is very proud of) - the creation of proxy classes for BCL classes, which slows down the analysis and slightly spoils the code obtained by decompilation.
Converting manged to unmanaged .net code is also used. Those. everything is rebuilt with unmanaged marks. Almost all the functionality within the domain is preserved, but the code cannot be seen by the reflector.
Thanks to the user Exaktus for comments and additions.
Next will be part 1.2. Obfuscator review
to be continued ...
* All source code was highlighted with MSVS and wordpad;)