Cracking Strong Named .NET Assemblies
A while ago I read .NET Security Framework by LaMacchia et al (Addison Wesley, 2002, ISBN 0-672-32184-X). This is an excellent book. It explains just about everything you would want to know about .NET security. Four of the five authors are on the .NET security team, so I am sure you'll agree that the book is authoritative. One of the interesting aspects about the early chapters of the book is that it thoroughly explains the process that .NET goes through when it loads a .NET assembly. It describes the various tests that are performed to make sure that the assembly satisfies various security criteria. I found the various statements about strong names interesting, in particular Chapter 9 (written by Rudi Martin) has a section called Signing and Verifying Assemblies. This section describes a scenario of a cracker obtaining an assembly, changing it, and then redistributing the assembly as if it is the assembly distributed by the original publisher. This would create a Trojan: the user thinks that the library comes from a trusted publisher, but really the assembly contains malicious code. This is a common scenario and so this chapter of the LaMacchia book explains how .NET 'solves' this problem.
Strong Name Signatures
The explanation goes like this. The name of an assembly is made up of the culture, version and short name of the assembly, but since these items are not unique it means that there is a possibility of two assemblies having the same values for these items and hence we get into the issue of 'DLL Hell' that has plagued Windows through out its history. 'DLL Hell' describes the situation when one DLL over writes another with the same name, or is loaded instead of another DLL with the same name but located elsewhere because of the search path used to locate the DLL. It all comes down to unique names. A strong name is a name based on the public key of a publisher's key pair. The key generator creates a random key so no two publishers can have the same key. This means that if you sign an assembly (by giving it a strong name) you are creating an assembly with a unique name.
However, applying a strong name requires more than simply adding a public key to the assembly. If this was the case then a cracker could simply extract the public key from one assembly and insert it into another assembly they she wants to pass off as being produced by a legitimate publisher. A key pair does not uniquely identify the publisher, however, if you have two assemblies with the same public key then you can say that they are from the same publisher. Or at least you can if the public key can be verified. This is the reason for the strong name signature, it exists so that the strong name (the public key) can be validated. Recall that the public key will decrypt anything that the private key encrypts. If the private key is kept secret and you can verify that the public key has decrypted an item, then the public key is verified. To do this you need some piece of data that is available to both the compiler (that has the private key) and the runtime (that has the public key). This piece of data is the assembly itself.
When you add a strong name to a library the compiler creates a hash over most of the assembly (the space that is omitted are the location used for the X509 certificate and the strong name signature). The hash is encrypted with the publisher's private key to form the strong name signature. The strong name signature and the public key are then placed in the assembly. When you tell the compiler that you will use a strong named assembly, the compiler stores the full name of the signed assembly into the assembly it is creating. The full name includes the public key, and because this is large, the runtime uses part of the hash of the public key, an item called the public key token. When .NET loads the referenced assembly it verifies that the assembly has specified the public key, and of course, it can do this by extracting the public key and hashing it to be able to extract the public key token. It also needs to verify the public key, and it can do this by generating the hash of the referenced assembly. The strong name signature is this hash encrypted with the private key, so only the correct public key can decrypt it. Thus the runtime extracts the strong name signature decrypts it with the public key and if the result does not agree with the hash it generated the runtime will not load the assembly.
Review what I have said: if the public key does not decrypt the strong name signature to the value of the hash of the assembly then it means that the public key is not the right key, and so it is not verified. This action can also mean that the hash of the assembly has changed since the strong name signature was created. In other words, the assembly has changed.
[adding a strong name creates a] signature, which validates the right to use the public key and allows the consistency of the assembly contents to be verified. [...] The verification of any strong name assemblies is performed automatically when needed by the .NET Framework. Any assembly claiming a strong name but failing verification will fail to install into the global assembly cache or download cache or will fail to load at runtime.
It is easy to deduce from this explanation that adding a strong name to an assembly protects that assembly from tampering. If the verification fails, that is, the hash of the assembly does not agree with the signed hash, then the assembly is not loaded. I took this at face value, tested it out and found that if I changed metadata, IL or resources in a strong named assembly and tried to load it, the framework would refuse. I wrote this up in an article I wrote for Dr Dobb's Journal (July 2004).
Controversy Over the Use of Strong Names
Well, everything I said in my article was true, but unfortunately there's a bug in .NET which means that a cracker can still change a strong named assembly. I will explain the process on a later page, but it is worth making some other comments about the process. When I first realized that strong names do not create tamper-proof assemblies I did some searches and came up with the references about the issue. The best reference is Valery Pryamikov. Valery takes a pedantic view about strong names (his two blog entries on the subject are sadly no longer available> here and here). Valery goes back to the ECMA spec and quotes the sections referring to strong names and says (quite correctly) that there is no mention in the spec of using a strong name for security related tasks. Valery points out that the ECMA spec does not mandate that the public key should be verified: "A conforming implementation of the CLI need not perform this validation [of the public key], but it is permitted to do so, and it may refuse to load an assembly for which the validation fails". He also points out that the strong name is really only used to give unique names to assemblies to aid in .NET versioning.
However, while this is the case, and is documented in the ECMA spec, it makes no sense to use a public key without verifying it. An unverified public key can be used by anyone and so the uniqueness of strong names, and hence versioning, will be broken. Verifying the strong name signature verifies the public key rather than the contents of the assembly. However, the two are inter-dependent, so while I accept Valery's comment about versioning I still think he is wrong: versioning needs a unique name, a unique name based on a public key needs strong name validation, and by association, this also verifies the contents of the assembly.
Finally, we all know about Microsoft's lack of adherence to standards. Although there are other implementations of the ECMA spec of the CLI, Microsoft's is the most widespread, and will always be the most widely used. In short, the behaviour of Microsoft's implementation is the de facto standard regardless of any published standards. Indeed, even Microsoft's online documentation says that strong names provide an integrity check:
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.
Before I describe how to crack strong names assemblies I will first explain how a strong name signature makes an assembly 'tamper proof'.