Building Assemblies using VB.NET

This article discusses how to build assemblies and secure them from being tampered.

This article discusses how to build assemblies and secure them from being tampered.

Basics

Every assembly built using the .NET compiler has a four-part name consisting of the following elements:

  1. Friendly name
  2. Version number
  3. Culture setting
  4. Public key or public key token

The first part is a friendly name Like MyAssembly.

The second part is the version number say 1.0.5.0. These four numbers represent the major version, minor version, build number, and revision number respectively.

The assembly can be built with a specific version number by placing the following code to the top of any of the project source files:

Imports System.Reflection

<Assembly: AssemblyVersion ("1.0.24.0")>

The AssemblyVersion attribute is defined in System.Reflection namespace. If no version number is specified the compiler assigns a version numnber 0.0.0.0.

The third part is the culture setting indicating that the assembly has been localized for a particular spoken language.

The fourth and the last part of an assembly name is the public key. This value is unique and represents a particular company or developer. Two companies should never use the same public key.

The public key occupies 128 bytes and is stored as a binary format in a key file. It can also be written using a text based hexadecimal format as shown below:

00 24 00 00 04 80 00 00 94 00 00 00 06 02 00 00 
00 24 00 00 52 53 41 31 00 04 00 00 01 00 01 00 
CD 79 18 40 51 8C D7 25 A5 49 13 BA 4B 4F CF 34 
03 4A 7B 6E 17 8E 5C 41 9E 95 EA 78 0B E9 60 5D 
9D EE 52 24 25 0A 25 90 E0 7D 4A EC 13 90 21 45 
76 BB AE FB F4 B6 08 1D F4 7B 6C B9 A4 EC 62 AF 
1F C8 54 B2 92 96 11 B6 B8 9D 41 8C 24 B5 30 10 
0B 1C F5 1C CA D3 4A 2F D5 59 16 D5 E0 B5 B9 D6 
68 61 18 4D 3B D0 E7 C4 73 6C 6B DF 41 D0 00 4F 
A6 E0 9F 98 52 6C 68 FA 1E 86 CA 1D AE CE A5 B7

Because the public key is so large, a smaller 8 byte value known as a public key token is used in its place. The public key token is usually represented by a 16-character hexadecimal string value that looks like this:

A9 FA E6 01 40 67 F5 F1

There is one to one correspondence between a public key value and a public key token i.e. for a specific public key token there is exactly one corresponding public key value.

How to build a strong name assembly?

An assembly is said to have a strong name when it has a public key and a digital signature. This protects the assembly from being tampered.

The first step to build a assembly with a strong name is get a pair of public-private key pair. The public key is written into the physical image of the assembly. The private key is used to generate digital signatures.

The key is generated using the .NET framework strong name utility SN.exe by specifying -k switch and the target key file from the command line.

SN.exe -k keyfilename.snk

This key file can be used by several different assembly projects.

The next step is to add the following code at the top of one of the source files in a project:

Imports System.Reflection
<Assembly:AssemblyVersion("1.0.24.0")>
<Assembly:AssemblyKeyFile("..\..\Keyfilename.snk")>

When the project is build an assembly with a strong name is generated. The compiler records the full 128 byte value into the manifest of the assembly file it outputs. The compiler uses the private key to generate the digital signature.

Signing of the assembly and prevent tampering

The .NET framework relies on two industry-standard forms of cryptography know as one way hash functions and asymmetric encryption. The hash function allows you to create a digital fingerprint from a large digital object such as a public key or a file image and returns a smaller value known hash value. The hash value of an assembly serves as its digital finger print for a particular build

The next step in generating a digital signature involves asymmetric encryption. While signing an assembly, the private key is used to generate a digital signature by encrypting the hash value of an assembly file. Later the public key can be used to decrypt the digital signature and retrieve the hash value of the assembly file.

Hence, the sequence of compilation involves the following steps:

First the compiler generates a image of the assembly file containing its manifest, its type information, and its executable code in the form of IL. However the compiler does not generate the digital signature and leaves a blank space at the end of the assembly file to act as a placeholder.

Second, the compiler generates the Hash value from the image of the assemble file that was built during the first phase. Then the compiler encrypts the hash value with the private key to generate the digital signature and writes it into the placeholder at the end of the assembly file.

Once the assembly has been distributed, let us discuss how the CLR verifies the authenticity of the assembly's digital signature. The CLR uses the public key to decrypt the digital signature to get the hash value of the assembly that was present during compilation. Next the CLR uses the same Hash function to generate the hash value form the assembly's physical image.

The CLR compares the two hash values and. If the hash values are equal then the CLR considers the test a success because it is sure of two things-namely, that the digital signature was generated by someone in possession of the private key and the physical image of the assembly file originally signed. If the CLR determines that the two hash values are not equal it considers the verification test a failure. In case MyAssembly.dll is in the global assembly cache it is verified when it was placed in the GAC and not retrieved at run time.

Lets discuss a typical case using MyApp.exe and MyAssembly.dll. Imagine you compiled MyAssembly.dll with a strong name and, after that, you have compiled Myapp.exe with a reference to MyAssembly.dll. It is now impossible for someone without the private key to change the behavior of the application on the production machine by replacing the real version of the MyAssembly.dll with a tampered version. Note that the assemble manifest for MyApp.exe contains a reference with the public key token associated with the same public key compile into MyLibarary.dll. Therefore you have the guarantee that the CLR meets two important criteria. The first is the assembly file for MyAssembly.dll contains a public key value that matches the public key token in the assembly manifest of MyApp.exe. The second, MyAssembly.dll has been signed by someone in procession of the private key. When the user runs MyApp.exe and it executes code that makes the first call to MyAssembly.dll. At this point the CLR will attempt to load Myassembly.dll that has a strong name, so it runs a verification check. This verification check allows the CLR to detect if someone without access to the private key has made changed to the physical image of the assembly file.

Now if some one wants to tamper with MyAssembly.dll on the production machine, the verification scheme thwarts the bad guy. There fore any changes made to the physical image of the after it has been signed renders the existing digital signature invalid. Note that this verification scheme doesn't really prevent tampering. It only prevents tampering from going undetected. When the CLR attempts to load a strongly named assembly with a digital signature that doesn't match the physical layout of the assembly file, it fails to load attempt by throwing System.IO.FileLoadExecption. This scheme would not be as reliable if your application depended on a strongly named assemble which in turn , depended on another assembly that did not have a strong name. The bad guy could replace the assembly that did not have a string name and the CLR would not be able to detect this. The CLR enforces this restriction so that you are guaranteed that a strongly named assembly depends on only other strongly named assemblies.

The following figure shows an assembly manifest for reference. This assemble manifest is got using the IL dissembler tool of the .NET framework.

.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 1:0:3300:0
}

.assembly MyAssembly
{
.custom instance void [mscorlib]System.Reflection.AssemblyKeyFileAttribute::.ctor(
string) = ( 01 00 10 69 6E 73 69 64 65 43 73 68 61 72 70 2E // ...insideCsharp.
6B 65 79 00 00 ) // key..
// --- The following custom attribute is added automatically, do not uncomment -------
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(bool,
// bool) = ( 01 00 00 01 00 00 ) 
.publickey = (00 24 00 00 04 80 00 00 94 00 00 00 06 02 00 00 // .$..............
00 24 00 00 52 53 41 31 00 04 00 00 01 00 01 00 // .$..RSA1........
CD 99 07 7B 3B 4A 0C 85 5B FC 74 0E 20 CF 11 5F // ...{;J..[.t. .._
1A E6 E7 B1 9A 76 49 8A 91 89 97 B2 FD 14 8F 13 // .....vI.........
5A 81 78 6D F7 83 B1 0E D7 88 A5 AD B3 80 3D F6 // Z.xm..........=.
C8 8C E3 D9 34 53 06 B8 7F CB 09 60 0B 00 4A E5 // ....4S.....`..J.
B0 F9 29 EB FF A2 74 81 35 FC AB 36 29 C0 DE 79 // ..)...t.5..6)..y
28 A2 31 C3 91 F7 37 D3 1C 60 31 7E 61 FE 64 A9 // (.1...7..`1~a.d.
58 B0 E4 85 D2 6E B8 49 49 0E 24 DF B4 02 00 4C // X....n.II.$....L
53 6C 2E 90 49 3F B1 2A 7A 6F 85 EE AC BA C8 B6 ) // Sl..I?.*zo......
.hash algorithm 0x00008004
.ver 0:0:0:0
}

.module MyAssembly.exe
// MVID: {9F7C6908-6359-46AA-B016-C81C15458D33}
.imagebase 0x00400000
.subsystem 0x00000003
.file alignment 512
.corflags 0x00000009
// Image base: 0x03b50000