One of the test apps that I am updating needs to determine what type of assembly a particular file is (I386, Itanium, x64, etc.) and also to determine whether a file is managed or not. I talked to Josh Willams, a dev on the 64-bit team, and he pointed me to checking out the file header with a myriad of complicated structures. All the articles I saw on the web was in unmanaged code, I thought it would be fun to explore this from a managed perspective. So, the first thing I did was to scour the web to find out how a pe file header is laid out. There was a great jpg at http://jfmasmtuts.blowsearch.ws/Ch2/peheader2.jpg but sadly, it is no longer there.

The first 64 bytes is for the DOS Header. I put the original unmanaged structure from the winnt.h file on the left, and then translated into managed on the right (see the topic in msdn "Platform Invoke data types" on how to translate unmanaged types into managed types).

Unmanaged Managed

typedef struct _IMAGE_DOS_HEADER
{

    WORD e_magic;

// Magic number

    WORD e_cblp;

// Bytes on last page of file

    WORD e_cp;

// Pages in file

    WORD e_crlc;

// Relocations

    WORD e_cparhdr;

// Size of header in paragraphs

    WORD e_minalloc;

// Minimum extra paragraphs needed

    WORD e_maxalloc;

// Maximum extra paragraphs needed

    WORD e_ss;

// Initial (relative) SS value

    WORD e_sp;

// Initial SP value

    WORD e_csum;

// Checksum

    WORD e_ip;

// Initial IP value

    WORD e_cs;

// Initial (relative) CS value

    WORD e_lfarlc;

// File address of relocation table

    WORD e_ovno;

// Overlay number

    WORD e_res[4];

// Reserved words

    WORD e_oemid;

// OEM identifier (for e_oeminfo)

    WORD e_oeminfo;

// OEM information; e_oemid specific

    WORD e_res2[10];

// Reserved words

    LONG e_lfanew;

// File address of new exe header

} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

public struct IMAGE_DOS_HEADER
{
    public ushort e_magic;
    public ushort e_cblp;
    public ushort e_cp;
    public ushort e_crlc;
    public ushort e_cparhdr;
    public ushort e_minalloc;
    public ushort e_maxalloc;
    public ushort e_ss;
    public ushort e_sp;
    public ushort e_csum;
    public ushort e_ip;
    public ushort e_cs;
    public ushort e_lfarlc;
    public ushort e_ovno;
    public ushort[] e_res;
    public ushort e_oemid;
    public ushort e_oeminfo;
    public ushort[] d_res2;
    public int e_lfanew;
}


The last item in the structure, e_lfanew, is the offset that tells us where to find the next structure that we need, IMAGE_NT_HEADERS/IMAGE_NT_HEADERS64. 64-bit machines have a slightly different structure, and since I want my program to be able to view the header of both 32 or 64 bit assemblies, I need to declare both structures.

Unmanaged Managed

typedef struct _IMAGE_NT_HEADERS
{
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

public struct IMAGE_NT_HEADERS32
{
    public uint Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
}

typedef struct _IMAGE_NT_HEADERS64 {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

public struct IMAGE_NT_HEADERS64
{
    public uint Signature;
    public IMAGE_FILE_HEADER FileHeader;
    public IMAGE_OPTIONAL_HEADER64 OptionalHeader;
}


Now, you're probably looking at the next three that we need, IMAGE_FILE_HEADER and IMAGE_OPTIONAL_HEADER32/IMAGE_OPTIONAL_HEADER64. IMAGE_FILE_HEADER will tell us what type of machine it is. IMAGE_OPTIONAL_HEADER will tell us if it is a managed file or not.

Unmanaged Managed

typedef struct _IMAGE_FILE_HEADER
{
    WORD Machine;
    WORD NumberOfSections;
    DWORD TimeDateStamp;
    DWORD PointerToSymbolTable;
    DWORD NumberOfSymbols;
    WORD SizeOfOptionalHeader;
    WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

public struct IMAGE_FILE_HEADER
{
    public ushort Machine;
    public ushort NumberOfSections;
    public ulong TimeDateStamp;
    public ulong PointerToSymbolTable;
    public ulong NumberOfSymbols;
    public ushort SizeOfOptionalHeader;
    public ushort Characteristics;
}

typedef struct _IMAGE_OPTIONAL_HEADER
{
    WORD Magic;
    BYTE MajorLinkerVersion;
    BYTE MinorLinkerVersion;
    DWORD SizeOfCode;
    DWORD SizeOfInitializedData;
    DWORD SizeOfUninitializedData;
    DWORD AddressOfEntryPoint;
    DWORD BaseOfCode;
    DWORD BaseOfData;
    DWORD ImageBase;
    DWORD SectionAlignment;
    DWORD FileAlignment;
    WORD MajorOperatingSystemVersion;
    WORD MinorOperatingSystemVersion;
    WORD MajorImageVersion;
    WORD MinorImageVersion;
    WORD MajorSubsystemVersion;
    WORD MinorSubsystemVersion;
    DWORD Win32VersionValue;
    DWORD SizeOfImage;
    DWORD SizeOfHeaders;
    DWORD CheckSum;
    WORD Subsystem;
    WORD DllCharacteristics;
    DWORD SizeOfStackReserve;
    DWORD SizeOfStackCommit;
    DWORD SizeOfHeapReserve;
    DWORD SizeOfHeapCommit;
    DWORD LoaderFlags;
    DWORD NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

public struct IMAGE_OPTIONAL_HEADER32
{
    public ushort Magic;
    public byte MajorLinkerVersion;
    public byte MinorLinkerVersion;
    public uint SizeOfCode;
    public uint SizeOfInitializedData;
    public uint SizeOfUninitializedData;
    public uint AddressOfEntryPoint;
    public uint BaseOfCode;
    public uint BaseOfData;
    public uint ImageBase;
    public uint SectionAlignment;
    public uint FileAlignment;
    public ushort MajorOperatingSystemVersion;
    public ushort MinorOperatingSystemVersion;
    public ushort MajorImageVersion;
    public ushort MinorImageVersion;
    public ushort MajorSubsystemVersion;
    public ushort MinorSubsystemVersion;
    public uint Win32VersionValue;
    public uint SizeOfImage;
    public uint SizeOfHeaders;
    public uint CheckSum;
    public ushort Subsystem;
    public ushort DllCharacteristics;
    public uint SizeOfStackReserve;
    public uint SizeOfStackCommit;
    public uint SizeOfHeapReserve;
    public uint SizeOfHeapCommit;
    public uint LoaderFlags;
    public uint NumberOfRvaAndSizes;
    public IMAGE_DATA_DIRECTORY[] DataDirectory;
}

typedef struct _IMAGE_OPTIONAL_HEADER64
{
    WORD Magic;
    BYTE MajorLinkerVersion;
    BYTE MinorLinkerVersion;
    DWORD SizeOfCode;
    DWORD SizeOfInitializedData;
    DWORD SizeOfUninitializedData;
    DWORD AddressOfEntryPoint;
    DWORD BaseOfCode;
    ULONGLONG ImageBase;
    DWORD SectionAlignment;
    DWORD FileAlignment;
    WORD MajorOperatingSystemVersion;
    WORD MinorOperatingSystemVersion;
    WORD MajorImageVersion;
    WORD MinorImageVersion;
    WORD MajorSubsystemVersion;
    WORD MinorSubsystemVersion;
    DWORD Win32VersionValue;
    DWORD SizeOfImage;
    DWORD SizeOfHeaders;
    DWORD CheckSum;
    WORD Subsystem;
    WORD DllCharacteristics;
    ULONGLONG SizeOfStackReserve;
    ULONGLONG SizeOfStackCommit;
    ULONGLONG SizeOfHeapReserve;
    ULONGLONG SizeOfHeapCommit;
    DWORD LoaderFlags;
    DWORD NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

public struct IMAGE_OPTIONAL_HEADER64
{
    public ushort Magic;
    public byte MajorLinkerVersion;
    public byte MinorLinkerVersion;
    public uint SizeOfCode;
    public uint SizeOfInitializedData;
    public uint SizeOfUninitializedData;
    public uint AddressOfEntryPoint;
    public uint BaseOfCode;
    public UInt64 ImageBase;
    public uint SectionAlignment;
    public uint FileAlignment;
    public ushort MajorOperatingSystemVersion;
    public ushort MinorOperatingSystemVersion;
    public ushort MajorImageVersion;
    public ushort MinorImageVersion;
    public ushort MajorSubsystemVersion;
    public ushort MinorSubsystemVersion;
    public uint Win32VersionValue;
    public uint SizeOfImage;
    public uint SizeOfHeaders;
    public uint CheckSum;
    public ushort Subsystem;
    public ushort DllCharacteristics;
    public UInt64 SizeOfStackReserve;
    public UInt64 SizeOfStackCommit;
    public UInt64 SizeOfHeapReserve;
    public UInt64 SizeOfHeapCommit;
    public uint LoaderFlags;
    public uint NumberOfRvaAndSizes;
    public IMAGE_DATA_DIRECTORY[] DataDirectory;
}


ok, and now the final one, IMAGE_DATA_DIRECTORY.

Unmanaged Managed

typedef struct _IMAGE_DATA_DIRECTORY
{
    DWORD VirtualAddress;
    DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

public struct IMAGE_DATA_DIRECTORY
{
    public uint VirtualAddress;
    public uint Size;
}

Ok, now that we got all that structure business out of the way, we can move forward on to actually reading the file and figuring out how to get the data into these new fangled structures. The first thing we need to do is crack the file open and read the first few bytes.

// Create a byte array to hold the information.
byte[] Data = new byte[4096];

// Open the file
FileInfo m_FileInfo = new FileInfo("foo.exe");
FileStream fin = m_FileInfo.Open(FileMode.Open, FileAccess.Read);

// Put in the first 4k of the file into our byte array
int iRead = fin.Read(Data, 0, 4096);

// Flush any buffered data and close (we don't need to read anymore)
fin.Flush();
fin.Close();

OK, we got our header now, what the hell do we do with it? We need to push it into the structures we created earlier, so it calls for some pointers, unsafe code and pinning!!! Ooooohhhhh...

To make things easier, I created a enum called MachineType and set it to the following values (the values are in winnt.h under IMAGE_FILE_MACHINE_XXXX):

public enum MachineType
{
    Native = 0,
    I386 = 0x014c,
    Itanium = 0x0200,
    x64 = 0x8664
}

The unsafe keyword denotes an unsafe context, which is required for any operation involving pointers. You also need to be sure to declare "/unsafe" when compiling. In VS, go into the Configuration Properties of the project, and set "Allow Unsafe Code Blocks" to true.

unsafe
{
    // The fixed statement prevents relocation of a variable
    // by the garbage collector. It pins the location of the Data object
    // in memory so that they will not be moved. The objects will
    // be unpinned when the fixed block completes. In other words,
    // The gosh darn efficient garbage collector is always working
    // and always moving stuff around. We need to tell him to keep
    // off our property while we're working with it.

    fixed(byte *p_Data = Data)
    {
        // Get the first 64 bytes and turn it into a IMAGE_DOS_HEADER
        IMAGE_DOS_HEADER *idh = (IMAGE_DOS_HEADER *)p_Data;

        // Now that we have the DOS header, we can get the offset
        // (e_lfanew) add it to the original address (p_Data) and
        // squeeze those bytes into a IMAGE_NT_HEADERS32 structure
        // (I'll talk about the 64 bit stuff in a bit

        IMAGE_NT_HEADERS32 *inhs = (IMAGE_NT_HEADERS32 *)(idh->e_lfanew + p_Data);

        // Now that we have the NT_HEADERS, let's get what kind of
        // machine it's build for. I cast it into my enum MachineType.

        MachineType m_MachineType = (MachineType)inhs->FileHeader.Machine;
    }
}

I had a lot of problems trying to figure out why this bugger wasn't compiling. I kept getting a "Cannot take the address or size of a variable of a managed type" error. At first, I thought something was wrong with my pointer code. But then, I did a search in msdn, and found that a pointer is not permitted to point to a struct that contains references (arrays). So, in order to work around this, I removed the array, and placed each ushort individually in it's place. So, the array now looked like:

public struct IMAGE_DOS_HEADER
{
    ...
    public ushort e_ovno;
    public ushort e_res1;
    public ushort e_res2;
    public ushort e_res3;
    public ushort e_res4;
    public ushort e_oemid;
    public ushort e_oeminfo;
    public ushort d_res5;
    public ushort d_res6;
    public ushort d_res7;
    public ushort d_res8;
    public ushort d_res9;
    public ushort d_res10;
    public ushort d_res11;
    public ushort d_res12;
    public ushort d_res13;
    public ushort d_res14;
    public int e_lfanew;
}

This struct sure is getting big. In my research, I found that there is also an alternative! Since we really don't care for the rest of the variables, I can make use of the StructLayout attribute and point explicitly to what I need. So, in essence, I could change that struct to look like this:

[ StructLayout( LayoutKind.Explicit )]
public struct IMAGE_DOS_HEADER
{
    [FieldOffset(60)]public int e_lfanew;
}

and it would give me only what I need. The StructLayout attribute allows me to control the physical layout of the data fields of the structure. LayoutKind.Explicit means that the precise position of each member of an object is explicitly controlled. The FieldOffsetAttribute(60) indicates the position of that field within the type. Since an int is 32 bytes, give me the value at bytes 60 - 63. Pretty cool, eh?

So, now it is compiled. Stepping through the code, we see that e_lfanew = 128. Now we go on to get the IMAGE_NT_HEADERS structure. But wait a minute, the Machine value is 51233! That isn't I386 (332)! Something is wrong. After many hours of banging my head, I realized that we need to apply the StructLayout attribute here as well, to guide it through the process. So, I applied it to all of the structures I am working with. It's pretty easy to calculate, start the first one at 0, then add however many bytes to it and that's the next field offset. Here, the IMAGE_FILE_HEADER is 20 bytes:

[ StructLayout( LayoutKind.Explicit )]
public struct IMAGE_NT_HEADERS32
{
    [FieldOffset(0)] public uint Signature;
    [FieldOffset(4)] public IMAGE_FILE_HEADER FileHeader;
    [FieldOffset(24)]public IMAGE_OPTIONAL_HEADER32 OptionalHeader;
}

Now everything's making sense! inhs->FileHeader.Machine = 332!!! We're done with the first objective! Woo hoo! That was a lot of work just to tell you "yeah, this assembly is I386". Whoopee...

Ok, now the next part. We need to find out if the assembly is managed or not. You could do it a couple of different ways. The first way is to load the assembly using Assembly.LoadFile. Remember that you would also have to create it in a separate AppDomain since you can't unload the assembly without unloading the AppDomain. But since we're already in the header, why not just search the header for it?

In order to do this, I had to change the IMAGE_OPTIONAL_HEADER structure. The last item in the structure is a IMAGE_DATA_DIRECTORY array. In the winnt.h file, we see that the CLR information (IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR) is located at the 15th position in the array. Since we found out that we can eliminate all the other "junk" that we don't need by using ExplicitLayout, I changed it to look like:

[ StructLayout( LayoutKind.Explicit )]
public struct IMAGE_OPTIONAL_HEADER32
{
    [FieldOffset(0)] public ushort Magic;
    [FieldOffset(208)]public IMAGE_DATA_DIRECTORY DataDirectory;
}

[ StructLayout( LayoutKind.Explicit )]
public struct IMAGE_OPTIONAL_HEADER64
{
    [FieldOffset(0)] public ushort Magic;
    [FieldOffset(224)]public IMAGE_DATA_DIRECTORY DataDirectory;
}

Now I need to check the DataDirectory field. In order to do that, we need to know which IMAGE_OPTIONAL_HEADER to use (32 or 64 bit). So, I check the OptionalHeader.Magic (since the location is the same on both). If it is 64-bit, then I cast my IMAGE_NT_HEADERS32 to be a pointer to a IMAGE_NT_HEADERS64 structure.

If it is an unmanaged assembly, the DataDirectory.Size field will be 0. Otherwise, if it is a managed assembly, it will be > 0. The code below was added to the "fixed" code above.

         // Here, I used the OptionalHeader.Magic. It tells you whether
         // the assembly is PE32 (0x10b) or PE32+ (0x20b).
         // PE32+ just means 64-bit. So, instead of checking if it is
         // an X64 or Itanium, I just check if it's a PE32+.

         if(inhs->OptionalHeader.Magic == 0x20b)
         {
             // If it is a PE32+, I want to be sure I get the correct Optional
             // Header. I cast it as an IMAGE_NT_HEADERS64 pointer.
             // All you have to do is check the size!

             if(((IMAGE_NT_HEADERS64 *)inhs)->OptionalHeader.DataDirectory.Size > 0)
                 m_IsManaged = true;
         }
         else
         {
             if(inhs->OptionalHeader.DataDirectory.Size > 0)
                 m_IsManaged = true;
         }



This posting is provided "AS IS" with no warranties, and confers no rights.