.NET Security Workshop
Home About Workshops Articles Writing Talks Books Contact

3. Strong Name Validation and Assembly Hash Validation

When you give a strong name to an assembly you provide the public key that is part of a public-private key pair. The private key is owned by you, the publisher, and is not available to anyone else. The public key corresponds only to that private key, so this means that the public key should be unique to you. Since the strong name includes your public key (in fact a token derived from the public key) this means that the strong name is unique. However, a public key (and the derived token) is just a collection of bits, and anyone could generate some bits, or extract them from a published assembly. For example, you could make your assembly public, by selling it on the open market, or distributing it through shareware, and a cracker could then extract your public key from this assembly. This cracker could attach the public key to their own assemblies and, because those assemblies now have your public key, they can claim that these assemblies come from you and hence attach your reputation to those assemblies. There must be some way to get around this problem. There is, it is called signing the assembly.

When an assembly is signed the compiler performs a one way hash over the assembly (excluding some sections, as will be explained later), and then it encrypts the hash with the publisher's private key. Public and private keys have an important property - data encrypted with one key is decrypted with the other (indeed the public and private descriptions are purely subjective, either one can be used to encrypt, but once you have chosen which to use, only the other one can be used to decrypt the data. The public key is merely the key that you make public, the remaining key is kept private). If signed data can be decrypted with a public key it proves that this public key corresponds to the private key that was used to sign the data; and since the private key is held by a single publisher, this proves that that specific publisher created the encrypted data because no one else has access to that private key. This data, of course, is the hash of the assembly, and the hash is something that anyone can perform. Therefore, validation of the public key is a simple operation that anyone can perform (as you'll find out later).

When the runtime loads an assembly Fusion will generate a hash of the assembly and then it will decrypt the strong name signature with the public key to create the hash created by the publisher. These two hashes should agree. If they do not agree there are two possible reasons: the first is that the public key in the assembly does not correspond to the private key used to create the strong name signature, and the second reason is that the hash of the assembly generated at compile time is different to the hash generated at load time. In both cases there is a security breach and the runtime will refuse to load the assembly.

The whole point of the strong name signature is to allow the runtime to validate the public key and hence the strong name, if the strong name is not validated then the strong name cannot be guaranteed to be unique and hence the versioning will not work and you will not be able to share the assembly through the GAC. The side effect of strong name validation is that it also checks that the contents of the assembly has not changed since it was published and hence this protects the assembly from tampering.

I should caution you here. In theory strong name validation should protect the assembly from tampering, but there is a bug in v1.0 and v1.1 of the runtime that allows a cracker to turn off strong name validation. So you should not rely on this mechanism. I will give more details later.

3.1 Spoofing by Tampering an Assembly

In this example we'll see how a cracker can alter your code to make you perform an unsafe action. In a later section you'll see that by giving an assembly a strong name has the side effect of making the assembly tamperproof.

Create a library called lib.cs:

using System;

public class LibraryClass
{
   private string errorCode=null;
   private string description=null;
   public string ErrorCode{get{return errorCode;}}
   public string Description{get{return description;}}
   public void InputData()
   {
      Console.Write("Error code: ");
      errorCode = Console.ReadLine();
      Console.Write("Description: ");
      description = Console.ReadLine();
   }
}

This code allows you to log information about bugs reported to a help desk, the code requests that the user enters an error code and its description. In this implementation the data obtained from the user is simply stored in instance properties. However, another implementation of this class might store your data into a database or a web application could use this data to generate a page on a support web site. This means that whatever data you provide will become public data.

Here's a rather lame user of this library (app.cs):

using System;

class App
{
   static void Main()
   {
      LibraryClass lc = new LibraryClass();
      lc.InputData();
      Console.WriteLine("You entered: {0}, {1}",
         lc.ErrorCode, lc.Description);
   }
}

Compile these assemblies and run them:

C:\TestFolder>app
Error code: Invalid Stock Number
Description: Returned with a stock number of 345
You entered: Invalid Stock Number, Returned with a stock number of 345

Now imagine that a cracker gets access to your code. She opens the library with a hex viewer and alters the literal strings. Visual Studio has a hex viewer and you can access this through the File, Open, File menu item, select the file and then select Open With by clicking on the down arrow button on the Open button.

Now select Binary Editor from the list box and click on Open.

.NET Version 3.0
With Visual Studio 2005 you have to click OK on the Open With dialog, there is no Open button.

The literal strings in an assembly are stored in an area called the #US (user strings) stream. To find this, scroll down until you find a string BSJB, this is the magic number that indicates the start of the metadata in the assembly. Record this address (0x000002dc in the screenshot below), scroll until you find #US and then backup 8 bytes to get the relative address of the user strings stream (0x00000270 in the example). Add the relative address to the address of the start of the metadata to get the address of the user strings stream (0x0000054c). The screenshot highlights the items I have been describing (the screenshot is for the assembly built for v1.1 of the framework, but assemblies built for version 3.0/2.0 have the same format).

The address that you have determined gives a table of a series of Unicode strings. Each entry has a zero byte followed by the count of bytes in the string (including the count). These entries are not zero terminated. The following shows the two user string items highlighted.

You can use the binary editor to change these values, by simply typing over the characters, or typing the hex value in the hex viewer part of the display. (Make sure that each character has two bytes, and the second one is a zero byte.) Replace 'Error code: ' with 'Username: ' and 'Description: ' with 'Password: ' ensure that the replacement strings are the same length as the originals by padding with spaces. Save the library in Visual Studio and then run the process. You'll get something like this:

C:\TestFolder>app
Username: Richard
Password: secret
You entered: Richard, secret

Oops! I have been tricked into providing my user name and password. If the application adds this data to a database and the database is used to generate a web page, it means that my username and password have been posted on a public web site! All the cracker needs to do is monitor the site regularly, access my account and change the password, then she can party with my account!

Once the cracker has altered my code she simply needs to distribute this code. Trojans work like this: they appear to be trusted code and yet they contain malicious code.

3.2 Signing the Assembly

As I mentioned earlier, when an assembly contains a strong name signature the assembly loader will attempt to validate the signature and a side effect of this is that it checks to see if an assembly has been tampered. (Remember the caution about relying on this mechanism.)

To add a strong name signature you should sign the file. To do this obtain a key pair:

sn -k key.snk

Now add the following to the library source used in the previous section:

using System.Reflection;
[assembly:AssemblyKeyFile("key.snk")]


.NET Version 3.0
In version 3.0/2.0 the [AssemblyKeyFile] is deprecated, instead you should use the /keyfile switch on the compiler command line.

Now compile the library and the process. Next, load the library into the binary editor in Visual Studio and make the alterations as before.

Now run the process. You'll get the following with version 1.1 of the runtime:

Unhandled Exception: System.IO.FileLoadException: Strong name validation failed for assembly 'lib'.
File name: "lib"
   at App.Main()

In addition you'll see output from Fusion, so from this you can deduce that the exception has clearly come from Fusion. I have always found this a little odd, if strong name validation has failed, this is a security issue, not a problem with how the files are loaded.

.NET Version 3.0
Version 2.0 corrects this:

Unhandled Exception: System.IO.FileLoadException: Could not load file or assembly 'lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=bf83c2d3b24f9bc5' or one of its dependencies. Strong name validation failed. (Exception from HRESULT: 0x8013141A)
File name: 'lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=bf83c2d3b24f9bc5' ---> System.Security.SecurityException: Strong name validation failed. (Exception from HRESULT: 0x8013141A)
The Zone of the assembly that failed was:
MyComputer
at App.Main()


This time you can see that a security exception is thrown, albeit one that is caught by Fusion and rethrown as a file load exception.

What has happened here is the runtime has determined that the hash for the library does not match the signed hash in the assembly. From this it can deduce one of two things: either the private key that generated the strong name signature does not correspond to the public key, or the library has been tampered. We know that the latter is the case, but it does not matter, because there is clearly a security breach and so the runtime refuses to load the library.

Strong name signature validation works for process assemblies as well as library assemblies. If you sign a process then this assembly will be checked before it is loaded. To test this add the following to the process.

using System.Reflection;
[assembly:AssemblyKeyFile("key.snk")]

Rebuild the process and the library and then open the process assembly into the binary editor and alter one of the literal strings. In version 1.1 of the runtime you will get a dialog like the following:

.NET Version 3.0
When you perform this test with version 3.0/2.0 of the runtime you'll get a FileLoadException wrapping a SecurityException as for the library, rather than the separate dialog shown above.

The runtime has refused to load the process assembly. Note that this dialog title is 'Strong name validation failed', and the caption goes into further details. The caption gives two reasons. Firstly, it says that the assembly may have been tampered. Microsoft are indicating here that they are performing a check against tampering. The second reason is a little garbled, it says that the assembly could have been 'partially signed but not fully signed with the correct private key'. Partial signing means that when the assembly was signed the public key was available, but the private key was not. This is a security measure used during development to ensure that developers in a team do not have access to the private key. Partial signing (or delay signing) means that the public key is placed in the assembly and uninitialized space is allocated for the strong name signature. The developer can then test the assembly on her machine by switching off validation for this assembly. Before the assembly is published a trusted developer re-signs the assembly with the private key, which initializes the strong name signature.

This error message indicates that the assembly may not have been re-signed, or it might have been re-signed with the wrong private key. A third reason for the strong name validation failure is implicit: the public key does not correspond to the private key used to create the strong name signature.

Note that a strong named assembly can only call other strong named assemblies (from the same, or another publisher). Thus, in this case the process app.exe is signed and so lib.dll must also be signed. For the rest of this tutorial the process should not be signed, so remove the [AssemblyKeyFile] attribute from app.cs.

3.3 Multi Module Assemblies

It is worth pointing out that the signing process does not hash all of the assembly, specifically it does not hash the area where the strong name signature would be stored; the area where a X509 signature would be stored; or files other than the PE file that contains the manifest. This means that if your assembly is made up of additional code modules or if it contains external resource files then these files are not hashed as part of the signing process. However, those files are hashed when they are added to the assembly and this other hash is stored in the manifest, which of course is hashed when the assembly is signed.

An assembly can have multiple code modules or it can contain external resource files, so let's see if those files are included in strong name validation. Here is a makefile for an application that uses a multi-module library:

all : app.exe

app.exe : app.cs lib.dll
   csc app.cs /r:lib.dll

lib.dll : lib.cs one.netmodule
   csc /t:library /addmodule:one.netmodule lib.cs

one.netmodule : one.cs
   csc /t:module /out:one.netmodule one.cs

Each module is part of the assembly, so there is no point in specifying a key file for it, because the module file will not contain a strong name signature. The code for the module is:

using System;

internal class GetItems
{
   public static string ErrorCode()
   {
      Console.Write("Error code: ");
      return Console.ReadLine();
   }
   public static string Description()
   {
      Console.Write("Description: ");
      return Console.ReadLine();
   }
}

Note that this class is internal which means that it can be accessed by any class in the assembly, but not by any classes outside of the assembly. The library code now looks like this:

using System;
using System.Reflection;

[assembly:AssemblyKeyFile("key.snk")]

public class LibraryClass
{
   private string errorCode=null;
   private string description=null;
   public string ErrorCode{get{return errorCode;}}
   public string Description{get{return description;}}
   public void InputData()
   {
      errorCode = GetItems.ErrorCode();
      description = GetItems.Description();
   }
}

Recall this this assembly is signed, so the runtime will check its hash when the assembly is loaded. Compile all the code by invoking the build tool nmake.exe (simply type nmake on the command line). Now load the module into the binary editor and change the metadata user strings. Run the process and you'll get the following exception:

Unhandled Exception: System.IO.FileLoadException: The check of the module's hash failed for file 'one.netmodule'. File name: "one.netmodule"
   at LibraryClass.InputData()
   at App.Main()

There is no output from Fusion and this is a file load exception.

.NET Version 3.0
It is interesting to note that .NET version 3.0/2.0 also treats this as a file load problem. The exception stack gives three FileLoadException objects. The first exception notes that the check of the module's hash failed, this is rethrown as another FileLoadException that indicates that one.netmodule or one of its dependencies could not be loaded. Finally, a combined exception is thrown that indicates that the module could not be loaded because the check on its hash failed. 

To see what is happening you should take a look at the manifest of the library using ILDASM (ildasm lib.dll). There you'll see something like:

.file one.netmodule
.hash = (F8 73 53 99 65 80 5D 32 C0 A9 CE CF BD 88 EE AA 6D 90 A1 27 )

This is not a signed hash, it is just a hash of the module. However, when the assembly is hashed to generate the strong name signature the module's hash is included as part of the signing process. This means that the side effect of strong name validation protecting an assembly from being tampered also protects the module too. When the runtime loads the module it performs a hash on the module and compares this with the hash in the manifest of the assembly. In this example these two hashes disagree. Note that the module is loaded the first time it is used and hence the library has already started to execute when the tampering is detected.

Remove the [AssemblyKeyFile] attribute from the library, delete one.netmodule (so that it will be recompile) and built the entire application. Load the module into the binary editor alter one of the literal string, now run the application. You'll find that the application will run with the tampered string.

This is important: the hash of the module stored in the assembly manifest is only checked if the assembly has a strong name.

Replace the [AssemblyKeyFile] attribute in the library.

3.4 Assembly Resource Files

A similar situation occurs with external resources. Edit the makefile to change the dependencies of the library, and add another target:

lib.dll : lib.cs one.netmodule strings.resources
   csc /t:library /addmodule:one.netmodule lib.cs /linkres:strings.resources

strings.resources : strings.txt
   resgen strings.txt

The assembly will have a separate resource file called strings.resources that is part of the assembly.

The strings.txt file looks like this:

ONE=ErrorCode
TWO=Description

The module code looks like this:

using System;
using System.Resources;
using System.Reflection;

internal class GetItems
{
   static string GetString(string name)
   {
      ResourceManager rm = new ResourceManager("strings",
      Assembly.GetExecutingAssembly());
      return rm.GetString(name);
   }
   public static string ErrorCode()
   {
      Console.Write("{0}: ", GetString("ONE"));
      return Console.ReadLine();
   }
   public static string Description()
   {
      Console.Write("{0}: ", GetString("TWO"));
      return Console.ReadLine();
   }
}

The GetString method accesses the external resource using a ResourceManager object. Now compile this code and run the app to confirm that the application displays the expected behaviour.

The resgen tool compiles the text file into a .NET resource file, if you load this file into the binary editor you'll see that the strings are shown in clear text, so you can change these strings. If you do this and compile for v1.1 of the runtime and then run the process you'll get the following exception:

Unhandled Exception: System.Resources.MissingManifestResourceException: Could not find any resources appropriate for the specified culture (or the neutral culture) in the given assembly. Make sure "strings.resources" was correctly embedded or linked into assembly "lib".
baseName: strings locationInfo: <null> resource file name: strings.resourcesassembly: lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=578b3c819f5c2bdb

This rather obscure error indicates that the hash for the external resource file does not match the hash in the manifest. In fact, the actual error occurs in the method GetManifestResourceStream, but rather than throwing an exception it handles the error condition by returning a null reference. It is this null reference that causes the caller of GetManifestResourceStream to generate the exception that you see.

I consider this a bug. It is clear that the problem is that the resource file is corrupted so the exception that should be returned should reflect this. This is an example where there is a lack of 'joined up thinking' at Microsoft.

The simple action of signing an assembly prevents the assembly, its modules and external resources from being tampered.

.NET Version 3.0
This problem has been fixed in .NET version 3.0/2.0. Again, a FileLoadException is thrown, but this time the exception stack shows two other FileLoadException objects. The first indicates that the check on the file's hash failed, the next one wraps this and indicates that the file strings.resources could not be loaded, and this is exposed in the final exception which indicates that strings.resources could not be loaded because the check on its hash failed. I still would have preferred a security exception, rather than a file load exception. 

3.5 Entry Point

One final point to make is that a PE file will have an unmanaged entry point and you can see the address of this with dumpbin /headers. The unmanaged entry point merely initializes the .NET runtime and allows the runtime to find and execute the managed entry point. The unmanaged entry point is required to allow assemblies to be used on operating systems before Windows XP. This is a possible source of an attack because an attacker can add extra code to the PE file and change the entry point entry to point to this new code. Windows XP is not vulnerable to this attack because it recognises that the file is an assembly because it can find a value for the so-called COM Descriptor Directory. This value points to the .NET header (dumpbin /clrheader) and Windows can use this to find the .NET entry point. Thus on XP, the operating system will automatically initialize the .NET runtime and start the process's entry point.

3.6 Caution

Throughout this part of the tutorial I have been careful to say that the assembly hash is validated as a side effect of validating the strong name signature and hence validating the public key. The CLI spec does not specify that .NET assemblies should be tested for tampering before they are loaded (an obvious oversight in my opinion). Microsoft does mention the side effect of strong name validation in some of their online documentation, but they do not promote it as a sure way of providing an integrity check. Surprisingly, Microsoft are pretty quiet about integrity checks on assemblies.

In version 1.0 and 1.1 of the .NET runtime there is a bug that allows a cracker to turn off strong name validation. You can find a more detail discussion about this here. This bug has been fixed in version 3.0/2.0 of the framework. However, because of this bug you should not make any assumption about the integrity of an assembly other than the results of the verification and validation checks discussed on the previous page. Since strong name validation can be disabled you should make no assumptions about the validity of the strong name. This means that you should not make any decisions based on strong name evidence nor use strong name identity permissions. 

Strong name validation can be disabled by a cracker on version 1.0 and version 1.1 of the runtime, so you should not rely on strong name evidence, nor should you protect your code with StrongNameIdentityPermission.

We will take a further look at validation and verification on the next page.

I hope that you enjoy this tutorial and value the knowledge that you will gain from it. I am always pleased to hear from people who use this tutorial (contact me). If you find this tutorial useful then please also email your comments to mvpga@microsoft.com.

Errata

If you see an error on this page, please contact me and I will fix the problem.

Page Four

This page is (c) 2007 Richard Grimes, all rights reserved