Monday, 1 February 2010

Tampering with a Strong-Named Assembly

Introduction

If you've read the Microsoft document on .NET you'll know that the use of Strong Names is recommended. The Code Analyser within Visual Studio goes as far as generating error CA2210, "Assemblies should have valid strong names", if the assembly does not have a strong name.

One of the reasons given for the use of strong names is that they're tamper-proof. For example, Strong Names Assemblies says: Strong names provide a strong integrity check. Passing the .NET Framework security checks guarantees that the contents of the assembly have not been changed since it was built. Also, Assemblies should have valid strong names says: This rule retrieves and verifies the strong name of an assembly. A violation occurs if... [t]he assembly was altered after signing.

So, strong-names assemblies are tamper-proof. Right? Wrong.

Watch, I'll Show You

Let's create a really simple class library and ensure it has a strong name. We start by firing up Visual Studio 2008 and creating a new .NET 3.5 Class Library called StrongNamedLibrary. We then replace the contents of Class1.cs with this code:

namespace StrongNamedLibrary
{
    public class Class1
    {
        public string Message
        {
            get { return "I am a friend"; }
        }
    }
}

We can then use the Signing section of the Project Properties pane to create and assign a public/private key pair. We do this by clicking the Sign the assembly check box and selecting New from the Choose a strong name key file drop-down. When prompted, we specify a name of StrongNamedLibrary (with no extension) and whatever password we like.

Before we finish with StrongNamedLibrary, I want to make sure is easy for us to tweak the version number outside of Visual Studio, so let's open Properties\AssemblyInfo.cs and change the AssemblyVersion attribute to 8961.26437.43913.61389 (this should be easy to spot in the generated binary file as it'll appear as 0123456789ABCDEF in hexadecimal).

Perfect. Now lets add a .NET 3.5 Console Application called StrongNamedConsole to the same solution. We replace the contents of Program.cs with this code:

using System;

namespace StrongNamedConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                (new Program()).Run();
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex);
            }

            Console.ReadLine();
        }

        void Run()
        {
            StrongNamedLibrary.Class1 o = new StrongNamedLibrary.Class1();
            Console.WriteLine(o.Message);
        }
    }
}

Obviously this code won't compile right now because StrongNameConsole has know idea where StrongNamedLibrary is (even though it's in the same solution) - we need to add a reference to it. So we right-click References for StrongNamedConsole in Solution Explorer and choose Add Reference. After the usual wait the Add Reference dialog appears and asks us what we're trying to reference. We click the Projects tab and select StrongNamedLibrary. Job done.

Now that the reference has been added we can select StrongNameLibrary from the References node of Solution Explorer and inspect its properties. The two we're interested in at the moment are Copy Local (i.e. where StrongNameLibrary.dll is copied into the bin\Debug folder of StrongNamedConsole when StrongNamedConsole is built) and Strong Name (i.e. does the referenced assembly have a strong name). They'll both be set to True.

Does .NET Spot a Version Change?

Okay, the first thing we should do it make sure that a change in version is detected. We don't want to change this within Visual Studio for fear of everything being auto-updated by Visual Studio itself, so let's close the solution.

Before we do anything outside of Visual Studio we should just convince ourselves that the project reference from StrongNamedConsole to StrongNamedLibrary isn't only available to Visual Studio. So let's use Windows Explorer to navigate to StrongNameConsole's bin\Debug folder and double-click StrongNamedConsole.exe. We should see the following output:

I am a friend

Hit [Enter] to close the application. Now, within Visual Studio, use File | Open | File... to browse to StrongNameConsole's bin\Debug folder and single-click StrongNamedLibrary.dll. Use the drop-down list attached to the Open button to select Open With...; then choose the Binary Editor and click OK.

If you page down to around offset 00000510 you notice the hexadecimal value 0123456789ABCDEF - that's our version number. So switch to overwrite mode by pressing your [Insert] key (you should see OVR in the status bar) and tweak that version number a little (it doesn't matter what to). Save the DLL and double-click StrongNamedConsole.exe again.

This time we should get a nice exception thrown by StrongNamedConsole saying that the version of StrongNamedLibrary that it loaded doesn't match that it was expecting.

System.IO.FileLoadException: Could not load file or assembly 'StrongNamedLibrary
, Version=8961.26437.43913.61389, Culture=neutral, PublicKeyToken=220058f58416b5
85' or one of its dependencies. The located assembly's manifest definition does
not match the assembly reference. (Exception from HRESULT: 0x80131040)
File name: 'StrongNamedLibrary, Version=8961.26437.43913.61389, Culture=neutral,
 PublicKeyToken=220058f58416b585'
   at StrongNamedConsole.Program.Run()
   at StrongNamedConsole.Program.Main(String[] args) in C:\Users\Ian.Picknell\Do
cuments\Blog\Strong Names\Tampering with a Strong-Named Assembly\StrongNamedCons
ole\Program.cs:line 11

WRN: Assembly binding logging is turned OFF.
To enable assembly bind failure logging, set the registry value [HKLM\Software\M
icrosoft\Fusion!EnableLog] (DWORD) to 1.
Note: There is some performance penalty associated with assembly bind failure lo
gging.
To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fus
ion!EnableLog].

Good. .NET correctly spotted that the version number had been messed with. But will it spot a change to the message we're returning?

Does .NET Spot a Real Change?

Back in the Binary Editor view of StrongNamedLibrary.dll, change the version number back to 0123456789ABCDEF. (If you'd rather you can just re-compile it instead, but you need to close and then re-open the DLL if you do.)

Now scroll down in the Binary Editor window until you see the text "I am a friend". It's in Unicode so there's be an 0x00 between each character. Making sure you're in overwrite mode again, change the word "f.r.i.e.n.d" to "h.a.c.k.e.r" (where each . represents the 0x00 bytes which you can leave intact).

Save StrongNamedLibrary.dll and then double-clicking StrongNamedConsole.exe will produce the following output.

I am a hacker

So, the change in version was picked up. But not the change in the actual message being returned. What was it that the documentation says again? Passing the .NET Framework security checks guarantees that the contents of the assembly have not been changed since it was built.

I'm not sure I agree.

Mitigation

It appears that the above behaviour is by design. At least according to How to: Disable the Strong-Name Bypass Feature it is.

We can ask that .NET actually check that the strong name is valid by creating an application configuration file for StrongNamedConsole and setting its contents to:

<configuration>
  <runtime>
     <bypassTrustedAppStrongNames enabled="false" />
  </runtime>
</configuration>

That's something to bear in mind the next time you're relying upon the integrity of strong names.

See Also

2 comments:

  1. Hi, thanks for the article. I am also interested in knowing if that's normal behavior or not? Also will it always remain like that? Thanks.

    ReplyDelete
    Replies
    1. Well, I say can't talk as to whether the behaviour will remain as it is, but is is the expected behaviour (at least by Microsoft, if not by the rest of us). If you take a look at http://msdn.microsoft.com/en-us/library/cc713694.aspx you'll note that Microsoft say "Starting with the .NET Framework version 3.5 Service Pack 1 (SP1), strong-name signatures are not validated when an assembly is loaded into a full-trust AppDomain object, such as the default AppDomain for the MyComputer zone." That's quite explicit - they don't validate strong names by default. (I mention this is the Mitigation section at the end of the article.)

      Delete