Tuesday, February 7, 2017

Reading Structured Binary files in C#: Part 4

Now the fun begins.  The next section is the COFF Optional Header which is variable in size.  We need the value from the previous section to get the size, but we also need the first portion (the Magic Number talked about in Dissecting C# Executables: Part 4) to determine what type of image it is, a PE32 or a PE32+. We could just pull the Magic number and then switch, but both Optional Header variants start with the same Standard Fields, so let's leverage that and just pull them in.  Here is the struct for the COFF Optional Header standard fields:


    [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi, Pack = 1)]
    public struct COFFOptionalHeaderStandardFields
    {
        [FieldOffset(0x0)]
        [MarshalAs(UnmanagedType.U2)]
        public UInt16 Magic;

        [FieldOffset(0x2)]
        [MarshalAs(UnmanagedType.U1)]
        byte MajorLinkerVersion;

        [FieldOffset(0x3)]
        [MarshalAs(UnmanagedType.U1)]
        byte MinorLinkerVersion;

        [FieldOffset(0x4)]
        [MarshalAs(UnmanagedType.U4)]
        UInt32 SizeOfCode;

        [FieldOffset(0x8)]
        [MarshalAs(UnmanagedType.U4)]
        UInt32 SizeOfInitializedData;

        [FieldOffset(0xC)]
        [MarshalAs(UnmanagedType.U4)]
        UInt32 SizeOfUninitializedData;

        [FieldOffset(0x10)]
        [MarshalAs(UnmanagedType.U4)]
        UInt32 AddressOfEntryPoint;

        [FieldOffset(0x14)]
        [MarshalAs(UnmanagedType.U4)]
        UInt32 BaseOfCode;

        public override string ToString()
        {
            StringBuilder returnValue = new StringBuilder();
            returnValue.AppendFormat("Magic: 0x{0:X}", Magic);
            returnValue.AppendLine();
            returnValue.AppendFormat("Magic (decoded): {0}", 
                DecodeMagic(Magic));
            returnValue.AppendLine();
            returnValue.AppendFormat("MajorLinkerVersion: {0}", 
                MajorLinkerVersion);
            returnValue.AppendLine();
            returnValue.AppendFormat("MinorLinkerVersion: {0}", 
                MinorLinkerVersion);
            returnValue.AppendLine();
            returnValue.AppendFormat("SizeOfCode: {0}", SizeOfCode);
            returnValue.AppendLine();
            returnValue.AppendFormat("SizeOfInitializedData: {0}", 
                SizeOfInitializedData);
            returnValue.AppendLine();
            returnValue.AppendFormat("SizeOfUninitializedData: {0}",
                SizeOfUninitializedData);
            returnValue.AppendLine();
            returnValue.AppendFormat("AddressOfEntryPoint: 0x{0:X}",
                AddressOfEntryPoint);
            returnValue.AppendLine();
            returnValue.AppendFormat("BaseOfCode: 0x{0:X}", BaseOfCode);
            returnValue.AppendLine();
            return returnValue.ToString();
        }

        private string DecodeMagic(ushort magic)
        {
            string returnValue = string.Empty;
            switch (magic)
            {
                case 0x10b:
                    returnValue = "PE32";
                    break;
                case 0x20b:
                    returnValue = "PE32+";
                    break;
                default:
                    returnValue = "Undefined";
                    break;
            }
            return returnValue;
        }
    }

Once we read them in, we can determine if we need to pull in the BaseOfCode and which sizes and offsets to use for the Windows-Specific fields.  I am going to cheat a little bit and stash the BaseOfCode with the PE32 Windows-Specific fields rather than pulling it in separately.  The only problem with pulling these fields out separately is that the offsets in the struct won't match up with nicely with the documentation.  It isn't a big deal really, but anything that adds cognitive overhead is to be avoided when possible.  Wait though, I am already deviating pretty heavily because I am giving my offsets in hex rather than decimal.  Nevermind.
The struct for the PE32 Windows-Specific fields section is quite long, so I have included it as a separate page.  I built a PE32+ Windows-Specific struct, but will leave it out here since we aren't going to be using it for any of our examples.  The output now is an impressive length and is:

Signature: MZ?
OffsetToPEHeader: 80

PE Signature: PE

MachineType: 0x14C
MachineType (decoded): IMAGE_FILE_MACHINE_I386
NumberOfSections: 3
TimeDateStamp: 0x58475109
PointerToSymbolTable: 0x0
NumberOfSymbols: 0
SizeOfOptionalHeader: 224
Characteristics: 0x102
Characteristics (decoded): IMAGE_FILE_EXECUTABLE_IMAGE,IMAGE_FILE_32BIT_MACHINE

Magic: 0x10B
Magic (decoded): PE32
MajorLinkerVersion: 8
MinorLinkerVersion: 0
SizeOfCode: 1024
SizeOfInitializedData: 1536
SizeOfUninitializedData: 0
AddressOfEntryPoint: 0x23AE
BaseOfCode: 0x2000

BaseOfData: 0x4000
ImageBase: 0x400000
SectionAlignment: 0x2000
FileAlignment: 0x200
MajorOperatingSystemVersion: 4
MinorOperatingSystemVersion: 0
MajorImageVersion: 0
MinorImageVersion: 0
MajorSubsystemVersion: 4
MinorSubsystemVersion: 0
Win32VersionValue: 0
SizeOfImage: 32768
SizeOfHeaders: 512
CheckSum: 0x0
Subsystem: 0x3
Subsystem (decoded): IMAGE_SUBSYSTEM_WINDOWS_CUI
DllCharacteristics: 0x8540
DllCharacteristics (decoded): IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE,IMAGE_DLLCHA
RACTERISTICS_NX_COMPAT,IMAGE_DLLCHARACTERISTICS_ NO_SEH,IMAGE_DLLCHARACTERISTICS
_TERMINAL_SERVER_AWARE
SizeOfStackReserve: 1048576
SizeOfStackCommit: 4096
SizeOfHeapReserve: 1048576
SizeOfHeapCommit: 4096
LoaderFlags: 0x0
NumberOfRvaAndSizes: 0x10

Press return to exit

This is looking good so far.  Almost caught up with the Dissection articles.  We should sync up by the end of the week and I will run them in parallel after that.
Keep your code clean!

No comments:

Post a Comment