Reputation: 3431
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?
Upvotes: 41
Views: 32673
Reputation: 1
Turns out Microsoft made a documentation article about exactly that in late 2021. I tested it myself and it works like a charm.
https://learn.microsoft.com/en-us/windows/win32/debug/enumerating-symbols
In case the link ever goes down, here's the code they presented :
#include <windows.h>
#include <stdio.h>
#include <dbghelp.h>
BOOL CALLBACK EnumSymProc(
PSYMBOL_INFO pSymInfo,
ULONG SymbolSize,
PVOID UserContext)
{
UNREFERENCED_PARAMETER(UserContext);
printf("%08X %4u %s\n",
pSymInfo->Address, SymbolSize, pSymInfo->Name);
return TRUE;
}
void main()
{
HANDLE hProcess = GetCurrentProcess();
DWORD64 BaseOfDll;
char *Mask = "*";
BOOL status;
status = SymInitialize(hProcess, NULL, FALSE);
if (status == FALSE)
{
return;
}
BaseOfDll = SymLoadModuleEx(hProcess,
NULL,
"foo.dll",
NULL,
0,
0,
NULL,
0);
if (BaseOfDll == 0)
{
SymCleanup(hProcess);
return;
}
if (SymEnumSymbols(hProcess, // Process handle from SymInitialize.
BaseOfDll, // Base address of module.
Mask, // Name of symbols to match.
EnumSymProc, // Symbol handler procedure.
NULL)) // User context.
{
// SymEnumSymbols succeeded
}
else
{
// SymEnumSymbols failed
printf("SymEnumSymbols failed: %d\n", GetLastError());
}
SymCleanup(hProcess);
}
Upvotes: 0
Reputation: 736
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.
Upvotes: 0
Reputation:
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 (§ions[i].VirtualSize, 4, 1, hFile);
fread (§ions[i].VirtualAddress, 4, 1, hFile);
fread (§ions[i].SizeOfRawData, 4, 1, hFile);
fread (§ions[i].PointerToRawData, 4, 1, hFile);
fread (§ions[i].PointerToRelocations, 4, 1, hFile);
fread (§ions[i].PointerToLineNumbers, 4, 1, hFile);
fread (§ions[i].NumberOfRelocations, 2, 1, hFile);
fread (§ions[i].NumberOfLineNumbers, 2, 1, hFile);
fread (§ions[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
Upvotes: 13
Reputation: 204966
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.)
Upvotes: 57
Reputation: 490583
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).
Upvotes: 4
Reputation: 1471
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.
Upvotes: 0
Reputation: 3236
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.
Upvotes: 1
Reputation: 6076
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.
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)
Upvotes: 7
Reputation: 100718
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.
Upvotes: 0