Delay Signing


Most people know about the delay signing feature of the CLR.  (For those who don’t check out MSDN’s Delay Signing an Assembly for more details).  Basically, delay signing allows a developer to add the public key token to an assembly, without having access to the private key token.  Since the public key token is part of an assembly’s strong name, it allows assemblies under development to carry the same identity as they will have when they are actually signed, however it does not require every developer to have access to the private keys.


For instance, at Microsoft, in order to get an assembly signed, we have to submit it to a special signing group, which are the only people that have access to the full Microsoft key pair.  Obviously we don’t want to go through this process for every daily build of the framework, let alone for each developer’s private builds (imagine the debugging process if you had to wait for a central key group to sign each and every build you created).  Instead of going through all of this overhead, we simply delay sign our assemblies until we get ready to make a release to the public, at which point we go through the formal signing process.


A delay signed assembly contains only the public key token of the signing key, not an actual signature.  (Since, the person producing the delay signed assembly most likely doesn’t have access to the private key necessary to create a signature).  Inside the PE file produced, a delay signed assembly has space reserved for a signature to be placed in the future, but that signature is actually just a block of zeros until the real signature is computed.  Because this block is not likely to be the actual signature value of the assembly, these assemblies will all fail to verify upon loading.  (since their signatures are incorrect).


Obviously, it wouldn’t be very useful if a delay signed assembly were completely unable to load.  To work around this problem, assemblies need to be added to the skip verification list.  This is done with the sn command.  The specific command line is:



sn -Vr assembly [users]


Where assembly is the name of the assembly to skip.  You can also use the format *,publicKeyToken to skip verification for all assemblies with a given public key token.  Users is a comma separated list of users that verification will be skipped for.  If this part is left out, verification is skipped for all users.


The Problem


What this command does is tell the runtime to not verify the signature on an assembly that has the given public key token (if you use the *,publicKeyToken format), or just on a specific assembly.  However, when you stop to think about it, this is a gigantic security hole.


Public key tokens are easily read from any assembly that you have access to.  By running ILDasm on System.dll, inside the manifest, I find the line:



.publickey = (00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 )


Which corresponds to the public key assigned to any assembly standardized by ECMA / ISO.  The token can be easily computed from this value, however an easier way to get it would be to look at ILDasm on any assembly that references mscorlib.  For instance, looking at the manifest of System.Xml.dll under ILDasm shows me the line:



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


Which shows the ECMA / ISO public key token.  Now, as a malicious developer, its very easy for me to write an assembly that is named System.dll, with an assembly version of 1.0.5000.00, and put the public key I just extracted from System.dll into my assembly.  I won’t be able to compute a valid signature, since I don’t have access to the ECMA / ISO private key pair, but that hardly matters to me, since you’ve turned off strong name verification for this particular public key token.  All I have to do is install this assembly in place of System.dll in your GAC, and I now own your machine.


For this reason, it’s vital to only skip verification for assemblies that you are developing yourself, and be extra careful about what code gets downloaded onto your machine that may claim to be from your organization.


Protection


There was some confusion about this issue on the public newsgroups recently.  Even if you take precautions inside your company, how can you be sure that someone external to your company does not get into a situation where they disable checking the strong name on your assemblies and yours gets swapped with an evil counterpart?


The short answer to this is you can’t.  The skip verification list is stored in the registry under HKLM\Software\Microsoft\StrongName\Verification\<asmName,publicKeyToken>, which is protected by an ACL such that anyone can read it, but only administrators can write to it.  So if a malicious developer has managed to write a public key token into your user’s skip verification list, this means that one of two things have happened



  1. Someone has modified the ACL, allowing more write access to this key than usual
  2. The malicious developer is already an administrator on the machine

If 1 is true, then the ACL should be reverted to only allow administrators to write to the key, thus closing the hole.  If 2 is true, then the malicious developer already owns your machine.  As an admin, they could conceivably replace the CLR with a hacked version that doesn’t verify assembly signatures, or perhaps doesn’t implement CAS.  If you’ve gotten into situation #2, game over.


Delay signed assemblies serve to increase security in development shops, by reducing the number of people that need access to an organization’s private keys.  However, the requirement that delay signed assemblies need to be registered in the skip verification list means that developers machines are open to various forms of attack.  Making sure that your developers are aware of the situation, in combination with not overusing your skip verification list will help to make your machines more secure in these environments.

Comments (28)

  1. Meno says:

    brilliant article it hits the point. I hope that Microsoft will fix this in the future.

  2. Shawn says:

    Hi Meno — glad you liked the post. However, I’m not pointing out a shortcoming with delay signing. I’d like to clarify that there’s really not anything to fix. Developers just need to be aware of what they’re allowing when they register delay signed assemblies for skip verification.

  3. You have been Taken Out! Comments about your posting in this link. Thanks!

  4. Meno says:

    Hi Shawn, sorry for the long delay. Ok but the important is not to fix this problem. I think you(MS) had to write it in the documentation. And in my point of view this feature is a good idea as it is, but it make no sense if you are care about security that are a little bit based on where the code that is executed is coming from. If you have an application which should only run your signed code you can`t use delayed signing. So your development process need a other technology to handle this. So there is the still question why is there this feature.

  5. Shawn says:

    There actually is some documentation on MSDN about this. In the page titled "Delay Signing an Assembly" (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpcondelayedsigningassembly.asp), step 4 says:

    "4. Because the assembly does not have a valid strong name signature, the verification of that signature must be turned off. You can do this by using the –Vr option with the Strong Name tool.

    The following example turns off verification for an assembly called myAssembly.dll.

    sn –Vr myAssembly.dll

    CAUTION Use the -Vr option only during development. Adding an assembly to the skip verification list creates a security vulnerability. A malicious assembly could use the fully specified assembly name (assembly name, version, culture, and public key token) of the assembly added to the skip verification list to fake its identity. This would allow the malicious assembly to also skip verification."

    If you want an example of how this feature can be used, check back up in my post. I gave an example of how delay signing is used internally on the CLR team.

    -Shawn

  6. Stefan says:

    What if the software I write contains code for checking a license.

    If the customer succesfully reverse engineers my assembly and he can rebuild it with the license checking code always returning true and with delayed key signing. Because the customer owns his own machine he/she can easily sn –Vr myAssembly.dll to replace my assembly with his cracked one and continue working without needing a license. Can this be prevented, isn’t this a problem??

    Stef

  7. Shawn says:

    Hi Stef,

    Yes if your customer has a physical copy of your code then he could reverse engineer it, delay sign it, then register it for skip verification. Any code that your customer has a physical copy of will be impossible to fully protect. You could raise the bar some by obfuscating your assembly, but if the customer is determined to bypass your license check, they will. Even in unmanaged code, people who want to bypass license checks generally can. Usually its just a matter of finding the magic "compare and branch" instructions that branch to the license check failed portion of your code, and switching the condition on the compare.

    The best thing you can do if you want complete security is to not allow the customer to host the licensing code. Instead provide it via a web service, and you hold onto the licensing code. Again, this won’t prevent really determined customers from bypassing your system, but it will continue to raise the bar.

    -Shawn

  8. Rhonabwy says:

    It’s surprisingly easier and lower stress for me working late at night when I can take a route 4 bus…

  9. ds says:

    what is private key in delay siging

  10. There is no private key in delay signing — generally it is used on developer desktops where they don’t have access to the company private key.

    -Shawn

  11. Matthew Copeland says:

    Hi Shawn,

    Love that you are making the under the hood more accessible. I am a little confused though. You say "Yes if your customer has a physical copy of your code then he could reverse engineer it, delay sign it, then register it for skip verification."

    Why would they even need to delay sign it? Couldn’t they just SN -Vr *?

    Which leads me to my main question.

    Lets take a hypothetical application which contains 10 strongly named assemblies, all signed with the same key, with no obfuscation.

    Is the following possible?

    Disassemble all 10 assemblies.

    Manually edit the IL of each assembly to remove the public key, and remove any reference public key tokens for the 10 assemblies.

    Create a new Public/Private key pair, and sign the 10 assemblies.

    ———————————————–

    I understand that it is impossible to fully protect code on a customers computer. If the above scenario is possible, protection becomes much more problematic.

    As I see it now.

    I can defend against the user manually removing all SN data from my assemblies by programmatically checking an executing assembly’s PublicKeyToken value for null. If it is null (as it would be after removing the SN data from IL) I can have the application exit. Obviously, this code needs to be in as many places as possible to make it as difficult as possible for someone to change the IL to skip these checks (Obfuscation is a big help here).

    I can defend against the user turning off SN verification with SN -Vr *. Using StrongNameSignatureVerificationEx it is possible to programmatically check if the file’s signature is valid. As above, this code needs to be in as many places possible and obfuscated.

    These two techniques would seem to be useless if the user can change the bits, AND then give the assembly a valid signature using a different key pair.

    I would appreciate any thoughts you may have.

    Matthew

  12. Matthew Copeland says:

    After rereading my post… would it be possible to securely store the public key token of the key i used to sign my assemblies with, and double check against that even if the signature is valid?

  13. First the easy question — SN -Vr is not a general purpose mechanism to allow tampering with assemblies. It only works if the assembly is not fully signed, so yes they would have to delay sign the assembly.

    As I mention, if the attacker has physical acceess to the assembly, there’s nothing you can do but raise the bar.  Replacing the public key is certainly possible (although it will also change the identity of the assembly, so the -Vr route may be more desirable).  The attacker can also disassemble your code and remove all checks for the public key (or cause StrongNameSignatureVerification to ignore the skip verification entry, or replace the public key you’re checking for with their own, or replace the CLR with a hacked up version of a different CLI implementaiton which lies to your API call).

    -Shawn

  14. Matthew Copeland says:

    Hi Shawn,

    Thanks for the response. I "get it" as far as raising the bar, and understand the fact that every bar can be overcome by someone who has the skills and is determined.

    I would like to clarify something about your first statement. I can take a strongly named assembly, disassemble change the IL and reassemble without making any changes to the signature data. I will get an invlaid signature exception when the CLR attempts to load this assembly.

    The command line, SN -Vr *, instructs the CLR to skip SN verification for all assemblies. It is not necessary to have a public key token. I can load the above assembly with no exception, and I have not delay signed it.

    I understand you to be saying that I must delay sign the modified assembly. Am I missing something?

  15. I don’t think I follow your example.  If you do:

    ildasm stronglynamed.exe /out=stronglynamed.il

    ilasm stronglynamed.il

    Without specifying a key file, the resulting stronglynamed.exe will not have a strong name.

    The skip verification list only works for assemblies which are delay or test signed. If they are fully signed and tampered with, being on the list does not stop the runtime from throwing an exception when the assembly is loaded.

    -Shawn

  16. aditya says:

    Great article cleared my doubts

  17. Nitin Arora says:

    Hi Shawn,

    Yesterday night when I delay signed one of my assemblies. I noticed this abnormal behavior. Here is the scenario:

    I have an asp.net application where in Page_Load event of private pages, i am doing comparison of values stored in session. Session values are added when user successfully logs In.

    Session.Add("key1", "value1");

    Session.Add("key2", "value1");

    if(Session["key1"] != Session["key2"])

    {

     Response.Write("Keys not equal);

    }

    else

    {

     Response.Write("Keys are equal");

    }

    When my asp.net assembly was not delay signed it was showing "Keys are equal". But when i delay signed my app’s assembly, it started showing "Keys not equal". I dont know what went wrong when i delay signed my assembly.

    After a lot of brainstorming and debugging i changed to code to

    if(Session["key1"].ToString() != Session["key2"].ToString())

    {

     Response.Write("Keys not equal);

    }

    else

    {

     Response.Write("Keys are equal");

    }

    Then everytime it showed Keys are equal.

    Can you throw some light on this as what went wrong with the intial code when i delay signed it?

    Many Thanks

    Nitin Arora

  18. Hi Nitin,

    Unfortunately I’m not going to be much help to you.  From a CLR perspective a delay signed assembly vs a fully signed assembly (with the same public key) are the same assembly.  That would indicate that something else is going on here.

    -Shawn

  19. Ritesh says:

    Hi,

    My assemblies run perfectly if I run with sn -Vr commands but if I give sn -Vu command for my assembly, it gives exception as {"Could not load file or assembly ‘ShaderEffectLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c199d42a01e99449’ or one of its dependencies. Strong name validation failed. (Exception from HRESULT: 0x8013141A)":"ShaderEffectLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c199d42a01e99449"}"

    Kindly suggest.

  20. That indicates that you have a delay signed assembly which requires a skip verification entry to be used.  When you do -Vu, you remove the entry, and the assembly will no longer load.

    -Shawn