Wednesday, December 2, 2009

Framework Injection Part One of Two - How to Patch mscorlib.dll and put it back in the GAC

“ What the F@#$! A rootkit where?!?!”


Microsoft .Net developers have, from the first iterations of the .Net Framework, taken shelter in a solid security ideology about the code they offer. Strong naming, assembly evidence, etc…. basically “Safety through managed code” was the general consensus. However some had their doubts. About 9 months ago, at a small security conference Erez Metula first gave a speech on .Net Framework Rookits, then he gave a revised version at BlackHat 2009.

Having taken a serious look at this for the past year or (Since DC16 ) I have decided to elaborate on his attempts, and make efforts to ensure a hybrid rootkit – one that can use traditional DKOM (Direct Kernel Object Manipulation) and SSDT/IAT patching to help obscure and persist a .Net Framework Rootkit. And then utilize the .Net Framework rootkit to help persist the unmanaged kit. The busiest code will be managed, making heuristics a very hard endeavor to say the least.

This ‘secure’ ideology is brought back down to earth by a key element in Microsoft’s implementation of the Global Assembly Cache. This fatal flaw brings about new vectors as to what you can do in application layered attacks – which is literally anything you want. I am talking undetected, unfettered, and persisted access to user land / kernel land. This enables you to root the box and along with it all business programs built in .Net. All it takes is some free software and you are set to writing your first .Net Framework rootkit.

I hope you can take more away from this than just another tool or paper. The point here is to transfer a basic understanding about what you can do after you exploit when you find yourself in the need to persist access to a system. No special script tool needed, just pay intimate attention to the detail. Please Enjoy.

-blakdayz ^t

Credits: Erez Metula for his BlackHat conference talk on this – way to bring awareness to this glorious fault

5houtz 2 v1ru5, DT, Carric, Queeg, datalus, Pyro, Ed, and Delchi (he throws one Hell of a party), and KingPin… and to the crew of DC225 & 303 … to the rest – much thanks to the beer and entertainment in the Red Stick & Vegas.

The Tools You Require

These are the droids you are looking for (your tools, go… get them… quickly now):

· ProcMon from SysInternals (Free)

· .Net Reflector by RedGate (Free)

· Visual Studio Express (Free)

· .Net Framework 2.0 SDK (Free)

· A text editor

· Ngen (Native Compiler)

All of these tools are free and easily located. Install them.

>Section A: The gist of the Strong Naming bypass that allows this is an extreme .Net Framework weakness
(read on, I will explain)

It seems that the touted reliance on SN (Strong Naming), and the promotion of its assembly integrity verification propaganda were not entirely truthful on Microsoft’s part. While gacutil.exe may look at this evidence when it is copying a dll to the GAC, nothing looks at evidence, or prevents a true file system copy of this Dll to its appropriate location in the GAC file system. For those that are new to the GAC, it resides at c:\windows\assembly location. If you browse to this location in Windows Explorer, you get a .Net representative interface into the file system. If you drag and drop just any DLL there, you will get the expected gacutil Strong Name violations for a non-strong named replacement.

Important Note

If you alter a .Net Framework assembly that is already strong named, it cannot load an assembly that is Strong Named. Doing so would cause lazy-loaded assemblies to fail the manifest checking of the CLR assembly load operations. You may be able to patch this loading process to the point where you can push yourself into DLLs that do depend on Strong Naming, if you first break the Strong Naming mechanism check.

How do you get around the Strong Naming mechanisms in the GAC and .Net? If you were to navigate to the GAC location directly via a command prompt (shell) you would see a completely different picture. Through the shell you can copy our malicious DLL over the one currently cached – without any interference (provided you have install rights to the box).

To get your new malicious code loaded another step is required. You must wipe any image of the victim DLL from the native images location. The native images location contains non-JIT code that is representative of the .Net managed library. This means there is a machine code variant of most .Net Framework DLLs. This optimizes the speed of the .Net Framework assemblies’ code via bypassing the JIT compiler and just running machine code.

To force load the malicious assembly when something requests it, you must delete this optimized dll from the Native Images location that corresponds to the framework dll you are injecting. This will force load the JIT compliant DLL that you have injected into the referencing clients app domain. We will talk more on this subject when we perform the injection of our target code.

Example: A Different Kind of Hello World Example

Browse to your GAC via the shell. Our example will victimize mscorlib.dll.

1. Change your location to the assemblies directory (it’s the filename of the assembly, minus the extension).

2. List the directory. Notice the version of the DLL and the public key token of the DLL form this directory? This is the VERSION_TOKEN. This is where a .Net assembly’s information in the GAC listing is derived from. When these are loaded, no evidence is looked at (normally it’s a native - not managed DLL - that is loaded). When you delete that native DLL, the system auto-magically looks for it in the GAC and loads it, no checking of the Strong Name of the DLL is done. This is a major design flaw that MS is aware of, but lacks the ability to fix (since, as an administrator, you have access to the file system directly).

7. In Reflector, Select File>Open then browse to the location you copied your DLL to. Click the Open button. Now you can expand the namespace mscorlib and browse to the library CommonLanguageRuntime. Browse to the namespace ‘System’, and find Console. Expand Console and right click the Writeline(string) function. Choose Disassemble from the menu.

Opening ILDASM – If you have installed the SDK at this point you can find it in the ‘Program Files\Microsoft.NET\SDK\v2.0\Bin’ location. In IL DASM select File > Open and locate the malicious code DLL. Expand the System tree menu until you find System.Console. Under System.Console find

WriteLine : void(string) . Double click that entry and you should get the ILASM dump of that function. Copy the entire contents of that file into your text editor.

The method’s IL ASM should look like this:

method public hidebysig static void WriteLine(string 'value') cil managed


.permissionset linkcheck

= {class 'System.Security.Permissions.HostProtectionAttribute, mscorlib, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089' = {property bool 'UI' = bool(true)}}

// Code size 12 (0xc)

.maxstack 8

IL_0000: call class System.IO.TextWriter System.Console::get_Out()

IL_0005: ldarg.0

IL_0006: callvirt instance void System.IO.TextWriter::WriteLine(string)

IL_000b: ret

} // end of method Console::WriteLine

Take a look at the line IL_0000 thru IL_0006. This code first selects an output method (stdout) and then calls TextWriter:WriteLine(string). We want this action to occur twice for our example project. Note that there is no .locals directive, and we do not wish to change the value of .maxstack, we just want the already encoded action to occur twice.

Injecting IL ASM Basics

Line Numbering

In regards to those line numbers of the IL dump. They are necessary to get right, because when we recompile they need to be straight. There are shortcuts around some of these steps, but line numbering skill is necessary as the research involved in accomplishing unlocks other nuggets of understanding.

Without using a tool there is a way to know what line comes next and it is based on what IL opcode is getting called on that particular line. You simply add up the number of bytes offset by calling that opcode and your result will be your following line number. This is necessary when we plan on injecting our code into Microsoft’s libraries. To get a good idea of this, look to ECMA 335 Partition III Opcode specification.


A very good example is the following code, taken from the System.Console.WriteLine(string) function IL above:

IL_0000: call class System.IO.TextWriter System.Console::get_Out()

IL_0005: ldarg.0

The first line here with “IL_0000:”. In this line of code, we have the ‘call’ opcode with 4 parameters which are (per the ECMA Specs):

the method to call, and the number, type, and order [implied] of the arguments that have been placed on the stack to be passed to that method, as well as the calling convention to be used”

So that’s four(4) parameters, plus the opcode itself (28 in this case), which is 5 total bytes .So, after the ‘call’ line, the next following line number should be 5 more than the line that contained the ‘call’ opcode. This is the line number offset (a 5 byte offset in the ‘call’ opcode case). When mating this code to another function, we would take a look at the line number immediately before the insertion and, if the preceding opcode was ‘call’, we would add 5 to the line number (in Hex) to get the new line number for our first line of code. This will be shown in an example later.

.maxstack Opcode/Directive

One of the topics where I and the original author of the first whitepaper on this attack differ is the .maxstack directive.. This opcode is a little confusing by its name - as it is not the maximum number of bytes on the stack, nor the number of items. The .maxstack directive helps a tracking tool (like a debugger or profiler) keep track of X amount of objects (if you wanted good debuggable or profilable code, then you would add your new IL maxstack to the value of the victim code and set the total). However, I would not like my malicious code to be compatible with debugging, thank you very much. Leave this alone.

For your reference in any ideological front where you may need to explain yourself, I include this snippet from page 17 of ECMA Partition III Specification:

“[Note: Maxstack is related to analysis of the program, not to the size of the stack at runtime. It does not specify

the maximum size in bytes of a stack frame, but rather the number of items that shall be tracked by an analysis

tool. end note]

[Rationale: By analyzing the CIL stream for any method, it is easy to determine how many items will be

pushed on the CIL Evaluation stack. However, specifying that maximum number ahead of time helps a CIL-tonative-

code compiler (especially a simple one that does only a single pass through the CIL stream) in allocating

internal data structures that model the stack and/or verification algorithm. end rationale]”

Like I said, if you don’t want easy debug analysis of your unfriendly code, leave this alone. If you want to build a debug/profiler enabled version, add your .maxstack value to the injection method’s .maxstack value.

How to Mate Your Code

Some functions are purely static - meaning that they instantiate no locals at all (you won’t need this info), such as in the example project’s Console.WriteLine(string) method. If this is the case with your victim code, but your malicious code has a .locals directive – you must include that .locals directive on the calling code. There is more information regarding locals at the end of the tutorial.

Altering the Assembly

In order to alter the assembly we must take the following steps

1. Renumber the lines via the documentation

2. Fix and remember the locals init order, as well as the stlocals (if required)

The resultant, ready to mate code for doubling the writeline() function would look like the following:

.method public hidebysig static void WriteLine(string 'value') cil managed


.permissionset linkcheck

= {class 'System.Security.Permissions.HostProtectionAttribute, mscorlib, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089' = {property bool 'UI' = bool(true)}}

// Code size 12 (0xc)

.maxstack 8

IL_0000: call class System.IO.TextWriter System.Console::get_Out()

IL_0005: ldarg.0

IL_0006: callvirt instance void System.IO.TextWriter::WriteLine(string)

IL_000b: call class System.IO.TextWriter System.Console::get_Out()

IL_0010: ldarg.0

IL_0015: callvirt instance void System.IO.TextWriter::WriteLine(string)

IL_001b: ret

} // end of method Console::WriteLine

...... Next post will show you how to edit, compile, and move the assembly back into the GAC.


  1. When people refer to a managed framework being safe, I never thought they meant from exploit, but rather safe from coding errors that could destabilize or outright crash an OS. What about the framework makes people think it's safe from outside exploitation? Surely not just Strong-Naming and memory segmentation... Strong-Naming is intentionally weak to allow ease of use.

  2. That is partially true. Managed code is 'type-safe' so that traditional buffer attacks and heap attacks are harder to accomplish - tho they too are still possible.

    However, Strong Naming is used to verify the authenticity of an assembly prior to load as being the assembly originally referenced by developer. Microsoft relies upon Strong Naming to offer a signed, verifiable framework on each machine.

    The real problem is that all other forms of implemented .Net security features (code access security,role based security, encryption, etc.) all derive themselves as secure because Microsoft signed the assembly they are built into (mscorlib), and that assembly undergoes (supposedly) rigorous encryption routines to verify its authenticity.

    Tell me, where on the following page
    Does Microsoft inform you that all the rhetoric may not be true?

    Most .Net developers read this and auto assume SN protects them. Microsoft does not disclose that all of its security features contain this achille's heel.

    The problem is that malicious code can be placed in Microsoft DLL's that report that strong name has been verified as signed by Microsoft and validated. This, in and of itself is a terrific flaw that the .Net community should be fully aware of.

  3. Sorry, forgot to note - first page, last paragraph....

    "I hope you can take more away from this than just another tool or paper. The point here is to transfer a basic understanding about what you can do after you exploit when you find yourself in the need to persist access to a system. No special script tool needed, just pay intimate attention to the detail. Please Enjoy."