• Man in the middle C# Attacks

    by  • March 23, 2012 • .Net, Programming • 0 Comments

    Previously I discussed the idea of creating a Man-in-the-middle style tools for analysing or compromising C# assemblies. In this blog post I go one step further and create a rough proof of concept demonstrating the possibility. It should be noted that there are ways of easily preventing this sort of attack which I have previously covered.

    As shown in a previous blog post, I showed how to analyse a .Net assembly. By using various properties exposed by reflection, using these tools we can rebuild and dynamically invoke a callee assembly and impersonate it to the original caller.

    So far this is my ‘rough around he edges’ implementation of the code to generate the middle assembly.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    
    public static class MethodIntercepter
    {
        ///
    <summary> /// Constructs the source code for a man in the middle intercepter.
     /// </summary>
        ///The assembly to intercept
        public static void BuildIntercepter(Assembly assembly)
        {
            Console.WriteLine("// Generated by the Method Intercepter");
            Console.WriteLine("// Created by Kevin Pfister.");
    
            Console.WriteLine("namespace {0}\n{{",
                assembly.GetName().Name);
    
            // Get the name of all assemblies referenced apart form mscorlib (System.Core)
            var assembies = assembly.GetReferencedAssemblies()
                 .Where(assem => !assem.Name.Equals("mscorlib"))
                 .Select(assem => assem.Name);
    
            // Add all the using statements.
            foreach (var assem in assembies)
            {
                // Only use the higher level part of the assembly
                string usingAssem = assem;
                if (usingAssem.IndexOf('.') != -1)
                {
                    usingAssem = usingAssem.Substring(0, usingAssem.LastIndexOf('.'));
                }
    
                Console.WriteLine("\tusing {0};", usingAssem);
            }
    
            if (!assembies.Contains("System.Reflection"))
            {
                // System.Reflection wasn't automatically added.
                Console.WriteLine("\tusing System.Reflection;");
            }
    
            Console.WriteLine();
    
            // Only iterate through exported public Types.
            foreach (Type type in assembly.GetExportedTypes())
            {
                // Only search for methods that originate from the Assemblies associated modules.
                if (type.GetMethods().Count(m =>
                    !m.IsVirtual &&
                    !m.IsAbstract &&
                    assembly.GetModules().Contains(m.Module)) > 0)
                {
                    // Write the Class header
                    Console.WriteLine("\tpublic class {0}\n\t{{", type.Name);
    
                    // Write additional information for the class, these variables
                    // are used by the software.
                    Console.WriteLine("\t\tprivate Assembly assembly;");
                    Console.WriteLine("\t\tprivate Type type;");
                    Console.WriteLine("\t\tprivate object classObject = null;");
    
                    if (type.GetMethods().Count(m => m.IsConstructor) == 0)
                    {
                        // The class does not contain a constructor, write one to contain
                        // the initisation code for the new functionality.
                        Console.WriteLine("\n\t\tpublic {0}()\n\t\t{{",
                            type.Name);
    
                        Console.WriteLine("\t\t\t// Auto Generated Code");
    
                        // The constructor has additional code to handle the loading of the assembly
                        Console.WriteLine("\t\t\tassembly = Assembly.LoadFile(\"{0}\");",
                            assembly.Location);
                        Console.WriteLine("\t\t\ttype = assembly.GetType(\"{0}\");",
                            type.Name);
    
                        Console.WriteLine("\t\t}\n");
                    }
    
                    foreach (MethodInfo methodInfo in type.GetMethods())
                    {
                        // Ignore particular method attributes.
                        if (!methodInfo.IsVirtual &&
                            !methodInfo.IsAbstract)
                        {
                            // Ignore methods that have no method body
                            MethodBody body = methodInfo.GetMethodBody();
                            if (body != null)
                            {
                                // Build the parameters that go to the original function.
                                StringBuilder paramBuilder = new StringBuilder();
                                StringBuilder paramNameBuilder = new StringBuilder();
                                foreach (ParameterInfo parameter in methodInfo.GetParameters())
                                {
                                    paramBuilder.Append(parameter.ParameterType + " " + parameter.Name);
    
                                    if (paramNameBuilder.Length > 0)
                                    {
                                        paramNameBuilder.Append(", ");
                                    }
                                    paramNameBuilder.Append(parameter.Name);
                                }
    
                                // Build the method
                                Console.WriteLine("\n\t\tpublic {0} {1}({2})\n\t\t{{",
                                    methodInfo.ReturnType.FullName,
                                    methodInfo.Name,
                                    paramBuilder.ToString());
    
                                if (methodInfo.IsConstructor)
                                {
                                    // The constructor has additional code to handle the loading of the assembly
                                    Console.WriteLine("\t\t\tassembly = Assembly.LoadFile(\"{0}\");",
                                        assembly.Location);
                                    Console.WriteLine("\t\t\ttype = assembly.GetType(\"{0}\");",
                                        type.Name);
    
                                    // Call the original constructor
                                    Console.WriteLine("\t\t\tclassObject = type.GetConstructor(Type.EmptyTypes).Invoke(\"{0}\");",
                                        paramNameBuilder.ToString());
    
                                }
    
                                Console.WriteLine("\t\t\t// Write Man-in-the-middle code here\n");
    
                                // Write the code to invoke the original function.
                                string returnCode = "";
                                if (methodInfo.ReturnType != typeof(void))
                                {
                                    returnCode = string.Format("return ({0})",
                                        methodInfo.ReturnType.FullName);
                                }
                                Console.WriteLine("\t\t\t{0}type.GetMethod(\"{1}\").Invoke(classObject, new object[] {{ {2} }});\n",
                                    returnCode,
                                    methodInfo.Name,
                                    paramNameBuilder.ToString());
    
                                Console.WriteLine("\t\t}\n");
                            }
                        }
                    }
                    Console.WriteLine("\t}\n");
                }
            }
    
            Console.WriteLine("}");
        }
    }
    

    The concept of the code above works by imitating the source assemblies method and class signatures and dynamically calling the originating assemblies method.

    The easiest way of course to stop this from happening would be to sign your assemblies, which create a public-private key link between the caller and callee assemblies. Otherwise the caller could perform more thorough checks (e.g. MD5, signature matches etc.)

    The code that is produced has commented sections on where additional code can be added to edit / extract the parameter information passed between the methods. This area could also be used as a way to inject Performance Counters to gain information around memory usage, leaks and hot paths of code.

    About

    Software engineer. Tea drinker

    http://MrPfister.com

    Leave a Reply

    Your email address will not be published. Required fields are marked *