Unexporting a function from a DLL at runtime by name obfuscation

Usually, the set of functions exported by a Windows DLL is considered to be immutable. When load-time binding is used, functions appearing in the loading module’s import table are resolved against the export table of the loaded DLL. If any imported functions are missing, the loader aborts the module load attempt and unmaps the modules.

Library clients which require a more flexible reaction to the presence or absence of specific exports opt for a more dynamic binding approach, either late binding via GetProcAddress directly or through the Visual C++ delay loading facility, which allows for setting callbacks for missing modules or exports.

Recently, as I was experimenting with implementing an LSA proxy authentication package (more on that in a followup post) I considered the issue of properly implementing a proxy DLL for a DLL whose set of exports is only partially known to the proxy. Modern LSA APs have a callback function that provides a function dispatch table and do no rely on the module’s export table, but for pedagogic purposes let us consider an hypothetical situation in which the export table is the only lookup apparatus in use by the LSA.

In the case of an LSA authentication package, the package may provide an implementation of either LsaApLogonUser, LsaApLogonUserEx or LsaApLogonUserEx2. An original LSA AP can export one or more of these functions. If we want to create a generic LSA AP proxy DLL, we may wish to have a specific export from our module, loaded in lieu of the original AP, only if the original module also had it. This presents difficulty since we may not be able to predict the specific set of exports during proxy compile time.

If we recognize that DLL exports are in fact a poor man’s native code reflection mechanism, we can adopt a non-traditional approach of dynamic modification of our module’s set of exported functions. Our proxy DLL shall initially (at compile time) export all functions that could potentially be exported by the original DLL, e.g. by a module definition (.DEF) file like this:

LIBRARY "lsaprxap"
EXPORTS
LsaApInitializePackage
LsaApLogonUser
LsaApLogonUserEx
LsaApLogonUserEx2

LsaApCallPackage
LsaApCallPackagePassthrough
LsaApLogonTerminated
LsaApCallPackageUntrusted
SpInitialize
SpInstanceInit
SpLsaModeInitialize
SpUserModeInitialize

Notice how we export all three functions, even though some may not appear in the original DLL. LSA uses late binding via GetProcAddress to decide which function in the AP to call. If we export LsaApLogonUserEx2 and our original DLL does not have that function, we’ll have nothing to do when our proxy function is called (no original to forward to after our own processing). There is no telling what will happen if we return STATUS_NOT_IMPLEMENTED. Besides, LSA AP’s are only an illustration, and in other cases the export might not even have the option to return a failure exit code. Therefore, the behavior we desire is that GetProcAddress for LsaApLogonUserEx2 will fail and return NULL if the original DLL for which we are acting as a proxy does not export LsaApLogonUserEx2 itself.

The names and addresses of exported functions from a DLL appear in the PE image’s export directory. By accessing the export directory, looking up an exported function of interest and removing it we can alter the behavior of GetProcAddress for the exported function at runtime, after the module has been loaded. Note that this alteration is only useful for GetProcAddress invocations from that time forward, and callers that invoked GetProcAddress earlier and cached the result or callers that used load-time binding against our export table already obtained a function pointer to the exported function. Therefore, this technique is only useful in limited circumstances.

The export directory points to a block of null-terminated string pointers, indexed by export ordinal. In order to outright remove an export from the middle of the export table, we’d have to copy the export table aside, remove the desired functions and point the PE header to the new export table. This is a feasible, but cumbersome approach. Instead I opted for an alternative technique – obfuscating the name of the export to prevent GetProcAddress callers from resolving it by its well-known name. The function is still exported, but its name is unknown to other callers. This is probably sufficient for the vast majority of cases. As for the obfuscation itself, in this illustration we’ll merely increment the character value of the first letter in the export:

// Set by the linker to the base address of the module.
EXTERN_C IMAGE_DOS_HEADER __ImageBase;

void UnexportFunction(LPSTR ExportName)
{
	IMAGE_DOS_HEADER* dosHeader = &__ImageBase;
	assert(dosHeader->e_magic == IMAGE_DOS_SIGNATURE);
	IMAGE_NT_HEADERS* ntHeaders = reinterpret_cast<IMAGE_NT_HEADERS*>(
		reinterpret_cast<BYTE*>(dosHeader) + dosHeader->e_lfanew);
	assert(ntHeaders->Signature == 0x00004550);
	IMAGE_OPTIONAL_HEADER* optionalHeader = &ntHeaders->OptionalHeader;
	IMAGE_DATA_DIRECTORY* exportDataDirectory =
		&optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
	IMAGE_EXPORT_DIRECTORY* exportDirectory = reinterpret_cast<IMAGE_EXPORT_DIRECTORY*>(
		reinterpret_cast<BYTE*>(dosHeader) + exportDataDirectory->VirtualAddress);

	ULONG* addressOfNames = reinterpret_cast<ULONG*>(
		reinterpret_cast<BYTE*>(dosHeader) + exportDirectory->AddressOfNames);
	for (DWORD i = 0; i < exportDirectory->NumberOfFunctions; i++)
	{
		LPSTR exportFunctionName = reinterpret_cast<LPSTR>(
			reinterpret_cast<BYTE*>(dosHeader) + addressOfNames[i]);
		if (strcmp(exportFunctionName, ExportName) == 0)
		{
			DWORD oldProtect = 0;
			BOOL rc = VirtualProtect(
				exportFunctionName,
				strlen(exportFunctionName),
				PAGE_READWRITE,
				&oldProtect);
			if (rc == FALSE)
			{
				OutputDebugString(TEXT("VirtualProtect failed.\n"));
			}
			exportFunctionName[0]++;
			break;
		}
	}
}

The sample UnexportFunction function iterates over the current module’s export table until the function of interest is encountered. Since the export table is mapped as read-only memory, VirtualProtect must be used to allow for its modification. The string containing the name of the exported function is modified in place as a trivial obfuscation. This is sufficient to result in the “unexporting” of the symbol:

FARPROC before = GetProcAddress((HMODULE)&__ImageBase, "LsaApLogonUser");
UnexportFunction("LsaApLogonUser");
FARPROC after = GetProcAddress((HMODULE)&__ImageBase, "LsaApLogonUser");
assert(before != after);
assert(after == NULL);

With this tool of export entry removal at our disposal, we can devise an architecture in which the proxy DLL contains all exports that are feasible to be present in the original DLL at runtime. Through “reflection” of the original DLL, the proxy shall determine which redundant exports it wishes to hide, resulting it runtime behavior consistent with that of the original DLL (again, assuming only late binding with GetProcAddress is used).

What if we are unable to construct a superset of all possible exports from the original DLL? Perhaps we wish to be future-proof as new, unknown exports are added to the original DLL. For that a variety of solutions may be considered. In particular, copying the original DLL’s export table to our own and inserting hooks as needed comes to mind. Perhaps a topic for a future post…

Advertisements

3 thoughts on “Unexporting a function from a DLL at runtime by name obfuscation

  1. Hooking GetProcAddress by import hooking requires knowledge of the identity of the importing module (which would be OK in the specific case of LSA APs today, but not really future proof if the module that loads APs ever changes in Windows 7 or beyond)
    Hooking GetProcAddress by binary patching may require special infrastructure in some cases (like the non-free x64 Microsoft Detours Professional) or rolling your own binary patch which is a lot more fragile than obfuscating your own export table entry, IMHO.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s