Paul Mason
Overcoming the switch statement in IL

In the last article I mentioned how the community had started using the NCloak application and started finding places where it did not work. The main problems occurred with code injection; before injection it worked fine, after injection we had an invalid program. The last article looked at fixing instruction operand overfow whereby a short form branch statement was now referencing a long form instruction. In this article we take a look at an instruction that I had neglected to fix, and why the fix wasn’t as simple as I’d hoped…

Before I dive into the problem I was having, let’s just take a step back and look at code injection and it’s implications with Mono.Cecil.

Injecting Code

Injecting code is made super easy within Mono.Cecil. Once we have a CilWorker object it is a straight forward process:

CilWorker il = methodBody.CilWorker;
Instruction loadSalt = il.Create(OpCodes.Ldc_I4, salt);
il.InsertBefore(methodBody.Instructions[0], loadSalt);

Branch statements, as an operand, do not store the “jump to” instruction address, but rather the jump offset from the current instruction. One consequence of injecting code is that we then need to adjust all of the branch style instruction offsets so that they continue to point the correct position.

For example, if we inserted a NOP instruction then all branch statement offsets before the NOP statement need to increase by one when referencing an instruction after the new NOP statement. Updating offsets is pretty easy; an oversimplified example (well, pretty close to my original code) is:

//Fix all branch statements
for (int i = 0; i < body.Instructions.Count; i++)
{
    //Get the instruction
    Instruction instruction = body.Instructions[i];

    //We need to find the target as it may have changed
    if (instruction.Operand is Instruction)
    {
        Instruction target = (Instruction)instruction.Operand;
        OpCode opCode = instruction.OpCode;

        //Work out the new offset
        int originalOffset = target.Offset;
        int offset = target.Offset;
        foreach (int movedOffsets in offsets)
        {
            if (originalOffset > movedOffsets)
                offset += adjustBy;
        }
        target.Offset = offset;
        Instruction newInstr = il.Create(opCode, target);
        il.Replace(instruction, newInstr);
    }
}

What could go wrong?

The switch statement

The code above was pretty close to what my original code did: adjust all offset pointers for anything which uses an Instruction as an operand. After some more complex testing it quickly became apparent that switch statements didn’t work at all. Why?

Well, switch statements are a little bit different than a normal branch statement as they require a jump to a number of different locations. For example, say you had the following code:

switch (index) {
    case 1:
        //Do something
        break;
    case 2:
        //Do something else
        break;
    case 3:
        //Do something else again
        break;
    default:
        throw new Exception();
}

Within IL, this will get converted into something similar to the following:

IL_0008:  switch     ( 
                      IL_001b,
                      IL_001d,
                      IL_001f)

Within each “case”, we’ll also see the equivalent of break; something like:

IL_0019:  br.s       IL_0021

Ok, so the switch statement is just an array of Instructions right? Surely we just do the same as we were doing above, extended for Instruction[] as an operand?

...
else if (instruction.Operand is Instruction[]) //e.g. Switch statements
{
    Instruction[] targets = (Instruction[])instruction.Operand;
    foreach (Instruction target in targets)
    {
        //Work out the new offset
        int originalOffset = target.Offset;
        int offset = target.Offset;
        foreach (int movedOffsets in offsets)
        {
            if (originalOffset > movedOffsets)
                offset += adjustBy;
        }
        target.Offset = offset;
    }
    Instruction newInstr = il.Create(instruction.OpCode, targets);
    il.Replace(instruction, newInstr);
}
...

Well, that was my first thought - but it didn’t work! Why not?

Why can’t we adjust and be done with?

The other characteristic of a switch statement that I wasn’t testing for was multiple instructions pointing to the same instruction. The “break” statements within each case were all branching to the same instruction. So why the problem?

Well, my original thinking was that each branch statement’s operand should be uniquely different due to using different offsets. That was until I dived into the Mono.Cecil code. Mono.Cecil is smart enough to realise that these are all the same instruction, thus being in OO land - these all point to the same instruction object. Because my loop was seeing this same instruction multiple times it was also increasing the offset many times causing it to become WAY out of bounds. This would have also been an issue with loop statements with multiple breaks, therefore was a fairly major oversight!

There was an easy fix to this though…

The fix

After realising this I realised that I needed to identify whether or not I’d seen the instruction before. To do this I needed a unique identifier - being in object orientated land I thought about the hash code. After a little testing I discovered that this was being handled correctly therefore could be used as my unique identifier! Thus the fix was fairly easy; the final code is listed below:

public static void AdjustOffsets(this CilWorker il, MethodBody body, IList<int> offsets, int adjustBy)
{
    //Unfortunately one thing Mono.Cecil doesn't do is adjust instruction offsets for branch statements
    //and exception handling start points. We need to fix these manually
    if (offsets.Count == 0)
        return;

    //We need to make sure we don't fix any instructions twice!
    List<int> seenHashCodes = new List<int>();

    //Fix all branch statements
    for (int i = 0; i < body.Instructions.Count; i++)
    {
        //Get the instruction
        Instruction instruction = body.Instructions[i];

        //We need to find the target as it may have changed
        if (instruction.Operand is Instruction)
        {
            Instruction target = (Instruction)instruction.Operand;
            int hashCode = target.GetHashCode();
            if (seenHashCodes.Contains(hashCode))
                continue;
            seenHashCodes.Add(hashCode);

            OpCode opCode = instruction.OpCode;

            //Work out the new offset
            int originalOffset = target.Offset;
            int offset = target.Offset;
            foreach (int movedOffsets in offsets)
            {
                if (originalOffset > movedOffsets)
                    offset += adjustBy;
            }
            target.Offset = offset;
            Instruction newInstr = il.Create(opCode, target);
            il.Replace(instruction, newInstr);
        }
        else if (instruction.Operand is Instruction[]) //e.g. Switch statements
        {
            Instruction[] targets = (Instruction[])instruction.Operand;
            foreach (Instruction target in targets)
            {
                int hashCode = target.GetHashCode();
                if (seenHashCodes.Contains(hashCode))
                    continue;
                seenHashCodes.Add(hashCode);

                //Work out the new offset
                int originalOffset = target.Offset;
                int offset = target.Offset;
                foreach (int movedOffsets in offsets)
                {
                    if (originalOffset > movedOffsets)
                        offset += adjustBy;
                }
                target.Offset = offset;
            }
            Instruction newInstr = il.Create(instruction.OpCode, targets);
            il.Replace(instruction, newInstr);
        }
    }
    //If there is a try adjust the starting point also
    foreach (ExceptionHandler handler in body.ExceptionHandlers)
    {
        //Work out the new offset
        Instruction target = handler.TryStart;
        int hashCode = target.GetHashCode();
        if (seenHashCodes.Contains(hashCode))
            continue;
        seenHashCodes.Add(hashCode);

        int originalOffset = target.Offset;
        int offset = target.Offset;
        foreach (int movedOffsets in offsets)
        {
            if (originalOffset > movedOffsets)
                offset += adjustBy;
        }
        target.Offset = offset;
    }
}

Conclusion

Switch statements were a forgotten instruction during the original write of NCloak. The original take assumed that all instructions that needed offset manipulation had an Instruction object as an argument. From community testing it quickly became apparent that switch statements didn’t work.

The basic fix was to also check for an Instruction[] as an operand, however unfortunately this also did not output valid programs. The problem was due to the break statements generating branches to the same location. Mono.Cecil dealt with branch statements using an Instruction object as an operand which meant that we ended up updating the same Instruction offset more than once. We got around this problem by ensuring that we only saw an Instruction once by using the hashcode as a unique identifier. If we had already “seen” the offset then we could assume that it had already been “fixed”.

The latest release of NCloak includes this fix within the code.

Other Thoughts

Obviously testing is an important part of any project, however possibly more importantly is defining an appropriate test surface. Being a part time project at the moment, my test surface has been weak at best. As more people start using the product it is getting much more exposure to scenarios that I haven’t thought of, or wouldn’t think of.

So far, from the help of the community, I’ve managed to find three scenarios that I hadn’t been testing for (instruction operand overflow, switch statements and instruction reuse), and have another three more I have to fix in the issue tracker.

If you have a spare moment, run the obfuscator on one of your applications. If you find a bug then please let me know. With your help I can improve this product so that it works the way that you want it to!

Coming Soon

In coming articles we will be taking a look at:

  • Using Mono.Cecil to automate string decryption decompilation
  • Code flow transformations
  • Dead code removal
  • Tamper proofing improvements
  • Dispatcher driven tamperproofing
  • The .NET hacking toolkit
  • Anti-debugging techniques
  • Watermarking
  • Creating a managed IL parser

As always, if you have anything else you’d like to see then let me know!

Media_httpwwwdotnetki_qpemd
 
Media_httpdotnetshout_isdue
    1. Timestamp: Friday 2010/03/12 7:49:00JustMigratecode injectionilncloakprotecting your precious code