# SECUREAUTH LABS. Copyright 2020 SecureAuth Corporation. All rights reserved. # # This software is provided under a slightly modified version # of the Apache Software License. See the accompanying LICENSE file # for more information. # # Description: # [MS-NSPI]: Name Service Provider Interface (NSPI) Protocol # [MS-OXNSPI]: Exchange Server Name Service Provider Interface (NSPI) Protocol # # Authors: # Arseniy Sharoglazov / Positive Technologies (https://www.ptsecurity.com/) # # Tested for MS-OXNSPI, some operation may not work for MS-NSPI # # ToDo: # [ ] Test commented NDRCALLs and write helpers for them # [ ] Test restriction structures from __future__ import division from __future__ import print_function from struct import unpack from datetime import datetime from six import PY2 import binascii from impacket import hresult_errors, mapi_constants, uuid from impacket.uuid import EMPTY_UUID from impacket.structure import Structure from impacket.dcerpc.v5.dtypes import NULL, STR, DWORD, LPDWORD, UUID, PUUID, LONG, ULONG, \ FILETIME, PFILETIME, BYTE, SHORT, LPSTR, LPWSTR, USHORT, LPLONG, DWORD_ARRAY from impacket.ldap.ldaptypes import LDAP_SID from impacket.dcerpc.v5.ndr import NDR, NDRCALL, NDRPOINTER, NDRSTRUCT, NDRUNION, \ NDRUniConformantVaryingArray, NDRUniConformantArray, NDRUniVaryingArray from impacket.dcerpc.v5.rpcrt import DCERPCException from impacket.uuid import string_to_bin, uuidtup_to_bin, EMPTY_UUID MSRPC_UUID_NSPI = uuidtup_to_bin(('F5CC5A18-4264-101A-8C59-08002B2F8426', '56.0')) class DCERPCSessionError(DCERPCException): def __str__( self ): key = self.error_code if key in mapi_constants.ERROR_MESSAGES: error_msg_short = mapi_constants.ERROR_MESSAGES[key] return 'NSPI SessionError: code: 0x%x - %s' % (self.error_code, error_msg_short) elif key in hresult_errors.ERROR_MESSAGES: error_msg_short = hresult_errors.ERROR_MESSAGES[key][0] error_msg_verbose = hresult_errors.ERROR_MESSAGES[key][1] return 'NSPI SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose) else: return 'NSPI SessionError: unknown error code: 0x%x' % self.error_code ################################################################################ # STRUCTURES ################################################################################ class handle_t(NDRSTRUCT): structure = ( ('context_handle_attributes',ULONG), ('context_handle_uuid',UUID), ) def __init__(self, data=None, isNDR64=False): NDRSTRUCT.__init__(self, data, isNDR64) self['context_handle_uuid'] = b'\x00'*16 def isNull(self): return self['context_handle_uuid'] == b'\x00'*16 # 2.2.1 Permitted Property Type Values PtypEmbeddedTable = 0x0000000D PtypNull = 0x00000001 PtypUnspecified = 0x00000000 # 2.2.3 Display Type Values DT_MAILUSER = 0x00000000 DT_DISTLIST = 0x00000001 DT_FORUM = 0x00000002 DT_AGENT = 0x00000003 DT_ORGANIZATION = 0x00000004 DT_PRIVATE_DISTLIST = 0x00000005 DT_REMOTE_MAILUSER = 0x00000006 DT_CONTAINER = 0x00000100 DT_TEMPLATE = 0x00000101 DT_ADDRESS_TEMPLATE = 0x00000102 DT_SEARCH = 0x00000200 # 2.2.4 Default Language Code Identifier NSPI_DEFAULT_LOCALE = 0x00000409 # 2.2.5 Required Codepages CP_TELETEX = 0x00004F25 CP_WINUNICODE = 0x000004B0 # 2.2.6.1 Comparison Flags NORM_IGNORECASE = 1 << 0 NORM_IGNORENONSPACE = 1 << 1 NORM_IGNORESYMBOLS = 1 << 2 SORT_STRINGSORT = 1 << 12 NORM_IGNOREKANATYPE = 1 << 16 NORM_IGNOREWIDTH = 1 << 17 # 2.2.7 Permanent Entry ID GUID GUID_NSPI = string_to_bin("C840A7DC-42C0-1A10-B4B9-08002B2FE182") # 2.2.8 Positioning Minimal Entry IDs MID_BEGINNING_OF_TABLE = 0x00000000 MID_END_OF_TABLE = 0x00000002 MID_CURRENT = 0x00000001 # 2.2.9 Ambiguous Name Resolution Minimal Entry IDs MID_UNRESOLVED = 0x00000000 MID_AMBIGUOUS = 0x00000001 MID_RESOLVED = 0x00000002 # 2.2.10 Table Sort Orders SortTypeDisplayName = 0 SortTypePhoneticDisplayName = 0x00000003 SortTypeDisplayName_RO = 0x000003E8 SortTypeDisplayName_W = 0x000003E9 # 2.2.11 NspiBind Flags fAnonymousLogin = 0x00000020 # 2.2.12 Retrieve Property Flags fSkipObjects = 0x00000001 fEphID = 0x00000002 # 2.2.13 NspiGetSpecialTable Flags NspiAddressCreationTemplates = 0x00000002 NspiUnicodeStrings = 0x00000004 # 2.2.14 NspiQueryColumns Flags NspiUnicodeProptypes = 0x80000000 # 2.2.15 NspiGetIDsFromNames Flags NspiVerifyNames = 0x00000002 # 2.2.16 NspiGetTemplateInfo Flags TI_TEMPLATE = 0x00000001 TI_SCRIPT = 0x00000004 TI_EMT = 0x00000010 TI_HELPFILE_NAME = 0x00000020 TI_HELPFILE_CONTENTS = 0x00000040 # 2.2.17 NspiModLinkAtt Flags fDelete = 0x00000001 # 2.3.1.1 FlatUID_r FlatUID_r = UUID PFlatUID_r = PUUID # 2.3.1.2 PropertyTagArray_r class PropertyTagArray(NDRUniConformantVaryingArray): item = DWORD class PropertyTagArray_r(NDRSTRUCT): structure = ( ('cValues', ULONG), ('aulPropTag', PropertyTagArray) ) class PPropertyTagArray_r(NDRPOINTER): referent = ( ('Data', PropertyTagArray_r), ) # 2.3.1.3 Binary_r class Binary(NDRUniConformantArray): item = 'c' class PBinary(NDRPOINTER): referent = ( ('Data', Binary), ) class Binary_r(NDRSTRUCT): structure = ( ('cValues', DWORD), ('lpb', PBinary), ) # 2.3.1.4 ShortArray_r class ShortArray(NDRUniConformantArray): item = SHORT class PShortArray(NDRPOINTER): referent = ( ('Data', ShortArray), ) class ShortArray_r(NDRSTRUCT): structure = ( ('cValues', DWORD), ('lpi', PShortArray), ) # 2.3.1.5 LongArray_r class LongArray(NDRUniConformantArray): item = LONG class PLongArray(NDRPOINTER): referent = ( ('Data', LongArray), ) class LongArray_r(NDRSTRUCT): structure = ( ('cValues', DWORD), ('lpl', PLongArray) ) # 2.3.1.6 StringArray_r class StringArray(NDRUniConformantArray): item = LPSTR class PStringArray(NDRPOINTER): referent = ( ('Data', StringArray), ) class StringArray_r(NDRSTRUCT): structure = ( ('cValues', DWORD), ('lppszA', PStringArray) ) # 2.3.1.7 BinaryArray_r class BinaryArray(NDRUniConformantArray): item = Binary_r class PBinaryArray(NDRPOINTER): referent = ( ('Data', BinaryArray), ) class BinaryArray_r(NDRSTRUCT): structure = ( ('cValues', DWORD), ('lpbin', PBinaryArray) ) # 2.3.1.8 FlatUIDArray_r class FlatUIDArray(NDRUniConformantArray): item = PFlatUID_r class PFlatUIDArray(NDRPOINTER): referent = ( ('Data', FlatUIDArray), ) class FlatUIDArray_r(NDRSTRUCT): structure = ( ('cValues', DWORD), ('lpguid', PFlatUIDArray) ) # 2.3.1.9 WStringArray_r class WStringArray(NDRUniConformantArray): item = LPWSTR class PWStringArray(NDRPOINTER): referent = ( ('Data', WStringArray), ) class WStringArray_r(NDRSTRUCT): structure = ( ('cValues', DWORD), ('lppszW', PWStringArray) ) # 2.3.1.10 DateTimeArray_r class DateTimeArray(NDRUniConformantArray): item = PFILETIME class PDateTimeArray(NDRPOINTER): referent = ( ('Data', DateTimeArray), ) class DateTimeArray_r(NDRSTRUCT): structure = ( ('cValues', DWORD), ('lpft', PDateTimeArray) ) # 2.3.1.11 PROP_VAL_UNION class PROP_VAL_UNION(NDRUNION): commonHdr = ( ('tag', DWORD), ) union = { 0x0002: ('i', SHORT), # PtypInteger16 0x0003: ('l', LONG), # PtypInteger32 0x000B: ('b', USHORT), # PtypBoolean 0x001E: ('lpszA', LPSTR), # PtypString8 0x0102: ('bin', Binary_r), # PtypBinary 0x001F: ('lpszW', LPWSTR), # PtypString 0x0048: ('lpguid', PFlatUID_r), # PtypGuid 0x0040: ('ft', FILETIME), # PtypTime 0x000A: ('err', ULONG), # PtypErrorCode 0x1002: ('MVi', ShortArray_r), # PtypMultipleInteger16 0x1003: ('MVl', LongArray_r), # PtypMultipleInteger32 0x101E: ('MVszA', StringArray_r), # PtypMultipleString8 0x1102: ('MVbin', BinaryArray_r), # PtypMultipleBinary 0x1048: ('MVguid', FlatUIDArray_r), # PtypMultipleGuid 0x101F: ('MVszW', WStringArray_r), # PtypMultipleString 0x1040: ('MVft', DateTimeArray_r), # PtypMultipleTime 0x0001: ('lReserved', LONG), # PtypNull 0x000D: ('lReserved', LONG), # PtypEmbeddedTable 0x0000: ('lReserved', LONG), # PtypUnspecified } # 2.3.1.12 PropertyValue_r class PropertyValue_r(NDRSTRUCT): structure = ( ('ulPropTag', DWORD), ('ulReserved', DWORD), # dwAlignPad ('Value', PROP_VAL_UNION), ) class PPropertyValue_r(NDRPOINTER): referent = ( ('Data', PropertyValue_r), ) # 2.3.2 PropertyRow_r class PropertyValue(NDRUniConformantArray): item = PropertyValue_r class PPropertyValue(NDRPOINTER): referent = ( ('Data', PropertyValue), ) class PropertyRow_r(NDRSTRUCT): structure = ( ('Reserved', DWORD), # ulAdrEntryPad ('cValues', DWORD), ('lpProps', PPropertyValue) ) class PPropertyRow_r(NDRPOINTER): referent = ( ('Data', PropertyRow_r), ) # 2.3.3 PropertyRowSet_r class PropertyRowSet(NDRUniConformantArray): item = PropertyRow_r class PropertyRowSet_r(NDRSTRUCT): structure = ( ('cRows', DWORD), ('aRow', PropertyRowSet), ) class PPropertyRowSet_r(NDRPOINTER): referent = ( ('Data', PropertyRowSet_r), ) # 2.3.4 Restrictions class Restriction_r(NDRSTRUCT): pass class PRestriction_r(NDRPOINTER): referent = ( ('Data', Restriction_r), ) # 2.3.4.1 AndRestriction_r, OrRestriction_r class AndRestriction(NDRUniConformantArray): item = Restriction_r class PAndRestriction(NDRPOINTER): referent = ( ('Data', AndRestriction), ) class AndRestriction_r(NDRSTRUCT): structure = ( ('cRes', DWORD), ('lpRes', PAndRestriction), ) OrRestriction_r = AndRestriction_r # 2.3.4.2 NotRestriction_r class NotRestriction_r(NDRSTRUCT): structure = ( ('lpRes', PRestriction_r), ) # 2.3.4.3 ContentRestriction_r class ContentRestriction_r(NDRSTRUCT): structure = ( ('ulFuzzyLevel', DWORD), ('ulPropTag', DWORD), ('lpProp', PPropertyValue_r), ) # 2.3.4.4 BitMaskRestriction_r class BitMaskRestriction_r(NDRSTRUCT): structure = ( ('relBMR', DWORD), ('ulPropTag', DWORD), ('ulMask', DWORD), ) # 2.3.4.5 PropertyRestriction_r class PropertyRestriction_r(NDRSTRUCT): structure = ( ('relop', DWORD), ('ulPropTag', DWORD), ('lpProp', PPropertyValue_r), ) # 2.3.4.6 ComparePropsRestriction_r class ComparePropsRestriction_r(NDRSTRUCT): structure = ( ('relop', DWORD), ('ulPropTag1', DWORD), ('ulPropTag2', DWORD), ) # 2.3.4.7 SubRestriction_r class SubRestriction_r(NDRSTRUCT): structure = ( ('ulSubObject', DWORD), ('lpRes', PRestriction_r), ) # 2.3.4.8 SizeRestriction_r class SizeRestriction_r(NDRSTRUCT): structure = ( ('relop', DWORD), ('ulPropTag', DWORD), ('cb', DWORD), ) # 2.3.4.9 ExistRestriction_r class ExistRestriction_r(NDRSTRUCT): structure = ( ('ulReserved1', DWORD), ('ulPropTag', DWORD), ('ulReserved2', DWORD), ) # 2.3.4.10 RestrictionUnion_r class RestrictionUnion_r(NDRUNION): commonHdr = ( ('tag', DWORD), ) union = { 0x00000000: ('resAnd', AndRestriction_r), 0x00000001: ('resOr', OrRestriction_r), 0x00000002: ('resNot', NotRestriction_r), 0x00000003: ('resContent', ContentRestriction_r), 0x00000004: ('resProperty', PropertyRestriction_r), 0x00000005: ('resCompareProps', ComparePropsRestriction_r), 0x00000006: ('resBitMask', BitMaskRestriction_r), 0x00000007: ('resSize', SizeRestriction_r), 0x00000008: ('resExist', ExistRestriction_r), 0x00000009: ('resSubRestriction', SubRestriction_r), } # 2.3.4.11 Restriction_r Restriction_r.structure = ( ('rt', DWORD), ('res', RestrictionUnion_r), ) # 2.3.5.1 PropertyName_r class PropertyName_r(NDRSTRUCT): structure = ( ('lpguid', PFlatUID_r), ('ulReserved', DWORD), ('lID', LONG), ) class PPropertyName_r(NDRPOINTER): referent = ( ('Data', PropertyName_r), ) # 2.3.5.2 PropertyNameSet_r class PropertyNameSet(NDRUniConformantArray): item = PropertyName_r class PropertyNameSet_r(NDRSTRUCT): structure = ( ('cNames', DWORD), ('aulPropTag', PropertyNameSet) ) class PPropertyNameSet_r(NDRPOINTER): referent = ( ('Data', PropertyNameSet_r), ) # 2.3.6.1 StringsArray_r class StringsArray(NDRUniConformantArray): item = LPSTR class StringsArray_r(NDRSTRUCT): structure = ( ('Count', DWORD), ('Strings', StringsArray) ) # 2.3.6.1 StringsArray_r class WStringsArray(NDRUniConformantArray): item = LPWSTR class WStringsArray_r(NDRSTRUCT): structure = ( ('Count', DWORD), ('Strings', WStringsArray) ) # 2.3.7 STAT class STAT(NDRSTRUCT): structure = ( ('SortType', DWORD), ('ContainerID', DWORD), ('CurrentRec', DWORD), ('Delta', LONG), ('NumPos', DWORD), ('TotalRecs', DWORD), ('CodePage', DWORD), ('TemplateLocale', DWORD), ('SortLocale', DWORD), ) class PSTAT(NDRPOINTER): referent = ( ('Data', STAT), ) # 2.3.8.1 MinimalEntryID MinEntryID = ' 0: for aulPropTag in pPropTags: prop = DWORD() prop['Data'] = aulPropTag request['pPropTags']['aulPropTag'].append(prop) request['pPropTags']['cValues'] = len(pPropTags) request.fields['pPropTags'].fields['Data'].fields['aulPropTag'].fields['MaximumCount'] = len(pPropTags) + 1 else: request['pPropTags'] = pPropTagsRaw if len(lpETable) > 0: for mID in lpETable: elem = DWORD() elem['Data'] = mID request['lpETable'].append(elem) request['dwETableCount'] = len(lpETable) else: request['lpETable'] = NULL request['dwETableCount'] = 0 resp = dce.request(request) return resp def hNspiSeekEntries(dce, handler, displayName, ContainerID=0, SortType=0, \ lpETable=[], lpETableRaw=NULL, pPropTags=[], pPropTagsRaw=NULL): request = NspiSeekEntries() request['hRpc'] = handler request['pStat']['ContainerID'] = ContainerID # MS-OXNSPI 3.1.4.1.9.9 # If the SortType field in the input parameter pStat has any value other than # SortTypeDisplayName, the server MUST return the value GeneralFailure. request['pStat']['SortType'] = SortTypeDisplayName # MS-OXNSPI 3.1.4.1.9.10 # If the SortType field in the input parameter pStat is SortTypeDisplayName and the property # specified in the input parameter pTarget is anything other than PidTagDisplayName (with either # the Property Type PtypString8 or PtypString), the server MUST return the value # GeneralFailure. request['pTarget']['ulPropTag'] = 0x3001001F request['pTarget']['Value']['tag'] = 0x0000001F request['pTarget']['Value']['lpszW'] = checkNullString(displayName) if len(lpETable) > 0: for mID in lpETable: elem = DWORD() elem['Data'] = mID request['lpETable'].append(elem) else: request['lpETable'] = lpETableRaw if len(pPropTags) > 0: for aulPropTag in pPropTags: prop = DWORD() prop['Data'] = aulPropTag request['pPropTags']['aulPropTag'].append(prop) request.fields['pPropTags'].fields['aulPropTag'].fields['MaximumCount'] = len(pPropTags) + 1 else: request['pPropTags'] = pPropTagsRaw resp = dce.request(request) return resp def hNspiDNToMId(dce, handler, pNames=[]): request = NspiDNToMId() request['hRpc'] = handler request['pNames']['Count'] = len(pNames) for name in pNames: lpstr = LPSTR() lpstr['Data'] = checkNullString(name) request['pNames']['Strings'].append(lpstr) resp = dce.request(request) return resp def hNspiGetPropList(dce, handler, dwMId=0, dwFlags=fSkipObjects, CodePage=CP_TELETEX): request = NspiGetPropList() request['hRpc'] = handler request['dwMId'] = dwMId request['dwFlags'] = dwFlags request['CodePage'] = CodePage resp = dce.request(request) return resp def hNspiGetProps(dce, handler, ContainerID=0, CurrentRec=0, dwFlags=fSkipObjects, CodePage=CP_TELETEX, pPropTags=[]): request = NspiGetProps() request['hRpc'] = handler request['dwFlags'] = dwFlags request['pStat']['CurrentRec'] = CurrentRec request['pStat']['ContainerID'] = ContainerID request['pStat']['CodePage'] = CodePage for aulPropTag in pPropTags: prop = DWORD() prop['Data'] = aulPropTag request['pPropTags']['aulPropTag'].append(prop) request['pPropTags']['cValues'] = len(pPropTags) + 1 request.fields['pPropTags'].fields['Data'].fields['aulPropTag'].fields['MaximumCount'] = len(pPropTags) + 1 resp = dce.request(request) return resp def hNspiGetSpecialTable(dce, handler, dwFlags=NspiUnicodeStrings, pStat=STAT(), lpVersion=NULL): request = NspiGetSpecialTable() request['hRpc'] = handler request['dwFlags'] = dwFlags request['pStat'] = pStat request['lpVersion'] = lpVersion resp = dce.request(request) return resp # Lookups specified LegacyDN or CN={ulType},CN={dwLocaleID},CN=Display-Templates,CN=Addressing in Configuration Naming Context def hNspiGetTemplateInfo(dce, handler, pDN=NULL, dwLocaleID=0, ulType=0, dwCodePage=0, dwFlags=0xFFFFFFFF): request = NspiGetTemplateInfo() request['hRpc'] = handler request['dwFlags'] = dwFlags request['ulType'] = ulType request['pDN'] = checkNullString(pDN) request['dwCodePage'] = dwCodePage request['dwLocaleID'] = dwLocaleID resp = dce.request(request) return resp def hNspiModLinkAtt(dce, handler, dwFlags, ulPropTag, dwMId, lpEntryIds): request = NspiModLinkAtt() request['hRpc'] = handler request['dwFlags'] = dwFlags request['ulPropTag'] = ulPropTag request['dwMId'] = dwMId for lpEntryId in lpEntryIds: prop = Binary_r() prop['lpb'] = lpEntryId.getData() prop['cValues'] = len(prop['lpb']) request['lpEntryIds']['lpbin'].append(prop) request['lpEntryIds']['cValues'] = len(lpEntryIds) resp = dce.request(request) return resp def hNspiQueryColumns(dce, handler, dwFlags=NspiUnicodeProptypes): request = NspiQueryColumns() request['hRpc'] = handler request['dwFlags'] = dwFlags resp = dce.request(request) return resp def hNspiGetNamesFromIDs(dce, handler, lpguid=EMPTY_UUID, pPropTags=[], pPropTagsRaw=NULL): request = NspiGetNamesFromIDs() request['hRpc'] = handler request['lpguid'] = lpguid if len(pPropTags) > 0: for aulPropTag in pPropTags: prop = DWORD() prop['Data'] = aulPropTag request['pPropTags']['aulPropTag'].append(prop) request['pPropTags']['cValues'] = len(pPropTags) request.fields['pPropTags'].fields['Data'].fields['aulPropTag'].fields['MaximumCount'] = len(pPropTags) + 1 elif pPropTagsRaw == NULL: request.fields['pPropTags'] = NULL else: request['pPropTags'] = pPropTagsRaw resp = dce.request(request) return resp def hNspiResolveNames(dce, handler, ContainerID=0, pPropTags=[], pPropTagsRaw=NULL, paStr=[]): request = NspiResolveNames() request['hRpc'] = handler request['pStat']['ContainerID'] = ContainerID if len(pPropTags) > 0: for aulPropTag in pPropTags: prop = DWORD() prop['Data'] = aulPropTag request['pPropTags']['aulPropTag'].append(prop) request['pPropTags']['cValues'] = len(pPropTags) request.fields['pPropTags'].fields['Data'].fields['aulPropTag'].fields['MaximumCount'] = len(pPropTags) + 1 elif pPropTagsRaw == NULL: request.fields['pPropTags'] = NULL else: request['pPropTags'] = pPropTagsRaw if len(paStr) > 0: for paStrElem in paStr: value = LPSTR() value['Data'] = checkNullString(paStrElem) request['paStr']['Strings'].append(value) request['paStr']['Count'] = len(paStr) resp = dce.request(request) return resp def hNspiResolveNamesW(dce, handler, ContainerID=0, pPropTags=[], pPropTagsRaw=NULL, paStr=[]): request = NspiResolveNamesW() request['hRpc'] = handler request['pStat']['ContainerID'] = ContainerID if len(pPropTags) > 0: for aulPropTag in pPropTags: prop = DWORD() prop['Data'] = aulPropTag request['pPropTags']['aulPropTag'].append(prop) request['pPropTags']['cValues'] = len(pPropTags) request.fields['pPropTags'].fields['Data'].fields['aulPropTag'].fields['MaximumCount'] = len(pPropTags) + 1 elif pPropTagsRaw == NULL: request.fields['pPropTags'] = NULL else: request['pPropTags'] = pPropTagsRaw if len(paStr) > 0: for paStrElem in paStr: value = LPWSTR() value['Data'] = checkNullString(paStrElem) request['paStr']['Strings'].append(value) request['paStr']['Count'] = len(paStr) resp = dce.request(request) return resp