When we write an obfuscator, control flow looks like a difficult one. But i’ll show you that make a basis control flow is easier that you may think !
First, let’s give a definition to see what is it :
.net Reactor definition :
Control Flow Obfuscation converts the code inside your methods into spaghetti code, which whilst retaining the function of the code makes it extremely difficult for human eyes and decompilers to follow the program logic. Decompilers are not able to decompile the spaghetti code back to your original source code.
Agile.net definition :
Control flow obfuscation hides the control flow information of the program by transforming exiting code flow patterns to semantically equivalent constructs, however different than the code originally written. The control flow obfuscation algorithm converts the original implementation into spaghetti code thus making it extremely harder to infer program logic. Agile.NET .NET obfuscator ensures that application code flow of the obfuscated assembly remains intact.
NetGuard definition :
The goal of this protection is to « spaghettify » the code execution flow while keeping its original functionality. That means the code should execute the same original tasks and instructions while providing a numbers of new proxy flow within the method.
So, as you understood, the aim of control flow is to mix the blocks of a method so that a human cannot read it but the machine still interpret it in the right order !
A question you may ask me now is : but what the hell is a block???
A block is simply a set of instructions BUT you cannot take randomly some instruction and call that a block !
Each block must have the same stack size
An interesting method in dnlib is : instruction.CalculateStackUsage(out pushes, out pops);
This will return you two integers : pushes and pops
push = putting something into the stack
pop = getting the last value of the stack
If you consider a stack of book, you can only take the upper one. This is what pop does.
So for each block, the difference between pushes and pops must be equal to 0
Now that you understand what is pushes, popes and blocks, you can write your own Splitting method !
You now have splitted block but you must bind them !
The simpliest way is using br.s opcodes but it’s easy removable with de4dot so let’s use some switch opcodes
switch works if you give it a case to go. So we’ll set at the end of each block the number of the following block
As you saw, it’s not very difficult. If you are not able to write a splitting method, use de4dot.blocks which handle it !
The next challenge is now to randomize the order of the blocks but to still read them in the correct order. To make it more hader, add junk blocks and put some mutation to hide the number of the blocks !
I hope this topic was clear, if you need some inspiration, you can read confuserex source code to see how does a more complex control flow works.
I also advice you to read this article if you do not understand MSIL correctly :