1

The Reko decompiler crashes while trying to load the PE delay import directory of a particular binary I'm looking at. For 32-bit executables, the PE spec states that the directory consists of a sequence of records where offset 4 contains:

[the] RVA of the name of the DLL to be loaded. The name resides in the read-only data section of the image (szName)

When I use dumpbin to look at the image, I see that the PE header

      185000 [     2C6] RVA [size] of Delay Import 

And the .didata section's raw data is:

00585000: 00 00 00 00 90 51 58 00 00 00 00 00 A0 50 58 00  .....QX......PX.
00585010: B4 50 58 00 C8 50 58 00 DC 50 58 00 00 00 00 00  �PX.�PX.�PX..... 
(etc)

Notice that at 00585004, the szName field has what looks to me a virtual address (00585190) and not a RVA (which would have been 00185190). Still, dumpbin manages to interpret this as:

USER32.DLL
          00000000 Characteristics
          00000000 Address of HMODULE
          005850A0 Import Address Table
          005850B4 Import Name Table
          005850C8 Bound Import Name Table
          005850DC Unload Import Name Table
                 0 time date stamp

where it follows the 00585190 to find the string USER32.DLL.

So how should the entries in the Delay Import Directory be interpreted? Should a PE loader first attempt to read the szName field as an RVA, and only when it discovers that it isn't a valid RVA, attempt to read it as VA?

Note that for small EXE files, which get loaded at address 0x0040000, the range of valid RVA's will be [0x00000000..MAX_RVA) while the range of valid virtual addresses will be [0x00400000..MAX_RVA + 0x00400000], so RVA's and VA's could theoretically be distinguished by looking at their numerical values. But once the binary size exceeds 0x00400000 bytes (4194304 bytes) these ranges overlap and you can't tell the difference anymore.

Update: Interestingly, many PE viewers and editors crash or go off the rails on this binary. Dumpbin, IDA, and -- most significantly -- the Windows loader don't crash. Wonder what algorithm they are using to avoid dying on this binary?

  • I know it's been a while. I found this thru Google. I'm trying to see if my software that deals with PE format can handle a binary file that you described. Can you please upload it to some file sharing service and give us a link? It'd be very helpful. Thanks. – c00000fd Feb 27 '19 at 18:51
3

Delayed imports are not processed by the system loader, so the programmer can put into it any kind of data, as long as they're prepared to handle it. By convention (mostly because Visual C++ did it), delayed imports are expected to use the same format as "normal" imports, but since this is not enforced by the OS it's not a requirement, and a specific program can use its own format or put any garbage into it.

IIRC the issue with szName comes from the first implementation of the delayed imports (VC 6.0) which by mistake used full addresses instead of RVAs. This can be seen in the source code of the delayed import helper shipped with Visual C++ (delayhlp.cpp):

// For our own internal use, we convert to the old
// format for convenience.
//
struct InternalImgDelayDescr {
    DWORD           grAttrs;        // attributes
    LPCSTR          szName;         // pointer to dll name
    HMODULE *       phmod;          // address of module handle
    PImgThunkData   pIAT;           // address of the IAT
    PCImgThunkData  pINT;           // address of the INT
    PCImgThunkData  pBoundIAT;      // address of the optional bound IAT
    PCImgThunkData  pUnloadIAT;     // address of optional copy of original IAT
    DWORD           dwTimeStamp;    // 0 if not bound,
                                    // O.W. date/time stamp of DLL bound to (Old BIND)
    };

(note how it says "old format" and most field, including szName, are full pointers and not RVAs).

The issue is also mentioned in the MSDN article Changes in the DLL Delayed Loading Helper Function Since Visual C++ 6.0:

Since the pointers in the delay descriptor (ImgDelayDescr in delayimp.h) have been changed from absolute addresses (VAs) to relative addresses (RVAs) to work as expected in both 32- and 64-bit programs, you need to convert these back to pointers. A new function has been introduced: PFromRva, found in delayhlp.cpp. You can use this function on each of the fields in the descriptor to convert them back to either 32- or 64-bit pointers. The default delay load helper function continues to be a good template to use as an example.

If you open the above-mentioned header, you can see this definition:

enum DLAttr {                   // Delay Load Attributes
    dlattrRva = 0x1,                // RVAs are used instead of pointers
                                    // Having this set indicates a VC7.0
                                    // and above delay load descriptor.
    };

This is how IDA detects the correct format of delayed imports (absolute addresses in your case) and can handle them without crashing.

  • Thanks for the clarifications. It seems my copy of Microsoft's documentation of the delay import directory is incomplete; it makes no mention of the delay load attribute you mention above. – John Källén Sep 5 '17 at 10:07
  • the whole thing should be documented as "implementation defined, but here's how Visual C++ does it". The OS does not care about it. – Igor Skochinsky Sep 5 '17 at 10:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.