To start off 2010 we're jumping straight back into the obfuscation series - first up for the year we're going to be implementing a method to fool Reflector as described in the last article in the series.
Recap and overview
In the last article of the series we took a look at two different methods for stopping Reflector being able to successfully decompile our code: inserting invalid IL, and modifying header information. Today we'll implement the more common approach: inserting invalid IL into each method inside our assembly making it unreadable in Reflector.
The plan is to search through each method and insert three new instructions before any other instruction. Specifically, the three instructions to insert are a br.s and two invalid made up opcodes reminiscent of nop. For example:
IL_0000: br.s IL_0004 IL_0002: 0x00c0 IL_0003: 0x00be IL_0004: ldarg.0 IL_0005: callvirt instance char[] [mscorlib]System.String::ToCharArray() IL_0010: stloc.0 IL_0011: ldc.i4.0
The reason I mention that the two opcodes should be reminiscent of nop is because we don't want to specify an operand for either of them - it just doesn't make sense. Therefore we create two invalid "nop"ish tokens (indicated by the two left bytes being 0x00) and use them in its place.
Implementing the task into NCloak
The implementation of this isn't that complex compared to some other tasks that we've covered. We essentially insert our invalid IL pattern at the beginning of each method.
The first part of our code is relatively trivial; we basically create two fake instructions and one branch statement and insert them into our method body:
//Get the instructions and cil worker
InstructionCollection instructions = methodBody.Instructions;
if (instructions.Count <= 0)
return; //We can only do this if we have instructions to work with
CilWorker il = methodBody.CilWorker;
//First create an invalid il instruction
OpCode fakeOpCode = CreateInvalidOpCode();
Instruction invalidIlInstr1 = il.Create(fakeOpCode);
Instruction invalidIlInstr2 = il.Create(fakeOpCode);
Instruction originalFirst = instructions[0];
//Insert invalid il at the start
il.InsertBefore(originalFirst, invalidIlInstr1);
il.InsertBefore(invalidIlInstr1, invalidIlInstr2);
//Create the branch statement
Instruction branchStatement = il.Create(OpCodes.Br_S, originalFirst);
//Add the branch to the start
il.InsertBefore(invalidIlInstr2, branchStatement);
Of course, we've learnt from other articles that Mono.Cecil isn't quite smart enough to adjust all our instruction offsets for us. This means we also need to go through and fix all the offsets to take into account the new instructions that we've inserted:
//Now readjust other branch statements
for (int i = 2; i < instructions.Count; i++)
{
//We only need to do this if the operand is an instruction
if (instructions[i].Operand is Instruction)
{
//We need to find the target as it may have changed
Instruction target = (Instruction)instructions[i].Operand;
//Work out the new offset
int offset = target.Offset + 4; //br.s opcode + invalid opcode
target.Offset = offset;
Instruction newInstr = il.Create(instructions[i].OpCode, target);
il.Replace(instructions[i], newInstr);
}
}
//If there is a try adjust the starting point also
foreach (ExceptionHandler handler in methodBody.ExceptionHandlers)
{
//Work out the new offset
Instruction target = handler.TryStart;
target.Offset = target.Offset + 4;
}
In this case our magic number to increase the offset by is 4. If you recall, each opcode is 1 byte, and the branch instruction is also 1 byte. i.e. 1 + 1 + 1 + 1 = 4!
Generating an invalid OpCode
One thing that I haven't mentioned on purpose is the CreateInvalidOpCode() method which "magically" returns an invalid opcode. Now, those of you that are familiar with Mono.Cecil will realise that an OpCode is a struct... with an internal constructor... with readonly properties... AND with various checks to make sure that each time it is instantiated that it is a valid OpCode. Hmmm... so how are we creating an invalid OpCode then? Well, we are actually doing some tricks inside the framework to get this going!
An OpCode, as mentioned above, is a structure as opposed to a class. It is not JUST a structure though, it is a sequential structure. When you get down and dirty, a sequential structure doesn't have any type information attached to it - it is simply a block of data of which the structure says how to interpret. This is great news for us as it means that we can take advantage of this and "mock" an OpCode with an equivalent sequential structure - which is exactly what we do. It's definitely dodgy - but it works, and works well:
private static OpCode CreateInvalidOpCode()
{
//We create an opcode using a pretty dodgy method...
byte op2;
switch (random.Next(0, 8))
{
default:
op2 = 0xc1;
break;
case 1:
op2 = 0xae;
break;
case 2:
op2 = 0xc9;
break;
case 3:
op2 = 0xca;
break;
case 4:
op2 = 0xaf;
break;
case 5:
op2 = 0xa7;
break;
case 6:
op2 = 0xc0;
break;
case 7:
op2 = 0xbe;
break;
}
InvalidOpCode invalidOpCode = new InvalidOpCode(0xff, op2);
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(invalidOpCode));
OpCode opCode;
try
{
Marshal.StructureToPtr(invalidOpCode, ptr, false);
opCode = (OpCode) Marshal.PtrToStructure(ptr, typeof (OpCode));
}
finally
{
Marshal.FreeHGlobal(ptr);
}
return opCode;
}
[StructLayout(LayoutKind.Sequential)]
private struct InvalidOpCode
{
public InvalidOpCode(byte op1, byte op2)
{
Value = (short)((op1 << 8) | op2);
Code = op2;
FlowControl = (byte)Mono.Cecil.Cil.FlowControl.Next;
OpCodeType = (byte)Mono.Cecil.Cil.OpCodeType.Primitive;
OperandType = (byte)Mono.Cecil.Cil.OperandType.InlineNone;
StackBehaviourPop = (byte)StackBehaviour.Pop0;
StackBehaviourPush = (byte)StackBehaviour.Push0;
}
public short Value;
public byte Code;
public byte FlowControl;
public byte OpCodeType;
public byte OperandType;
public byte StackBehaviourPop;
public byte StackBehaviourPush;
}
As you can see, I'm giving it a list of valid "invalid" values that it can have. The reason for this is that I found it easier to do this than to get a true random value. It also limits the number of invalid opcodes which hopefully makes things a bit cleaner in the output assembly.
Seeing it in action
I guess the real question is: what does it all look like? Well, after running ncloak over our code we've got some positive looking consequences:
Invalid Method Body - C#
As you can see, we've successfully hidden our code from C#! What about IL view?
Invalid Instruction - IL
Great! IL view has also been affected. Assume we have protection on our code - does Monodis decompile it ok? Well, actually, yes it does:
.method family hidebysig virtual instance int32
'a'(string[] args) cil managed
{
// Code size 111 (0x6f)
.maxstack 4
.locals init (class SimpleLibrary.'a' V_0,
int32 V_1,
bool V_2)
IL_0000: br.s IL_0004
IL_0002: unused13
IL_0003: unused24
IL_0004: nop
IL_0005: newobj instance void SimpleLibrary.'b'::.ctor()
...
Well, we can live with that for now; we'll see if we can do something about it perhaps in later weeks.
Conclusion
Mono.Cecil does a great job helping us with our Obfuscator; in fact with a wee bit of a hack it also helps us insert invalid opcodes relatively easily also. The main take away points from this article are:
- Invalid opcodes are relatively simple to insert... so long as you have the ability to create an invalid opcode
- Mono.Cecil doesn't allow for invalid opcodes to be created, but with a bit of a hack we can still do it nice and easily!
- We still need to go through and fix up all offsets after inserting these fields as it is a bit much to expect Mono.Cecil to do it for us.
So, all in all - our implementation is relatively straight forward, but as you can see: quite effective. It certainly helps from prying eyes using Reflector as a tool to look at your precious code!
Coming Soon
In coming weeks we'll look at:
- Reverse engineering the invalid IL technique
- Strategies for writing a GUI
- Anything else I can think of!
Until then, have a great weekend and week ahead. If you have any questions then please feel free to ask, or alternatively give me an email.
A great article!
I always enjoy your articles. ^^
Great article, you just revealed how $500 Obfuscator to confused Reflector, :P.