Securing Managed Assemblies with Native EXE Interoperability

Introduction

The Problem

Reflection

.NET and Interoperability

The Solution

Proof of Concept Demo

Conclusions

Introduction

While it is easy and fun to use the disassembly features of .NET to disclose the managed assembly contents, it also poses the problem of piracy of otherwise copyrighted content. If you forget for a moment the issues related to copyright and piracy and so forth, most of you at some point or another might have shown concern about securing your own assemblies from such easy exposure. Towards this extent, the following article describes a simple yet elegant way of securing your managed assemblies from easy disassembly by using the powerful interoperability features of .NET.

The Problem

Before you actually take up our final solution, let's first clearly specify what your problem is. In a nutshell, you could specify it something like, "No one should be able to disassemble my managed assemblies." Now, the first question is, how does one disassemble any assembly? Reflection is one of the most powerful features of .NET that makes this kind of facility possible. Theoretically, one could load any managed assembly to see what all types are available in it and create instances of desired types, invoke methods, query for referenced assemblies, and whatnot. The metadata produced by the compiler as part of assembly generation makes all these operations trivial for Reflection. Without this metadata, Reflection could not do its work. And, the point to be noted is: Native executables do not have any such metadata.

Thus, you can safeguard your managed assembly if you can hide its metadata from the dissembler. However, hiding the metadata is a very hazardous option given the fact that CLR also needs access to it to execute the assembly. However, you could overcome this if you allow yourselves to hide the entire assembly instead. This is a simpler option; further, you could use the very Reflection (that caused all this trouble) as your tool to achieve it.

Reflection

The classes required for Reflection are available under the System.Reflection namespace and among them Assemblyis one very important class. This class allows you to load and execute assemblies at runtime. It supports overloaded Load() methods to load an assembly from an external file or raw bytes. Once properly loaded, the EntryPoint property allows one to get details about the entry point of the loaded assembly. EntryPoint is a MethodInfo-type property that gives all information, such as parameter types, return type, and so on, for the entry point method. One important method that is supported by MethodInfo is Invoke(). This allows one to invoke the method for execution. For example, the following code loads a managed assembly from the supplied file path and invokes the entry point method.

public void LoadAndExecute(string strAssemblyPath)
{
   try
   {
      Assembly asm = Assembly.Load(strAssemblyPath);
      asm.EntryPoint.Invoke(null,null);
   }
   catch(Exception ex)
   {
      MessageBox.Show(ex.Message);
   }
}

The typical entry point for any C# assembly would be main(). Now, once we are equipped with the above code, let us see how we can use it to solve our problem.

.NET and Interoperability

Anyone who is familiar with .NET could easily recollect the interoperability as a feature of .NET allowing code reusability between managed assemblies and native executables. In other words, we could write a component in .NET and use it in native code and vice-versa. That means we could use Interoperability to bring all the features of .NET to native code. And, that also includes Reflection.

Note that earlier we discussed how Reflection allows one to load an assembly and execute it. Now, what would be more easier than hiding the managed assembly in a native EXE and using Reflection at run time to load and execute it?

That is, we could store our managed assembly as a resource in native EXE and, whenever we wish to execute the assembly, we could dump it onto a hard disk in some unknown location (with share-exclusive permissions so that the user cannot open it) and invoke it by using the Interoperated Reflection mechanism. Once we are done with the execution, we could delete the assemblies from the hard disk and leave no traces.

Even better, we could, instead of dumping the assemblies onto a disk and invoking them from there, send them directly to the Interoperated Reflection as an in-memory byte array and execute them then and there. As we noted earlier, it is possible to load and execute an assembly from a byte array by using the Assembly class's Load() method. The following code presents the same technique.

public interface IInvokeAssembly
{
   void LoadAndExecute(byte[] pBuf);
};

public class CInvokeAssembly : IInvokeAssembly
{
   public CInvokeAssembly()
   {
   }
   public void LoadAndExecute(byte[] pBuf)
   {
      try
      {
         Assembly asm = Assembly.Load(pBuf);
         asm.EntryPoint.Invoke(null,null);
      }
      catch(Exception ex)
      {
         MessageBox.Show(ex.Message);
      }
   }
}

The IInvokeAssembly interface is required to interoperate with the native EXE. Note that we are no longer using a string path to load the assembly from, but we are using a byte[] buffer. In the native EXE, we could create a COM pointer for this interface and invoke its LoadAndExecute() method, supplying the assembly contents in a buffer as the argument. We need to use a SafeArray for this purpose, as shown below.

void InvokeAssemblyResource()
{
   IInvokeAssemblyPtr pInvoker;    //COM Pointer to the .NET
                                   //Interface

   if(FAILED(pInvoker.CreateInstance(CLSID_CInvokeAssembly)))
   {
      MessageBox(NULL,_T("Unable to Create Invoke Assembly
                          Object!!"),_T("Error"),MB_OK|MB_ICONERROR);
      return;
   }

   HRSRC hRC = FindResource(NULL,MAKEINTRESOURCE
               (IDR_EMBEDDED_ASSEMBLY),
               "RT_EMBEDDED_ASSEMBLY");
   HGLOBAL hRes = LoadResource(NULL,hRC);
   DWORD dwSize = SizeofResource(NULL,hRC);

   SAFEARRAY* pSA = NULL;

   if(NULL !=(pSA = SafeArrayCreateVector(VT_UI1, 0, dwSize)))
   {
      LPVOID pBuf = NULL;

      if(FAILED(SafeArrayAccessData(pSA,&pBuf)))
         MessageBox(NULL,_T("Unable to Access SafeArray Data"),
                   _T("Error"),MB_OK|MB_ICONERROR);
      else
      {
         LPVOID hAsm = LockResource(hRes);

         memcpy(pBuf, hAsm, dwSize);

         UnlockResource(hRes);

         SafeArrayUnaccessData(pSA);
      }

      //Invoke the Reflection to load and Execute our Byte[]
      pInvoker->LoadAndExecute(pSA);
   }
   else
      MessageBox(NULL,_T("Unable to Allocate Memory"),
                _T("Memory Allocate Error"),MB_OK|MB_ICONERROR);

   if(pSA) SafeArrayDestroy(pSA);
}

The preceding code loads the assembly from a resource and creates a SafeArray from its contents. When supplied to the IInvokeAssemblyPtr, the SafeArray would be automatically converted into a byte[] by the .NET Interoperability marshalling mechanism.

Note: Do not forget to call the CoInitialize() and CoUnintialize() methods in the native code before and after, respectively. Without them, we cannot create the COM objects for the .NET interfaces.

Now, with this generate-on-demand mechanism, we have completely eliminated the chance of a user accessing the assemblies directly from the disk. Whenever the user wants to execute the managed assembly, we load it from our native EXE resource and execute it directly in memory. However, there is still a small loophole here. It is possible that the user can extract the resources of the native EXE by using any Resource Editor programs available and do whatever he/she wishes to do. We want to address that possibility also.

The Solution

Until now, we traced the following steps:

  1. We wanted to hide the metadata part of the managed assembly from the user. Towards this extent, we decided instead to hide the entire assembly itself.
  2. To hide the assembly from the user, we wanted to store it as resource data in native EXE and let the native EXE generate the assembly on demand and execute it using the Interoperated-Reflection methods. However, we realized that resource data in native EXEs is open to everyone.

Now, we all know that we could not hide the resources of native EXE. But, we could make them meaningless. That is, we could, instead of placing the managed assembly directly, place it encrypted. That way, anyone who extracts it from resource would not be able to use it directly. Towards further security, we could add custom headers to the managed assembly and encrypt the whole as a unit. Anyone who want to extract the managed assembly now not only needs to decrypt it but also needs to know about our custom header information to use it properly. A simple yet elegant solution. Of course, you could apply your own safety mechanisms to build on this further.

Proof of Concept Demo

You could download a proof of concept demo that generates a native EXE wrapper for any given managed assembly from the SecureAssembly.zip link. The operation of the demo is as follows:

  1. When you run the SecureAssembly.exe, you would be presented with a dialog box that accepts a managed assembly file path, a native EXE destination path, and an optional password.
  2. Once you select the appropriate managed assembly and native executable destination, you could create the secure native EXE for the managed assembly by clicking the "Go!" button.
  3. When you click the "Go!" button, a native EXE would be created at the selected destination path with the managed assembly embedded in its resources (after appropriately encrypting with the supplied password, of course).
  4. Now, your managed assembly is safe. You could access it any time by executing the generated native EXE.
  5. When you execute the native EXE, you would be prompted for the password first. You should supply the same password that you have used for its creation. (You can leave it blank if you did not specify any password.)
  6. The supplied password would be used for decrypting the embedded managed assembly. Supplying a wrong password would result in a malformed assembly that could not be executed.

The code required to load and execute the managed assembly is placed in a DLL named InvokeAssembly.dll for interoperability with the native code. You should first copy it to the directory of the native EXE and register it by using the RegAsm command at the command prompt before starting the native EXE. Usage of RegAsm is as shown below.

<Dir>:\> RegAsm InvokeAssembly.dll

If you try to run the native EXE without registering the DLL, you will encounter a "Unable to Create Invoke Assembly Object" error.

Conclusions

Reflection in .NET allows any assembly to be loaded and executed at run time. It also allows the managed assemblies to be disassembled easily. However, by embedding the assemblies in native executables as encrypted resources, we could hide them from being disassembled. Once safely hidden in native EXEs, they could be accessed any time by using Reflection through native-managed application interoperability.

Complete details on interoperability can be found in the documentation: Interoperating with Unmanaged Code and details on Reflection could be gathered from: Loading and Executing User Code. Further depth in Reflection could be gained from: Emitting Dynamic Assemblies.



Downloads

Comments

  • Error

    Posted by Bebo_s_80 on 03/01/2011 03:43pm

    Parameter mismatch

    Reply
  • Thanks and enhancement questions

    Posted by Andrible on 11/15/2008 10:19am

    Thank you so much for implementing this. I was thinking something like this ought to be possible, and then I found your solution. The previous comment (which I only just noticed) answers one of my questions so thanks for that too. I have 1 other main issue. The managed assembly that is being decrypted and loaded by the bootstrapper does not seem to be able to access WCF services whereas it is able to when it is just run directly. I am wondering if you might hazard a guess as to why this might be. I'm thinking itbs got something to do with the GAC. Am I right in thinking that the bootstrapper is loading the assembly into the GAC and running it from there?

    • License

      Posted by KrishnaPG on 11/25/2008 08:47am

      Hello Andri,
      
      Glad to hear your success. Yes, you should be able to implement such licensing system.
      
      Indeed, the whole goal of the boot-strapper was just that. 
      
      If you could tie your license in someway to the "password" that is used to decrypt the assembly, then you should be able to use it. Your custom bootstrapper can read the license file and "compute" the required password and decrypt the assembly. (To make it work you need to create the "password" from the machine settings and "encrypt" the assembly with that password during the setup.
      
      The points to be noted is:
      
       1. Never store the password anywhere.
       2. Ensure your setup mechanism is also secured. At no point during the setup the assembly should be on the disk.
       2. If you need to "compute" the password on-the-fly (based on the license) for the decryption, then ensure that the computing mechanism is secure (that is, implement it in native code so that it would be hard to disassemble).
      
      Finally, please note that No mechanism if Fool-proof. We can only stop the "obvious" hackers. The "determined" ones, we cannot stop. So, weigh the options strategically and use some simple and convenient mechanism. All the best. Gopalakrishna Palem. http://www.geocities.com/krishnapg/

      Reply
    • Progress

      Posted by Andrible on 11/24/2008 06:42pm

      Hi Palem. 
      
      I overcame my difficulties, and got the managed EXE I was targetting to work via your 
      bootstrapper. Much of my problem was just my own stupidity... WCF needed settings from
      the .exe.config file, and the secured native EXE had a different name so did not pull
      configuration from the right file. I also had problems with specific classes which were using
       serialization. For some reason the bootstrapper approach breaks my XML serialization. I didn't
       figure out why, but I guess it has something to do with reflection being used for the
       serialization. I have worked around this by separating serialised classes into a dependent
       DLL. So all well and good. Works just as you intended. 
      
      Now I am wondering if it would be possible to extend this bootstrapper to read an encrypted
       license file and on the basis of it containing correct information about the current machine, 
      either running or not running the encrypted embedded managed EXE. Do you think this would be 
      a reasonable approach to take to implementing a product licensing system?

      Reply
    • Progress

      Posted by Andrible on 11/24/2008 06:41pm

      Hi Palem. I overcame my difficulties, and got the managed EXE I was targetting to work via your bootstrapper. Much of my problem was just my own stupidity... WCF needed settings from the .exe.config file, and the secured native EXE had a different name so did not pull configuration from the right file. I also had problems with specific classes which were using serialization. For some reason the bootstrapper approach breaks my XML serialization. I didn't figure out why, but I guess it has something to do with reflection being used for the serialization. I have worked around this by separating serialised classes into a dependent DLL. So all well and good. Works just as you intended. 
      
      Now I am wondering if it would be possible to extend this bootstrapper to read an encrypted license file and on the basis of it containing correct information about the current machine, either running or not running the encrypted embedded managed EXE. Do you think this would be a reasonable approach to take to implementing a product licensing system?

      Reply
    • fuslogvw.exe

      Posted by Andrible on 11/17/2008 05:38am

      Hi Palem, thank you for the quick response. Very kind. I tried using fuslogvw.exe to determine the binding / accessing of the depended-on dlls. I discovered the difference in binding calls between opening regular managed EXE Secured Native EXE (with managed EXE as an encrypted resource) is that the managed EXE loads System.ServiceModel, System.IdentityModel, and Microsoft.VisualStudio.Diagnostics.ServiceModelSink whereas the native EXE does not. The thing that particular struck me was it appears to not even try to load these... it doesn't try and not find them and give up... it just doesn't even go looking. Not quite sure what to make of that. System.ServiceModel is definitely used by WCF. (Eg. see http://searchwindevelopment.techtarget.com/news/article/0,289142,sid8_gci1148900,00.html). I also noted that the native EXE loads SMDiagnostics, which the managed EXE does not. When I reviewed you post on http://www.geocities.com/krishnapg/ I noticed that you have extended your original article to incorporate CLR hosting, which removes the need for InvokeAssembly.Dll. This would be useful for me anyway, and I'm wondering if it might have some fall-out benefits. So I'm going to try this next and see what happens before returning to the issue of IAssemblyLocator or resource paths and so on... I'll let you know how I get on. 8-)

      Reply
    • Accessing WCF Service Dlls

      Posted by KrishnaPG on 11/16/2008 09:09am

      Hello Andrible, Not sure how the WCF service is being accessed. But you can use the .Net fusion logging to check how the service dll is being accessed from the direct application as well as from the memory resident application and compare the results (please search for "enabling .net fusion logging" on details on how to use this feature). The other way is to use the "ProcMon" tool from www.sysinternals.com and see from which location the service dll is getting loaded. Usually the memory resident application will have trouble loading its related dlls (because it will not be having a valid probing path). We have to override the "IAssemblyLocator" and related interfaces to resolve the paths of the related dlls. I did not experiment much with that method. The other-way of resolving the reference dlls would be to dynamically load them using reflection from the memory resident assembly. This way, we can have complete control over the path of the dll that need to be loaded (instead of relying on the loader to probe the correct path). Hope this helps. Thank you, http://www.geocities.com/krishnapg/

      Reply
    Reply
  • Error - Parameter mismatch (or unknown error) !!

    Posted by KrishnaPG on 07/19/2005 08:36am

    Tip: If this kind of error occurs while executing the generated executable then please checkout if the assembly entry point is having the same prototype as following: static void Main(void) { ... } The error occurs because of Entry point method invocation failure !!

    • Sending Command Line Parameters

      Posted by KrishnaPG on 12/15/2005 10:05pm

      Dear,
      
      Glad to hear that you got the code Working.
      
      All the best.
      
      Thanking you,
      Yours,
      P.GopalaKrishna.

      Reply
    • Sending Command Line Parameters to Managed Assembly from Native Exe

      Posted by theurbanlion on 12/15/2005 03:13pm

      Thank you very much! The code you provided is working excellent. I would like to thank you once more for your excellent code, and for sharing it!! You did a great job! I salute you from Argentine!!

      Reply
    • Sending Command Line Parameters to Managed Assembly from Native Exe

      Posted by KrishnaPG on 12/08/2005 12:18pm

      Command Line parameters can be passed by sending an array of strings to the managed assembly. The Typical Prototype of Managed Assembly looks like Main(string[] args) thus requires an array of strings. Hence we need to pass a safearray each element of which should be a BSTR. So we typically require to send a VT_ARRAY|VT_BSTR type of parameter. I have added few additional modules to the demo code to make this kind of parameter sending easy. Please refer to the modified code in the CLRHostExe.CPP file at http://www.geocities.com/krishnapg/SecureAssembly.zip for further details. Thanking you, Yours, P.GopalaKrishna.

      Reply
    • Error - Parameter mismatch (or unknown error) !!

      Posted by theurbanlion on 12/07/2005 01:13pm

      Do you know how to pass a safe array of command line parameters to the .net assembly? Because I tried doing it but till now I have been unsucssesful. Thanks for this great code, and if you can help me IB4ll apreciate it very much!!

      Reply
    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: May 7, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT This eSeminar will explore three popular games engines and how they empower developers to create exciting, graphically rich, and high-performance games for Android® on Intel® Architecture. Join us for a deep dive as experts describe the features, tools, and common challenges using Marmalade, App Game Kit, and Havok game engines, as well as a discussion of the pros and cons of each engine and how they fit into your development …

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds