=================================
Structure of Import Symbols table
=================================
A portable executable (PE) file contains
IMAGE_NT_HEADERS
This struct's members are:
DWORD Signature
IMAGE_FILE_HEADER FileHeader
IMAGE_OPTIONAL_HEADER OptionalHeader// not really optional, fyi
Then the IMAGE_OPTIONAL_HEADER struct contains:
//
// Standard fields.
//
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
//
// NT additional fields.
//
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Reserved1;
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];
Then the DataDirectory array :
0'th member:Export Symbols
1'st member:Import Symbols
2'nd member:Resources
...
11'th member:Bound Import
12'th member:Import Address Table
...
The IMAGE_DATA_DIRECTORY struct consists of two fields:
DWORD VirtualAddress;// the RVA of the data structure
DWORD Size;// the size of the data structure
Then if we have a PE that imports two modules, we'll have:
(1) one IMAGE_DATA_DIRECTORY structure for Import Symbols
(2) let's say that struct's VirtualAddress is 0xc7d8 -- recall this is an RVA
(3) then 0xc7d8 is where the first IMAGE_IMPORT_DESCRIPTOR lives
(4) since we are importing two modules, there will be 3 IMAGE_IMPORT_DESCRIPTOR
structs in our array (which starts at 0xc7d8, recall)
--> the 3rd IMAGE_IMPORT_DESCRIPTOR is all zeroes
(5) then the Size field above (for this IMAGE_DATA_DIRECTORY) will be 3 *
sizeof( IMAGE_IMPORT_DESCRIPTOR )
The IMAGE_IMPORT_DESCRIPTOR struct consists of five fields :
Union {
DWORD Characteristics;
PIMAGE_THUNK_DATA OriginalFirstThunk;
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
PIMAGE_THUNK_DATA FirstThunk;
--> Each of these fields is 4 bytes long so sizeof( IMAGE_IMPORT_DESCRIPTOR ) is
20 bytes == 0x1C
--> So the Size field will be 60 bytes == 0x3C
Now let's examine the contents of the Import Symbols table; recall that this table
is -- in this case -- an array of 3 IMAGE_IMPORT_DESCRIPTOR structs.
Now here are some potential values for the fields:
-------------------------------------
// 3rd IMAGE_IMPORT_DESCRIPTOR struct
-------------------------------------
0xc810FirstThunk:0
0xc80cName:0
0xc808ForwarderChain:0
0xc804TimeDateStamp:0
0xc800OriginalFirstThunk:0
-------------------------------------
// 2nd IMAGE_IMPORT_DESCRIPTOR struct
-------------------------------------
0xc7fcFirstThunk:0x8000
0xc7f8Name:0xc398 (the RVA of the name of this module)
0xc7f4ForwarderChain:0
0xc7f0TimeDateStamp:0
0xc7ecOriginalFirstThunk:0xc084
-------------------------------------
// 1st IMAGE_IMPORT_DESCRIPTOR struct
-------------------------------------
0xc7e8FirstThunk:0xc178
0xc7e4Name:0xc2c0 (the RVA of the name of this module)
0xc7e0ForwarderChain:0
0xc7dcTimeDateStamp:0
0xc7d8OriginalFirstThunk:0xc030
All addresses are RVAs.
------------------------------
Now at locations (from above):
------------------------------
0xc2c0contains"MSVCR80.DLL"
0xc398contains"KERNEL32.DLL"
Now OriginalFirstThunk points to the Import Name Table for the module and
FirstThunk points to the Import Address Table for the module.
Each of these two tables (Import Name and Import Address) consist of an
array of IMAGE_THUNK_DATA structs. An IMAGE_THUNK_DATA struct consists of:
Union {
PBYTE ForwarderString;
PDWORD Function;
DWORD Ordinal;
PIMAGE_IMPORT_BY_NAME AddressOfData;
};
In our case the Union member "AddressOfData" will be the member used for
each struct in these two tables. This value (AddressOfData) contains the
RVA for an IMAGE_IMPORT_BY_NAME struct. The contents of an IMAGE_IMPORT_NAME
struct are :
WORD Hint;// recall that WORDs are two bytes each
BYTE Name[1]; // this is NOT really 1 byte long; This is a variable-sized field
For the PE -- before it's loaded -- the Import Name Table and the Import Address
Table are merely two copies of the same table. For example let's say we're
importing twenty or so symbols from MSVCR80.dll then our Import Name Table starts
at 0xc030 (OriginalFirstThunk address above) and our Import Address Table starts
at 0xc178 (FirstThunk address above).
-------------------------------------
So we might have something like this:
-------------------------------------
------------------------------------
// Import Name Table for MSVCR80.dll
------------------------------------
0xc080AddressOfData:0// null terminate table
...
0xc044AddressOfData:0xc300
0xc040AddressOfData:0xc2f8
0xc03cAddressOfData:0xc2ea
0xc038AddressOfData:0xc2e0
0xc034AddressOfData:0xc2d8
0xc030AddressOfData:0xc2cc
---------------------------------------
// Import Address Table for MSVCR80.dll
---------------------------------------
0xc1c8AddressOfData:0// null terminate table
...
0xc18cAddressOfData:0xc300
0xc188AddressOfData:0xc2f8
0xc184AddressOfData:0xc2ea
0xc180AddressOfData:0xc2e0
0xc17cAddressOfData:0xc2d8
0xc178AddressOfData:0xc2cc
Note that the AddressOfData values don't increase by a fixed amount; that's
because the data contained at those addresses is variable length. So what
is the data contained at those addresses?
Starting at 0xc2cc is an array of IMAGE_IMPORT_BY_NAME structs. Each such
struct contains the name of a function (imported by this module which in
this case is MSVCR80.DLL) as well as a "Hint" for this function.
---------------------------------------------------------------------
Continuing the above example, we have the following values in memory:
---------------------------------------------------------------------
...
0xc3122-byte hint for mbstowcs
0xc308"mbstowcs"// null-terminated is 9 bytes long
0xc3062-byte hint for fopen
0xc300"fopen"// null-terminated is 6 bytes long
0xc2fe2-byte hint for atoi
0xc2f8"atoi"// null-terminated is 5 bytes long
0xc2f62-byte hint for _vsnprintf
0xc2ea"_vsnprintf"// null-terminated is 11 bytes long
0xc2e82-byte hint for _strdup
0xc2e0"_strdup"// null-terminated is 8 bytes long
0xc2de2-byte hint for _open
0xc2d8"_open"// null-terminated is 6 bytes long
0xc2d62-byte hint for _memccpy
0xc2cc"_memccpy"// null-terminated is 9 bytes long
0xc2c0"MSVCR80.DLL"// null-terminated is 12 bytes long
So now you see what the AddressOfData values -- contained in the Import Name
Table and the Import Address Table -- refer to. One small detail, when the
null-terminated name of a function is an odd number, then an extra byte must
be added to the name so that the WORD hint field starts on a WORD-aligned
boundary. This is why, for example, the size of the IMAGE_IMPORT_BY_NAME
struct for _memccpy is 12 bytes even though null-terminated "_memccpy" is
9 bytes and the WORD hint adds 2 bytes (total of 11 bytes); that extra byte
is to align the hint on a WORD boundary.
===============================================
So why do we have two copies of the same table?
===============================================
Perceptive question, Watson!
These two tables are used by the PE loader to map the function names to
addresses when loading the PE. Since we don't know at compile time WHERE
the various functions will exist in the address space (since these functions
are part of DLLs), the loader does that resolution for us. In particular
the loader will replace each entry in the Import Address Table (IAT) with
the actual address of the function.
For example, the first entry in the MSVCR80.DLL IAT is 0xc2cc and that
corresponds to "_memccpy". Usually MSVCR80.DLL loads at address 0x00360000,
i.e. that's the base address for this DLL. Moreover the RVA for _memccpy
is 0x49F10. Therefore if MSVCR80.dll is loaded in its usual spot then
the value stored at 0xc178 (which is originally 0xc2cc) will be changed
to ( 0x00360000 + 0x49F10 ) == 0x003a9f10 since that's the address where
the _memccpy code lives in memory at the time this PE is loaded.
Note that because we have *two* copies of this same initial table, after
we replace 0xc2cc in the IAT with the address for _memccpy (as above),
if for any reason we need to go in the other direction (i.e. to know
which function that address corresponds to) we can consult the Import
Name Table at 0xc030 which still contains the RVA 0xc2cc and see that
"_memccpy" is the function for this resolved address.
===========================
From : C:/lcc/include/win.h
===========================
//
// there's one IMAGE_IMPORT_DESCRIPTOR for every DLL that the PE uses
//
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
// points to Import Name Table (INT)
// contains RVA of an array of IMAGE_THUNK_DATA structs
PIMAGE_THUNK_DATA OriginalFirstThunk;
} ;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;// e.g. "USER32.dll"
PIMAGE_THUNK_DATA FirstThunk;// points to IAT
} IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
//
// the IAT is an array of IMAGE_THUNK_DATA structs
//
typedef struct _IMAGE_THUNK_DATA {
union {// is one of the following 4 things
PBYTE ForwarderString;// 4 bytes
PDWORD Function;// 4 bytes
DWORD Ordinal;// 4 bytes
// ------------- is usually this ------------- \PIMAGE_IMPORT_BY_NAME AddressOfData; // 4 bytes
} ;
} IMAGE_THUNK_DATA,*PIMAGE_THUNK_DATA;
//
// as above, usually we interpret an IMAGE_THUNK_DATA struct as a POINTER to an :
// "IMAGE_IMPORT_BY_NAME" struct
//
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;// 2 bytes
// contaisn index into the export table of the DLL
// from which we get (import) this function
BYTE Name[1];// 1 byte; contains name of the import function
// is an ASCIIZ string
} IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME;