First, a couple of clarifications
The Import Address Table is a table that only consists of the imported function's addresses. Unlike the Import Table
(which consists of IMAGE_IMPORT_DESCRIPTOR
s) that also details where to lookup those imports.
To reiterate: (this is slightly oversimplified) The Import Address Table is an array of the imported functions' actual addresses in memory in the current process. Not divided to modules, no extra information like the ordinal or name. Just an array of the addresses of all imported functions.
The IMAGE_IMPORT_DESCRIPTORS
are way more structured, clean and partitioned. They are what enables the loader to find the function it's looking for. The "RVAs of the resolved virtual address" as you called them (There are actually two, Characteristics
and FirstThunk
, in the IMAGE_IMPORT_DESCRIPTOR
structure) is not pointing to the actual imported function in the remote DLL, but to the location in the Import Address Table of the first item in that IMAGE_IMPORT_DESCRIPTOR
. Subsequent items in the same IMAGE_IMPORT_DESCRIPTOR
are also the following items in the Import Address Table.
As the loader goes over the IMPORT_TABLE
and the different IMAGE_IMPORT_DESCRIPTOR
s, it resolves each function and places that function's final address in the function's designated Import Address Table location. FirstThunk
is how the loader knows the Import Address Table location of a function described in IMAGE_IMPORT_DESCRIPTOR
s.
but, why? (AKA answering your questions)
So as you can see, the FirstThunk
only holds one address for multiple imported functions, and it only points to the address (and not holding it directly as your question implies), so we can't easily "use the thunk" instead of the Import Address Table.
As to why you can overwrite the actual content in the IAT for a PE on disk and everything will still work - that was answered implicitly but I'll answer it explicitly as well. The data on disk is the same data as in the Characteristics
structure, the pointers are the same. However that data should not ever be actually read according to Microsoft's own documentation, the IAT is mostly made to be populated during the PE loading process. I would guess the reason it is initialized with the relevant data was kind of a thought of "why the hell not" by the developers.
but still, why?
Now, if you've been following, you should be asking a similar question (and you were, that's what this question is still roughly about):
That structure is still more complicated than it could've been, and the main reason the Import Address Table was created in a condensed manner (instead of being included as addresses in the different IMPORT_NAME
or other similar structures) is actually an optimization.
I'm glad you asked! ;) I'll explain.
As you may know, DLLs are efficiently loaded into the same addresses and use a windows mechanism known as Copy On Write to only actually have a single copy of most Modules. This is also true when Modules are mapped to different Virtual Addresses. However, when the need to populate imported functions for a Module, writes to PE structures are required. No matter where you hold the address, it's gotta be changed. The Import Address Table is there to to only trigger Copy On Write for as few pages as possible (those being the actual Import Address Table pages themselves).
This is, by the way, partially the reason there's that extra level of indirection in the PE to begin with.