Problem
Wibo's import resolver walked the Import Lookup Table (ILT, aka Hint/Name Table) to discover the symbol each IAT slot should receive:
uint32_t *lookupTable = fromRVA<uint32_t>(dir->importLookupTable);
uint32_t *addressTable = fromRVA<uint32_t>(dir->importAddressTable);
while (*lookupTable) { ... }
Older PE files — notably the NT 3.5-era MSVC runtimes (MSVCRT20.DLL) —
omit the ILT entirely. Their import directory entry has:
Hint Table (ILT) = 0x00000000
First Thunk (IAT) = 0x0003b02c
The IAT on disk already holds the hint/name RVAs; the Windows loader overwrites them in place with resolved function pointers. Without a separate ILT, the pre-bind data lives only in the IAT.
Symptom
Because dir->importLookupTable == 0, fromRVA<uint32_t>(0) returned imageBase + 0 — a pointer at the MZ header. The resolver then iterated garbage dwords from the DOS stub, "resolved" them as unknown imports, and never touched the real IAT slots.
At runtime the guest executed call dword ptr [IAT_slot], which read the still-unmodified on-disk hint/name RVA and jumped to it — a low linear address, unmapped, producing a SEGV whose RIP matched the VMA of the hint/name entry (e.g. 0x3b300 for GetCommandLineA in MSVCRT20).
Fix
Fall back to the IAT as the lookup source when the ILT is absent. Reads and writes through the same IAT are safe because we capture each entry into a local before overwriting the slot:
uint32_t iltRVA = dir->importLookupTable
? dir->importLookupTable
: dir->importAddressTable;
uint32_t *lookupTable = fromRVA<uint32_t>(iltRVA);
uint32_t *addressTable = fromRVA<uint32_t>(dir->importAddressTable);
This matches the Windows loader's own handling of PEs without a separate ILT and is consistent with the PE/COFF spec, which states the ILT "may be absent" in which case the IAT alone describes the import.
Verified against
MSVCRT20.DLL (Microsoft C/C++ 8.50, NT 3.5 SDK) — loads cleanly.
CL.EXE 8.50.4102 — prints its banner under wibo.
There's also a script to fix individual DLLs: https://gist.github.com/HarryR/d7181d4c637e38f77da8985bfb24ebb0
Problem
Wibo's import resolver walked the Import Lookup Table (ILT, aka Hint/Name Table) to discover the symbol each IAT slot should receive:
Older PE files — notably the NT 3.5-era MSVC runtimes (
MSVCRT20.DLL) —omit the ILT entirely. Their import directory entry has:
The IAT on disk already holds the hint/name RVAs; the Windows loader overwrites them in place with resolved function pointers. Without a separate ILT, the pre-bind data lives only in the IAT.
Symptom
Because
dir->importLookupTable == 0,fromRVA<uint32_t>(0)returnedimageBase + 0— a pointer at the MZ header. The resolver then iterated garbage dwords from the DOS stub, "resolved" them as unknown imports, and never touched the real IAT slots.At runtime the guest executed
call dword ptr [IAT_slot], which read the still-unmodified on-disk hint/name RVA and jumped to it — a low linear address, unmapped, producing a SEGV whose RIP matched the VMA of the hint/name entry (e.g.0x3b300forGetCommandLineAin MSVCRT20).Fix
Fall back to the IAT as the lookup source when the ILT is absent. Reads and writes through the same IAT are safe because we capture each entry into a local before overwriting the slot:
This matches the Windows loader's own handling of PEs without a separate ILT and is consistent with the PE/COFF spec, which states the ILT "may be absent" in which case the IAT alone describes the import.
Verified against
MSVCRT20.DLL(Microsoft C/C++ 8.50, NT 3.5 SDK) — loads cleanly.CL.EXE8.50.4102 — prints its banner under wibo.There's also a script to fix individual DLLs: https://gist.github.com/HarryR/d7181d4c637e38f77da8985bfb24ebb0