41

I found similar questions but no answer to what I am looking for. So here goes:

For a native Win32 dll, is there a Win32 API to enumerate its export function names?

user15071
  • 3,391
  • 8
  • 31
  • 31

8 Answers8

54

dumpbin /exports is pretty much what you want, but that's a developer tool, not a Win32 API.

LoadLibraryEx with DONT_RESOLVE_DLL_REFERENCES is heavily cautioned against, but happens to be useful for this particular case – it does the heavy lifting of mapping the DLL into memory (but you don't actually need or want to use anything from the library), which makes it trivial for you to read the header: the module handle returned by LoadLibraryEx points right at it.

#include <winnt.h>
HMODULE lib = LoadLibraryEx("library.dll", NULL, DONT_RESOLVE_DLL_REFERENCES);
assert(((PIMAGE_DOS_HEADER)lib)->e_magic == IMAGE_DOS_SIGNATURE);
PIMAGE_NT_HEADERS header = (PIMAGE_NT_HEADERS)((BYTE *)lib + ((PIMAGE_DOS_HEADER)lib)->e_lfanew);
assert(header->Signature == IMAGE_NT_SIGNATURE);
assert(header->OptionalHeader.NumberOfRvaAndSizes > 0);
PIMAGE_EXPORT_DIRECTORY exports = (PIMAGE_EXPORT_DIRECTORY)((BYTE *)lib + header->
    OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
assert(exports->AddressOfNames != 0);
BYTE** names = (BYTE**)((int)lib + exports->AddressOfNames);
for (int i = 0; i < exports->NumberOfNames; i++)
    printf("Export: %s\n", (BYTE *)lib + (int)names[i]);

Totally untested, but I think it's more or less correct. (Famous last words.)

masterxilo
  • 2,503
  • 1
  • 30
  • 35
ephemient
  • 198,619
  • 38
  • 280
  • 391
  • 1
    Worked well enough that my quick port to Python (with ctypes) works fine. Thanks! – Peter Hansen Mar 24 '10 at 19:27
  • 15
    It is essential to note that **calling the functions** after loading with the `DONT_RESOLVE_DLL_REFERENCES` flag **might blow shit up**, because no `DllMain` is called for the loaded module. – Janusz Lenar Jan 14 '13 at 16:56
  • 1
    Why not just memory-map the file yourself instead of the DONT_RESOLVE_DLL_REFERENCES? Might be faster even. – masterxilo Jan 19 '16 at 18:34
  • 2
    @masterxilo: `LoadLibrary[Ex]` **do** memory-map the binary into the address space. Why complicate things? – IInspectable Feb 13 '16 at 21:12
  • 1
    It seems `DONT_RESOLVE_DLL_REFERENCES` is required as the virtual address in the exports dictionary isn't valid when just memory mapped. LoadLibrary apparently does some necessary transformation. At least I'm seeing this on my Win10 x64. – Chad Schouggins Mar 12 '17 at 06:54
  • The problem with this is when the file is not ok (like bad format, etc.), it will display an error message box that must be clicked. Also https://devblogs.microsoft.com/oldnewthing/20050214-00/?p=36463 – Simon Mourier Dec 05 '21 at 07:57
12

try this:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void EnumExportedFunctions (char *, void (*callback)(char*));
int Rva2Offset (unsigned int);

typedef struct {
    unsigned char Name[8];
    unsigned int VirtualSize;
    unsigned int VirtualAddress;
    unsigned int SizeOfRawData;
    unsigned int PointerToRawData;
    unsigned int PointerToRelocations;
    unsigned int PointerToLineNumbers;
    unsigned short NumberOfRelocations;
    unsigned short NumberOfLineNumbers;
    unsigned int Characteristics;
} sectionHeader;

sectionHeader *sections;
unsigned int NumberOfSections = 0;

int Rva2Offset (unsigned int rva) {
    int i = 0;

    for (i = 0; i < NumberOfSections; i++) {
        unsigned int x = sections[i].VirtualAddress + sections[i].SizeOfRawData;

        if (x >= rva) {
            return sections[i].PointerToRawData + (rva + sections[i].SizeOfRawData) - x;
        }
    }

    return -1;
}

void EnumExportedFunctions (char *szFilename, void (*callback)(char*)) {
    FILE *hFile = fopen (szFilename, "rb");

    if (hFile != NULL) {
        if (fgetc (hFile) == 'M' && fgetc (hFile) == 'Z') {
            unsigned int e_lfanew = 0;
            unsigned int NumberOfRvaAndSizes = 0;
            unsigned int ExportVirtualAddress = 0;
            unsigned int ExportSize = 0;
            int i = 0;

            fseek (hFile, 0x3C, SEEK_SET);
            fread (&e_lfanew, 4, 1, hFile);
            fseek (hFile, e_lfanew + 6, SEEK_SET);
            fread (&NumberOfSections, 2, 1, hFile);
            fseek (hFile, 108, SEEK_CUR);
            fread (&NumberOfRvaAndSizes, 4, 1, hFile);

            if (NumberOfRvaAndSizes == 16) {
                fread (&ExportVirtualAddress, 4, 1, hFile);
                fread (&ExportSize, 4, 1, hFile);

                if (ExportVirtualAddress > 0 && ExportSize > 0) {
                    fseek (hFile, 120, SEEK_CUR);

                    if (NumberOfSections > 0) {
                        sections = (sectionHeader *) malloc (NumberOfSections * sizeof (sectionHeader));

                        for (i = 0; i < NumberOfSections; i++) {
                            fread (sections[i].Name, 8, 1, hFile);
                            fread (&sections[i].VirtualSize, 4, 1, hFile);
                            fread (&sections[i].VirtualAddress, 4, 1, hFile);
                            fread (&sections[i].SizeOfRawData, 4, 1, hFile);
                            fread (&sections[i].PointerToRawData, 4, 1, hFile);
                            fread (&sections[i].PointerToRelocations, 4, 1, hFile);
                            fread (&sections[i].PointerToLineNumbers, 4, 1, hFile);
                            fread (&sections[i].NumberOfRelocations, 2, 1, hFile);
                            fread (&sections[i].NumberOfLineNumbers, 2, 1, hFile);
                            fread (&sections[i].Characteristics, 4, 1, hFile);
                        }

                        unsigned int NumberOfNames = 0;
                        unsigned int AddressOfNames = 0;

                        int offset = Rva2Offset (ExportVirtualAddress);
                        fseek (hFile, offset + 24, SEEK_SET);
                        fread (&NumberOfNames, 4, 1, hFile);

                        fseek (hFile, 4, SEEK_CUR);
                        fread (&AddressOfNames, 4, 1, hFile);

                        unsigned int namesOffset = Rva2Offset (AddressOfNames), pos = 0;
                        fseek (hFile, namesOffset, SEEK_SET);

                        for (i = 0; i < NumberOfNames; i++) {
                            unsigned int y = 0;
                            fread (&y, 4, 1, hFile);
                            pos = ftell (hFile);
                            fseek (hFile, Rva2Offset (y), SEEK_SET);

                            char c = fgetc (hFile);
                            int szNameLen = 0;

                            while (c != '\0') {
                                c = fgetc (hFile);
                                szNameLen++;
                            }

                            fseek (hFile, (-szNameLen)-1, SEEK_CUR);
                            char* szName = calloc (szNameLen + 1, 1);
                            fread (szName, szNameLen, 1, hFile);

                            callback (szName);

                            fseek (hFile, pos, SEEK_SET);
                        }
                    }
                }
            }
        }

        fclose (hFile);
    }
}

example:

void mycallback (char* szName) {
    printf ("%s\n", szName);
}

int main () {
    EnumExportedFunctions ("C:\\Windows\\System32\\user32.dll", mycallback);
    return 0;
}

output:

ActivateKeyboardLayout
AddClipboardFormatListener
AdjustWindowRect
AdjustWindowRectEx
AlignRects
AllowForegroundActivation
AllowSetForegroundWindow
AnimateWindow
AnyPopup
AppendMenuA
AppendMenuW
ArrangeIconicWindows
AttachThreadInput
BeginDeferWindowPos
BeginPaint
BlockInput
BringWindowToTop
BroadcastSystemMessage
BroadcastSystemMessageA
BroadcastSystemMessageExA
BroadcastSystemMessageExW
BroadcastSystemMessageW
BuildReasonArray
CalcMenuBar
.....etc
7

Go over to Microsoft research and grab the Detours Library. One of its examples does exactly what you are asking. The whole library basically makes detouring/rerouting win32 function calls extremely easy. Its pretty cool stuff.

Detours

Edit: Also note that if you just want to look at the export table, you can (at least in visual studios) set your project properties to print out the export/import tables. I can't remember the exact option but should be easy to google.

**Edit2:**The option is Project Properties->Linker->Debugging->Generate MapFile ->Yes(/MAP)

Community
  • 1
  • 1
DeusAduro
  • 5,971
  • 5
  • 29
  • 36
4

While ephemient is correct that LoadLibraryEx with DONT_RESOLVE_DLL_REFERENCES can simplify this task a great deal, you can make it even simpler than he shows. Instead of finding and enumerating the DLL's export directory yourself, you can use SymEnumerateSymbols to list the symbols for you.

Although only marginally simpler than his code (without the asserts, his is only half a dozen lines of code) this at least theoretically gives a little extra flexibility in case Microsoft should someday decide to change the executable format a bit, and/or change exactly what the HMODULE points at, so his no longer works (since most of these details aren't officially documented anyway).

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
1

If you don't want to go to the trouble of writing your own code and would rather use a DLL that already exists for this purpose, I recommend PE File Format DLL. Comes with source code so that you can modify if you wish. No GPL to worry about.

Also available is a GUI application that shows how to use the DLL.

Stephen Kellett
  • 3,078
  • 1
  • 22
  • 25
0

If you're just looking for a way to find out what functions are exported in a DLL, you can use Microsoft's dependency walker (depends.exe). This wont help you if you actually need to discover the exports programmatically, though.

Ferruccio
  • 98,941
  • 38
  • 226
  • 299
0

I may be wrong, and I haven't double checked to be honest, but I believe there may be some compatibility issues with using ephemient's code on a module that is built under a different architecture than that of your process. (Again, I may be speaking completely out of my ass right now)

There's a project on github, called dll2def that uses the same technique (though it loads the file into memory on its own), but seems to have some checks in place to find the exports depending on the architecture of the binary. The code you'd most likely be interested in is in this file.

Charles Grunwald
  • 1,441
  • 18
  • 22
0

It's been 12 years since this was asked, but wanted to point out some problems with the solutions presented.

None of them account for ordinals (exports without a name string). Complicated by potential gaps between ordinal indexes. Ordinals have a starting base (the "Base" field of IMAGE_EXPORT_DIRECTORY) but there is no guarantee that ordinal numbers are sequential.

Don't want to spend the time to code it up, but one way to do it is iterate by index 0 to NumberOfFunctions.
Then in a 2nd (inner) loop match that index up in 0 to NumberOfNames into the AddressOfNameOrdinals array.
If you match the function index to the AddressOfNameOrdinals array index, that is your index into AddressOfNames array (an offset you have to resolve). If you don't get a match (over NumberOfNames indexes) then it's an ordinal export.
If the function index in the AddressOfFunctions entry is 0 then it's just an ordinal gap and you skip to the next index.
To get the actual ordinal number (for printing out as a string) you add "Base" to the NumberOfFunctions loop index.

Sirmabus
  • 636
  • 8
  • 8