Injecting code into .NET applications


Recently I have been playing with Function Evaluation available in .NET debugging API. This functionality allows a managed debugger to inject some arbitrary code while the debuggee is stopped. The injected code might be simply a call to the object’s ToString method or to a property getter. As a developer you profit from this service when you use Watch windows or when you run some code in the Immediate window in Visual Studio. In this post I will present you my MDbg plugin (includes a command: inject) that adopts the funceval API and an example diagnostics case in which I used it.

Introducing inject plugin

A detailed description on how to use plugins in MDbg can be found in my previous post. In order to use inject you must attach to a process which code you would like to modify. Then you need to check if you are at the point where you can use Function Evaluation: you can simply evaluate an expression such as f System.Console.WriteLine. Function Evaluation might be disabled at a given location – the thread is not at a GC-safe point or the thread is in a sleep (you may read about other problems in a great Mark Stall’s post devoted to this topic). If you encounter an error you will need to find a better place to break into a process – setting a breakpoint might be a good choice here.

I tried to automate this process, but found too many different situations in which I would need to find the valid break location. For instance for console applications I was traversing stacks of all the running threads and placing breakpoints on managed stack frames. With this approach I was able to break just after the current function return. Unfortunately it did not work with web application where worker threads often do not have any managed frames and the best place to stop is at the beginning of a request (for instance ASP.global_asax.Application_BeginRequest). It would be again different with WCF applications. Finally I decided I’ll place the responsibility for a safe break location upon the user.

After the breakpoint is hit we can perform code injection. You can call inject command passing as parameters a name of the assembly containing the code to be executed. Example call could be inject c:\temp\testasm.exe. Additionally we may add an appdomain name in which we would like to run the code.

Under the hood, my plugin creates an evaluator object on the current active thread. Then it evaluates the current appdomain (System.AppDomain.CurrentDomain) and calls ExecuteAssembly on it:

CorEval eval = thread.CreateEval();
// if we do not create a local copy we will get write memory problem
String asm = String.Copy(assembly);
eval.NewString(asm);
debugger.Processes.Active.Go().WaitOne();
if (!(debugger.Processes.Active.StopReason is EvalCompleteStopReason))
{
    throw new InjectionException("ERROR: injection was unsuccesful: assembly name evaluation failed.");
}
var assemblyName = (debugger.Processes.Active.StopReason as EvalCompleteStopReason).Eval.Result.CastToReferenceValue();

MDbgFunction func = debugger.Processes.Active.ResolveFunctionNameFromScope(
            "System.AppDomain.get_CurrentDomain", appdomain);
eval.CallFunction(func.CorFunction, null);
debugger.Processes.Active.Go().WaitOne();
if (!(debugger.Processes.Active.StopReason is EvalCompleteStopReason))
{
    throw new InjectionException("ERROR: injection was unsuccesful: get_CurrentDomain failed");
}
var currentDomain = (debugger.Processes.Active.StopReason as EvalCompleteStopReason).Eval.Result.CastToReferenceValue();

// call execute assembly
func = debugger.Processes.Active.ResolveFunctionNameFromScope("System.AppDomain.ExecuteAssembly", appdomain);
eval.CallFunction(func.CorFunction, new[] { currentDomain, assemblyName });
debugger.Processes.Active.Go().WaitOne();

// now display result of the funceval
if (!(debugger.Processes.Active.StopReason is EvalCompleteStopReason))
{
    throw new InjectionException("ERROR: injection was unsuccessful: ExecuteAssembly failed");
}

If we were successful the code in the Main method of our assembly should have been executed in the target application appdomain. We can then detach from the target application and our changes will remain till it is restarted.

Code injection in application diagnostics

There are many different situations in which you can use inject plugin. I will show you one usage example in which it proved useful. We have a Windows service running in production which is responsible for processing emails to our clients. One day we observed that some of the emails are not going out. No error was logged, service CPU and memory usage was also normal. I prepared a simple code to inject into the app that will enable verbose tracing:

using System;
using System.Diagnostics;
using System.Reflection;

public static class Program
{
    public static void Main() {
        var listener = new TextWriterTraceListener(@"C:\logs\email.log");
        Trace.AutoFlush = true;
        Trace.Listeners.Add(listener);

        // we need to fool a bit framework
        var asm = typeof(Trace).Assembly;
        var logtype = asm.GetType("System.Net.Logging");
        if (!(bool)logtype.GetProperty("On", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null, null))
        {
            logtype.GetField("s_LoggingEnabled", BindingFlags.NonPublic | BindingFlags.Static).SetValue(null, true);
        }

        var traceSourceProps = new[] { "Web", "Http", "Sockets", "WebSockets" };
        var sw = new SourceSwitch("sw", "Verbose");
        foreach (var tsp in traceSourceProps)
        {
            var source = (TraceSource)logtype.GetProperty(tsp, BindingFlags.NonPublic | BindingFlags.Static).GetValue(null, null);
            source.Switch = sw;
            source.Listeners.Add(listener);
        }

        AppDomain.CurrentDomain.FirstChanceException += (o, e) => {
            Trace.TraceWarning("First chance exception occured: {0}", e.Exception);
        };
        Trace.TraceInformation("injected");
    }
}

After examining the log file I was able to identify an error in one of the email templates. The main service was silently swallowing the exception and thus making it invisible to us.

Imagine other scenarios in which injecting might also work, such as dynamically enabling specific traces, registering proxy classes in the containers, adding global filters in ASP.NET MVC applications etc. The good thing about this way of diagnostics is that it stops your application only for a moment in order to inject the code. Keep in mind though that if MDbg terminates without detaching it will also terminate the target application. The source code of the inject plugin and the binaries are available for download from my .NET Diagnostics Toolkit site.

Injecting code into .NET applications

One thought on “Injecting code into .NET applications

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s