Introduction

It is often very useful to know which DLLs and which functions are imported by an executable. The following program will show one way to access this information.

First of all, the PE (Portable Executable) is a specification of a format defined by Microsoft for executables, DLLs, device drivers... which is derived from the COFF (Common Object File Format) format of Unix. For detailed information about the PE header, you can refer to the official PECOFF specification of Microsoft. Apart from the code itself, a PE executable contains various headers which provide essential information to the OS in order to properly load the code in memory. A wrongly built PE header will, most of the time, result in a program that can not be started. PE64dump is a 64 bit program that can dump executables in 64 bit format. For this purpose, it loads a given executable in memory and reads the different fields of the PE header.

Using the code

PE64Dump does not include any GUI, it simply saves the dump into a text file. So first of all, the program opens two dialog boxes to let the user choose which program to dump and in which text file the dump has to be saved. Different methods would be possible to load the code in memory, by directly accessing the file on the hard drive every time we need an information, reading it into memory, or mapping it using the memory mapped file functions of WinAPI. PE64Dump uses the second method as it is quite simple, reliable, and fast.

The code

After having checked that we really deal with an x64 PE, we retrieve the relative virtual address of the import table. To do this, we must analyze the last parameter of IMAGE_OPTIONAL_HEADER defined in Winnt.h. It looks like this:

IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];

IMAGE_DATA_DIRECTORY is a structure of two parameters: one virtual address and one size. As IMAGE_NUMBEROF_DIRECTORY_ENTRIES is equal to 16, it means that the end of the optional header is made up by 16 data directories. Each DataDirectory contains important data such as the export table (functions exported by a program), the import table (functions imported by the program), resource tables, Thread Local Storage table... So by putting this line of code, we read the virtual address of the import table:

OptPEheader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress

Why is this address virtual? Because it is the address in memory when the OS has loaded the code, and not the offset of the data on the hard drive file. This difference is very important, and if we missed that, we would not be able to go further. To be more precise, all addresses are relative, i.e., they are an offset to the beginning of the code in memory. So our next step will be to translate this virtual address into a real address. This is the aim of the function VAtoFileOffset. This function takes two parameters as input: the address of the structure IMAGE_NT_HEADERS, and the virtual address we want to convert. The address of IMAGE_NT_HEADERS will lead us to the section headers. We must iterate on each section header to check if the virtual address in the input is contained inside. If yes, then we just have to read the raw address of the beginning of the section and we can derive the actual address of the import table with the following formula:

dwFileOffset = dw_va - ImgSectionHdr1->VirtualAddress + 
               ImgSectionHdr1->PointerToRawData;

This is the actual address of a structure called IMAGE_IMPORT_DESCRIPTOR (all elements of this structure are defined in Winnt.h as well). Each IMAGE_IMPORT_DESCRIPTOR corresponds to one DLL; the structures are put after each other, the last one being filled with 0 to indicate the end of data.

The first important field of the structure is the Name field, it is actually the virtual address of the name of the imported DLL. So again, we use our conversion function to read the actual address in the file from this VA. We simply have to iterate on each of these structures to get all DLLs imported by the exe.

The second important field is the OriginalFirstThunk (OFT); it is the RVA of the first function imported by the program. It is important to notice that we do not use the FirstThunk field of the structure. Actually, the FirstThunk and the OFT both point to the same function inside the exe. So why use this one rather than the other? It is because when the executable is bound, the virtual address of the functions in the executable are overwritten by the actual address of the functions in memory. This drastically reduces the loading time of the executable, but it is then impossible to retrieve the address of the functions hard coded in the exe. For this reason, we will only work with the OFT.

The next thing to check is if we deal with an import by ordinal or an import by name. If the Most Significant Bit of the OFT RVA is set, then it means the import is done by ordinal. We use the macro available in winnt.h:

if (IMAGE_SNAP_BY_ORDINAL(*pdw_OFT) == 1){
//....

If it is the case, we simply write the string "import by ordinal" in our dump, and we loop to the next DLL. If we deal with an import by name, then we go on and dump the functions. The OFT points to the structure IMAGE_THUNK_DATA64 which contains the information we are looking for. The AddressOfData field of the union u1 is the RVA of the imported function. So me proceed like for DLLs by looping until we find 0, which means we reached the last thunk. Here also, we can use the macro IMAGE_SNAP_BY_ORDINAL to check if we are dealing with an ordinal. If yes, the lowest word of the field u1.ordinal is the actual ordinal number. If the import is by name, then we simply print it, after having retrieved the executable offset. That's it for the code. Please do not forget, if you compile the program with Visual Studio, to specify x64 as the solution platform. Enjoy!

History

First release.

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"