Exemple #1
0
    async def open(self):
        if not self.dce:
            await rr(self.connect())

        await rr(self.dce.bind(drsuapi.MSRPC_UUID_DRSUAPI))
        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, _ = await rr(self.dce.request(request))

        # Let's dig into the answer to check the dwReplEpoch. This field should match the one we send as part of
        # 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
            if logger.level == logging.DEBUG:
                logger.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, _ = await rr(self.dce.request(request))

        self.handle = resp['phDrs']

        # Now let's get the NtdsDsaObjectGuid UUID to use when querying NCChanges
        resp, _ = await rr(
            drsuapi.hDRSDomainControllerInfo(self.dce, self.handle,
                                             self.domainname, 2))
        if logger.level == logging.DEBUG:
            logger.debug('DRSDomainControllerInfo() answer %s' % resp.dump())

        if resp['pmsgOut']['V2']['cItems'] > 0:
            self.__NtdsDsaObjectGuid = resp['pmsgOut']['V2']['rItems'][0][
                'NtdsDsaObjectGuid']
        else:
            logger.error("Couldn't get DC info for domain %s" %
                         self.domainname)
            raise Exception('Fatal, aborting!')

        return True, None
Exemple #2
0
    async def __handle_smb_in(self):
        """
		Waits from SMB messages from the NetBIOSTransport in_queue, and fills the connection table.
		This function started automatically when calling connect.
		"""
        try:
            while True:
                msg, err = await self.netbios_transport.in_queue.get()
                self.activity_at = datetime.datetime.utcnow()
                if err is not None:
                    logger.error(
                        '__handle_smb_in got error from transport layer %s' %
                        err)
                    #setting all outstanding events to finished
                    for mid in self.OutstandingResponsesEvent:
                        self.OutstandingResponses[mid] = None
                        self.OutstandingResponsesEvent[mid].set()

                    await self.terminate()
                    return

                logger.log(
                    1, '__handle_smb_in got new message with Id %s' %
                    msg.header.MessageId)

                if isinstance(msg, SMB2Transform):
                    #message is encrypted
                    #this point we should decrypt it and only store the decrypted part in the OutstandingResponses table
                    #but for now we just thropw exception bc encryption is not implemented
                    raise Exception(
                        'Encrypted SMBv2 message recieved, but encryption is not yet supported!'
                    )

                if msg.header.Status == NTStatus.PENDING:
                    self.pending_table[msg.header.MessageId] = SMBPendingMsg(
                        msg.header.MessageId, self.OutstandingResponses,
                        self.OutstandingResponsesEvent)
                    await self.pending_table[msg.header.MessageId].run()
                    continue

                if msg.header.MessageId in self.pending_table:
                    await self.pending_table[msg.header.MessageId].stop()
                    del self.pending_table[msg.header.MessageId]

                self.OutstandingResponses[msg.header.MessageId] = msg
                if msg.header.MessageId in self.OutstandingResponsesEvent:
                    self.OutstandingResponsesEvent[msg.header.MessageId].set()
                else:

                    #here we are loosing messages, the functionality for "PENDING" and "SHARING_VIOLATION" should be implemented
                    continue
        except asyncio.CancelledError:
            #the SMB connection is terminating
            return
        except:
            logger.exception('__handle_smb_in')
Exemple #3
0
    async def keepalive(self):
        """
		Sends an echo message every X seconds to the server to keep the channel open
		"""
        try:
            sleep_time = 10
            if self.target.timeout < 0:
                return
            elif self.target.timeout > 0:
                sleep_time = max(self.target.timeout - 1, sleep_time)
            while True:
                await asyncio.sleep(sleep_time)
                if (datetime.datetime.utcnow() -
                        self.activity_at).seconds > sleep_time:
                    await self.echo()

        except asyncio.CancelledError:
            return
        except Exception as e:
            logger.error('Keepalive failed! Server probably disconnected!')
            await self.disconnect()
Exemple #4
0
    async def negotiate(self):
        """
		Initiates protocol negotiation.
		First we send an SMB_COM_NEGOTIATE_REQ with our supported dialects
		"""

        #let's construct an SMBv1 SMB_COM_NEGOTIATE_REQ packet
        header = SMBHeader()
        header.Command = SMBCommand.SMB_COM_NEGOTIATE
        header.Status = NTStatus.SUCCESS
        header.Flags = 0
        header.Flags2 = SMBHeaderFlags2Enum.SMB_FLAGS2_UNICODE

        command = SMB_COM_NEGOTIATE_REQ()
        command.Dialects = ['SMB 2.???']

        msg = SMBMessage(header, command)
        message_id = await self.sendSMB(msg)
        #recieveing reply, should be version2, because currently we dont support v1 :(
        rply = await self.recvSMB(message_id)  #negotiate MessageId should be 1
        if rply.header.Status == NTStatus.SUCCESS:
            if isinstance(rply, SMB2Message):
                if rply.command.DialectRevision == NegotiateDialects.WILDCARD:
                    command = NEGOTIATE_REQ()
                    command.SecurityMode = NegotiateSecurityMode.SMB2_NEGOTIATE_SIGNING_ENABLED | NegotiateSecurityMode.SMB2_NEGOTIATE_SIGNING_REQUIRED
                    command.Capabilities = 0
                    command.ClientGuid = self.ClientGUID
                    command.Dialects = self.dialects

                    header = SMB2Header_SYNC()
                    header.Command = SMB2Command.NEGOTIATE
                    header.CreditReq = 0

                    msg = SMB2Message(header, command)
                    message_id = await self.sendSMB(msg)
                    rply = await self.recvSMB(
                        message_id)  #negotiate MessageId should be 1
                    if rply.header.Status != NTStatus.SUCCESS:
                        print('session got reply!')
                        print(rply)
                        raise Exception(
                            'session_setup_1 (authentication probably failed) reply: %s'
                            % rply.header.Status)

                if rply.command.DialectRevision not in self.supported_dialects:
                    raise SMBUnsupportedDialectSelected()

                self.selected_dialect = rply.command.DialectRevision
                self.signing_required = NegotiateSecurityMode.SMB2_NEGOTIATE_SIGNING_ENABLED in rply.command.SecurityMode
                logger.log(
                    1, 'Server selected dialect: %s' % self.selected_dialect)

                self.MaxTransactSize = min(0x100000,
                                           rply.command.MaxTransactSize)
                self.MaxReadSize = min(0x100000, rply.command.MaxReadSize)
                self.MaxWriteSize = min(0x100000, rply.command.MaxWriteSize)
                self.ServerGuid = rply.command.ServerGuid
                self.SupportsMultiChannel = NegotiateCapabilities.MULTI_CHANNEL in rply.command.Capabilities

            else:
                logger.error(
                    'Server choose SMB v1 which is not supported currently')
                raise SMBUnsupportedSMBVersion()

        else:
            print('session got reply!')
            print(rply)
            raise Exception(
                'session_setup_1 (authentication probably failed) reply: %s' %
                rply.header.Status)

        self.status = SMBConnectionStatus.SESSIONSETUP
Exemple #5
0
    async def get_user_secrets(self, username):
        ra = {
            'userPrincipalName': '1.2.840.113556.1.4.656',
            'sAMAccountName': '1.2.840.113556.1.4.221',
            'unicodePwd': '1.2.840.113556.1.4.90',
            'dBCSPwd': '1.2.840.113556.1.4.55',
            'ntPwdHistory': '1.2.840.113556.1.4.94',
            'lmPwdHistory': '1.2.840.113556.1.4.160',
            'supplementalCredentials': '1.2.840.113556.1.4.125',
            'objectSid': '1.2.840.113556.1.4.146',
            'pwdLastSet': '1.2.840.113556.1.4.96',
            'userAccountControl': '1.2.840.113556.1.4.8'
        }
        formatOffered = drsuapi.DS_NT4_ACCOUNT_NAME_SANS_DOMAIN

        crackedName, _ = await rr(
            self.DRSCrackNames(formatOffered,
                               drsuapi.DS_NAME_FORMAT.DS_UNIQUE_ID_NAME,
                               name=username))

        ###### TODO: CHECKS HERE

        #guid = GUID.from_string(crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['pName'][:-1][1:-1])
        guid = crackedName['pmsgOut']['V1']['pResult']['rItems'][0][
            'pName'][:-1][1:-1]

        userRecord, _ = await rr(self.DRSGetNCChanges(guid, ra))

        replyVersion = 'V%d' % userRecord['pdwOutVersion']
        if userRecord['pmsgOut'][replyVersion]['cNumObjects'] == 0:
            raise Exception('DRSGetNCChanges didn\'t return any object!')

        #print(userRecord.dump())
        #print(userRecord['pmsgOut'][replyVersion]['PrefixTableSrc']['pPrefixEntry'])

        record = userRecord
        prefixTable = userRecord['pmsgOut'][replyVersion]['PrefixTableSrc'][
            'pPrefixEntry']
        ##### decryption!
        logger.debug('Decrypting hash for user: %s' %
                     record['pmsgOut'][replyVersion]['pNC']['StringName'][:-1])

        us = SMBUserSecrets()
        user_properties = None

        rid = int.from_bytes(record['pmsgOut'][replyVersion]['pObjects']
                             ['Entinf']['pName']['Sid'][-4:],
                             'little',
                             signed=False)

        for attr in record['pmsgOut'][replyVersion]['pObjects']['Entinf'][
                'AttrBlock']['pAttr']:

            try:
                attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp'])
                LOOKUP_TABLE = self.ATTRTYP_TO_ATTID
            except Exception as e:
                logger.error(
                    'Failed to execute OidFromAttid with error %s, fallbacking to fixed table'
                    % e)
                logger.error('Exception', exc_info=True)
                # Fallbacking to fixed table and hope for the best
                attId = attr['attrTyp']
                LOOKUP_TABLE = self.NAME_TO_ATTRTYP

            if attId == LOOKUP_TABLE['dBCSPwd']:
                if attr['AttrVal']['valCount'] > 0:
                    encrypteddBCSPwd = b''.join(
                        attr['AttrVal']['pAVal'][0]['pVal'])
                    encryptedLMHash = drsuapi.DecryptAttributeValue(
                        self.dce.get_session_key(), encrypteddBCSPwd)
                    us.lm_hash = drsuapi.removeDESLayer(encryptedLMHash, rid)
                else:
                    us.lm_hash = bytes.fromhex(
                        'aad3b435b51404eeaad3b435b51404ee')

            elif attId == LOOKUP_TABLE['unicodePwd']:
                if attr['AttrVal']['valCount'] > 0:
                    encryptedUnicodePwd = b''.join(
                        attr['AttrVal']['pAVal'][0]['pVal'])
                    encryptedNTHash = drsuapi.DecryptAttributeValue(
                        self.dce.get_session_key(), encryptedUnicodePwd)
                    us.nt_hash = drsuapi.removeDESLayer(encryptedNTHash, rid)
                else:
                    us.nt_hash = bytes.fromhex(
                        '31d6cfe0d16ae931b73c59d7e0c089c0')

            elif attId == LOOKUP_TABLE['userPrincipalName']:
                if attr['AttrVal']['valCount'] > 0:
                    try:
                        us.domain = b''.join(
                            attr['AttrVal']['pAVal'][0]['pVal']).decode(
                                'utf-16le').split('@')[-1]
                    except:
                        us.domain = None
                else:
                    us.domain = None

            elif attId == LOOKUP_TABLE['sAMAccountName']:
                if attr['AttrVal']['valCount'] > 0:
                    try:
                        us.username = b''.join(attr['AttrVal']['pAVal'][0]
                                               ['pVal']).decode('utf-16le')
                    except Exception as e:
                        logger.error('Cannot get sAMAccountName for %s' %
                                     record['pmsgOut'][replyVersion]['pNC']
                                     ['StringName'][:-1])
                        us.username = '******'
                else:
                    logger.error('Cannot get sAMAccountName for %s' %
                                 record['pmsgOut'][replyVersion]['pNC']
                                 ['StringName'][:-1])
                    us.username = '******'

            elif attId == LOOKUP_TABLE['objectSid']:
                if attr['AttrVal']['valCount'] > 0:
                    us.object_sid = SID.from_bytes(b''.join(
                        attr['AttrVal']['pAVal'][0]['pVal']))
                else:
                    logger.error('Cannot get objectSid for %s' %
                                 record['pmsgOut'][replyVersion]['pNC']
                                 ['StringName'][:-1])
                    us.object_sid = rid
            elif attId == LOOKUP_TABLE['pwdLastSet']:
                if attr['AttrVal']['valCount'] > 0:
                    try:

                        us.pwd_last_set = FILETIME.from_bytes(
                            b''.join(attr['AttrVal']['pAVal'][0]
                                     ['pVal'])).datetime.isoformat()
                    except Exception as e:

                        logger.error('Cannot get pwdLastSet for %s' %
                                     record['pmsgOut'][replyVersion]['pNC']
                                     ['StringName'][:-1])
                        us.pwd_last_set = None

            elif attId == LOOKUP_TABLE['userAccountControl']:
                if attr['AttrVal']['valCount'] > 0:
                    us.user_account_status = int.from_bytes(b''.join(
                        attr['AttrVal']['pAVal'][0]['pVal']),
                                                            'little',
                                                            signed=False)
                else:
                    us.user_account_status = None

            if attId == LOOKUP_TABLE['lmPwdHistory']:
                if attr['AttrVal']['valCount'] > 0:
                    encryptedLMHistory = b''.join(
                        attr['AttrVal']['pAVal'][0]['pVal'])
                    tmpLMHistory = drsuapi.DecryptAttributeValue(
                        self.dce.get_session_key(), encryptedLMHistory)
                    for i in range(0, len(tmpLMHistory) // 16):
                        LMHashHistory = drsuapi.removeDESLayer(
                            tmpLMHistory[i * 16:(i + 1) * 16], rid)
                        us.lm_history.append(LMHashHistory)
                else:
                    logger.debug('No lmPwdHistory for user %s' %
                                 record['pmsgOut'][replyVersion]['pNC']
                                 ['StringName'][:-1])
            elif attId == LOOKUP_TABLE['ntPwdHistory']:
                if attr['AttrVal']['valCount'] > 0:
                    encryptedNTHistory = b''.join(
                        attr['AttrVal']['pAVal'][0]['pVal'])
                    tmpNTHistory = drsuapi.DecryptAttributeValue(
                        self.dce.get_session_key(), encryptedNTHistory)
                    for i in range(0, len(tmpNTHistory) // 16):
                        NTHashHistory = drsuapi.removeDESLayer(
                            tmpNTHistory[i * 16:(i + 1) * 16], rid)
                        us.nt_history.append(NTHashHistory)
                else:
                    logger.debug('No ntPwdHistory for user %s' %
                                 record['pmsgOut'][replyVersion]['pNC']
                                 ['StringName'][:-1])

            elif attId == LOOKUP_TABLE['supplementalCredentials']:
                if attr['AttrVal']['valCount'] > 0:
                    blob = b''.join(attr['AttrVal']['pAVal'][0]['pVal'])
                    supplementalCredentials = drsuapi.DecryptAttributeValue(
                        self.dce.get_session_key(), blob)
                    if len(supplementalCredentials) < 24:
                        supplementalCredentials = None

                    else:
                        try:
                            user_properties = samr.USER_PROPERTIES(
                                supplementalCredentials)
                        except Exception as e:
                            # On some old w2k3 there might be user properties that don't
                            # match [MS-SAMR] structure, discarding them
                            pass

        if user_properties is not None:
            propertiesData = user_properties['UserProperties']
            for propertyCount in range(user_properties['PropertyCount']):
                userProperty = samr.USER_PROPERTY(propertiesData)
                propertiesData = propertiesData[len(userProperty):]
                # For now, we will only process Newer Kerberos Keys and CLEARTEXT
                if userProperty['PropertyName'].decode(
                        'utf-16le') == 'Primary:Kerberos-Newer-Keys':
                    propertyValueBuffer = bytes.fromhex(
                        userProperty['PropertyValue'].decode())
                    kerbStoredCredentialNew = samr.KERB_STORED_CREDENTIAL_NEW(
                        propertyValueBuffer)
                    data = kerbStoredCredentialNew['Buffer']
                    for credential in range(
                            kerbStoredCredentialNew['CredentialCount']):
                        keyDataNew = samr.KERB_KEY_DATA_NEW(data)
                        data = data[len(keyDataNew):]
                        keyValue = propertyValueBuffer[
                            keyDataNew['KeyOffset']:][:keyDataNew['KeyLength']]

                        if keyDataNew['KeyType'] in self.KERBEROS_TYPE:
                            answer = (
                                self.KERBEROS_TYPE[keyDataNew['KeyType']],
                                keyValue)
                        else:
                            answer = (hex(keyDataNew['KeyType']), keyValue)
                        # We're just storing the keys, not printing them, to make the output more readable
                        # This is kind of ugly... but it's what I came up with tonight to get an ordered
                        # set :P. Better ideas welcomed ;)
                        us.kerberos_keys.append(answer)
                elif userProperty['PropertyName'].decode(
                        'utf-16le') == 'Primary:CLEARTEXT':
                    # [MS-SAMR] 3.1.1.8.11.5 Primary:CLEARTEXT Property
                    # This credential type is the cleartext password. The value format is the UTF-16 encoded cleartext password.
                    # SkelSec: well, almost. actually the property is the hex-encoded bytes of an UTF-16LE encoded plaintext string
                    encoded_pw = bytes.fromhex(
                        userProperty['PropertyValue'].decode('ascii'))
                    try:
                        answer = encoded_pw.decode('utf-16le')
                    except UnicodeDecodeError:
                        # This could be because we're decoding a machine password. Printing it hex
                        answer = encoded_pw.decode('utf-8')

                    us.cleartext_pwds.append(answer)

        return us, None