-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.