Now and then it's necessary to use "private" or "internal-only" functions in DLLs that you did not write, don't have the source for, and/or cannot get public interfaces for. I recently had to do this again for a utility application, so I thought it would be nice to document the process a little more clearly for others.
A starting assumption is that the reader is familiar with GetProcAddress() in its "main" form, which is when you know the full signature of the function you need to call. For example, if you have a copy of foo.dll that exports Bar(LPWSTR, DWORD*):
// library function is: Bar(LPWSTR lpParam, DWORD *pdwParam)
But what if you need to access a function that is not included in the named exports of the library? 90% of the time the functions will be exported with a name, but sometimes not, and for various reasons. The most common reason is for "security through obscurity", because using the function is supposed to be something that you, the user, "should never have to do". Unfortunately, there are scenarios when you need to do just that, for reasons the original publisher never considered, such as if you need to tweak the way something behaves in your mission-critical server process, and the company that made the original custom component has gone out of business.
Enter the "ordinal" function access method. If you look at the MSDN docs, they even allude to using a function's ordinal with GetProcAddress, but it's a bit of a mystery how to a) get the ordinal, and b) how to use it even if you get it. Once you know the ordinal and prototype of a function, it's simple to use it--getting the function prototypes is the fun part.
Say you got your hands on a .DEF file that lists the function ordinals, or have used link.exe and through sufficiently wizardly use of a hex editor have examined the [NONAME] functions and determined what their prototypes are. Let's pretend that the Bar function above was actually a [NONAME] function, and you got a listing like this from link.exe /dump /exports foo.dll:
Microsoft (R) COFF/PE Dumper Version 7.10.4035
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file c:\myprojects\foo.dll
File Type: DLL
Section contains the following exports for foo.dll
41107692 time date stamp Tue Aug 03 22:39:30 2005
1 ordinal base
6 number of functions
4 number of names
ordinal hint RVA name
3 0 00006948 Connect
4 1 00007798 Close
5 2 00012000 GetFoo
6 3 00014D12 SetFoo
1 0000F3A0 [NONAME]
2 0000D20A [NONAME]
The depends.exe tool provides similar data in a GUI fashion. You can examine the binary at offsets F3A0 and D20A to determine what the function signature is (well, what the base pointer sig is), and match them up to the expected signature of Bar(LPWSTR, DWORD*) manually to get the proper ordinal. Or more easily, if you know that one of the methods is the droid you are looking for, you can just code up a quick test app and poke at the method as if it were the one you wanted. If it works without throwing an exception... ;-)
Getting back to the main point, to load the exported-by-ordinal function, there's a handy macro pre-defined in the Visual Studio (v6 or later, IIRC) build environment that can package up that ordinal into the "expected" LPSTR for GetProcAddress. In my example, assuming Bar was actually at ordinal 1, you would do this:
The only difference is that you use the MAKEINTRESOURCEA macro to convert that ordinal into something that GetProcAddress can understand, rather than the string name of the function.
If you really want to get tricky, you don't actually need GetProcAddress at all to do this magic. If you can guarantee that the DLL won't change underneath you, or you don't mind breaking if it does, or you have smart scanning code that can "find" the offsets again, you can plug the RVA directly into the FARPROC variable. It's all just numbers under the hood, after all. Using the example output of link.exe from above, you can do this:
Why does this work? LoadLibrary(dll) loads the specified library into the process' address space, and returns a HANDLE value. This HANDLE is the starting address of the library, and since the RVA values for a DLL are offsets from the starting address, you can perform simple addition to the base to get a function's entry point. Pretty cool, huh? All GetProcAddress does is read the DLL's export table into a struct and performs the math for you, returning the result. I'm a big fan of not reinventing the wheel, so I use GetProcAddress when there's an exports entry for what I need.
What do you do if the function you need is not exported at all? Nine times out of ten it's much easier to just get it exported, but if that's not possible, well, that's a topic for another day.