I've been playing around with some code lately that uses dynamic method generation fairly extensively. In the course of doing so, I've written the odd dodgy bit of IL out. Interestingly, a couple of time I got some very strange results when assigning fields from one object to another - specifically, if I got the types mismatched I just got garbage in the destination rather than some form of Cast exception (which I'd expect the runtime to generate during execution) or Verification exception (which I'd expect when I finally surface my generated method through a call to DynamicMethod.CreateDelegate()).
Finally had some time today to take a closer look, and the results are very interesting and not at all clear from the documentation. Specifically, if you create a dynamic method using the following constructor:
public DynamicMethod(
string name,
Type returnType,
Type[] parameterTypes,
Module m
)
and pass in "Assembly.GetExecutingAssembly().ManifestModule" for the module, then it appears that all type safety within the generated code is turned off. i.e., you can pretty much assign anything to anything. The following code, for example, enables you to dump the memory address of any reference type:
/// <summary>
/// Return a method that gives the memory address of any object
/// </summary>
static Func<object, int> Get_GetAddress_Method()
{
DynamicMethod d = new DynamicMethod("", typeof (int), new Type[] {typeof (Object)},
Assembly.GetExecutingAssembly().ManifestModule);
ILGenerator ilGen = d.GetILGenerator();
ilGen.Emit(OpCodes.Ldarg_0); // Load arg_0 onto the stack (of type object)
ilGen.Emit(OpCodes.Ret); // And return - note that the return type is an int...
return (Func<object, int>)d.CreateDelegate(typeof(Func<object, int>));
}
You can use this in the following way:
Func<object, int> getAddress = Get_GetAddress_Method();
const string greeting = "Hello";
// Get the address of the "Hello" string
int x = getAddress(greeting);
x now contains the memory address of the string "Hello". So what? Well, you can also write a method like this:
/// <summary>
/// Return a method that "maps" any type to a particular memory location
/// </summary>
static Func<int, T> Get_ObjectAtAddress_Method<T>()
{
DynamicMethod d = new DynamicMethod("", typeof (T), new Type[] {typeof (int)},
Assembly.GetExecutingAssembly().ManifestModule);
ILGenerator ilGen = d.GetILGenerator();
ilGen.Emit(OpCodes.Ldarg_0); // Load arg_0 onto the stack (of type int)
ilGen.Emit(OpCodes.Ret); // And return - note that the return type is T
return (Func<int, T>)d.CreateDelegate(typeof(Func<int, T>));
}
This chap lets you take any memory address, and "pretend" that an object of type T resides there. So you can do something like this:
Func<int, byte[]> getData = Get_ObjectAtAddress_Method<byte[]>();
// Get a byte array on the same location
byte[] data = getData(x);
where x is a memory location that you've acquired previously. It doesn't matter if the type that really resides at address x is a byte[] or not. This basically lets you get access to the whole address space within your AppDomain (and possibly the whole Win32 process) and write whatever you like into it.
This seems plain wrong to me - I haven't specified the "unsafe" keyword anywhere, nor is this code built with the "Allow unsafe code" box checked. Without jumping through those hoops, I should not be able to write code like this. I'll concede that this only works in a full trust environment, but it still smells like a very serious hole in the type safety of .Net. Interestingly, if you use the DynamicMethod constructor that doesn't take a Module parameter, then everything works as you'd expect - you are politely served a VerficationException when you try to compile the method. According to the docs, the constructor overload that takes a module is only supposed to allow access to internals of the specified module, not to skip type safety. I wonder if the implementation of DynamicMethod in that scenario is flawed.
Below is a big lump of code - it compiles and shows the issue quite clearly. I'd be interested in your views on whether this is a bug or "by design". If the latter, what exactly was the scenario that they were designing for?
using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main()
{
// Get some methods generated...
Func<object, int> getAddress = Get_GetAddress_Method();
Func<int, byte[]> getData = Get_ObjectAtAddress_Method<byte[]>();
const string greeting = "Hello";
// Print the greeting
Console.WriteLine(greeting);
// Get the address of the "Hello" string
int x = getAddress(greeting);
// Get a byte array on the same location
byte[] data = getData(x);
// Change some data...
SetString("Bye!!", data);
// And display the greeting again (remember, strings are immutable...)
Console.WriteLine(greeting);
// And just to show it against other bits of the framework...
Console.WriteLine(Assembly.GetExecutingAssembly().FullName);
SetString("Hacked!", getData(getAddress(Assembly.GetExecutingAssembly().FullName)));
Console.WriteLine(Assembly.GetExecutingAssembly().FullName);
}
/// <summary>
/// Return a method that gives the memory address of any object
/// </summary>
static Func<object, int> Get_GetAddress_Method()
{
DynamicMethod d = new DynamicMethod("", typeof (int), new Type[] {typeof (Object)},
Assembly.GetExecutingAssembly().ManifestModule);
ILGenerator ilGen = d.GetILGenerator();
ilGen.Emit(OpCodes.Ldarg_0); // Load arg_0 onto the stack (of type object)
ilGen.Emit(OpCodes.Ret); // And return - note that the return type is an int...
return (Func<object, int>)d.CreateDelegate(typeof(Func<object, int>));
}
/// <summary>
/// Return a method that "maps" any type to a particular memory location
/// </summary>
static Func<int, T> Get_ObjectAtAddress_Method<T>()
{
DynamicMethod d = new DynamicMethod("", typeof (T), new Type[] {typeof (int)},
Assembly.GetExecutingAssembly().ManifestModule);
ILGenerator ilGen = d.GetILGenerator();
ilGen.Emit(OpCodes.Ldarg_0); // Load arg_0 onto the stack (of type int)
ilGen.Emit(OpCodes.Ret); // And return - note that the return type is T
return (Func<int, T>)d.CreateDelegate(typeof(Func<int, T>));
}
/// <summary>
/// Little helper method to copy a string into a byte[]
/// </summary>
static void SetString(string requiredString, byte[] dest)
{
UnicodeEncoding encoder = new UnicodeEncoding();
byte[] requiredBytes = encoder.GetBytes(requiredString);
// Need to do the copy by hand, since Array.Copy bleats
// about the dimensions of the destination. No surprise really,
// since the destination isn't really an array...
for (int i = 0; i < requiredBytes.Length; i++)
{
dest[i] = requiredBytes[i];
}
}
}
}
1 comment:
Not sure why there are no comments in 7 years. I've seen it referenced elsewhere... Anyway just wanted to thank you for this great post it helped me a lot ~Justin
Post a Comment