Continuing on along the lines of the Protecting your precious code series, we'll take a look at pre-empting a bug in our obfuscator! One assumption I had made during writing these articles, which fell through into the implementation of NCloak was that an address size was 32 bits long. While this will work for MOST people (Any Cpu or 32 bit compilation); when obfuscating a 64 bit assembly, it will start causing a few execution problems.
So where is this problem exactly...
A few weeks ago, when I wrote about string encryption I mentioned that we needed to re-adjust any instructions that had an instruction as an operand. For this we used the magic number "10" which I mentioned was made up of:
- 5 bytes - ldc.i4 statement (1 byte for the operand and 4 bytes for the integer)
- 5 bytes - call statement (1 byte for the operand and 4 bytes for the method pointer)
This is oversimplified as it assumes that the size of a method pointer is 4 bytes. While this is true in 32 bit assemblies, it is unfortunately too small for our 64 bit equivalent which uses 8 bytes for address pointers. Therefore we need to support 64 bit assemblies - but how can we tell if we are dealing with one or not?
Identifying 64 bit assemblies
Fortunately, Mono.Cecil makes it easier for us again (although very much hidden away). The way that an assembly is distinguished between a 32bit assembly and a 64bit assembly is via a "magic" field found in the PE Optional Header, specifically the Standard Fields section. More information about this section can be found under 25.2.3.1 of the Partition II Metadata document. The "magic" field's value is either:
- 0x10B = this is the value specified for PE32 assemblies or more specifically 32 bit assemblies for the framework.
- 0x20B = this is the value specified for PE32+ assemblies or more specifically 64 bit assemblies for the framework.
So all we need to do is load this unsigned 16 bit magic field and check what it's value is...
Fixing the hole...
As per usual; Mono.Cecil makes using this field fairly transparent. To help further, we'll create a couple of extension methods to make using this simple:
public static int GetAddressSize(this AssemblyDefinition assemblyDefinition)
{
if (Is64BitAssembly(assemblyDefinition))
return 8;
return 4;
}
public static bool Is64BitAssembly(this AssemblyDefinition assemblyDefinition)
{
if (assemblyDefinition == null) throw new ArgumentNullException("assemblyDefinition");
switch (assemblyDefinition.MainModule.Image.DebugHeader.Magic)
{
case 0x10b:
default:
return false;
case 0x20b:
return true;
}
}
Now when we're making our offset adjustments for string encryption, we simply use 6 + assemblyDef.GetAddressSize() as our "magic" number.
Conclusion
While this bug won't affect many people, it is still better to pre-empt! We'd made a "buggy" assumption that all address sizes were 4 bytes long which is incorrect for 64 bit assemblies. The fix didn't take much to do, however should make things a bit smoother for those of you that compile 64 bit assemblies.
Coming soon...
In our next posts we'll be looking at various methods of making Reflector trip over itself; and of course the methods of getting around this limitation. In particular:
- Strategies to trip up Reflector
- Implementation of one (or more) of those strategies
- Making Reflector work again
- Also, might throw in design strategies for making a GUI for this product
Until then, if you have any questions, bugs or suggestions then please let me know. It's always great to hear feedback, positive or negative, as it helps me improve both NCloak and this content!