-17-

 

Appendix 2

 

Demystifying ildasm.exe

 

In this chapter we will solve a great mystery for you. We unravel as to how the programmers at Microsoft wrote the Disassembler for IL, 'ildasm.exe'. To write such a program, you need to be familiar with the C programming language. If you are not then understanding the C programs is futile. We will therefore not try to teach you C, but instead, in the next couple of pages we will attempt to teach you the structure of the PE file format under Windows and how ildasm creates its magic.

 

We first created a file b.il that reads as follows:

 

b.il

.assembly mukhi{}

.class public auto ansi zzz extends [mscorlib]System.Object

{

.method public hidebysig static void  vijay() il managed

{

.entrypoint

ldstr "vijay!"

call void [mscorlib]System.Console::WriteLine(class System.String)

ret

}

}

 

We create b.exe and then run ildasm with the following syntax:

 

ildasm /out=a.txt b.exe /All

 

The output created by ildasm is as follows.

 

a.txt

// PE Header:

// Subsystem:                      00000003

// Native entry point address:     0000227e

// Image base:                     00400000

// Section alignment:              00002000

// File alignment:                 00000200

// Stack reserve size:             00100000

// Stack commit size:              00001000

// Directories:                    00000010

// 0        [0       ] address [size] of Export Directory:         

// 2230     [4b      ] address [size] of Import Directory:         

// 0        [0       ] address [size] of Resource Directory:       

// 0        [0       ] address [size] of Exception Directory:      

// 0        [0       ] address [size] of Security Directory:       

// 4000     [c       ] address [size] of Base Relocation Table:    

// 0        [0       ] address [size] of Debug Directory:          

// 0        [0       ] address [size] of Architecture Specific:    

// 0        [0       ] address [size] of Global Pointer:           

// 0        [0       ] address [size] of TLS Directory:            

// 0        [0       ] address [size] of Load Config Directory:    

// 0        [0       ] address [size] of Bound Import Directory:   

// 2000     [8       ] address [size] of Import Address Table:     

// 0        [0       ] address [size] of Delay Load IAT:           

// 2008     [48      ] address [size] of COM+ Header:              

 

// Import Address Table

//     mscoree.dll

//              00002000 Import Address Table

//              0000226e Import Name Table

//              0        time date stamp

//              0        Index of first forwarder reference

//

//                    0  _CorExeMain

 

// Delay Load Import Address Table

// No data.

// CLR Header:

// 72       Header Size

// 2        Major Runtime Version

// 0        Minor Runtime Version

// 1        Flags

// 6000001  Entrypoint Token

// 205c     [1d4     ] address [size] of Metadata Directory:       

// 0        [0       ] address [size] of Resources Directory:      

// 0        [0       ] address [size] of Strong Name Signature:    

// 0        [0       ] address [size] of CodeManager Table:        

// 0        [0       ] address [size] of VTableFixups Directory:   

// 0        [0       ] address [size] of Export Address Table:     

// 0        [0       ] address [size] of Precompile Header:        

// Code Manager Table:

//  default

// VTableFixup Directory:

// No data.

// Export Address Table Jumps:

// No data.

 

.subsystem 0x00000003

.corflags 0x00000001

.assembly extern /*23000001*/ mscorlib

{

  .ver 0:0:0:0

}

.assembly /*20000001*/ mukhi

{

  .ver 0:0:0:0

}

.module b.EXE

// MVID: {DE224FE0-1C36-11D5-A55B-444553540000}

.class /*02000002*/ public auto ansi zzz

       extends [mscorlib/* 23000001 */]System.Object/* 01000001 */

{

  .method /*06000001*/ public hidebysig static

          void vijay() il managed

  // SIG: 00 00 01

  {

    .entrypoint

    // Method begins at RVA 0x2050

    // Code size       11 (0xb)

    .maxstack  8

    IL_0000:  /* 72   | (70)000001       */ ldstr      "vijay!"

    IL_0005:  /* 28   | (0A)000001       */ call       void [mscorlib/* 23000001 */]System.Console/* 01000002 */::WriteLine(class System.String) /* 0A000001 */

    IL_000a:  /* 2A   |                  */ ret

  } // end of method zzz::vijay

} // end of class zzz

 

Let us see as to how ildasm creates such an output. We will start with the simplest C program.

 

a.c

#include <stdio.h>

#include <sys/stat.h>

struct stat st;

main()

{

FILE *fp;

fp = fopen("c:\\il\\b.exe","rb");

fstat(fileno(fp),&st);

printf("%d\n",st.st_size);

}

 

Output

2048

 

We compiled this program, and all the others, by calling the C compiler as follows:

 

>cl a.c

 

This generates an executable file called a.exe.

 

fp is a pointer to a structure called FILE, a structure tag, in the header file stdio.h. The function fopen opens a file b.exe stored in the IL subdirectory, for reading, in the binary mode. The binary mode means all characters are to be treated equally. Thus, when the number 26 is encountered, the file system should not read it as a -1.

Now, to determine the size of the file, we use a function called fstat that accepts two parameters.

 

The first parameter is the file handle returned by the fileno function. This function accepts a higher level file handle i.e. a pointer returned by fopen, and returns an int, signifying the lower level file handle that is returned by the function open.

 

The second parameter to fstat is a structure st, that looks like a structure tag stat, found in the header file sys/stat.h. We pass the address of this structure as the second parameter which the function fstat fills. Using the function printf, we display the member st_size that holds the size of the file b.exe.

 

a.c

#include <stdio.h>

#include <sys/stat.h>

struct stat st;

char *p;

main() {

FILE *fp;

fp = fopen("c:\\il\\b.exe","rb");

fstat(fileno(fp),&st);

p=malloc( st.st_size );

fread(p,st.st_size,1,fp);

printf("%c %c \n",*p,*(p+1));

}

 

Output

M Z

 

In the above program, a function called malloc is used to allocate 2048 bytes of memory. This function returns a pointer to where this memory allocation begins. We store the beginning address of this memory, returned by malloc, in a pointer to a char called p.

 

Now to read the entire file in memory:

 

To accomplish this, we use a function called fread, that requires the starting memory location of the file and the size of the file to be read. We have asked for the entire file.

Thus, the first byte of the file will be stored at the memory location p, the second byte, at memory location p+1 etc. So, using our handy printf function, we display the first two bytes of the file as chars. They happen to be M and Z.

 

Every file under DOS begins with these two numbers. To refresh your memory, a file on disk is made up of numbers ranging from 0 to 255 only, in the same manner in which the computer memory is structured. These two numbers are called the signature of the file. If we change any of these two bytes, the operating system refuses to recognize our file as a valid DOS file.

 

Legend has it that the person at Microsoft who designed the memory management sub system of DOS had the same initials. Why are we talking about DOS when we are programming Windows? The reason for this is that every Windows file is a DOS compatible file.

 

a.c

#include <stdio.h>

#include <sys/stat.h>

#define WORD short

#define LONG long

struct _IMAGE_DOS_HEADER1 {   

    WORD   e_magic; 

    WORD   e_cblp;  

    WORD   e_cp;    

    WORD   e_crlc;  

    WORD   e_cparhdr;

    WORD   e_minalloc;

    WORD   e_maxalloc;

    WORD   e_ss;     

    WORD   e_sp;     

    WORD   e_csum;   

    WORD   e_ip;     

    WORD   e_cs;     

    WORD   e_lfarlc; 

    WORD   e_ovno;   

    WORD   e_res[4];  

    WORD   e_oemid;   

    WORD   e_oeminfo; 

    WORD   e_res2[10];

    LONG   e_lfanew;  

  };

struct _IMAGE_DOS_HEADER1*imagedoshdr;

struct stat st;

char *p,*p1;

char *pe;

main()

{

FILE *fp;

fp = fopen("c:\\il\\b.exe","rb");

fstat(fileno(fp),&st);

p=(char *)malloc(st.st_size);

fread(p,st.st_size,1,fp);

p1 = p;

imagedoshdr =(struct _IMAGE_DOS_HEADER1 *) p1;

printf("Magic No %x \n",imagedoshdr->e_magic);

printf("e_lfanew %x  %d\n",imagedoshdr->e_lfanew,imagedoshdr->e_lfanew);

pe = p+imagedoshdr->e_lfanew;

printf("%c %c %d %d\n",*pe,*(pe+1),*(pe+2),*(pe+3));

}

 

Output

Magic No 5a4d

e_lfanew 80  128

P E 0 0

 

We have initialized another pointer p1 to p, so that we maintain at least one pointer to the starting location of our file in memory. We next create a new pointer, imagedoshdr to a structure tag_IMAGE_DOS_HEADER1 and initialized it to p.

 

A DOS file begins with a structure that looks like _IMAGE_DOS_HEADER1. Two members of this structure are displayed, the fields e_magic, i.e. MZ, and the member e_lfanew. E_lfanew stores a decimal value of 128. This number signifies that starting point of the Windows PE header from the start of the file.

 

We jump 128 bytes and print the next four bytes or the first 4 bytes of a Windows PE file. These are P E 0 0. PE is  the signature of a Windows file. If we change even one byte, Windows will not recognize this file. All files have a unique signature. In a Java class file , the signature is spread over 4 bytes, and in hex, it reads as CA FE BA BE.

 

a.c

#include <windows.h>

#include <stdio.h>

#include <sys/stat.h>

struct _IMAGE_DOS_HEADER *imagedoshdr;

struct stat st;

char *p,*p1;

char *pe;

main()

{

FILE *fp;

fp = fopen("c:\\il\\b.exe","rb");

fstat(fileno(fp),&st);

p=(char *)malloc(st.st_size);

fread(p,st.st_size,1,fp);

p1 = p;

imagedoshdr =(struct _IMAGE_DOS_HEADER *) p1;

printf("Magic No %x \n",imagedoshdr->e_magic);

printf("e_lfanew %x  %d\n",imagedoshdr->e_lfanew,imagedoshdr->e_lfanew);

pe = p+imagedoshdr->e_lfanew;

printf("%c %c %d %d\n",*pe,*(pe+1),*(pe+2),*(pe+3));

}

 

We see the same output as earlier. The reason being that the structure tag _IMAGE_DOS_HEADER is already present in the file Winnt.h that is included by windows.h. Thus the PE file format is documented by Microsoft. The only addition that has to be made while running the c compiler is as follows:

 

cl /nologo  a.c c:\progra~1\micros~1\vc98\lib\uuid.lib

 

a.c

#include <windows.h>

#include <stdio.h>

#include <sys/stat.h>

struct _IMAGE_DOS_HEADER *imagedoshdr;

struct _IMAGE_FILE_HEADER *imagefilehdr;

struct stat st;

char *p,*p1,*p2;

char *pe;

main()

{

FILE *fp;

fp = fopen("c:\\il\\b.exe","rb");

fstat(fileno(fp),&st);

p=(char *)malloc(st.st_size);

fread(p,st.st_size,1,fp);

p1 = p;

imagedoshdr =(struct _IMAGE_DOS_HEADER *) p1;

pe = p+imagedoshdr->e_lfanew;

p2 = pe+4;

imagefilehdr = p2;

printf("Machine %x\n",imagefilehdr->Machine);

printf("Number Of Sections %d\n",imagefilehdr->NumberOfSections);

printf("Pointer To Symbol Table %d\n",imagefilehdr->PointerToSymbolTable);

printf("Number Of Symbols %d\n",imagefilehdr->NumberOfSymbols);

printf("Size Of Optional Header %d\n",imagefilehdr->SizeOfOptionalHeader);

printf("Characteristics %x\n",(unsigned short)imagefilehdr->Characteristics);

}

 

Output

Machine 14c

Number Of Sections 2

Pointer To Symbol Table 0

Number Of Symbols 0

Size Of Optional Header 224

Characteristics 10e

 

The PE file starts with a magic number PE00, that takes up 4 bytes. We need to skip over these 4 bytes. Hence, we add 4 to the variable p. This is the starting position of the structure _IMAGE_FILE_HEADER, from the header file. We created a pointer imagefilehdr to the structure tag and initialized it to the new value of p. Thereafter, the members of this structure are displayed. Our exe file is made up of a large number of entities. Two of them are code and data.

 

Different entities are stored in different places or sections. Our tiny exe file comprises of these two sections.

 

After this header, comes another header, that is optional for an obj file. The size of this header can change, but as of now, it is 224 bytes. This header follows the above header thus proving that our PE file is made up of a series of C structures.

 

a.c

#include <windows.h>

#include <stdio.h>

#include <sys/stat.h>

struct _IMAGE_DOS_HEADER *imagedoshdr;

struct _IMAGE_FILE_HEADER *imagefilehdr;

struct _IMAGE_OPTIONAL_HEADER *imageoptionalhdr;

struct stat st;

char *p,*p1,*p2,*p3;

char *pe;

main()

{

FILE *fp;

fp = fopen("c:\\il\\b.exe","rb");

fstat(fileno(fp),&st);

p=(char *)malloc(st.st_size);

fread(p,st.st_size,1,fp);

p1 = p;

imagedoshdr =(struct _IMAGE_DOS_HEADER *) p1;

pe = p+imagedoshdr->e_lfanew;

p2 = pe+4;

imagefilehdr = p2;

p3 = (long)imagefilehdr+20;

imageoptionalhdr = (struct _IMAGE_OPTIONAL_HEADER *)p3;

printf("Subsystem %d\n",imageoptionalhdr->Subsystem);

printf("Magic                            %x\n",imageoptionalhdr->Magic);

printf("MajorLinkerVersion         %d\n",imageoptionalhdr->MajorLinkerVersion);

printf("MinorLinkerVersion         %d\n",imageoptionalhdr->MinorLinkerVersion);

printf("SizeOfCode                     %d\n",imageoptionalhdr->SizeOfCode);

printf("SizeOfInitializedData       %d\n",imageoptionalhdr->SizeOfInitializedData);

printf("SizeOfUninitializedData %d\n",imageoptionalhdr->SizeOfUninitializedData);

printf("AddressOfEntryPoint       %d %x\n",imageoptionalhdr->AddressOfEntryPoint,imageoptionalhdr-> AddressOfEntryPoint);

printf("BaseOfCode                    %d\n",imageoptionalhdr->BaseOfCode);

printf("BaseOfData                    %d\n",imageoptionalhdr->BaseOfData);

printf("ImageBase                      %d %x\n",imageoptionalhdr->ImageBase,imageoptionalhdr->ImageBase);

printf("SectionAlignment            %d %x\n",imageoptionalhdr->SectionAlignment,imageoptionalhdr->SectionAlignment);

printf("FileAlignment                 %d %x\n",imageoptionalhdr->FileAlignment,imageoptionalhdr->FileAlignment);

printf("MajorOperatingSystemVersion %d\n",imageoptionalhdr->MajorOperatingSystemVersion);

printf("MinorOperatingSystemVersion %d\n",imageoptionalhdr->MinorOperatingSystemVersion);

printf("SizeOfImage                   %d\n",imageoptionalhdr->SizeOfImage);

printf("SizeOfHeaders                %d\n",imageoptionalhdr->SizeOfHeaders);

printf("DllCharacteristics            %x\n",imageoptionalhdr->DllCharacteristics);

printf("LoaderFlags                    %x\n",imageoptionalhdr->LoaderFlags);

printf("NumberOfRvaAndSizes    %d\n",imageoptionalhdr->NumberOfRvaAndSizes);

printf("Stack Reserve %d %x\n",imageoptionalhdr->SizeOfStackReserve,imageoptionalhdr->SizeOfStackReserve);

printf("Stack Commit %d %x\n",imageoptionalhdr->SizeOfStackCommit,imageoptionalhdr->SizeOfStackCommit);

}

 

Output

Subsystem 3

Magic    10b

MajorLinkerVersion 6

MinorLinkerVersion 0

SizeOfCode   1024

SizeOfInitializedData  512

SizeOfUninitializedData 0

AddressOfEntryPoint  8830 227e

BaseOfCode   8192

BaseOfData   16384

ImageBase   4194304 400000

SectionAlignment  8192 2000

FileAlignment   512 200

MajorOperatingSystemVersion 4

MinorOperatingSystemVersion 0

SizeOfImage   24576

SizeOfHeaders   512

DllCharacteristics  0

LoaderFlags   0

NumberOfRvaAndSizes  16

Stack Reserve 1048576 100000

Stack Commit 4096 1000

 

We have now displayed the members of the third structure, _IMAGE_OPTIONAL_HEADER. We are not going to explain all the members. We will focus only those that are present in the ildasm output, and those that are important for our understanding of PE files.

 

The size of the structure _IMAGE_FILE_HEADER is 20 bytes. So we add 20 to imagefilehdr, which has been cast to a long, to get to the address of the third structure. We store it in the pointer imageoptionalhdr. The member Subsystem has a value of 3, which means that it is a Windows executable file. The address of Entry point is the relocation where the first executable instruction starts. The number 8830 is 227e in hex. The image base tells us as to where the program will be loaded in memory by the Windows loader. It has a value of 400000 hex. This means that every program loaded under Windows will start at this memory location.

 

The section alignment means that each section will start in memory at a location that is divisible by 0x2000. Thus, even if the size of the data is 2 bytes, it will take up an entire section i.e. 4098 bytes of memory. The file alignment provides the same functionality for the file stored in memory. The section will take up a minimum 512 bytes of disk space. Then, we have the amount of stack memory to be allocated for our program.

 

a.c

#include <windows.h>

#include <stdio.h>

#include <sys/stat.h>

struct _IMAGE_DOS_HEADER *imagedoshdr;

struct _IMAGE_FILE_HEADER *imagefilehdr;

struct _IMAGE_OPTIONAL_HEADER *imageoptionalhdr;

int i;

char *dirtype[]={

"EXPORT","IMPORT","RESOURCE","EXCEPTION",

"SECURITY","BASERELOC","DEBUG","ARCHITECTURE",

"GLOBALPTR","TLS","LOAD_CONFIG","BOUND_IMPORT",

"IAT","DELAY_IMPORT","COM_DESCRIPTOR"

};

struct stat st;

char *p,*p1,*p2,*p3;

char *pe;

main() {

FILE *fp;

fp = fopen("c:\\il\\b.exe","rb");

fstat(fileno(fp),&st);

p=(char *)malloc(st.st_size);

fread(p,st.st_size,1,fp);

p1 = p;

imagedoshdr =(struct _IMAGE_DOS_HEADER *) p1;

pe = p+imagedoshdr->e_lfanew;

p2 = pe+4;

imagefilehdr = p2;

p3 = (long)imagefilehdr+20;

imageoptionalhdr = (struct _IMAGE_OPTIONAL_HEADER *)p3;

printf("Virtual Address Size   Name         \n");

for(i=0;i<15;i++)

{

printf("%08x        %4d   ", imageoptionalhdr->DataDirectory[i].VirtualAddress ,imageoptionalhdr->DataDirectory[i].Size);

printf("%-15s\n",dirtype[i]);

}

}

Output

Virtual Address Size   Name        

00000000           0   EXPORT        

00002230          75   IMPORT        

00000000           0   RESOURCE      

00000000           0   EXCEPTION     

00000000           0   SECURITY      

00004000          12   BASERELOC     

00000000           0   DEBUG         

00000000           0   ARCHITECTURE  

00000000           0   GLOBALPTR     

00000000           0   TLS           

00000000           0   LOAD_CONFIG   

00000000           0   BOUND_IMPORT  

00002000           8   IAT           

00000000           0   DELAY_IMPORT  

00002008          72   COM_DESCRIPTOR

 

Our pointer imageoptionalhdr is a pointer located at the start of the third header. The last member of this structure is an array of 16 IMAGE_DATA_DIRECTORY structures and is called DataDirectory. This structure has two members, VirtualAddress and Size. Each of these entries tells us the size and the location in memory, i.e. the virtual address, where we can find the respective data for these entities.

 

We prefer to handle numbers in the decimal format, whereas ildasm prefers to handle them in hex. The array member 15 will always store the data for COM_DESCRIPTOR. This has been pre-decided, and that is how the array of pointers dirtype is filled up.

 

a.c

#include <windows.h>

#include <stdio.h>

#include <sys/stat.h>

int i;

char *dirtype[]={

"EXPORT","IMPORT","RESOURCE","EXCEPTION",

"SECURITY","BASERELOC","DEBUG","ARCHITECTURE",

"GLOBALPTR","TLS","LOAD_CONFIG","BOUND_IMPORT",

"IAT","DELAY_IMPORT","COM_DESCRIPTOR"

};

struct _IMAGE_DOS_HEADER *imagedoshdr;

struct _IMAGE_FILE_HEADER *imagefilehdr;

struct _IMAGE_OPTIONAL_HEADER *imageoptionalhdr;

struct _IMAGE_SECTION_HEADER *imagesectionhdr;

struct stat st;

char *p,*p1,*p2,*p3,*p4;

char *pe;

main()

{

FILE *fp;

fp = fopen("c:\\il\\b.exe","rb");

fstat(fileno(fp),&st);

p=(char *)malloc(st.st_size);

fread(p,st.st_size,1,fp);

p1 = p;

imagedoshdr =(struct _IMAGE_DOS_HEADER *) p1;

pe = p+imagedoshdr->e_lfanew;

p2 = pe+4;

imagefilehdr = p2;

p3 = (long)imagefilehdr+20;

imageoptionalhdr = (struct _IMAGE_OPTIONAL_HEADER *)p3;

p4 = p3+224;

imagesectionhdr = p4;

printf("Name     P Addr   V Addr  SzOfRData   PtrToRData ");

printf("PtrToRel   PtrToLinenos NoOfRel NoLinenos  Char\n");

for(i=0; i <imagefilehdr->NumberOfSections;i++ )

{

printf("%-8s %08x %08x %-8d    %08x ",imagesectionhdr->Name,imagesectionhdr->Misc.PhysicalAddress,imagesectionhdr->VirtualAddress,

imagesectionhdr->SizeOfRawData,imagesectionhdr->PointerToRawData);

printf(" %08x    %08x   %4d   %4d       %08x \n",imagesectionhdr->PointerToRelocations,

imagesectionhdr->PointerToLinenumbers,imagesectionhdr->NumberOfRelocations,

imagesectionhdr->NumberOfLinenumbers,imagesectionhdr->Characteristics);

imagesectionhdr++;

}

}

Output

Name     P Addr      V Addr     SzOfRData   PtrToRData

PtrToRel     PtrToLinenos  NoOfRel   NoLinenos  Char

 

.text       00000284  00002000 1024          00000200 

00000000   00000000       0             0             60000020

 

.reloc     0000000c   00004000  512           00000600 

00000000   00000000       0             0             42000040

 

We are now displaying the sections of the file. It is in these sections that data of the file is stored. The sections start immediately after the Optional Header. We have a structure that represents each section. If you recall, we have two sections as specified by the second header. We display the members of each section along with the name of the section. The name always starts with a dot. The section .text is where the code resides. We are then provided with the memory location and size of the section.

 

a.c

#include <windows.h>

#include <stdio.h>

#include <sys/stat.h>

int i,j;

struct _IMAGE_DOS_HEADER *imagedoshdr;

struct _IMAGE_FILE_HEADER *imagefilehdr;

struct _IMAGE_OPTIONAL_HEADER *imageoptionalhdr;

struct stat st;

char *p,*p1,*p2,*p3,*pe;

struct complus

{

int size;

short major;

short minor;

long maddr,msize;

long flags,token;

long raddr,rsize;

long saddr,ssize;

long caddr,csize;

long vaddr,vsize;

long eaddr,esize;

long paddr,psize;

};

struct complus *a;

main()

{

FILE *fp;

fp = fopen("c:\\il\\b.exe","rb");

fstat(fileno(fp),&st);

p=(char *)malloc(st.st_size);

fread(p,st.st_size,1,fp);

p1 = p;

imagedoshdr =(struct _IMAGE_DOS_HEADER *) p1;

pe = p+imagedoshdr->e_lfanew;

p2 = pe+4;

imagefilehdr = p2;

p3 = (long)imagefilehdr+20;

imageoptionalhdr = (struct _IMAGE_OPTIONAL_HEADER *)p3;

i = imageoptionalhdr->DataDirectory[14].VirtualAddress%0x2000;

i = i + 512;

p = p + i;

a = p;

printf("%d Header size\n",a->size);

printf("%d\ Major version n",a->major);

printf("%d Minor version \n",a->minor);

printf("%ld Flags \n",a->flags);

printf("%x Token \n",a->token);

printf("%x %x MetaData\n",a->maddr,a->msize);

 

printf("%x %x Resources\n",a->raddr,a->rsize);

printf("%x %x Strong Name\n",a->saddr,a->ssize);

printf("%x %x Code MAnager\n",a->caddr,a->csize);

printf("%x %x Vtable Fixups\n",a->vaddr,a->csize);

printf("%x %x Export Address Table\n",a->eaddr,a->esize);

printf("%x %x PreCompile Header\n",a->paddr,a->psize);

}

 

Output

72 Header size

2 Major version n0 Minor version

1 Flags

6000001 Token

205c 1d4 MetaData

0 0 Resources

0 0 Strong Name

0 0 Code MAnager

0 0 Vtable Fixups

0 0 Export Address Table

0 0 PreCompile Header

 

Now to display the COM+ header. The address of the starting location of this header is given in the 14th member of the DataDirectory Array. This value tells us as to where in memory the COM+ header starts. Here, it happens to be 0x2000.

 

Unfortunately, we have loaded our exe file in memory using malloc, and not the mmap series of functions. Thus, we have used a quick albeit messy shortcut. We simply took this virtual address and got the remainder after dividing it by 0x2000 because the section alignment in memory is 0x2000. This remainder is added to 512, because the file section alignment is 512. Thus we arrive at the number 520. This is the offset from the start of the file for the location of the COM+ header.

 

We have created a structure that maps a COM+ header and simply displays the members. The COM+ structure starts with the width of the structure, i.e. 72, followed by the version number. Then, it tells us the starting location of the metadata in memory. The concept of metadata is one of the linchpins of the .NET world. Then, there is a flags member, followed by a series of other directory entries.

 

a.c

#include <windows.h>

#include <stdio.h>

#include <sys/stat.h>

int i,j;

struct _IMAGE_DOS_HEADER *imagedoshdr;

struct _IMAGE_FILE_HEADER *imagefilehdr;

struct _IMAGE_OPTIONAL_HEADER *imageoptionalhdr;

struct stat st;

char *p,*p1,*p2,*p3,*pe, *p4;

struct _IMAGE_IMPORT_DESCRIPTOR *a;

long *b;short *b1;

main()

{

FILE *fp;

fp = fopen("c:\\il\\b.exe","rb");

fstat(fileno(fp),&st);

p=(char *)malloc(st.st_size);

fread(p,st.st_size,1,fp);

p1 = p;

imagedoshdr =(struct _IMAGE_DOS_HEADER *) p1;

pe = p+imagedoshdr->e_lfanew;

p2 = pe+4;

imagefilehdr = p2;

p3 = (long)imagefilehdr+20;

imageoptionalhdr = (struct _IMAGE_OPTIONAL_HEADER *)p3;

i = imageoptionalhdr->DataDirectory[1].VirtualAddress%0x2000;

printf("%x\n",i);

i = i + 512;

p = p + i;

a = p;

printf("%08x Import Address Table\n",a->FirstThunk);

printf("%08x Import Name Table\n",a->Name);

printf("%x time date stamp\n",a->TimeDateStamp);

printf("%x Index of first Forwarder reference\n",a->ForwarderChain);

printf("%08x Characteristics\n",a->Characteristics%0x2000);

p4 = p1 + 512 + a->Name%0x2000;

printf("Name %s\n",p4);

p4 = p1 + 512 + a->Characteristics %0x2000;

b = p4;

printf("Address %x\n",*b);

printf("Address %x\n",*b%0x2000);

p4 = p1 + 512 + *b%0x2000;

b1 = p4;

printf("Hint %d\n", *b1);

b1++;

printf("%s\n",b1);

}

 

Output

230

00002000 Import Address Table

0000226e Import Name Table

0 time date stamp

0 Index of first Forwarder reference

00000258 Characteristics

Name mscoree.dll

Address 2260

Address 260

Hint 0

_CorExeMain

 

We finally display the Import Address Table. We have written this code in such an unstructured manner, knowing the purists of programming conventions will throw a fit and ask for this book to be banned.

 

The second member of the DataDirectory array tells us as to where the Import directory starts. We store this value in a variable called i after taking the remainder. Microsoft devised the PE file format in such a way that retrieving information at runtime would be a breeze. Thus, the first section would be loaded at memory location 0x2000 hex from the start of the base. Thus, at runtime, we will find this Import directory at memory location 0x2300. Since we have used malloc, we add 512 to obtain the starting address of the Import table. This gives us the starting memory location of the structure  IMAGE_IMPORT_DESCRIPTOR, whose members are then displayed. The member name is a RVA, or a relative virtual address.

 

We do the same computation for Name to obtain another memory location that tells us the name of the dll that we are importing from. The member Characteristics is an RVA. We obtain a memory location that points to another RVA. This points to a structure where the first member is a short for the hint and the second member is a NULL terminated string containing the name of the function in the dll.

 

This is definitely not a pretty looking program, but it works well to prove the concept. We can use loops to make the program generic.

 

a.c

#include <windows.h>

#include <stdio.h>

#include <sys/stat.h>

int i;

struct _IMAGE_DOS_HEADER *imagedoshdr;

struct _IMAGE_FILE_HEADER *imagefilehdr;

struct _IMAGE_OPTIONAL_HEADER *imageoptionalhdr;

struct _IMAGE_SECTION_HEADER *imagesectionhdr;

struct stat st;

char *p,*p1,*p2,*p3,*p4,*p5;

char *pe;

main()

{

FILE *fp;

fp = fopen("c:\\il\\b.exe","rb");

fstat(fileno(fp),&st);

p=(char *)malloc(st.st_size);

fread(p,st.st_size,1,fp);

p1 = p;

imagedoshdr =(struct _IMAGE_DOS_HEADER *) p1;

pe = p+imagedoshdr->e_lfanew;

p2 = pe+4;

imagefilehdr = p2;

p3 = (long)imagefilehdr+20;

imageoptionalhdr = (struct _IMAGE_OPTIONAL_HEADER *)p3;

printf("%x\n",imageoptionalhdr->AddressOfEntryPoint);

p4 = p1 + 512 + imageoptionalhdr->AddressOfEntryPoint%0x2000;

for(i=0; i<7 ; i++)

printf("%x ",(unsigned char)p4[i]);

printf("\n");

}

 

Output

227e

ff 25 0 20 40 0 0

 

The last program in this series displays the initial bytes of the first function to be called. This function begins at RVA 0x227e and we simply jump to that location in our program and display the value of the initial bytes. An ff 25 is a jump instruction in the Intel Assembler. Thus, a disassembler would convert these bytes to a jump, followed by the memory location to jump to. The documentation very clearly states that the op code for ldstr is 0x72 hex. The IL code and metadata is thus stored in a PE file within a section. This completes our exploration into PE files.