Skip to content

Commit

Permalink
Merge pull request byt3bl33d3r#37 from Cyb3rC3lt/master
Browse files Browse the repository at this point in the history
LDAP Enumeration and Add Computer Modules
  • Loading branch information
mpgn committed Jul 12, 2023
2 parents 46af570 + 19aa2d9 commit 5d2a9eb
Show file tree
Hide file tree
Showing 3 changed files with 492 additions and 0 deletions.
307 changes: 307 additions & 0 deletions cme/modules/add_computer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
#!/usr/bin/env python3

# -*- coding: utf-8 -*-

import ldap3
from impacket.dcerpc.v5 import samr, epm, transport

class CMEModule:
'''
Module by CyberCelt: @Cyb3rC3lt
Initial module:
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
Thanks to the guys at impacket for the original code
'''

name = 'add-computer'
description = 'Adds or deletes a domain computer'
supported_protocols = ['smb']
opsec_safe = True
multiple_hosts = False

def options(self, context, module_options):
'''
add-computer: Specify add-computer to call the module using smb
NAME: Specify the NAME option to name the Computer to be added
PASSWORD: Specify the PASSWORD option to supply a password for the Computer to be added
DELETE: Specify DELETE to remove a Computer
CHANGEPW: Specify CHANGEPW to modify a Computer password
Usage: cme smb $DC-IP -u Username -p Password -M add-computer -o NAME="BADPC" PASSWORD="Password1"
cme smb $DC-IP -u Username -p Password -M add-computer -o NAME="BADPC" DELETE=True
cme smb $DC-IP -u Username -p Password -M add-computer -o NAME="BADPC" PASSWORD="Password2" CHANGEPW=True
'''

self.__baseDN = None
self.__computerGroup = None
self.__method = "SAMR"
self.__noAdd = False
self.__delete = False
self.noLDAPRequired = False

if 'DELETE' in module_options:
self.__delete = True

if 'CHANGEPW' in module_options and ('NAME' not in module_options or 'PASSWORD' not in module_options):
context.log.error('NAME and PASSWORD options are required!')
elif 'CHANGEPW' in module_options:
self.__noAdd = True

if 'NAME' in module_options:
self.__computerName = module_options['NAME']
if self.__computerName[-1] != '$':
self.__computerName += '$'
else:
context.log.error('NAME option is required!')
exit(1)

if 'PASSWORD' in module_options:
self.__computerPassword = module_options['PASSWORD']
elif 'PASSWORD' not in module_options and not self.__delete:
context.log.error('PASSWORD option is required!')
exit(1)

def on_login(self, context, connection):

#Set some variables
self.__domain = connection.domain
self.__domainNetbios = connection.domain
self.__kdcHost = connection.hostname + "." + connection.domain
self.__target = self.__kdcHost
self.__username = connection.username
self.__password = connection.password
self.__targetIp = connection.host
self.__port = context.smb_server_port
self.__aesKey = context.aesKey
self.__hashes = context.hash
self.__doKerberos = connection.kerberos
self.__nthash = ""
self.__lmhash = ""

if context.hash and ":" in context.hash[0]:
hashList = context.hash[0].split(":")
self.__nthash = hashList[-1]
self.__lmhash = hashList[0]
elif context.hash and ":" not in context.hash[0]:
self.__nthash = context.hash[0]
self.__lmhash = "00000000000000000000000000000000"

# First try to add via SAMR over SMB
self.doSAMRAdd(context)

# If SAMR fails now try over LDAPS
if not self.noLDAPRequired:
self.doLDAPSAdd(connection,context)
else:
exit(1)

def doSAMRAdd(self,context):

if self.__targetIp is not None:
stringBinding = epm.hept_map(self.__targetIp, samr.MSRPC_UUID_SAMR, protocol = 'ncacn_np')
else:
stringBinding = epm.hept_map(self.__target, samr.MSRPC_UUID_SAMR, protocol = 'ncacn_np')
rpctransport = transport.DCERPCTransportFactory(stringBinding)
rpctransport.set_dport(self.__port)

if self.__targetIp is not None:
rpctransport.setRemoteHost(self.__targetIp)
rpctransport.setRemoteName(self.__target)

if hasattr(rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,
self.__nthash, self.__aesKey)

rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost)

dce = rpctransport.get_dce_rpc()
servHandle = None
domainHandle = None
userHandle = None
try:
dce.connect()
dce.bind(samr.MSRPC_UUID_SAMR)

samrConnectResponse = samr.hSamrConnect5(dce, '\\\\%s\x00' % self.__target,
samr.SAM_SERVER_ENUMERATE_DOMAINS | samr.SAM_SERVER_LOOKUP_DOMAIN )
servHandle = samrConnectResponse['ServerHandle']

samrEnumResponse = samr.hSamrEnumerateDomainsInSamServer(dce, servHandle)
domains = samrEnumResponse['Buffer']['Buffer']
domainsWithoutBuiltin = list(filter(lambda x : x['Name'].lower() != 'builtin', domains))

if len(domainsWithoutBuiltin) > 1:
domain = list(filter(lambda x : x['Name'].lower() == self.__domainNetbios, domains))
if len(domain) != 1:
context.log.highlight(u'{}'.format(
'This domain does not exist: "' + self.__domainNetbios + '"'))
logging.critical("Available domain(s):")
for domain in domains:
logging.error(" * %s" % domain['Name'])
raise Exception()
else:
selectedDomain = domain[0]['Name']
else:
selectedDomain = domainsWithoutBuiltin[0]['Name']

samrLookupDomainResponse = samr.hSamrLookupDomainInSamServer(dce, servHandle, selectedDomain)
domainSID = samrLookupDomainResponse['DomainId']

if logging.getLogger().level == logging.DEBUG:
logging.info("Opening domain %s..." % selectedDomain)
samrOpenDomainResponse = samr.hSamrOpenDomain(dce, servHandle, samr.DOMAIN_LOOKUP | samr.DOMAIN_CREATE_USER , domainSID)
domainHandle = samrOpenDomainResponse['DomainHandle']

if self.__noAdd or self.__delete:
try:
checkForUser = samr.hSamrLookupNamesInDomain(dce, domainHandle, [self.__computerName])
except samr.DCERPCSessionError as e:
if e.error_code == 0xc0000073:
context.log.highlight(u'{}'.format(
self.__computerName + ' not found in domain ' + selectedDomain))
self.noLDAPRequired = True
raise Exception()
else:
raise

userRID = checkForUser['RelativeIds']['Element'][0]
if self.__delete:
access = samr.DELETE
message = "delete"
else:
access = samr.USER_FORCE_PASSWORD_CHANGE
message = "set the password for"
try:
openUser = samr.hSamrOpenUser(dce, domainHandle, access, userRID)
userHandle = openUser['UserHandle']
except samr.DCERPCSessionError as e:
if e.error_code == 0xc0000022:
context.log.highlight(u'{}'.format(
self.__username + ' does not have the right to ' + message + " " + self.__computerName))
self.noLDAPRequired = True
raise Exception()
else:
raise
else:
if self.__computerName is not None:
try:
checkForUser = samr.hSamrLookupNamesInDomain(dce, domainHandle, [self.__computerName])
self.noLDAPRequired = True
context.log.highlight(u'{}'.format(
'Computer account already exists with the name: "' + self.__computerName + '"'))
raise Exception()
except samr.DCERPCSessionError as e:
if e.error_code != 0xc0000073:
raise
else:
foundUnused = False
while not foundUnused:
self.__computerName = self.generateComputerName()
try:
checkForUser = samr.hSamrLookupNamesInDomain(dce, domainHandle, [self.__computerName])
except samr.DCERPCSessionError as e:
if e.error_code == 0xc0000073:
foundUnused = True
else:
raise
try:
createUser = samr.hSamrCreateUser2InDomain(dce, domainHandle, self.__computerName, samr.USER_WORKSTATION_TRUST_ACCOUNT, samr.USER_FORCE_PASSWORD_CHANGE,)
self.noLDAPRequired = True
context.log.highlight('Successfully added the machine account: "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"')
except samr.DCERPCSessionError as e:
if e.error_code == 0xc0000022:
context.log.highlight(u'{}'.format(
'The following user does not have the right to create a computer account: "' + self.__username + '"'))
raise Exception()
elif e.error_code == 0xc00002e7:
context.log.highlight(u'{}'.format(
'The following user exceeded their machine account quota: "' + self.__username + '"'))
raise Exception()
else:
raise
userHandle = createUser['UserHandle']

if self.__delete:
samr.hSamrDeleteUser(dce, userHandle)
context.log.highlight(u'{}'.format('Successfully deleted the "' + self.__computerName + '" Computer account'))
self.noLDAPRequired=True
userHandle = None
else:
samr.hSamrSetPasswordInternal4New(dce, userHandle, self.__computerPassword)
if self.__noAdd:
context.log.highlight(u'{}'.format(
'Successfully set the password of machine "' + self.__computerName + '" with password "' + self.__computerPassword + '"'))
self.noLDAPRequired=True
else:
checkForUser = samr.hSamrLookupNamesInDomain(dce, domainHandle, [self.__computerName])
userRID = checkForUser['RelativeIds']['Element'][0]
openUser = samr.hSamrOpenUser(dce, domainHandle, samr.MAXIMUM_ALLOWED, userRID)
userHandle = openUser['UserHandle']
req = samr.SAMPR_USER_INFO_BUFFER()
req['tag'] = samr.USER_INFORMATION_CLASS.UserControlInformation
req['Control']['UserAccountControl'] = samr.USER_WORKSTATION_TRUST_ACCOUNT
samr.hSamrSetInformationUser2(dce, userHandle, req)
if not self.noLDAPRequired:
context.log.highlight(u'{}'.format(
'Successfully added the machine account "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"'))
self.noLDAPRequired = True

except Exception as e:
if logging.getLogger().level == logging.DEBUG:
import traceback
traceback.print_exc()
finally:
if userHandle is not None:
samr.hSamrCloseHandle(dce, userHandle)
if domainHandle is not None:
samr.hSamrCloseHandle(dce, domainHandle)
if servHandle is not None:
samr.hSamrCloseHandle(dce, servHandle)
dce.disconnect()

def doLDAPSAdd(self, connection, context):
ldap_domain = connection.domain.replace(".", ",dc=")
spns = [
'HOST/%s' % self.__computerName,
'HOST/%s.%s' % (self.__computerName, connection.domain),
'RestrictedKrbHost/%s' % self.__computerName,
'RestrictedKrbHost/%s.%s' % (self.__computerName, connection.domain),
]
ucd = {
'dnsHostName': '%s.%s' % (self.__computerName, connection.domain),
'userAccountControl': 0x1000,
'servicePrincipalName': spns,
'sAMAccountName': self.__computerName,
'unicodePwd': ('"%s"' % self.__computerPassword).encode('utf-16-le')
}
tls = ldap3.Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1_2, ciphers='ALL:@SECLEVEL=0')
ldapServer = ldap3.Server(connection.host, use_ssl=True, port=636, get_info=ldap3.ALL, tls=tls)
c = Connection(ldapServer, connection.username + '@' + connection.domain, connection.password)
c.bind()

if (self.__delete):
result = c.delete("cn=" + self.__computerName + ",cn=Computers,dc=" + ldap_domain)
if result:
context.log.highlight(u'{}'.format('Successfully deleted the "' + self.__computerName + '" Computer account'))
elif result == False and c.last_error == "noSuchObject":
context.log.highlight(u'{}'.format('Computer named "' + self.__computerName + '" was not found'))
elif result == False and c.last_error == "insufficientAccessRights":
context.log.highlight(
u'{}'.format('Insufficient Access Rights to delete the Computer "' + self.__computerName + '"'))
else:
context.log.highlight(u'{}'.format(
'Unable to delete the "' + self.__computerName + '" Computer account. The error was: ' + c.last_error))
else:
result = c.add("cn=" + self.__computerName + ",cn=Computers,dc=" + ldap_domain,
['top', 'person', 'organizationalPerson', 'user', 'computer'], ucd)
if result:
context.log.highlight('Successfully added the machine account: "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"')
context.log.highlight(u'{}'.format('You can try to verify this with the CME command:'))
context.log.highlight(u'{}'.format(
'cme ldap ' + connection.host + ' -u ' + connection.username + ' -p ' + connection.password + ' -M group-mem -o GROUP="Domain Computers"'))
elif result == False and c.last_error == "entryAlreadyExists":
context.log.highlight(u'{}'.format('The Computer account "' + self.__computerName + '" already exists'))
elif not result:
context.log.highlight(u'{}'.format(
'Unable to add the "' + self.__computerName + '" Computer account. The error was: ' + c.last_error))
c.unbind()
85 changes: 85 additions & 0 deletions cme/modules/comp_desc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
import sys

class CMEModule:
'''
Module by CyberCelt: @Cyb3rC3lt
Initial module:
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
'''

name = 'comp-desc'
description = 'Retrieves computers containing the specified description'
supported_protocols = ['ldap']
opsec_safe = True
multiple_hosts = False

def options(self, context, module_options):
'''
comp-desc: Specify comp-desc to call the module
DESC: Specify the DESC option to enter your description text to search for
Usage: cme ldap $DC-IP -u Username -p Password -M comp-desc -o DESC="server"
cme ldap $DC-IP -u Username -p Password -M comp-desc -o DESC="XP"
'''

self.DESC = ''

if 'DESC' in module_options:
self.DESC = module_options['DESC']
else:
context.log.error('DESC option is required!')
exit(1)

def on_login(self, context, connection):

# Building the search filter
searchFilter = "(&(objectCategory=computer)(operatingSystem=*"+self.DESC+"*))"

try:
context.log.debug('Search Filter=%s' % searchFilter)
resp = connection.ldapConnection.search(searchFilter=searchFilter,
attributes=['dNSHostName','operatingSystem'],
sizeLimit=0)
except ldap_impacket.LDAPSearchError as e:
if e.getErrorString().find('sizeLimitExceeded') >= 0:
context.log.debug('sizeLimitExceeded exception caught, giving up and processing the data received')
resp = e.getAnswers()
pass
else:
logging.debug(e)
return False

answers = []
context.log.debug('Total no. of records returned %d' % len(resp))
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
dNSHostName = ''
operatingSystem = ''
try:
for attribute in item['attributes']:
if str(attribute['type']) == 'dNSHostName':
dNSHostName = str(attribute['vals'][0])
elif str(attribute['type']) == 'operatingSystem':
operatingSystem = attribute['vals'][0]
if dNSHostName != '' and operatingSystem != '':
answers.append([dNSHostName,operatingSystem])
except Exception as e:
context.log.debug("Exception:", exc_info=True)
context.log.debug('Skipping item, cannot process due to error %s' % str(e))
pass
if len(answers) > 0:
context.log.success('Found the following computers: ')
for answer in answers:
try:
IP = socket.gethostbyname(answer[0])
context.log.highlight(u'{} ({}) ({})'.format(answer[0],answer[1],IP))
context.log.debug('IP found')
except socket.gaierror as e:
context.log.debug('Missing IP')
context.log.highlight(u'{} ({}) ({})'.format(answer[0],answer[1],"No IP Found"))
else:
context.log.success('Unable to find any computers with the description "' + self.DESC + '"')
Loading

0 comments on commit 5d2a9eb

Please sign in to comment.