Coverage for /root/GitHubProjects/impacket/impacket/dcerpc/v5/transport.py : 71%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Impacket - Collection of Python classes for working with network protocols.
2#
3# SECUREAUTH LABS. Copyright (C) 2020 SecureAuth Corporation. 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# Transport implementations for the DCE/RPC protocol.
11#
12# Author:
13# Alberto Solino (@agsolino)
14#
15from __future__ import division
16from __future__ import print_function
18import binascii
19import os
20import re
21import socket
23try:
24 from urllib.parse import urlparse, urlunparse
25except ImportError:
26 from urlparse import urlparse, urlunparse
28from impacket import ntlm
29from impacket.dcerpc.v5.rpcrt import DCERPCException, DCERPC_v5, DCERPC_v4
30from impacket.dcerpc.v5.rpch import RPCProxyClient, RPCProxyClientException, RPC_OVER_HTTP_v1, RPC_OVER_HTTP_v2
31from impacket.smbconnection import SMBConnection
34class DCERPCStringBinding:
35 parser = re.compile(r"(?:([a-fA-F0-9-]{8}(?:-[a-fA-F0-9-]{4}){3}-[a-fA-F0-9-]{12})@)?" + # UUID (opt.)
36 r"([_a-zA-Z0-9]*):" + # Protocol Sequence
37 r"([^\[]*)" + # Network Address (opt.)
38 r"(?:\[([^]]*)])?") # Endpoint and options (opt.)
40 def __init__(self, stringbinding):
41 match = DCERPCStringBinding.parser.match(stringbinding)
42 self.__uuid = match.group(1)
43 self.__ps = match.group(2)
44 self.__na = match.group(3)
45 options = match.group(4)
46 if options:
47 options = options.split(',')
49 self.__endpoint = options[0]
50 try:
51 self.__endpoint.index('endpoint=')
52 self.__endpoint = self.__endpoint[len('endpoint='):]
53 except:
54 pass
56 self.__options = {}
57 for option in options[1:]: 57 ↛ 58line 57 didn't jump to line 58, because the loop on line 57 never started
58 vv = option.split('=', 1)
59 self.__options[vv[0]] = vv[1] if len(vv) > 1 else ''
60 else:
61 self.__endpoint = ''
62 self.__options = {}
64 def get_uuid(self):
65 return self.__uuid
67 def get_protocol_sequence(self):
68 return self.__ps
70 def get_network_address(self):
71 return self.__na
73 def set_network_address(self, addr):
74 self.__na = addr
76 def get_endpoint(self):
77 return self.__endpoint
79 def get_options(self):
80 return self.__options
82 def get_option(self, option_name):
83 return self.__options[option_name]
85 def is_option_set(self, option_name):
86 return option_name in self.__options
88 def unset_option(self, option_name):
89 del self.__options[option_name]
91 def __str__(self):
92 return DCERPCStringBindingCompose(self.__uuid, self.__ps, self.__na, self.__endpoint, self.__options)
95def 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 += ']'
108 return s
111def DCERPCTransportFactory(stringbinding):
112 sb = DCERPCStringBinding(stringbinding)
114 na = sb.get_network_address()
115 ps = sb.get_protocol_sequence()
116 if 'ncadg_ip_udp' == ps: 116 ↛ 117line 116 didn't jump to line 117, because the condition on line 116 was never true
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: 130 ↛ 131line 130 didn't jump to line 131, because the condition on line 130 was never true
131 rpctransport = HTTPTransport(na, int(port))
132 else:
133 rpctransport = HTTPTransport(na)
134 elif 'ncacn_np' == ps: 134 ↛ 141line 134 didn't jump to line 141, because the condition on line 134 was never false
135 named_pipe = sb.get_endpoint()
136 if named_pipe: 136 ↛ 140line 136 didn't jump to line 140, because the condition on line 136 was never false
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.")
147 rpctransport.set_stringbinding(sb)
148 return rpctransport
150class DCERPCTransport:
152 DCERPC_class = DCERPC_v5
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 = ''
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')
191 def get_connect_timeout(self):
192 return self.__connect_timeout
193 def set_connect_timeout(self, timeout):
194 self.__connect_timeout = timeout
196 def getRemoteName(self):
197 return self.__remoteName
199 def setRemoteName(self, remoteName):
200 """This method only makes sense before connection for most protocols."""
201 self.__remoteName = remoteName
203 def getRemoteHost(self):
204 return self.__remoteHost
206 def setRemoteHost(self, remoteHost):
207 """This method only makes sense before connection for most protocols."""
208 self.__remoteHost = remoteHost
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
216 def get_stringbinding(self):
217 return self._stringbinding
219 def set_stringbinding(self, stringbinding):
220 self._stringbinding = stringbinding
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])
229 def set_kerberos(self, flag, kdcHost = None):
230 self._doKerberos = flag
231 self._kdcHost = kdcHost
233 def get_kerberos(self):
234 return self._doKerberos
236 def get_kdcHost(self):
237 return self._kdcHost
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: 243 ↛ 244line 243 didn't jump to line 244, because the condition on line 243 was never true
244 self.set_default_max_fragment_size()
245 else:
246 self._max_send_frag = send_fragment_size
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
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
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)
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: 277 ↛ 278line 277 didn't jump to line 278, because the condition on line 277 was never true
278 lmhash = '0%s' % lmhash
279 if len(nthash) % 2: 279 ↛ 280line 279 didn't jump to line 280, because the condition on line 279 was never true
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
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
293 def get_dce_rpc(self):
294 return DCERPC_v5(self)
296class UDPTransport(DCERPCTransport):
297 "Implementation of ncadg_ip_udp protocol sequence"
299 DCERPC_class = DCERPC_v4
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 = ''
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)
316 return 1
318 def disconnect(self):
319 try:
320 self.__socket.close()
321 except socket.error:
322 self.__socket = None
323 return 0
324 return 1
326 def send(self,data, forceWriteAndx = 0, forceRecv = 0):
327 self.__socket.sendto(data, (self.getRemoteHost(), self.get_dport()))
329 def recv(self, forceRecv = 0, count = 0):
330 buffer, self.__recv_addr = self.__socket.recvfrom(8192)
331 return buffer
333 def get_recv_addr(self):
334 return self.__recv_addr
336 def get_socket(self):
337 return self.__socket
339class TCPTransport(DCERPCTransport):
340 """Implementation of ncacn_ip_tcp protocol sequence"""
342 def __init__(self, remoteName, dstport = 135):
343 DCERPCTransport.__init__(self, remoteName, dstport)
344 self.__socket = 0
345 self.set_connect_timeout(30)
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
358 def disconnect(self):
359 try:
360 self.__socket.close()
361 except socket.error:
362 self.__socket = None
363 return 0
364 return 1
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)
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
387 def get_socket(self):
388 return self.__socket
390class HTTPTransport(TCPTransport, RPCProxyClient):
391 """Implementation of ncacn_http protocol sequence"""
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
399 DCERPCTransport.__init__(self, remoteName, dstport)
400 RPCProxyClient.__init__(self, remoteName, dstport)
401 self.set_connect_timeout(30)
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)
407 def rpc_proxy_init(self):
408 self._useRpcProxy = True
409 self._transport = RPCProxyClient
411 def set_rpc_proxy_url(self, url):
412 self.rpc_proxy_init()
413 self._rpcProxyUrl = urlparse(url)
415 def get_rpc_proxy_url(self):
416 return urlunparse(self._rpcProxyUrl)
418 def set_stringbinding(self, set_stringbinding):
419 DCERPCTransport.set_stringbinding(self, set_stringbinding)
421 if self._stringbinding.is_option_set("RpcProxy"): 421 ↛ 422line 421 didn't jump to line 422, because the condition on line 421 was never true
422 self.rpc_proxy_init()
424 rpcproxy = self._stringbinding.get_option("RpcProxy").split(":")
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")
436 def connect(self):
437 if self._useRpcProxy == False: 437 ↛ 452line 437 didn't jump to line 452, because the condition on line 437 was never 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
444 TCPTransport.connect(self)
446 # Reading legacy server response
447 data = self.get_socket().recv(8192)
449 if data != b'ncacn_http/1.0': 449 ↛ 450line 449 didn't jump to line 450, because the condition on line 449 was never true
450 raise DCERPCException("%s:%s service is not ncacn_http" % (self.__remoteName, self.__dstport))
451 else:
452 RPCProxyClient.connect(self)
454 def send(self, data, forceWriteAndx=0, forceRecv=0):
455 return self._transport.send(self, data, forceWriteAndx, forceRecv)
457 def recv(self, forceRecv=0, count=0):
458 return self._transport.recv(self, forceRecv, count)
460 def get_socket(self):
461 if self._useRpcProxy == False: 461 ↛ 464line 461 didn't jump to line 464, because the condition on line 461 was never false
462 return TCPTransport.get_socket(self)
463 else:
464 raise DCERPCException("This method is not supported for RPC Proxy connections")
466 def disconnect(self):
467 return self._transport.disconnect(self)
469class SMBTransport(DCERPCTransport):
470 """Implementation of ncacn_np protocol sequence"""
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
484 if remote_host != '': 484 ↛ 485line 484 didn't jump to line 485, because the condition on line 484 was never true
485 self.setRemoteHost(remote_host)
487 if smb_connection == 0:
488 self.__existing_smb = False
489 else:
490 self.__existing_smb = True
491 self.set_credentials(*smb_connection.getCredentials())
493 self.__prefDialect = None
494 self.__smb_connection = smb_connection
495 self.set_connect_timeout(30)
497 def preferred_dialect(self, dialect):
498 self.__prefDialect = dialect
500 def setup_smb_connection(self):
501 if not self.__smb_connection: 501 ↛ exitline 501 didn't return from function 'setup_smb_connection', because the condition on line 501 was never false
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: 504 ↛ 505line 504 didn't jump to line 505, because the condition on line 504 was never true
505 self.__smb_connection.setHostnameValidation(self._strict_hostname_validation, self._validation_allow_absent, self._accepted_hostname)
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
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
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
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)
555 def get_smb_connection(self):
556 return self.__smb_connection
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
563 def get_smb_server(self):
564 # Raw Access to the SMBServer (whatever type it is)
565 return self.__smb_connection.getSMBServer()
567 def get_socket(self):
568 return self.__socket
570 def doesSupportNTLMv2(self):
571 return self.__smb_connection.doesSupportNTLMv2()
573class 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 """
579 def __init__(self, filename = ''):
580 DCERPCTransport.__init__(self, '', 0)
581 self.__filename = filename
582 self.__handle = 0
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
590 def disconnect(self):
591 os.close(self.__handle)
593 def send(self,data, forceWriteAndx = 0, forceRecv = 0):
594 os.write(self.__handle, data)
596 def recv(self, forceRecv = 0, count = 0 ):
597 data = os.read(self.__handle, 65535)
598 return data