Skip to content

Commit

Permalink
Fixed #7
Browse files Browse the repository at this point in the history
  • Loading branch information
p0dalirius committed Dec 14, 2022
1 parent 4ea32b0 commit d63e4fb
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 275 deletions.
290 changes: 16 additions & 274 deletions FindUncommonShares.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from impacket import version
from impacket.smbconnection import SMBConnection, SMB2_DIALECT_002, SMB2_DIALECT_21, SMB_DIALECT, SessionError
from impacket.spnego import SPNEGO_NegTokenInit, TypesMech
from sectools.windows.ldap import raw_ldap_query
from sectools.windows.crypto import nt_hash, parse_lm_nt_hashes
import argparse
import binascii
import dns.resolver, dns.exception
Expand Down Expand Up @@ -78,49 +80,6 @@ def STYPE_MASK(stype_value):
return flags


def get_domain_computers(ldap_server, ldap_session):
page_size = 1000
# Controls
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/3c5e87db-4728-4f29-b164-01dd7d7391ea
LDAP_PAGED_RESULT_OID_STRING = "1.2.840.113556.1.4.319"
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/f14f3610-ee22-4d07-8a24-1bf1466cba5f
LDAP_SERVER_NOTIFICATION_OID = "1.2.840.113556.1.4.528"
results = {}

target_dn = ldap_server.info.other["defaultNamingContext"]

# https://ldap3.readthedocs.io/en/latest/searches.html#the-search-operation
paged_response = True
paged_cookie = None
while paged_response == True:
ldap_session.search(
target_dn, "(objectCategory=computer)", attributes=["dNSHostName", "sAMAccountName"],
size_limit=0, paged_size=page_size, paged_cookie=paged_cookie
)
#
if "controls" in ldap_session.result.keys():
if LDAP_PAGED_RESULT_OID_STRING in ldap_session.result["controls"].keys():
next_cookie = ldap_session.result["controls"][LDAP_PAGED_RESULT_OID_STRING]["value"]["cookie"]
if len(next_cookie) == 0:
paged_response = False
else:
paged_response = True
paged_cookie = next_cookie
else:
paged_response = False
else:
paged_response = False
#
for entry in ldap_session.response:
if entry['type'] != 'searchResEntry':
continue
results[entry['dn']] = {
'dNSHostName': entry["attributes"]['dNSHostName'],
'sAMAccountName': entry["attributes"]['sAMAccountName']
}
return results


def parse_args():
print("FindUncommonShares v%s - by @podalirius_\n" % VERSION)

Expand Down Expand Up @@ -178,215 +137,6 @@ def get_machine_name(options, domain):
return s.getServerName()


def init_ldap_connection(target, tls_version, options, domain, username, password, lmhash, nthash):
user = '%s\\%s' % (domain, username)
if tls_version is not None:
use_ssl = True
port = 636
tls = ldap3.Tls(validate=ssl.CERT_NONE, version=tls_version)
else:
use_ssl = False
port = 389
tls = None
ldap_server = ldap3.Server(target, get_info=ldap3.ALL, port=port, use_ssl=use_ssl, tls=tls)

if options.use_kerberos:
ldap_session = ldap3.Connection(ldap_server)
ldap_session.bind()
ldap3_kerberos_login(ldap_session, target, username, password, domain, lmhash, nthash, options.auth_key, kdcHost=options.dc_ip)
elif options.auth_hashes is not None:
if lmhash == "":
lmhash = "aad3b435b51404eeaad3b435b51404ee"
ldap_session = ldap3.Connection(ldap_server, user=user, password=lmhash + ":" + nthash, authentication=ldap3.NTLM, auto_bind=True)
else:
ldap_session = ldap3.Connection(ldap_server, user=user, password=password, authentication=ldap3.NTLM, auto_bind=True)

return ldap_server, ldap_session


def init_ldap_session(options, domain, username, password, lmhash, nthash):
if options.use_kerberos:
target = get_machine_name(options, domain)
else:
if options.dc_ip is not None:
target = options.dc_ip
else:
target = domain

if options.use_ldaps is True:
try:
return init_ldap_connection(target, ssl.PROTOCOL_TLSv1_2, options, domain, username, password, lmhash, nthash)
except ldap3.core.exceptions.LDAPSocketOpenError:
return init_ldap_connection(target, ssl.PROTOCOL_TLSv1, options, domain, username, password, lmhash, nthash)
else:
return init_ldap_connection(target, None, options, domain, username, password, lmhash, nthash)


def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, TGS=None, useCache=True, debug=False):
from pyasn1.codec.ber import encoder, decoder
from pyasn1.type.univ import noValue
"""
logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported.
:param string user: username
:param string password: password for the user
:param string domain: domain where the account is valid for (required)
:param string lmhash: LMHASH used to authenticate using hashes (password is not used)
:param string nthash: NTHASH used to authenticate using hashes (password is not used)
:param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication
:param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho)
:param struct TGT: If there's a TGT available, send the structure here and it will be used
:param struct TGS: same for TGS. See smb3.py for the format
:param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False
:return: True, raises an Exception if error.
"""

if lmhash != '' or nthash != '':
if len(lmhash) % 2:
lmhash = '0' + lmhash
if len(nthash) % 2:
nthash = '0' + nthash
try: # just in case they were converted already
lmhash = binascii.unhexlify(lmhash)
nthash = binascii.unhexlify(nthash)
except TypeError:
pass

# Importing down here so pyasn1 is not required if kerberos is not used.
from impacket.krb5.ccache import CCache
from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set
from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS
from impacket.krb5 import constants
from impacket.krb5.types import Principal, KerberosTime, Ticket
import datetime

if TGT is not None or TGS is not None:
useCache = False

if useCache:
try:
ccache = CCache.loadFile(os.getenv('KRB5CCNAME'))
except Exception as e:
# No cache present
print(e)
pass
else:
# retrieve domain information from CCache file if needed
if domain == '':
domain = ccache.principal.realm['data'].decode('utf-8')
if debug:
print('[debug] Domain retrieved from CCache: %s' % domain)

if debug:
print('[debug] Using Kerberos Cache: %s' % os.getenv('KRB5CCNAME'))
principal = 'ldap/%s@%s' % (target.upper(), domain.upper())

creds = ccache.getCredential(principal)
if creds is None:
# Let's try for the TGT and go from there
principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper())
creds = ccache.getCredential(principal)
if creds is not None:
TGT = creds.toTGT()
if debug:
print('[debug] Using TGT from cache')
else:
if debug:
print('[debug] No valid credentials found in cache')
else:
TGS = creds.toTGS(principal)
if debug:
print('[debug] Using TGS from cache')

# retrieve user information from CCache file if needed
if user == '' and creds is not None:
user = creds['client'].prettyPrint().split(b'@')[0].decode('utf-8')
if debug:
print('[debug] Username retrieved from CCache: %s' % user)
elif user == '' and len(ccache.principal.components) > 0:
user = ccache.principal.components[0]['data'].decode('utf-8')
if debug:
print('[debug] Username retrieved from CCache: %s' % user)

# First of all, we need to get a TGT for the user
userName = Principal(user, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
if TGT is None:
if TGS is None:
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, aesKey, kdcHost)
else:
tgt = TGT['KDC_REP']
cipher = TGT['cipher']
sessionKey = TGT['sessionKey']

if TGS is None:
serverName = Principal('ldap/%s' % target, type=constants.PrincipalNameType.NT_SRV_INST.value)
tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, sessionKey)
else:
tgs = TGS['KDC_REP']
cipher = TGS['cipher']
sessionKey = TGS['sessionKey']

# Let's build a NegTokenInit with a Kerberos REQ_AP

blob = SPNEGO_NegTokenInit()

# Kerberos
blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']]

# Let's extract the ticket from the TGS
tgs = decoder.decode(tgs, asn1Spec=TGS_REP())[0]
ticket = Ticket()
ticket.from_asn1(tgs['ticket'])

# Now let's build the AP_REQ
apReq = AP_REQ()
apReq['pvno'] = 5
apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value)

opts = []
apReq['ap-options'] = constants.encodeFlags(opts)
seq_set(apReq, 'ticket', ticket.to_asn1)

authenticator = Authenticator()
authenticator['authenticator-vno'] = 5
authenticator['crealm'] = domain
seq_set(authenticator, 'cname', userName.components_to_asn1)
now = datetime.datetime.utcnow()

authenticator['cusec'] = now.microsecond
authenticator['ctime'] = KerberosTime.to_asn1(now)

encodedAuthenticator = encoder.encode(authenticator)

# Key Usage 11
# AP-REQ Authenticator (includes application authenticator
# subkey), encrypted with the application session key
# (Section 5.5.1)
encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None)

apReq['authenticator'] = noValue
apReq['authenticator']['etype'] = cipher.enctype
apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator

blob['MechToken'] = encoder.encode(apReq)

request = ldap3.operation.bind.bind_operation(connection.version, ldap3.SASL, user, None, 'GSS-SPNEGO',
blob.getData())

# Done with the Kerberos saga, now let's get into LDAP
if connection.closed: # try to open connection if closed
connection.open(read_server_info=False)

connection.sasl_in_progress = True
response = connection.post_send_single_response(connection.send('bindRequest', request, None))
connection.sasl_in_progress = False
if response[0]['result'] != 0:
raise Exception(response)

connection.bound = True

return True


def init_smb_session(options, target_ip, domain, username, password, address, lmhash, nthash, port=445, debug=False):
smbClient = SMBConnection(address, target_ip, sess_port=int(port))
dialect = smbClient.getDialect()
Expand Down Expand Up @@ -421,7 +171,7 @@ def worker(options, target_name, domain, username, password, address, lmhash, nt
dns_answer = None
# Try UDP
try:
dns_answer = dns_resolver.resolve(domain, rdtype=record_type, tcp=False)
dns_answer = dns_resolver.resolve(target_name, rdtype="A", tcp=False)
except dns.resolver.NXDOMAIN:
# the domain does not exist so dns resolutions remain empty
pass
Expand All @@ -436,7 +186,7 @@ def worker(options, target_name, domain, username, password, address, lmhash, nt
if dns_answer is None:
# Try TCP
try:
dns_answer = dns_resolver.resolve(domain, rdtype=record_type, tcp=True)
dns_answer = dns_resolver.resolve(target_name, rdtype="A", tcp=True)
except dns.resolver.NXDOMAIN:
# the domain does not exist so dns resolutions remain empty
pass
Expand All @@ -450,7 +200,7 @@ def worker(options, target_name, domain, username, password, address, lmhash, nt

target_ip = []
if dns_answer is not None:
target_ip = dns_answer.answer
target_ip = [ip.address for ip in dns_answer]

if len(target_ip) != 0:
target_ip = target_ip[0]
Expand Down Expand Up @@ -540,28 +290,19 @@ def worker(options, target_name, domain, username, password, address, lmhash, nt

if __name__ == '__main__':
options = parse_args()

auth_lm_hash = ""
auth_nt_hash = ""
if options.auth_hashes is not None:
if ":" in options.auth_hashes:
auth_lm_hash = options.auth_hashes.split(":")[0]
auth_nt_hash = options.auth_hashes.split(":")[1]
else:
auth_nt_hash = options.auth_hashes

ldap_server, ldap_session = init_ldap_session(
options=options,
domain=options.auth_domain,
username=options.auth_username,
password=options.auth_password,
lmhash=auth_lm_hash,
nthash=auth_nt_hash
)
auth_lm_hash, auth_nt_hash = parse_lm_nt_hashes(options.auth_hashes)

if not options.quiet:
print("[>] Extracting all computers ...")
computers = get_domain_computers(ldap_server, ldap_session)
computers = raw_ldap_query(
auth_domain=options.auth_domain,
auth_dc_ip=options.dc_ip,
auth_username=options.auth_username,
auth_password=options.auth_password,
auth_hashes=options.auth_hashes,
query='(objectCategory=computer)',
attributes=["dNSHostName", "sAMAccountName"]
)

if not options.quiet:
print("[+] Found %d computers in the domain. \n" % len(computers.keys()))
Expand Down Expand Up @@ -653,4 +394,5 @@ def worker(options, target_name, domain, username, password, address, lmhash, nt
conn.commit()
conn.close()
print("done.")
print("[+] Bye Bye!")

1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
impacket
nslookup
xlsxwriter

0 comments on commit d63e4fb

Please sign in to comment.