The location and size of the imported table can be obtained from the data directory Field of the image_optional_header32 structure in the PE file header. The corresponding project is the 2nd image_data_directory structure of the datadirectory field, obtain the RVA value of the import table from the virtualaddress field of the structure.
An import table consists of a series of image_import_descriptor structures. The number of structures depends on the number of DLL files to be used by the program. Each structure corresponds to a DLL file. At the end of all these structures, end with an image_import_descriptor structure whose content is all 0.
The image_import_descriptor structure is defined as follows:
Image_import_descriptor = packed record
Case INTEGER
0: characteristics: DWORD;
1: originalfirstthunk: DWORD;
End;
Timedatestamp: DWORD;
Forwarderchain: DWORD;
Name: DWORD;
Firstthunk: DWORD;
End;
The name field in the structure is an RVA that points to the name of the DLL file corresponding to the structure. The file name is a string ending with null.
Originalfirstthunk and firstthunk fields point to an array containing a series of image_thunk_data structures. Each image_thunk_data structure in the array defines the information of an import function, the end of the array is an image_thunk_data structure with the content 0.
An image_thunk_data structure is actually a dual word. The reason why it is defined as a structure is that it has different meanings at different times. The structure is defined as follows:
Image_thunk_data = record
Case INTEGER
0: forwarderstring: DWORD;
1: function: DWORD;
2: ordinal: DWORD;
3: addressofdata: DWORD;
End;
End;
How can I specify an import function in an image_thunk_data structure? When the maximum bit of a double character (structure) is 1, it indicates that the function is imported as a serial number. At this time, the low bit of the double character is the serial number of the function. When the maximum bit of a double-character is 0, it indicates that the function is imported as a string function name. At this time, the double-character value is an RVA, pointing to a image_import_by_name structure used to define the name of the imported function, the structure is defined as follows:
Image_import_by_name = record
Hint: word;
Name: array [0 .. 0] of char;
End;
The hint field in the structure also indicates the serial number of the function. However, this field is optional. Some compilers always set it to 0. The name field defines the name string of the imported function, this is a string ending with 0.
Why do we need two identical image_thunk_data arrays? The answer is that when the PE file is loaded into the memory, the value of an array will be changed for another use, the Windows loader replaces the RVA at the XXXXXXXX specified in the jmp dword ptr [XXXXXXXX] command with the real function address. In fact, the XXXXXXXX address is a member of the array directed by the firstthunk field.
In fact, after the PE file is loaded into the memory, each double word in the array directed by the firstthunk field is replaced with the real function entry address, in the PE file, we use two copies of the image_thunk_data array and modify one of them so that we can also leave a copy to query the import function name corresponding to the address in turn.