mirror of
https://github.com/sploutchy/impacket
synced 2026-06-08 17:32:01 +00:00
623 lines
30 KiB
Python
Executable File
623 lines
30 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# Impacket - Collection of Python classes for working with network protocols.
|
|
#
|
|
# SECUREAUTH LABS. Copyright (C) 2022 SecureAuth Corporation. All rights reserved.
|
|
#
|
|
# This software is provided under a slightly modified version
|
|
# of the Apache Software License. See the accompanying LICENSE file
|
|
# for more information.
|
|
#
|
|
# Description:
|
|
# This script will add a computer account to the domain and set its password.
|
|
# Allows to use SAMR over SMB (this way is used by modern Windows computer when
|
|
# adding machines through the GUI) and LDAPS.
|
|
# Plain LDAP is not supported, as it doesn't allow setting the password.
|
|
#
|
|
# Author:
|
|
# JaGoTu (@jagotu)
|
|
#
|
|
# Reference for:
|
|
# SMB, SAMR, LDAP
|
|
#
|
|
# ToDo:
|
|
# [ ]: Complete the process of joining a client computer to a domain via the SAMR protocol
|
|
#
|
|
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
from __future__ import unicode_literals
|
|
|
|
from impacket import version
|
|
from impacket.examples import logger
|
|
from impacket.examples.utils import parse_credentials
|
|
from impacket.dcerpc.v5 import samr, epm, transport
|
|
from impacket.spnego import SPNEGO_NegTokenInit, TypesMech
|
|
|
|
import ldap3
|
|
import argparse
|
|
import logging
|
|
import sys
|
|
import string
|
|
import random
|
|
import ssl
|
|
from binascii import unhexlify
|
|
|
|
|
|
class ADDCOMPUTER:
|
|
def __init__(self, username, password, domain, cmdLineOptions):
|
|
self.options = cmdLineOptions
|
|
self.__username = username
|
|
self.__password = password
|
|
self.__domain = domain
|
|
self.__lmhash = ''
|
|
self.__nthash = ''
|
|
self.__hashes = cmdLineOptions.hashes
|
|
self.__aesKey = cmdLineOptions.aesKey
|
|
self.__doKerberos = cmdLineOptions.k
|
|
self.__target = cmdLineOptions.dc_host
|
|
self.__kdcHost = cmdLineOptions.dc_host
|
|
self.__computerName = cmdLineOptions.computer_name
|
|
self.__computerPassword = cmdLineOptions.computer_pass
|
|
self.__method = cmdLineOptions.method
|
|
self.__port = cmdLineOptions.port
|
|
self.__domainNetbios = cmdLineOptions.domain_netbios
|
|
self.__noAdd = cmdLineOptions.no_add
|
|
self.__delete = cmdLineOptions.delete
|
|
self.__targetIp = cmdLineOptions.dc_ip
|
|
self.__baseDN = cmdLineOptions.baseDN
|
|
self.__computerGroup = cmdLineOptions.computer_group
|
|
|
|
if self.__targetIp is not None:
|
|
self.__kdcHost = self.__targetIp
|
|
|
|
if self.__method not in ['SAMR', 'LDAPS']:
|
|
raise ValueError("Unsupported method %s" % self.__method)
|
|
|
|
if self.__doKerberos and cmdLineOptions.dc_host is None:
|
|
raise ValueError("Kerberos auth requires DNS name of the target DC. Use -dc-host.")
|
|
|
|
if self.__method == 'LDAPS' and not '.' in self.__domain:
|
|
logging.warning('\'%s\' doesn\'t look like a FQDN. Generating baseDN will probably fail.' % self.__domain)
|
|
|
|
if cmdLineOptions.hashes is not None:
|
|
self.__lmhash, self.__nthash = cmdLineOptions.hashes.split(':')
|
|
|
|
if self.__computerName is None:
|
|
if self.__noAdd:
|
|
raise ValueError("You have to provide a computer name when using -no-add.")
|
|
elif self.__delete:
|
|
raise ValueError("You have to provide a computer name when using -delete.")
|
|
else:
|
|
if self.__computerName[-1] != '$':
|
|
self.__computerName += '$'
|
|
|
|
if self.__computerPassword is None:
|
|
self.__computerPassword = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(32))
|
|
|
|
if self.__target is None:
|
|
if not '.' in self.__domain:
|
|
logging.warning('No DC host set and \'%s\' doesn\'t look like a FQDN. DNS resolution of short names will probably fail.' % self.__domain)
|
|
self.__target = self.__domain
|
|
|
|
if self.__port is None:
|
|
if self.__method == 'SAMR':
|
|
self.__port = 445
|
|
elif self.__method == 'LDAPS':
|
|
self.__port = 636
|
|
|
|
if self.__domainNetbios is None:
|
|
self.__domainNetbios = self.__domain
|
|
|
|
if self.__method == 'LDAPS' and self.__baseDN is None:
|
|
# Create the baseDN
|
|
domainParts = self.__domain.split('.')
|
|
self.__baseDN = ''
|
|
for i in domainParts:
|
|
self.__baseDN += 'dc=%s,' % i
|
|
# Remove last ','
|
|
self.__baseDN = self.__baseDN[:-1]
|
|
|
|
if self.__method == 'LDAPS' and self.__computerGroup is None:
|
|
self.__computerGroup = 'CN=Computers,' + self.__baseDN
|
|
|
|
|
|
|
|
def run_samr(self):
|
|
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)
|
|
self.doSAMRAdd(rpctransport)
|
|
|
|
def run_ldaps(self):
|
|
connectTo = self.__target
|
|
if self.__targetIp is not None:
|
|
connectTo = self.__targetIp
|
|
try:
|
|
user = '%s\\%s' % (self.__domain, self.__username)
|
|
tls = ldap3.Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1_2)
|
|
try:
|
|
ldapServer = ldap3.Server(connectTo, use_ssl=True, port=self.__port, get_info=ldap3.ALL, tls=tls)
|
|
if self.__doKerberos:
|
|
ldapConn = ldap3.Connection(ldapServer)
|
|
self.LDAP3KerberosLogin(ldapConn, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
|
|
self.__aesKey, kdcHost=self.__kdcHost)
|
|
elif self.__hashes is not None:
|
|
ldapConn = ldap3.Connection(ldapServer, user=user, password=self.__hashes, authentication=ldap3.NTLM)
|
|
ldapConn.bind()
|
|
else:
|
|
ldapConn = ldap3.Connection(ldapServer, user=user, password=self.__password, authentication=ldap3.NTLM)
|
|
ldapConn.bind()
|
|
|
|
except ldap3.core.exceptions.LDAPSocketOpenError:
|
|
#try tlsv1
|
|
tls = ldap3.Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1)
|
|
ldapServer = ldap3.Server(connectTo, use_ssl=True, port=self.__port, get_info=ldap3.ALL, tls=tls)
|
|
if self.__doKerberos:
|
|
ldapConn = ldap3.Connection(ldapServer)
|
|
self.LDAP3KerberosLogin(ldapConn, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
|
|
self.__aesKey, kdcHost=self.__kdcHost)
|
|
elif self.__hashes is not None:
|
|
ldapConn = ldap3.Connection(ldapServer, user=user, password=self.__hashes, authentication=ldap3.NTLM)
|
|
ldapConn.bind()
|
|
else:
|
|
ldapConn = ldap3.Connection(ldapServer, user=user, password=self.__password, authentication=ldap3.NTLM)
|
|
ldapConn.bind()
|
|
|
|
|
|
|
|
if self.__noAdd or self.__delete:
|
|
if not self.LDAPComputerExists(ldapConn, self.__computerName):
|
|
raise Exception("Account %s not found in %s!" % (self.__computerName, self.__baseDN))
|
|
|
|
computer = self.LDAPGetComputer(ldapConn, self.__computerName)
|
|
|
|
if self.__delete:
|
|
res = ldapConn.delete(computer.entry_dn)
|
|
message = "delete"
|
|
else:
|
|
res = ldapConn.modify(computer.entry_dn, {'unicodePwd': [(ldap3.MODIFY_REPLACE, ['"{}"'.format(self.__computerPassword).encode('utf-16-le')])]})
|
|
message = "set password for"
|
|
|
|
|
|
if not res:
|
|
if ldapConn.result['result'] == ldap3.core.results.RESULT_INSUFFICIENT_ACCESS_RIGHTS:
|
|
raise Exception("User %s doesn't have right to %s %s!" % (self.__username, message, self.__computerName))
|
|
else:
|
|
raise Exception(str(ldapConn.result))
|
|
else:
|
|
if self.__noAdd:
|
|
logging.info("Succesfully set password of %s to %s." % (self.__computerName, self.__computerPassword))
|
|
else:
|
|
logging.info("Succesfully deleted %s." % self.__computerName)
|
|
|
|
else:
|
|
if self.__computerName is not None:
|
|
if self.LDAPComputerExists(ldapConn, self.__computerName):
|
|
raise Exception("Account %s already exists! If you just want to set a password, use -no-add." % self.__computerName)
|
|
else:
|
|
while True:
|
|
self.__computerName = self.generateComputerName()
|
|
if not self.LDAPComputerExists(ldapConn, self.__computerName):
|
|
break
|
|
|
|
|
|
computerHostname = self.__computerName[:-1]
|
|
computerDn = ('CN=%s,%s' % (computerHostname, self.__computerGroup))
|
|
|
|
# Default computer SPNs
|
|
spns = [
|
|
'HOST/%s' % computerHostname,
|
|
'HOST/%s.%s' % (computerHostname, self.__domain),
|
|
'RestrictedKrbHost/%s' % computerHostname,
|
|
'RestrictedKrbHost/%s.%s' % (computerHostname, self.__domain),
|
|
]
|
|
ucd = {
|
|
'dnsHostName': '%s.%s' % (computerHostname, self.__domain),
|
|
'userAccountControl': 0x1000,
|
|
'servicePrincipalName': spns,
|
|
'sAMAccountName': self.__computerName,
|
|
'unicodePwd': ('"%s"' % self.__computerPassword).encode('utf-16-le')
|
|
}
|
|
|
|
res = ldapConn.add(computerDn, ['top','person','organizationalPerson','user','computer'], ucd)
|
|
if not res:
|
|
if ldapConn.result['result'] == ldap3.core.results.RESULT_UNWILLING_TO_PERFORM:
|
|
error_code = int(ldapConn.result['message'].split(':')[0].strip(), 16)
|
|
if error_code == 0x216D:
|
|
raise Exception("User %s machine quota exceeded!" % self.__username)
|
|
else:
|
|
raise Exception(str(ldapConn.result))
|
|
elif ldapConn.result['result'] == ldap3.core.results.RESULT_INSUFFICIENT_ACCESS_RIGHTS:
|
|
raise Exception("User %s doesn't have right to create a machine account!" % self.__username)
|
|
else:
|
|
raise Exception(str(ldapConn.result))
|
|
else:
|
|
logging.info("Successfully added machine account %s with password %s." % (self.__computerName, self.__computerPassword))
|
|
except Exception as e:
|
|
if logging.getLogger().level == logging.DEBUG:
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
logging.critical(str(e))
|
|
|
|
|
|
def LDAPComputerExists(self, connection, computerName):
|
|
connection.search(self.__baseDN, '(sAMAccountName=%s)' % computerName)
|
|
return len(connection.entries) ==1
|
|
|
|
def LDAPGetComputer(self, connection, computerName):
|
|
connection.search(self.__baseDN, '(sAMAccountName=%s)' % computerName)
|
|
return connection.entries[0]
|
|
|
|
def LDAP3KerberosLogin(self, connection, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None,
|
|
TGS=None, useCache=True):
|
|
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 = unhexlify(lmhash)
|
|
nthash = 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
|
|
|
|
targetName = 'ldap/%s' % self.__target
|
|
if useCache:
|
|
domain, user, TGT, TGS = CCache.parseFile(domain, user, targetName)
|
|
|
|
# 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(targetName, 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
|
|
# try to open connection if closed
|
|
if connection.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 generateComputerName(self):
|
|
return 'DESKTOP-' + (''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(8)) + '$')
|
|
|
|
def doSAMRAdd(self, rpctransport):
|
|
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:
|
|
logging.critical("This server provides multiple domains and '%s' isn't one of them.", self.__domainNetbios)
|
|
logging.critical("Available domain(s):")
|
|
for domain in domains:
|
|
logging.error(" * %s" % domain['Name'])
|
|
logging.critical("Consider using -domain-netbios argument to specify which one you meant.")
|
|
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:
|
|
raise Exception("Account %s not found in domain %s!" % (self.__computerName, selectedDomain))
|
|
else:
|
|
raise
|
|
|
|
userRID = checkForUser['RelativeIds']['Element'][0]
|
|
if self.__delete:
|
|
access = samr.DELETE
|
|
message = "delete"
|
|
else:
|
|
access = samr.USER_FORCE_PASSWORD_CHANGE
|
|
message = "set password for"
|
|
try:
|
|
openUser = samr.hSamrOpenUser(dce, domainHandle, access, userRID)
|
|
userHandle = openUser['UserHandle']
|
|
except samr.DCERPCSessionError as e:
|
|
if e.error_code == 0xc0000022:
|
|
raise Exception("User %s doesn't have right to %s %s!" % (self.__username, message, self.__computerName))
|
|
else:
|
|
raise
|
|
else:
|
|
if self.__computerName is not None:
|
|
try:
|
|
checkForUser = samr.hSamrLookupNamesInDomain(dce, domainHandle, [self.__computerName])
|
|
raise Exception("Account %s already exists! If you just want to set a password, use -no-add." % self.__computerName)
|
|
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,)
|
|
except samr.DCERPCSessionError as e:
|
|
if e.error_code == 0xc0000022:
|
|
raise Exception("User %s doesn't have right to create a machine account!" % self.__username)
|
|
elif e.error_code == 0xc00002e7:
|
|
raise Exception("User %s machine quota exceeded!" % self.__username)
|
|
else:
|
|
raise
|
|
|
|
userHandle = createUser['UserHandle']
|
|
|
|
if self.__delete:
|
|
samr.hSamrDeleteUser(dce, userHandle)
|
|
logging.info("Successfully deleted %s." % self.__computerName)
|
|
userHandle = None
|
|
else:
|
|
samr.hSamrSetPasswordInternal4New(dce, userHandle, self.__computerPassword)
|
|
if self.__noAdd:
|
|
logging.info("Successfully set password of %s to %s." % (self.__computerName, self.__computerPassword))
|
|
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)
|
|
logging.info("Successfully added machine account %s with password %s." % (self.__computerName, self.__computerPassword))
|
|
|
|
except Exception as e:
|
|
if logging.getLogger().level == logging.DEBUG:
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
logging.critical(str(e))
|
|
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 run(self):
|
|
if self.__method == 'SAMR':
|
|
self.run_samr()
|
|
elif self.__method == 'LDAPS':
|
|
self.run_ldaps()
|
|
|
|
# Process command-line arguments.
|
|
if __name__ == '__main__':
|
|
# Init the example's logger theme
|
|
logger.init()
|
|
print((version.BANNER))
|
|
|
|
parser = argparse.ArgumentParser(add_help = True, description = "Adds a computer account to domain")
|
|
|
|
if sys.version_info.major == 2 and sys.version_info.minor == 7 and sys.version_info.micro < 16: #workaround for https://bugs.python.org/issue11874
|
|
parser.add_argument('account', action='store', help='[domain/]username[:password] Account used to authenticate to DC.')
|
|
else:
|
|
parser.add_argument('account', action='store', metavar='[domain/]username[:password]', help='Account used to authenticate to DC.')
|
|
parser.add_argument('-domain-netbios', action='store', metavar='NETBIOSNAME', help='Domain NetBIOS name. Required if the DC has multiple domains.')
|
|
parser.add_argument('-computer-name', action='store', metavar='COMPUTER-NAME$', help='Name of computer to add.'
|
|
'If omitted, a random DESKTOP-[A-Z0-9]{8} will be used.')
|
|
parser.add_argument('-computer-pass', action='store', metavar='password', help='Password to set to computer'
|
|
'If omitted, a random [A-Za-z0-9]{32} will be used.')
|
|
parser.add_argument('-no-add', action='store_true', help='Don\'t add a computer, only set password on existing one.')
|
|
parser.add_argument('-delete', action='store_true', help='Delete an existing computer.')
|
|
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
|
|
parser.add_argument('-method', choices=['SAMR', 'LDAPS'], default='SAMR', help='Method of adding the computer.'
|
|
'SAMR works over SMB.'
|
|
'LDAPS has some certificate requirements'
|
|
'and isn\'t always available.')
|
|
|
|
|
|
parser.add_argument('-port', type=int, choices=[139, 445, 636],
|
|
help='Destination port to connect to. SAMR defaults to 445, LDAPS to 636.')
|
|
|
|
group = parser.add_argument_group('LDAP')
|
|
group.add_argument('-baseDN', action='store', metavar='DC=test,DC=local', help='Set baseDN for LDAP.'
|
|
'If ommited, the domain part (FQDN) '
|
|
'specified in the account parameter will be used.')
|
|
group.add_argument('-computer-group', action='store', metavar='CN=Computers,DC=test,DC=local', help='Group to which the account will be added.'
|
|
'If omitted, CN=Computers will be used,')
|
|
|
|
group = parser.add_argument_group('authentication')
|
|
|
|
group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
|
|
group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
|
|
group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file '
|
|
'(KRB5CCNAME) based on account parameters. If valid credentials '
|
|
'cannot be found, it will use the ones specified in the command '
|
|
'line')
|
|
group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication '
|
|
'(128 or 256 bits)')
|
|
group.add_argument('-dc-host', action='store',metavar = "hostname", help='Hostname of the domain controller to use. '
|
|
'If ommited, the domain part (FQDN) '
|
|
'specified in the account parameter will be used')
|
|
group.add_argument('-dc-ip', action='store',metavar = "ip", help='IP of the domain controller to use. '
|
|
'Useful if you can\'t translate the FQDN.'
|
|
'specified in the account parameter will be used')
|
|
|
|
|
|
if len(sys.argv)==1:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
options = parser.parse_args()
|
|
|
|
if options.debug is True:
|
|
logging.getLogger().setLevel(logging.DEBUG)
|
|
# Print the Library's installation path
|
|
logging.debug(version.getInstallationPath())
|
|
else:
|
|
logging.getLogger().setLevel(logging.INFO)
|
|
|
|
domain, username, password = parse_credentials(options.account)
|
|
|
|
try:
|
|
if domain is None or domain == '':
|
|
logging.critical('Domain should be specified!')
|
|
sys.exit(1)
|
|
|
|
if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None:
|
|
from getpass import getpass
|
|
password = getpass("Password:")
|
|
|
|
if options.aesKey is not None:
|
|
options.k = True
|
|
|
|
|
|
executer = ADDCOMPUTER(username, password, domain, options)
|
|
executer.run()
|
|
except Exception as e:
|
|
if logging.getLogger().level == logging.DEBUG:
|
|
import traceback
|
|
traceback.print_exc()
|
|
print(str(e))
|