Here's a quick hack you can use to pry open those pesky closed types in a .NET assembly, even if you don't have the source code. It also demonstrates the openness of managed platforms like .NET and Java.
NOTE: The procedure involves some simple decompilation techniques, so be sure you have legal hudspa to reverse whatever you're reversing.
The hack is quite simple:
- disassemble the .NET assembly using the ILDASM disassembler available in the .NET SDK;
- modify the disassembled IL code, changing private types / methods / properties / fields to public as necessary;
- reassembly the .NET assembly using the ILASM assembler, also available in the .NET SDK.
I'll walk through the technique using a simple example. Take a quick look at the source code for a simple console application named myprogram.exe:
namespace My
{
static class Program
{
static void Main(string[] args)
{
Console.WriteLine( Message );
}
static string Message
{
get
{
return "this message is top secret";
}
}
}
}
The code defines one class, with one method and one property, and everything is private. Our goal for today will be to make all of these things publically accessible without accessing this original source code.
Step 0: Create the Assembly ...
Build the sample code into an assembly named myprogram.exe using your favorite C# compiler. If you want, try to referece the Program class in another assembly or project, and you'll notice that there are no publically visible types exposed by the myprogram.exe assembly.
If you have a flair for the dramatic, then you can delete the source code at this point since we don't need it anymore.
Step 1: Disassemble ...
The first step is to disassemble the .NET assembly using ILDASM:
ILDASM <assmebly-file> /nobar /out=<output-file>
where <assembly-file> is the file name you want to disassemble, the /nobar switch supresses a UI progress bar, and the /out argument specifies the <output-file> that will contain the disassembly code.
The result of the above command is usually two new files, an IL file containing the disassembly, and a RES file containing the compiled resources from the assembly.
For our purposes, we'll dump the IL into a temporary code file:
ILDASM myprogram.exe /nobar /out=temp.il
Step 2: Modify the IL ...
Step two is the nut of the hack. If you've ever worked in assembler, but have never seen IL, you're in for a real treat (if you've never seen assembler and prefer not to dwell into such lands, you may want to skip the next section).
Sidebar to IL-town
Many of the CLI constructs that occur in the C# language appear almost verbatim in IL, making our current task excessively simple. Crack open the temp.il file in your favorite code editor and gaze upon the IL rendition of myprogram.exe:
// note: assembly metadata removed for brevity
.class private abstract auto ansi sealed beforefieldinit My.Program extends [mscorlib]System.Object
{
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 13 (0xd)
.maxstack 8
IL_0000: nop
IL_0001: call string My.Program::get_Message()
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret
} // end of method Program::Main
.method private hidebysig specialname static string get_Message() cil managed
{
// Code size 11 (0xb)
.maxstack 1
.locals init ([0] string CS$1$0000)
IL_0000: nop
IL_0001: ldstr "this message is top secret"
IL_0006: stloc.0
IL_0007: br.s IL_0009
IL_0009: ldloc.0
IL_000a: ret
} // end of method Program::get_Message
.property string Message()
{
.get string My.Program::get_Message()
} // end of property Program::Message
} // end of class My.Program
Ignore all of the stuff that looks like gibberish for now - what I want you to note is that the class, method, and property-get accessor definitions are recognizable and follow predictable patterns. Note also that they are each explicitly marked as being "private." Sorry, but no prizes for guessing what comes next...
Modify the IL using simple text processing
The IL is just text, so we can do a simple search-and-replace with our favorite editor to make the modifications we need. If we need to get fancy or specific, the signatures adhere to patterns well-suited for simple regular expression matching. Whatever our scope, we simply need to change the declarations of interest from "private" to "public" (changes hilited):
.class public abstract auto ansi sealed beforefieldinit My.Program extends [mscorlib]System.Object
{
.method public hidebysig static void Main(string[] args) cil managed
// ...
.method public hidebysig specialname static string get_Message() cil managed
// ...
} // end of class My.Program
Step 3: Reassemble ...
The IL doesn't do us any good in text form - we need the binary the IL describes. Thanks to ILASM, the binary just a command line away...
ILASM /exe /output=<assembly-file> /res=<res-file> <source-file>
where /exe means we want to build an executable assembly, /output identifies the <assembly-file> we want to create, /res identifies a <res-file> referenced within the assembly. Let's reassemble the open version of myprogram.exe:
ILASM /exe /output=open.myprogram.exe /res:temp.res temp.il
This will produce a new executable named open.myprogram.exe that behaves identically to the original. Granted, the final result of this effort may not appear terribly impressive, until you try to reference this new assembly in another project. You'll find that the My.Program class and its members are all publically visible usable in your new project.
So ... What?
How often will you really need to expose hidden members and types without access to the original source? Granted it will be infrequent, if ever. I've done it enough times in the arcane projects I end up on to script the process in powershell.
And I'm not trying to prove that access modifiers fail to constitute a security platform.
The thing I find really interesting about this technique is the breadth of things you can accomplish with little to no background in reversing (the author being a case-in-point). With a little effort and some googling on IL you can use the same basic technique to accomplish all of the following:
- alter the behavior of a sealed type;
- virtualize type members at whim;
- remove the strong name from an assembly, or replace the existing strong name with another;
- add or remove metadata to the assembly, types, or type members;
- intercept and forward all calls from one method or type to another, as in a man-in-the-middle attack;
- etc.
Accomplishing these kinds of things used to require specialized and esoteric knowledge, like "e9 is the machine code for an absolute jump on an x86 processor". But in .NET and other managed platforms like Java, these things become so accessible you can practially leverage the original code during the reversing process.
... I'm not sure whether to stop grinning or shivering ...