Yesterday I blogged about a bug that you could exploit in JScript .NET, and the other day I made a comment on Eric's blog about compiler-enforced rules versus runtime-enforced rules. Here's a quick story about one such rule that we fixed before the CLR shipped. I'm warning you -- it's obscure and boring and you'll never get the 5 minutes of your life back if you keep reading, but someone wanted me to post about such things so here goes.
Most people probably know about constructors -- they are methods that are called to "construct" (or create new instances of) classes. They are called implicitly in most languages -- such as C#, JScript, and VB -- when you use the new (or New) operator. You may not be aware of class constructors, also called static constructors, which initialise the class (type) itself, rather than instances. Basically it is used to initialise and static variables or do other one-time-only operations before any other members of the class (static or instance) are accessed.
None of the high-level languages that Microsoft ships let you call the class constructor directly; not only is there no special syntax for calling it, but it has an illegal name in most languages because it is called .cctor (the dot is part of the name, not just the "member access operator") and so you can't even call it explicitly. But ILASM will let you call such a method -- it uses the double-colon as the member access operator, and can call things like .cctor and .ctor by name -- and so that means hackers can call it.
The problem is that programmers make assumptions about things such as their constructor (and static constructor) only being called once. After all, that's what the language says happens, and that's what makes sense. But not according to the CLR! If you look at Partition II of the CLI spec, you will find this snippet in section 188.8.131.52:
· A type initializer [sic] shall run exactly once for any given type, unless explicitly called by user code
Emphasis added by me. The CLI explicitly allows you to call the .cctor as many times as you like! I'm not really sure why this is so, except that it probably lets them save a few bits here and there for storing a "has it already been called?" field and a few processor cycles for checking the "has it already been called?" field, which no doubt improves performance and memory usage and whatnot. Plus, under normal circumstances (ie, assemblies built with C# or VB), the .cctor methods are all private and so no-one can actually call them to do anything interesting (the assumption being that you want to call .cctor twice to subvert someone else's presumably-more-trusted code into doing something that you want it to do; calling .cctor twice on your own hand-tweaked IL assembly doesn't make much sense since there's no elevation or privilege attack possible).
Or so we thought...
Apparently the Reflection.Emit developers didn't read the CLI spec quite as well as, uh, the people who did read the spec. Using JScript .NET (which was built in 100% managed code and therefore used Reflection.Emit to generate assemblies), it was possible to create a public .cctor in one assembly that could then be called by a second hand-tweaked IL-generated assembly (with minimal permissions), potentially subverting security measures and thereby elevating privileges or doing other nasty things. In fact, it was impossible not to create a public .cctor, since Reflection.Emit always generated them as public. This of course meant that any assembly created using Reflection.Emit (via JScript .NET or a 3rd-party managed language) was susceptible to this attack, and so Reflection.Emit was fixed to ensure that the .cctor methods were made private.
In case you want a demo of what this looks like (just for proof...) take this C# program:
private static bool
// Should only be called once per AppDomain
// The system automatically calls it when needed
Console.WriteLine("Class initialiser being called");
canCallSecond = true;
public static void
Console.WriteLine("Doing secure first thing");
// Ensure callers can't do SecondThing
canCallSecond = false;
public static void
Console.WriteLine("Doing secure second thing");
This basic program declares a class SecureClass that has two methods -- FirstThing and SecondThing -- and you can call either one of them as many times as you like, but if you ever call FirstThing then you are never allowed to subsequently call SecondThing. (It's a silly example, but I wanted something short). This is enforced by a static member field canCallSecond which is set to true in the class initialiser, but set to false once FirstThing is called.
If you compile and run this code with commands such as:
C:\Code> csc CallClassConstructorTwice.cs
Then you will see something like this:
Class initialiser being called
Doing secure first thing
All is well and as expected. We tried to call FirstThing and then SecondThing, which is against the rules.
Now dump the IL for the assembly and open it in Notepad with the following commands:
C:\Code> ildasm /out=CallClassConstructorTwiceHack.il CallClassConstructorTwice.exe
C:\Code> notepad CallClassConstructorTwiceHack.il
You will see a lot of goop, but we are interested in doing two things. First of all, locate the declaration of the .cctor for SecureClass; it should be on lines 56 and 57 and look like this:
.method private hidebysig specialname rtspecialname static
void .cctor() cil managed
Now change the "private" modifier to be "public" so it looks like this:
.method public hidebysig specialname rtspecialname static
This simulates the type of constructor generated by Reflection.Emit before the bug was fixed. Now scroll down to the Main method and find the lines where we call the two methods; it should start on line 118 and look like this:
IL_000a: call void SecureClass::FirstThing()
IL_000f: call void SecureClass::SecondThing()
IL_0014: leave.s IL_0024
} // end .try
Now what we're going to do is inject a call to the .cctor between the two method calls. Of course in the real attack, this class would be in a separate assembly that was going to be granted fewer permissions than the one containing SecureClass, but again this is only a simple demo of something that doesn't even "work" anymore.
One might think that you'd have to then re-shuffle all the IL_XXXX offsets, but they're just labels and we don't need to worry about it. So now we have:
IL_000c: call void SecureClass::.cctor()
And we're good to go. Compile it back up into an executable with the following command:
C:\Code> ilasm CallClassConstructorTwiceHack.il
And, just to be sure we're not doing anything too bad, let's run it through the verifier:
C:\Code> peverify CallClassConstructorTwiceHack.EXE
Microsoft (R) .NET Framework PE Verifier Version 1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
All Classes and Methods in CallClassConstructorTwiceHack.EXE Verified
And what does our new program output?
Doing secure second thing
Yay! We've subverted security and successfully called FirstThing and then SecondThing.
So that was one of the weird things that happened when the compilers enforced rules that were stricter than the underlying runtime, and developers weren't fully aware of it. The core "problem" of being able to call .cctor multiple times still exists -- it's part of the ECMA CLI standard, so presumably lots of people looked at it and thought it was an OK thing to do -- but all the high-level Microsoft compilers and Reflection.Emit ensure that only private .cctor methods are generated, so you should be safe from any actual security issues unless you're in the business of hand-generating IL all by yourself.
There was a similar problem with being able to explicitly call the .ctor (instance constructor) many times via Reflection in a late-bound manner, but that was fixed a different way and we might get around to discussing it another day.
P.S. There's another e-mail virus out... this one will send itself out via e-mail, install a backdoor onto your system, and then try to participate in a DDoS attack on SCO on February 1st. Make sure you visit www.microsoft.com/protect/ and never open executable e-mail attachments.