The CLR Header

Last, but not least – The CLR header.
The CLR header is a chunk of data that all .Net executables must support.
This header will only be present when there is a managed component in the executable.

typedef struct IMAGE_COR20_HEADER
{
  DWORD                   cb;                       //Size of this structure (0x48)
  WORD                    MajorRuntimeVersion;      //Major version of the CLR runtime
  WORD                    MinorRuntimeVersion;      //Minor version of the CLR runtime
  IMAGE_DATA_DIRECTORY    MetaData;                 //RVA to, and size of, the executables meta-data
  DWORD                   Flags;                    //Bitwise flags indicating attributes of this executable

  union
  {
    DWORD               EntryPointToken;            //If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT not set; EntryPointToken represents a managed entry point.
    DWORD               EntryPointRVA;              //If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT set; EntryPointRVA represents a RVA to a native entry point.
  } DUMMYUNIONNAME;

  IMAGE_DATA_DIRECTORY    Resources;                //RVA to, and size of, the executables resources
  IMAGE_DATA_DIRECTORY    CodeManagerTable;         //Always 0
  IMAGE_DATA_DIRECTORY    VTableFixups;             //Contains the location and size of an array of VtableFixups
  IMAGE_DATA_DIRECTORY    ExportAddressTableJumps;  //Always 0
  IMAGE_DATA_DIRECTORY    ManagedNativeHeader;      //Always 0

} IMAGE_COR20_HEADER, *PIMAGE_COR20_HEADER;

Something which I found quite strange was that irrespective of which version of the .Net Framework my executable was compiled against, the RuntimeVersion in the header always seems to be 2.05.
Even when <supportedRuntime version=”v4.0″ sku=”.NETFramework,Version=v4.0″/> is specified in the executables configuration files. – Guess I’ve more digging to do!

There are a number of existing tools which can be used to inspect the CLR header:

As an example : checking whether an executable contains only managed code (is /clrpure):

bool ContainsOnlyManagedCode(string filename)
{
  bool toReturn = false;
  fstream file_stream = fstream(filename, ios::in | ios::binary);
  file_stream.seekg(0, ios::beg);

  if(file_stream.is_open())
  {
    //read the dos header
    IMAGE_DOS_HEADER dosHeader ={};
    file_stream.read((char*)&dosHeader, sizeof(IMAGE_DOS_HEADER));

    const bool isExecutable = (dosHeader.e_magic == IMAGE_DOS_SIGNATURE);
    if(isExecutable)
    {
      //seek to the start of IMAGE_NT_HEADERS (skipping the Signature)
      file_stream.seekg(dosHeader.e_lfanew + sizeof(DWORD), ios::beg);

      //read the NTHeaders signature
      IMAGE_FILE_HEADER fileHeader ={};
      file_stream.read((char*)&fileHeader, sizeof(fileHeader));

      //read into the correct structure
      IMAGE_DATA_DIRECTORY clrHeaderLocation ={};
      if(sizeof(IMAGE_OPTIONAL_HEADER64) == fileHeader.SizeOfOptionalHeader)
      {
        IMAGE_OPTIONAL_HEADER64 nt64OptHeader ={};
        file_stream.read((char*)&nt64OptHeader, fileHeader.SizeOfOptionalHeader);
        clrHeaderLocation = nt64OptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR];
      }
      else if(sizeof(IMAGE_OPTIONAL_HEADER32) == fileHeader.SizeOfOptionalHeader)
      {
        IMAGE_OPTIONAL_HEADER32 nt32OptHeader ={};
        file_stream.read((char*)&nt32OptHeader, fileHeader.SizeOfOptionalHeader);
        clrHeaderLocation = nt32OptHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR];
      }

      IMAGE_COR20_HEADER clrHeader ={};
      const bool isManaged = (clrHeaderLocation.VirtualAddress != 0);
      if(isManaged)
      {
        //find out which section header the clr header lives in
        IMAGE_SECTION_HEADER sectClrHeaderIsIn ={};
        for(int i = 0; i < fileHeader.NumberOfSections; ++i)
        {
          IMAGE_SECTION_HEADER currentSection ={};
          file_stream.read((char*)&currentSection, sizeof(currentSection));
          if(currentSection.VirtualAddress <= clrHeaderLocation.VirtualAddress && currentSection.VirtualAddress + currentSection.SizeOfRawData > clrHeaderLocation.VirtualAddress)
            sectClrHeaderIsIn = currentSection;
        }

        //get the clr header
        file_stream.seekg(sectClrHeaderIsIn.PointerToRawData + (clrHeaderLocation.VirtualAddress - sectClrHeaderIsIn.VirtualAddress), ios::beg);
        file_stream.read((char*)&clrHeader, sizeof(IMAGE_COR20_HEADER));

        toReturn = (clrHeader.Flags & COMIMAGE_FLAGS_ILONLY) > 0;
      }

      file_stream.close();
    }
  }

  return toReturn;
}

Finding the header is relatively (lol) difficult. It’s Relative Virtual Address and Size live in the old COM_DESCRIPTOR entry in the OptionalHeader DataDirectory array. (Not entirely sure what was in here before, or if it was ever used.) The address stored in here is not relative to ImageBase, but to the start of the section in which the header lives.

In order to figure out which section header it lives in, we can iterate them all, testing to see which one the headers virtual address is bounded by. This should always be the .text section. After doing this we need to converting the RVA in a physical address by applying the difference between its virtual address and the sections virtual address to the sections raw data.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s