Hunting PDFs

This is where the protocol specification comes to our aid. Section 3.1.4: Message Processing Events and Sequencing Rules, basically details all operations which the server needs to support. The first one is R_DnssrvOperation, which contains a pszOperation parameter, which determines the operation performed by the server. While scrolling over the huge list of possible pszOperation values, we see this:

Right, we can tell the server to just load a DLL of our choice! Awesome! Upon searching the spec for ServerLevelPluginDll, we find the following useful piece of information:

Looks like the server does not even do any verification on the dll path specified in this operation. Before starting to implement this, I figured someone must have dug this up before. A google search for ServerLevelPluginDll raised nothing of the sort, however it did pop up the useful dnscmd command line tool, which I hadn’t known before.

Luckily, dnscmd already implements everything we need. A quick look at its help message and another glance at https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/dnscmd give us the following option:

dnscmd.exe /config /serverlevelplugindll \\path\to\dll

First, trying to run this as a weak domain user with no special permissions on the DNS server object (other than Generic Read, which is granted to all members of the Pre-Windows 2000 Compatible Access group, which by default contains the Domain Users group), the command fails with an access denied message. If we give our weak user write access to the server object, the command no longer fails. This means that members of DnsAdmins can successfully run this command.

Still lazy enough not to use IDA, trying to run this on a domain computer running with a member of DnsAdmins while running process monitor and process explorer on our DC, we see that no DLL is loaded into dns.exe’s address space, as expected. We do see, however, that the following registry key is now populated with the path we sent:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\DNS\Parameters\ServerLevelPluginDll.

Great. Now, for testing purposes, we restart the DNS server service. Whoops — it fails to start, and clearing the registry key value allows it to start. Apparently it needs something more from our DLL. Time to open IDA.

There are several possibilities to quickly reach the functionality we seek to reverse in this case — searching for relevant strings and searching for relevant APIs are usually the easiest and quickest. In our case, going through all xrefs to LoadLibraryW or GetProcAddress gives us what we need — going through the code of the function which LoadLibraryW’s our DLL and the one function it is called from, we see that no validation is performed at all on the path given to ServerLevelPluginDll.

The hurdle we’ve run into is indeed the only one — if either the DLL fails to load, or it does not contain one of the DnsPluginInitialize, DnsPluginCleanup or DnsPluginQuery exports, the service will fail to start. We also need to make sure that our exports all return 0 (success return value), otherwise they may cause the service to fail as well.

The pseudocode for the function responsible for loading the DLL is roughly as follows:

HMODULE hLib;

if (g_pluginPath && *g_pluginPath) {

hLib = LoadLibraryW(g_pluginPath);

g_hndPlugin = hLib;

if (!hLib) {...log and return error...} g_dllDnsPluginInitialize = GetProcAddress(hLib, "DnsPluginInitialize");

if (!g_dllDnsPluginInitialize) {...log and return error...}

g_dllDnsPluginQuery = GetProcAddress(hLib, "DnsPluginQuery")

if (!g_dllDnsPluginQuery) {...log and return error...}

g_dllDnsPluginCleanup = GetProcAddress(hLib, "DnsPluginCleanup")

if (!g_dllDnsPluginCleanup) {...log and return error...} if (g_dllDnsPluginInitialize){

g_dllDnsPluginInitialize(pCallback1, pCallback2);

}

}

Here’s a quick PoC to demonstrate how the code for such a DLL should look under Visual Studio 2015:

Sample code for our plugin dll

The pragmas are in place to modify the default export names to the ones that we desire. To verify that our exports are good, we can use dumpbin /exports path\to\dll.

Now we try running dnscmd with our new dll and voila, it works! All we need is to put our dll on a network path which is accessible by one of the domain controllers’ computer accounts (dns.exe runs under SYSTEM) (Read access for the Everyone SID should do the job), and we can run code as SYSTEM on a domain controller, thus taking control of the domain.

While this demonstrates that it is possible to take over a domain if you’re a member of DnsAdmins, it’s not limited to just that — all we need to successfully pull off this trick is an account with write access to a DNS server object. The ACLs for these objects are usually, from my experience, not kept as clean or monitored as ACLs for domain admins (or similar groups protected by AdminSDHolder), thus offering a nice chance for a small domain elevation of privilege.

As also noted in the spec, this should work across all recent Windows Server versions:

ServerLevelPluginDll support across OS versions

Microsoft’s MSRC have been contacted regarding this issue and have stated that it will be fixed by basically only allowing DC administrators to change the ServerLevelPluginDll registry key, and that it will be possible to toggle this feature off in future releases.

Regardless, dns.exe currently still runs as SYSTEM and boasts a good amount of attack surface, so it is probably a worthwhile candidate for some fuzzing — both for the DNS implementation and the management interface.