Click here to Skip to main content
Click here to Skip to main content

Removing Strong-Signing from Assemblies at File Level (byte patching)

By , 9 Mar 2013
 

Strong Name Remove application screenshot

Introduction

This article describes how Strong Signing works in .NET Framework 1.1 and 2.0. In particular, it is about how Strong Signing is implemented at file level - I mean, bytes in an assembly EXE or DLL. Knowing this allows me to best understand how security should and can be implemented in managed code. Lastly, we must be aware that Strong Signing assemblies is not a definitive way against hackers, as official Microsoft documentation says too.

Background

I'm going to explain how ideas in this article came to life using a particular (imaginary) scenario. John is a developer just been hired in a new company. His first task is to fix some typos in an application developed by his company. Some employee previously working there was not a native English speaker, so there were a lot of them (and that employee resigned some time ago). Anyway, he is asked to complete his assigned task by the next day. He first thinks it is a really easy job, but soon understands that the previous employee had not checked-in the latest application version to the source control. So, he only has the compiled application bits available, a hex editor, and some hours left (OK, this is a very bad situation, but this is just imaginary, so try to stay with me). He opens the executable and tries to find out the typos - he gets them and he fixes them, at least the worst ones, where the customer name is incorrect (yes, he can only overwrite existing bytes, but consider this enough for this scenario). At last, he starts the application, discovering it was a signed assembly (Sign an Assembly with a Strong Name on MSDN) and it won't load anymore. John has already read many articles on the assembly internal file format, like those by Matt Pietrek (part 1 and part 2) or Kevin Burton (here), and he knows ILDASM and Asmex, and obviously, he has a CLI Reference downloaded and waiting. With all that documentation available, he changes 6 bytes (!) in the file header, removing or disabling strong signing from the assembly, and getting a fully working application with no typos (but he has to complain about the lost source code to his boss...). 

Now, the article and the code... it's about those 6 bytes. 

Points of Interest

Questions are: what are the differences between a signed assembly and a normal one? Can a signed assembly be brought back to unsigned status simply by patching it at bytes level, without recompiling the source code? The answer to the second question is yes, it's possible. The answer to the first question would reveal how. I would not deal with the complete .NET header specifications here, there are a lot of articles explaining them (those already noted above and others like this).

For a complete Assembly Metadata reference, look at ECMA-335: CLI Partition II - Metadata (Word format) - this is referred in the next discussion. What follows are particular data structures and values related to assembly metadata. Patching (modifying) or removing (overwriting with zeroes) them restores an assembly to the unsigned status.

  1. Runtime flags in the CLI Header (documented in 25.3.3.1) - this byte means, in plain words, "Is the assembly signed?" - if yes, we have a COMIMAGE_FLAGS_STRONGNAMESIGNED value there (8). To remove the strong signing, simply reset this byte back to its current value minus COMIMAGE_FLAGS_STRONGNAMESIGNED. This usually leads to a flag value of COMIMAGE_FLAGS_ILONLY (1).
  2. StrongNameSignature RVA in CLI Header (documented in 25.3.3) - these are 8 bytes, giving an offset of public key hash data. Key is normally 160 (0xA0) bytes itself. To remove strong signing, simply overwrite those 8 bytes in the header with 00. This suggests to the assembly loader that there's no signature in the file, even if the original key is still there.
  3. Flags in Assembly Table (documented in 22.2) - this is a 4-byte bitmask of type AssemblyFlags (documented in 23.1.2). A value of PublicKey (0x0001) here means the assembly is strong signed. To remove strong signing, reset the first byte back to its current value minus PublicKey. This usually resets the flag to the SideBySideCompatible value (0x0000).
  4. PublicKey index in Assembly Table (documented in 22.2) - this is an index into the BLOB heap where the Public Key is stored. To remove strong signing, overwrite those two bytes with 00 (meaning, no Public Key available).

This process usually leads to a 6 to 12 bytes patching (depending on the bytes used for RVA), and is just enough to fool the .NET loader (version 1.1, 2.0, 3.0, 3.5, 4.0 and 4.5) into believing that the assembly is not strong signed at all. All you need is some reference and a hex editor. This approach removes strong signing from an EXE assembly and DLLs too. A bit more work (and experimenting) is needed if we are patching an assembly referenced by another one (eventually strong signed itself).

This situation requires removing the strong signing from the DLL (using the described method) and from the main executable (the one which references the DLL) and then modifying the main executable references table to report the DLL as not signed. The assembly reference table is named AssemblyRef (documented in 22.5), and contains PublicKeyOrToken, an index into the BLOB heap, indicating the public key or token that identifies the author of the referenced assembly. Overwriting the index (2 bytes) with 00 defines the assembly reference to the unsigned file.

Using the Code

Having to manually deal with .NET assembly metadata and data tables is very boring. The hard part there is to know the base table offset in the PE file, extract the index from the table, and jump to the correct address (this is usually referred to as RVA to Real Offsets). Experimenting at this using a hex editor was a very interesting job; anyway, we want a better way. At last, I found Asmex, a fantastic tool written in C# and available for free with full source code on CodeProject. Having to work with all those .NET headers and tables becomes really easy now.

Asmex source files are included unchanged, except for those exposing the two internal fields in the class Table (file TableStream.cs) using properties. We need that data so we can calculate the exact offsets in the file bytes for patching. Retrieving the file offset of particular structures and bytes is, in most cases, a simple property reading task using Asmex.

Getting file offsets and values from an assembly is done by the GetAssemblyData method. This is a relevant part (in source code, it's a bit more complex with error checking and comments):

MModule mod = new MModule(r);

cliHeaderFlag = mod.ModHeaders.COR20Header.Flags;
cliHeaderFlagOffset = mod.ModHeaders.COR20Header.Start + 16;
strongNameSignatureOffset =
 mod.ModHeaders.COR20Header.StrongNameSignature.Start;
compiledRuntimeVersion =
 mod.ModHeaders.MetaDataHeaders.StorageSigAndHeader.VersionString;
Table tableAssembly = mod.MDTables.GetTable(Types.Assembly);
publicKeyOffset =
 mod.BlobHeap.Start + tableAssembly[0][6].RawData + 1;
assemblyFlag = (uint)tableAssembly[0][5].Data;

// next loop sum tables byte length till
// reaching Assembly Table - this would
// give Assembly Table start offset
long assemblyTableOffset =
     mod.ModHeaders.MetaDataTableHeader.End;
for (int tablesCounter = 0; tablesCounter <
     Int32.Parse(Enum.Format(typeof(Types),
     Types.Assembly, "d")); tablesCounter++)
{
    assemblyTableOffset +=
         mod.MDTables.Tables[tablesCounter].RawData.Length;
}
publicKeyIndexOffset = assemblyTableOffset + 16;
assemblyFlagOffset = assemblyTableOffset + 12;

// next loop sum tables byte length till reaching
// Assembly References Table - this would give
// Assembly References Table start offset
long referenceTableOffset =
     mod.ModHeaders.MetaDataTableHeader.End;
for (int tablesCounter = 0; tablesCounter <
     Int32.Parse(Enum.Format(typeof(Types),
     Types.AssemblyRef, "d")); tablesCounter++)
{
    referenceTableOffset +=
         mod.MDTables.Tables[tablesCounter].RawData.Length;
}

At this point, we have all the data (to produce a basic user interface, take a look at the methods CLIHeaderFlagToString and AssemblyFlagToString decoding flag values to human readable format according to metadata specifications) and file offsets, so we can proceed with byte patching.

In the code, you'll find the PatchReference method to remove the Public Key evidences from the assembly references, and the PatchAssemblyStrongSigning method to modify the CLI Header and the Assembly Table.

All the stated methods are contained in the class Utility. You'll also find a helper class named AssemblyReference used to store assembly references data while reading and decoding it.

History

  • Version 2.3 is a refactored revision able to deal with PE and PE+ file formats (32 bit and 64 bit files). Thanks to sendersu for his help and hints on this!
  • Version 2.2 is a cleaned up and updated version. Project was upgraded to Visual Studio 2010, and I removed every reference to Memory-Mapped Files (to prevent crash on x64 systems), Vista Security, and changed a little icon (to reduce file size).
  • Version 2.1 is a bug fix for BLOB index sizing. Index was used as a fixed value, but this is not true. Bugs would happen on large files. Also changed application graphics to be more Vista-style, and added some code to be UAC aware.
  • Version 2.0 is the first public version. I experimented for a long time a 1.3 version, which was based on .NET Framework 1.1, but wasn't able to patch references table.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Andrea Bertolotto
Software Developer (Senior)
Italy Italy
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberEdo Tzumer15-Dec-12 23:39 
QuestionAdd support to open and process x64 .NET assembliesmemberMember 95975123-Nov-12 21:10 
AnswerRe: Add support to open and process x64 .NET assembliesmemberAndrea Bertolotto23-Nov-12 23:09 
GeneralRe: Add support to open and process x64 .NET assembliesmemberMember 95975124-Nov-12 1:07 
AnswerRe: Add support to open and process x64 .NET assembliesmemberAndrea Bertolotto15-Dec-12 3:55 
QuestionYou chose the long waymemberFatCatProgrammer6-Nov-12 3:24 
AnswerRe: You chose the long waymemberMember 95975123-Nov-12 21:11 
GeneralRe: You chose the long waymemberFatCatProgrammer28-Nov-12 8:26 
GeneralRe: You chose the long waymemberMember 95975128-Nov-12 10:08 
QuestionStrongly typed resourcesmemberMember 95975114-Jan-12 6:25 
QuestionProblem after removing Strong-Signingmemberspamaccount14-Nov-11 6:47 
QuestionReflector - Reflexil 1.2memberAndrewGMatondo12-Aug-11 20:24 
AnswerRe: Reflector - Reflexil 1.2memberDan Suthar16-Dec-12 21:20 
GeneralYou are the greatest!membersmt527-Mar-11 12:41 
GeneralStrong singing assemblies that doesn't have a signmemberPuchko Vasili16-Nov-10 10:00 
GeneralRe: Strong singing assemblies that doesn't have a signmemberAndrea Bertolotto16-Nov-10 20:00 
GeneralRe: Strong singing assemblies that doesn't have a signmemberPuchko Vasili16-Nov-10 21:30 
GeneralDivideByZeroExceptionmembergiammin15-Nov-10 0:27 
AnswerRe: DivideByZeroExceptionmemberAndrea Bertolotto15-Nov-10 3:47 
GeneralRe: DivideByZeroExceptionmembergiammin15-Nov-10 7:46 
GeneralProgram seems to crash on x64 OS when "patch" button is pressed.memberzespri15-Feb-10 18:21 
GeneralRe: Program seems to crash on x64 OS when "patch" button is pressed.memberjonorossi9-Apr-10 18:09 
GeneralRe: Program seems to crash on x64 OS when "patch" button is pressed.memberAndrea Bertolotto15-Nov-10 3:48 
GeneralGreat tool, but perhaps you could help us also!memberMember 47072181-Dec-09 8:23 
AnswerRe: Great tool, but perhaps you could help us also!memberAndrea Bertolotto1-Dec-09 20:36 
GeneralRe: Great tool, but perhaps you could help us also!memberMember 47072181-Dec-09 22:56 
GeneralYour utility has a SMALL problemmemberMackovic2-Nov-09 13:33 
AnswerRe: Your utility has a SMALL problemmemberAndrea Bertolotto1-Dec-09 20:13 
GeneralTwo small bugsmemberDanielRose198130-Oct-09 1:38 
GeneralRe: Two small bugsmemberAndrea Bertolotto1-Dec-09 20:16 
GeneralVery Nice!memberjay_dubal14-Jul-09 1:02 
GeneralProblemmemberI_gO_tO_schoOl_by_scoOter5-Jul-08 22:47 
GeneralRe: ProblemmemberAndrea Bertolotto6-Jul-08 5:13 
QuestionInternalsVisibleTo causing issuesmemberfnfrich27-Jun-08 10:15 
AnswerRe: InternalsVisibleTo causing issuesmemberAndrea Bertolotto27-Jun-08 20:44 
QuestionHow Remove the CLI header ?memberLucianoNet4-Jun-08 16:56 
AnswerRe: How Remove the CLI header ?memberAndrea Bertolotto4-Jun-08 22:48 
Generalwell writtenmemberlallous23-Oct-07 4:46 
GeneralSoftware not working under XPmember8844314-Aug-07 9:56 
AnswerRe: Software not working under XP [modified]memberAndrea Bertolotto14-Aug-07 23:24 
GeneralRe: Software not working under XPmemberCoolVini17-Oct-07 22:51 
GeneralRe: Software not working under XP [modified]memberalecan25-Oct-08 4:15 
GeneralAdd a strong namememberAlois Kraus23-Jan-07 12:37 
GeneralRe: Add a strong namememberNordin Rahman29-Jan-07 4:23 
GeneralRe: Add a strong namememberVertyg013-Feb-07 21:57 
GeneralRe: Add a strong namememberAlois Kraus14-Feb-07 8:06 
GeneralRe: Add a strong namememberAlois Kraus15-Feb-07 11:44 
GeneralRe: Add a strong namememberVertyg015-Feb-07 20:12 
GeneralRe: Add a strong namememberSuper Lloyd23-Jul-07 13:29 
GeneralRe: Add a strong namememberKent Boogaart23-Jul-07 13:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130617.1 | Last Updated 10 Mar 2013
Article Copyright 2006 by Andrea Bertolotto
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid