I wrote a program to test AV engines against encryption. When it runs, it will compute some key, decrypt an executable it has in its data section and copy it to the locations where the executable expects to be. The program has to fill the imports for the payload to run properly.
At first I was filling it from the thunks and it worked fine. Then I downloaded some malware samples and quickly discovered their thunks point to the wrong location, I even checked manually to make sure it wasn't some bug in the code and they really pointed at wrong location in the executable file. So I updated the code to follow the hints table every time it's present and it worked perfectly ever since.
The problem is that I read old Borland compilers didn't include a hints table, and knowing that made me uncomfortable. So I had the code check whether a hints table is present and go with the thunks otherwise.
I'm interested in confirming it's correct for all possible executables assuming you give a location containing an IMAGE_IMPORT_DESCRIPTOR
.
; // https://artfulcode.wordpress.com/
; // https://github.com/cmovz/
; // License: use it as you wish, just keep this notice. No liability taken.
#include <windows.h>
static inline BOOL _is_iid_end(IMAGE_IMPORT_DESCRIPTOR* iid)
{
return !iid->Characteristics && !iid->TimeDateStamp && !iid->ForwarderChain
&& !iid->Name && !iid->FirstThunk;
}
static BOOL _load_from_hints(IMAGE_IMPORT_DESCRIPTOR* iid, DWORD base)
{
HMODULE dll = LoadLibrary((LPCSTR)(base + iid->Name));
if(!dll){
return FALSE;
}
// now get the address of each of the functions in the thunks
DWORD* hints = (DWORD*)(base + iid->Characteristics);
DWORD* thunks = (DWORD*)(base + iid->FirstThunk);
while(*hints){
DWORD r;
if(*hints & 0x80000000){
// handle import by ordinal
r = (DWORD)GetProcAddress(dll, (LPCSTR)(*hints & 0xffff));
}
else {
// handle import by name
IMAGE_IMPORT_BY_NAME* iibn = (IMAGE_IMPORT_BY_NAME*)(base + *hints);
r = (DWORD)GetProcAddress(dll, iibn->Name);
}
if(!r){
return FALSE;
}
*thunks = r;
++thunks;
++hints;
}
return TRUE;
}
// old Borland compilers don't include hints...
static BOOL _load_from_thunks(IMAGE_IMPORT_DESCRIPTOR* iid, DWORD base)
{
HMODULE dll = LoadLibrary((LPCSTR)(base + iid->Name));
if(!dll){
return FALSE;
}
// now get the address of each of the functions in the thunks
DWORD* thunks = (DWORD*)(base + iid->FirstThunk);
while(*thunks){
DWORD r;
if(*thunks & 0x80000000){
// handle import by ordinal
r = (DWORD)GetProcAddress(dll, (LPCSTR)(*thunks & 0xffff));
}
else {
// handle import by name
IMAGE_IMPORT_BY_NAME* iibn = (IMAGE_IMPORT_BY_NAME*)(base + *thunks);
r = (DWORD)GetProcAddress(dll, iibn->Name);
}
if(!r){
return FALSE;
}
*thunks = r;
++thunks;
}
return TRUE;
}
/*
fill_idata() fills the function references in the .idata section. There's no
cleaning up if it fails because it assumes the program won't be able to run
anyway and will have to exit. References to modules are also lost because
the DLLs should be in the address space until the program exits. In short,
it behaves just like the Windows PE loader.
*/
BOOL fill_idata(DWORD* idata, DWORD base)
{
IMAGE_IMPORT_DESCRIPTOR* iid = (IMAGE_IMPORT_DESCRIPTOR*)idata;
while(!_is_iid_end(iid)){
if(iid->Characteristics){
if(!_load_from_hints(iid, base)){
return FALSE;
}
}
else {
if(!_load_from_thunks(iid, base)){
return FALSE;
}
}
++iid;
}
return TRUE;
}
Edit
Some assumptions:
- 32-bit
- well-formed import tables