Wednesday, January 4, 2017

Dissecting C# Executables: Part 3

Ok, now that we have a good supply of applications built under different environments, let's compare the binaries that were created.  First, let's compare the first 256 bytes of the Windows command line 2.0, the VS 2.0 and the Xamarin 2.0.  First up, the Windows command line bytes as printed out using the PrintBinaryFile.exe that I talk about in another post:

D:\Source\HelloWorld\CommandLine>PrintBinaryFile.exe HelloWorld_CSC_2.0.exe
00000000   4D 5A 90 00 03 00 00 00  04 00 00 00 FF FF 00 00   MZ?·········ÿÿ··
00000010   B8 00 00 00 00 00 00 00  40 00 00 00 00 00 00 00   ,·······@·······
00000020   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ················
00000030   00 00 00 00 00 00 00 00  00 00 00 00 80 00 00 00   ············?···
00000040   0E 1F BA 0E 00 B4 09 CD  21 B8 01 4C CD 21 54 68   ··º··'·I!,·LI!Th
00000050   69 73 20 70 72 6F 67 72  61 6D 20 63 61 6E 6E 6F   is program canno
00000060   74 20 62 65 20 72 75 6E  20 69 6E 20 44 4F 53 20   t be run in DOS
00000070   6D 6F 64 65 2E 0D 0D 0A  24 00 00 00 00 00 00 00   mode.···$·······
00000080   50 45 00 00 4C 01 03 00  58 5B 6C 58 00 00 00 00   PE··L···X[lX····
00000090   00 00 00 00 E0 00 02 01  0B 01 08 00 00 04 00 00   ····à···········
000000A0   00 06 00 00 00 00 00 00  AE 23 00 00 00 20 00 00   ········r#··· ··
000000B0   00 40 00 00 00 00 40 00  00 20 00 00 00 02 00 00   ·@····@·· ······
000000C0   04 00 00 00 00 00 00 00  04 00 00 00 00 00 00 00   ················
000000D0   00 80 00 00 00 02 00 00  00 00 00 00 03 00 40 85   ·?············@?
000000E0   00 00 10 00 00 10 00 00  00 00 10 00 00 10 00 00   ················
000000F0   00 00 00 00 10 00 00 00  00 00 00 00 00 00 00 00   ················

So, what is all this mess?  Well, looking in the Microsoft Portable Executable and Common ObjectFile Format Specification: Revision 10 (also known as the MS PE COFF spec) I see that the file is laid out starting with an MS-DOS 2.0 Compatible EXE Header as shown below in Figure 1 (from the doc):

MS-DOS 2.0 Compatible
EXE Header

Base of Image Header
unused


OEM Identifier
OEM Information

Offset to
PE Header



MS‑DOS 2.0 Section
(for MS­‑DOS compatibility only)
MS‑DOS 2.0 Stub Program
 and
Relocation Table


unused


PE Header
(aligned on 8-byte boundary)



Section Headers





Image Pages:
import info
export info
base relocations
resource info



Figure 1. Typical Portable EXE File Layout

So this must be the header.  What does it do?  Again, from the document, the MS-DOS stub is just there to detect if it is running in DOS mode and print out "This program cannot be run in DOS mode" if it is.  It ends at 0x3b (that is the HEX address for the 59th byte) and at 0x3C the offset to the PE signature (the 0x80 on the 4th line).

Skipping down to the offset for the PE signature (the 9th line, starting with 00000080) we find the PE  (0x50 0x45 0x00 0x00) that the definition tells us to expect.  Examining all three images, we are 100% identical to here.  Not surprising, as it is all defined for us in the MS PE COFF spec.  After that, we begin to diverge.

The next portion is the COFF File Header.  Let's lay all three out so that we can compare them and then I will disect the lines:

MS Command Line, .NET 2.0:
00000080   50 45 00 00 4C 01 03 00  58 5B 6C 58 00 00 00 00   PE··L···X[lX····

VS 2015, .NET 2.0
00000080   50 45 00 00 4C 01 03 00  43 5B 6C 58 00 00 00 00   PE··L···C[lX····

Xamarin Studio, .NET 2.0
00000080   50 45 00 00 4C 01 03 00  00 00 00 00 00 00 00 00   PE··L···········

Ok, the COFF file header is defined as:
Offset
Size
Field
Description
  0
2
Machine
The number that identifies the type of target machine. For more information, see section 3.3.1, “Machine Types.”
  2
2
NumberOfSections
The number of sections. This indicates the size of the section table, which immediately follows the headers.
  4
4
TimeDateStamp
The low 32 bits of the number of seconds since 00:00 January 1, 1970 (a C run-time time_t value), that indicates when the file was created.
  8
4
PointerToSymbolTable
The file offset of the COFF symbol table, or zero if no COFF symbol table is present. This value should be zero for an image because COFF debugging information is deprecated.
12
4
NumberOfSymbols
The number of entries in the symbol table. This data can be used to locate the string table, which immediately follows the symbol table. This value should be zero for an image because COFF debugging information is deprecated.
16
2
SizeOfOptionalHeader
The size of the optional header, which is required for executable files but not for object files. This value should be zero for an object file. For a description of the header format, see section 3.4, “Optional Header (Image Only).”
18
2
Characteristics
The flags that indicate the attributes of the file. For specific flag values, see section 3.3.2, “Characteristics.”

and starts just after the PE signature.  All three have 0x4C 0x01 which is 0x014C when we see that it is a two byte halfword and represents the Machine type.  If we look in the "Machine Types" table we find:

IMAGE_FILE_MACHINE_I386
0x14c
Intel 386 or later processors and compatible processors

which makes sense.  

The next halfword is 0x03 0x00 or 0x0003 which is the number of sections the file has.  The specification limits us to 96 (or 0x60) sections, so we are only using a fraction of what is available.

The next part is the TimeDateStamp which is the time that the file was created.  Not a shocker that the first two are different, but it is somewhat odd that the Xamarin file leaves this section blank.

The last part of this line is the PointerToSymbolTable which is an offset to the COFF symbol table or zero (as it is in all three) if there is no symbol table present.  The spec mentions that this should always be zero because COFF debugging is deprecated. 

Since I am sure that you are on the edge of your seat excited to finish off the COFF header, I will put the next lines:

MS Command Line, .NET 2.0:
00000090   00 00 00 00 E0 00 02 01  0B 01 08 00 00 04 00 00   ····à···········

VS 2015, .NET 2.0
00000090   00 00 00 00 E0 00 22 00  0B 01 30 00 00 08 00 00   ····à·"···0·····

Xamarin Studio, .NET 2.0
00000090   00 00 00 00 E0 00 02 01  0B 01 08 00 00 06 00 00   ····à···········

We are only going to be looking at the first half, which begins with the NumberOfSymbols in the deprecated symbol table, so again, all 0s.

The next part is 0xE0 0x00 or 0x00E0 which is the SizeOfOptionalHeader.  All three have 244 bytes.

The last part we will look at is the Characteristics which is a flag lookup to another table.  The Command Line and Xamarin versions both have 0x02 0x01 or 0x102 which gives us:
IMAGE_FILE_32BIT_MACHINE
0x0100
Machine is based on a 32-bit-word architecture.
IMAGE_FILE_EXECUTABLE_IMAGE
0x0002
Image only. This indicates that the image file is valid and can be run. If this flag is not set, it indicates a linker error.
Not a surprise.  Let's see what we get when we lookup the 0x22 0x00 or 0x0022 for the Visual Studio 2015 version:
IMAGE_FILE_LARGE_ADDRESS_ AWARE
0x0020
Application can handle > 2‑GB addresses.
IMAGE_FILE_EXECUTABLE_IMAGE
0x0002
Image only. This indicates that the image file is valid and can be run. If this flag is not set, it indicates a linker error.
Note that it doesn't say that it is for a 32 bit machine?  Maybe it is because the VS 2015 version is Any CPU?
JustDecompile Assembly Information for the HelloWorld_VS_2.0 file 
No, the Windows Command line compiled image is also listed as Any CPU:
At this point I don't know why they are different.  I will look into it, unless someone can clue me in in the comments section. 

Thanks for reading!  We will dig deeper into the files and see what else we can find next time.

To start at the beginning of our journey into the depths of the C# executable files, read Dissecting C# Executables.


Part 1
Part 2
Part 3
Part 4
Part 5
Part 6
Part 7
Part 8
Part 9

No comments:

Post a Comment