Tuesday 16 February 2010

Evading the Strong Name Integrity Check

Introduction

In a recent post, Tampering with a Strong-Named Assembly, I pointed out that the Strong-Name Bypass Feature in .NET 3.5 SP1 means that assemblies which have been tampered with are loaded into a full-trust AppDomain without complaint. This appears to be based upon the logic that if you can't even look after the assemblies in your own application folder, why should you expect Microsoft to do so?

But the Global Assembly Cache (GAC) is different. When you attempt to install an assembly into the GAC the integrity of its strong name is verified anyway. After all, GACUTIL doesn't know anything about the AppDomain into which the assembly will be loaded. If we attempt to install the "I am a hacker" version of the StrongNamedLibrary.dll we created during Tampering with a Strong-Named Assembly into the GAC we'll get an exception:

GACUTIL /i StrongNamedLibrary.dll

Microsoft (R) .NET Global Assembly Cache Utility.  Version 3.5.30729.1
Copyright (c) Microsoft Corporation.  All rights reserved.

Failure adding assembly to the cache: Strong name signature could not be verifie
d.  Was the assembly built delay-signed?

Good. That's what should happen. That version of StrongNamedLibrary.dll had been tampered with and GACUTIL picked up on the fact.

However, as we discovered in Where is the Global Assembly Cache?, using GACUTIL isn't the only way to install an assembly into the GAC. We can simply copy it to a folder named %SystemRoot%\assembly\GAC_ProcessorArchitecture\AssemblyName\Version_Culture_PublicKeyToken. Clearly if we do that, we'll evade the strong name integrity check which GACUTIL performs. Will anything else detect the tampering? Let's find out.

Out with the Private; in with the Shared

To test this we'll need to create a few folders beneath %SystemRoot%\assembly\GAC_MSIL, copy our tampered with StrongNamedLibrary.dll into that location, deleted it from the application folder of StrongNamedConsole.exe and then run StrongNamedConsole.exe.

The folder structure we'll need to create includes the Public Key Token for StrongNamedLibrary.dll. The easiest way to establish this is probably to load StrongNamedConsole.exe into ILDASM and check out the reference to StrongNamedLibrary.dll in the manifest. This is displayed as:

.assembly extern StrongNamedLibrary
{
  .publickeytoken = (22 00 58 F5 84 16 B5 85 )                         // ".X.....
  .ver 8961:26437:43913:61389
}

The version number looks odd, but you'll recall that we intentionally used this value as it displays as 01234567890ABCDEF when the assembly is opened with a binary editor.

Okay, we now have all the information we need to create the correct folder structure within the GAC and to copy our tampered-with assembly in there.

> MD %SystemRoot%\assembly\GAC_MSIL\StrongNamedLibrary\8961.26437.43913.61389__220058f58416b585
  
> COPY StrongNamedLibrary.dll %SystemRoot%\assembly\GAC_MSIL\StrongNamedLibrary\8961.26437.43913.61389__220058f58416b585
        1 file(s) copied.  

Let's use the Assembly Cache Viewer to confirm it's in the GAC by browsing to %SystemRoot%\assembly within Windows Explorer.

StrongNamedLibrary.dll within the GAC

Yup. Just copying the assembly into that location appears to have correctly installed it into the GAC.

Testing the Tampered-with Assembly from the GAC

So now we need to delete the private version and try running StrongNamedConsole.exe.

> DEL StrongNamedLibrary.*

> StrongNamedConsole
I am a hacker

Ooops. It appears that the strong name of assemblies loaded from the GAC don't have their integrity checked by default either. To be fair, when Microsoft introduced the Strong-Name Bypass Feature in .NET 3.5 SP1 they did say that 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. Note that they said nothing about whether the assembly loaded into the full-trust AppDomain came from your application folder or the GAC. So they're only being true to their word.

You can disable the strong-name bypass feature in .NET 3.5 SP1 (i.e. re-enable strong-name integrity checks) at the application or machine level. At the application-level you simply adjust your .config file to set bypassTrustedAppStrongNames to false.

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

This works fine for private assemblies loaded from the application directory. But it has no effect on assemblies loaded from the GAC. It's almost as if the assumption is that if an assembly is being loaded from the GAC then the integrity of the strong name has already been verified. But we know better than that, don't we?

See Also

2 comments:

  1. Awesome, was wondering if it was possible to bypass the strong name checks when going into the GAC.

    On a side note, something else I like to do as well is self-verification in the assembly when it's loaded using http://pinvoke.net/default.aspx/mscorsn/StrongNameSignatureVerificationEx.html. Without obfuscation (and with) it could still be easily removed but just makes it that little bit more difficult for people to tamper with your assemblies.

    ReplyDelete
  2. What is the point of using strong names if you can simply edit the msil byte code and evade the check? I'm seems worthless if it doesn't hash the entire assembly and only checks the header (or what ever it actually is)

    ReplyDelete