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*)¤tSection, 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.