Why does GetProcAddress() sometimes return an address outside of the DLL?

By Stephen Kellett
14 April, 2021

For most uses of GetProcAddress(), the address returned will be an address inside the DLL that you’re using to look up the function.

But sometimes, the address returned is not in the DLL. That’s odd! Is this a bug?

It’s not a bug. This intended behaviour. This article explains why it happens.

Before we get into what’s going on, let’s recap what GetProcAddress() does.

What is GetProcAddress()?

GetProcAddress() is a function exported from kernel32.dll.

It’s used for looking up the address of a function exported from a DLL.

The function can be exported by name (ASCII strings only) or by ordinal (an integer between 0 and 65535). Examples of DLLs that export by ordinal are the MFC DLLs. Most DLLs export by name.

GetProcAddress() examines the export address table in the DLL, looking for a function with the name (or ordinal) specified. If the function is found, the address is returned. Otherwise, NULL is returned.

Here’s a demonstration looking up the address of the StarCrossedLovers(); function in the theatre.dll. This DLL handles all functionality related to the theatre plays needed by the application.

If the function is found, it is called with the names of the two lovers, Romeo and Juliet.

HMODULE	hModTheatre;

hModTheatre = GetModuleHandle(_T("theatre.dll"));	// could be a call to LoadLibrary instead
if (hModTheatre != NULL)
{
    LOVERS_FUNC	p;

    p = (LOVERS_FUNC)GetProcAddress(hModTheatre, "StarCrossedLovers");
    if (p != NULL)
        (*p)("Romeo", "Juliet");
}

Most of the time, the returned address will be inside the DLL being queried.

Some of the time, the returned address is not from the DLL being queried. Where did the address come from?

Where did the address come from?

When the address returned from GetProcAddress() is from outside of the DLL being queried, the address returned is an address inside another DLL that is supplying functions to the original DLL using a process called Forwarding.

To demonstrate this, I’m going to examine the NetApiBufferAllocate() function, which is exported from netapi32.dll.

If we call GetProcAddress() to look up the address of NetApiBufferAllocate(), the address returned on my Windows 10 machine is 0x70342800.

But if we look at the load address for netapi32.dll it’s 0x6fcc0000, and its size is only 76KB. The address returned is outside of netapi32.dll. If we look at the other DLLs in the application and find which DLL contains address 0x70342800 we find it’s netutils.dll. Here’s a screenshot from VM Validator showing that.

DLL load addresses

OK, so we know the exported function address doesn’t come from the DLL we queried (netapi32.dll), but actually comes from netutils.dll. How does that work?

It’s going to be easier if I show two images of the netapi32.dll and netutils.dll while we examine the NetApiBufferAllocate() function.

These images are being viewed using PE File Browser. Both of these images show the DLLs loaded at their preferred load addresses (if you repeat this test with PE File Browser you’ll most likely get the same addresses).

netapi32.dll

Netapi32.dll exported functions

netutils.dll

Netutils.dll exported functions

What you can see in the first image is that netapi32.dll has a NULL exported address for NetApiBufferAllocate and the the forwarding column shows NETUTILS.NetApiBufferAllocate. The forwarded function is composed of a DLL name, a period and the name of the exporting function (which doesn’t have to be the same as the original function name).

When you look at the exports for netutils.dll you can see that it exports a function NetApiBufferAllocate with a non-NULL address.

What happens when you use GetProcAddress() to fetch the address of an exported function that has been forwarded from another DLL is that GetProcAddress() decodes the forwarded function name and looks up the forwarded function name in the forwarded function DLL. In the case of NetApiBufferAllocate in netapi32.dll that means GetProcAddress() returns the address of NetApiBufferAllocate in netutils.dll.

What’s the purpose of function forwarding?

I think it allows DLLs that were once large repositories for a variety of code to be split into many DLLs, each of which serves one purpose. These DLLs then forward the DLLs to the original DLL, which is now acting as a central location (for backward compatibility) to find the functions. Client applications never know the code they are calling now resides in a DLL dedicated to the particular task rather than in the original monolithic DLL. This can be useful for the maintainers of the DLLs, improving maintenance, testing and security of the component DLLs.

If we return to the original example of the StarCrossedLovers() function exported from theatre.dll this could be reimplemented by moving all Shakespeare related functionality out of theatre.dll into shakespeare.dll. StarCrossedLovers() in shakespeare.dll would then be forwarded to theatre.dll.

You could take it a stage further (sorry, no pun intended!) with each of Shakespeare’s plays being represented by their own DLL which forwards functions to shakespeare.dll (or theatre.dll).

Fully functional, free for 30 days