Finding the Raw Strong Name Signature

Wow ... there's been lots of interest in signatures lately :-)  In response to my last post about reserving a larger section of the PE file for the signature when you create a signature with a larger key, William wants to know if you can extract the actual signature bytes from the PE file.

Absolutely you can, it's even relatively easy to do.  Boiled down, the raw strong name signature is simply the array of bytes that are located at the RVA specified by the StrongNameSignature.VirtualAddress field of the CLR header.  The signature length is specified by the StrongNameSignature.Size field of the header.  So once you have the CLR header of a PE file, its very easy to find the signature itself.

In the spirit of picking the right tool for the job, I'll show how to do this in C++ code, since creating P/Invoke declarations for the various structures needed would take up a large chunk of time.  The code for a method that takes a memory mapped PE file and displays its raw signature would look something like this:

1   /// <summary>
2   /// Display the raw strong name signature of a memory mapped assembly
3   /// </summary>
4  HRESULT DisplaySignature(BYTE *pbFile)
5  {
6      _ASSERTE(pbFile != NULL);
7      _ASSERTE(0 < cbFile);
8  
9      PIMAGE_NT_HEADERS pNtHeader = ImageNtHeader(pbFile);
10      if(NULL == pNtHeader)
11      {
12          std::cout << "Error: Could not get NT headers for file" << std::endl;
13          return E_INVALIDARG;
14      }
15  
16       // find the data directories in the file
17      PIMAGE_DATA_DIRECTORY pDataDirectories = NULL;
18      switch(pNtHeader->OptionalHeader.Magic)
19      {
20          case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
21              pDataDirectories = reinterpret_cast<PIMAGE_NT_HEADERS32>(pNtHeader)->OptionalHeader.DataDirectory;
22              break;
23  
24          case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
25              pDataDirectories = reinterpret_cast<PIMAGE_NT_HEADERS64>(pNtHeader)->OptionalHeader.DataDirectory;
26              break;
27          
28          default:
29              return E_NOTIMPL;
30      }
31      
32       // Make sure there is a CLR header
33      _ASSERTE(pDataDirectories != NULL);
34      if(0 == pDataDirectories[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress ||
35         0 == pDataDirectories[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].Size)
36      {
37          std::cout << "Error: No CLR header" << std::endl;
38          return E_INVALIDARG;
39      }
40      
41       // get the CLR header
42      PIMAGE_COR20_HEADER pCorHeader = reinterpret_cast<PIMAGE_COR20_HEADER>(
43          ImageRvaToVa(
44              pNtHeader,
45              pbFile,
46              pDataDirectories[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress,
47              NULL));
48      if(NULL == pCorHeader)
49      {
50          std::cout << "Error: Could not get CLR header" << std::endl;
51          return E_INVALIDARG;
52      }
53  
54       // check to see if there is a signature
55      if( 0 == pCorHeader->StrongNameSignature.VirtualAddress ||
56          0 == pCorHeader->StrongNameSignature.Size)
57      {
58          std::cout << "Error: Image is not signed" << std::endl;
59          return S_FALSE;
60      }
61  
62       // pull the signature from the header
63      BYTE *pbSignature = reinterpret_cast<BYTE *>(
64          ImageRvaToVa(
65              pNtHeader,
66              pbFile,
67              pCorHeader->StrongNameSignature.VirtualAddress,
68              NULL));
69      if(NULL == pbSignature)
70      {
71          std::cout << "Error: Could not find signature directory" << std::endl;
72          return E_INVALIDARG;
73      }
74  
75       // display it
76      std::cout << "Strong name signature (" << pCorHeader->StrongNameSignature.Size << " bytes):";
77      std::cout << std::hex;
78      for(DWORD i = 0; i < pCorHeader->StrongNameSignature.Size; i++)
79      {
80          if(i % 16 == 0)
81              std::cout << std::endl;
82          std::cout << std::setw(2) << (DWORD)(pbSignature[i]) << " ";
83      }
84      
85      std::cout << std::dec << std::endl;
86      return S_OK;
87  }

OK, that's a chunk and a half of code right there.  However, the process its following is relatively straight forward.  On lines 9-14, I use the DbgHelp function ImageNtHeader to locate the IMAGE_NT_HEADERS structure in the file.  Once we have the NT headers, we need to find the data directories, but their location varies depending upon if the assembly is a PE32 or PE32+ file.  So on lines 16 through 30 we check the magic number in the beginning of the optional header, and get the correct pointer to the data directories.  If the PE is neither of these two formats, we bomb out.

So now that we've got the table of directory entries, on lines 32 through 52 we make sure that the entry for the COM descriptor directory exists, and if it does use another DbgHelp function ImageRvaToVa in order to convert the RVA of the directory entry to an address within the mapped file.  The beginning of this directory entry is the CLR header, in the form of a IMAGE_COR20_HEADER structure.

Once we have our IMAGE_COR20_HEADER, the information about the signature is located in the StrongNameSignature field.  In lines 54-73 we check if a signature exists, and if it does, use ImageRvaToVa in order to calculate the location in the file of the signature itself.

Once we have that offset, dumping the signature to the display is easy.  Lines 75-83 simple iterate over the memory in the memory mapped file that contains the signature until we've dumped out pCorHeader->StrongNameSignature.Size bytes.  At that point, we've displayed the entire signature, so the method returns.