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# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
2#
3# This software is provided under under a slightly modified version
4# of the Apache Software License. See the accompanying LICENSE file
5# for more information.
6#
7# Author: Alberto Solino (@agsolino)
8#
9# Description:
10# Transport implementations for the DCE/RPC protocol.
11#
12from __future__ import division
13from __future__ import print_function
15import binascii
16import os
17import re
18import socket
20try:
21 from urllib.parse import urlparse, urlunparse
22except ImportError:
23 from urlparse import urlparse, urlunparse
25from impacket import ntlm
26from impacket.dcerpc.v5.rpcrt import DCERPCException, DCERPC_v5, DCERPC_v4
27from impacket.dcerpc.v5.rpch import RPCProxyClient, RPCProxyClientException, RPC_OVER_HTTP_v1, RPC_OVER_HTTP_v2
28from impacket.smbconnection import SMBConnection
30class DCERPCStringBinding:
31 parser = re.compile(r'(?:([a-fA-F0-9-]{8}(?:-[a-fA-F0-9-]{4}){3}-[a-fA-F0-9-]{12})@)?' # UUID (opt.)
32 +'([_a-zA-Z0-9]*):' # Protocol Sequence
33 +'([^\[]*)' # Network Address (opt.)
34 +'(?:\[([^\]]*)\])?') # Endpoint and options (opt.)
36 def __init__(self, stringbinding):
37 match = DCERPCStringBinding.parser.match(stringbinding)
38 self.__uuid = match.group(1)
39 self.__ps = match.group(2)
40 self.__na = match.group(3)
41 options = match.group(4)
42 if options:
43 options = options.split(',')
45 self.__endpoint = options[0]
46 try:
47 self.__endpoint.index('endpoint=')
48 self.__endpoint = self.__endpoint[len('endpoint='):]
49 except:
50 pass
52 self.__options = {}
53 for option in options[1:]: 53 ↛ 54line 53 didn't jump to line 54, because the loop on line 53 never started
54 vv = option.split('=', 1)
55 self.__options[vv[0]] = vv[1] if len(vv) > 1 else ''
56 else:
57 self.__endpoint = ''
58 self.__options = {}
60 def get_uuid(self):
61 return self.__uuid
63 def get_protocol_sequence(self):
64 return self.__ps
66 def get_network_address(self):
67 return self.__na
69 def set_network_address(self, addr):
70 self.__na = addr
72 def get_endpoint(self):
73 return self.__endpoint
75 def get_options(self):
76 return self.__options
78 def get_option(self, option_name):
79 return self.__options[option_name]
81 def is_option_set(self, option_name):
82 return option_name in self.__options
84 def unset_option(self, option_name):
85 del self.__options[option_name]
87 def __str__(self):
88 return DCERPCStringBindingCompose(self.__uuid, self.__ps, self.__na, self.__endpoint, self.__options)
90def DCERPCStringBindingCompose(uuid=None, protocol_sequence='', network_address='', endpoint='', options={}):
91 s = ''
92 if uuid:
93 s += uuid + '@'
94 s += protocol_sequence + ':'
95 if network_address:
96 s += network_address
97 if endpoint or options:
98 s += '[' + endpoint
99 if options:
100 s += ',' + ','.join([key if str(val) == '' else "=".join([key, str(val)]) for key, val in options.items()])
101 s += ']'
103 return s
105def DCERPCTransportFactory(stringbinding):
106 sb = DCERPCStringBinding(stringbinding)
108 na = sb.get_network_address()
109 ps = sb.get_protocol_sequence()
110 if 'ncadg_ip_udp' == ps: 110 ↛ 111line 110 didn't jump to line 111, because the condition on line 110 was never true
111 port = sb.get_endpoint()
112 if port:
113 rpctransport = UDPTransport(na, int(port))
114 else:
115 rpctransport = UDPTransport(na)
116 elif 'ncacn_ip_tcp' == ps:
117 port = sb.get_endpoint()
118 if port:
119 rpctransport = TCPTransport(na, int(port))
120 else:
121 rpctransport = TCPTransport(na)
122 elif 'ncacn_http' == ps:
123 port = sb.get_endpoint()
124 if port: 124 ↛ 125line 124 didn't jump to line 125, because the condition on line 124 was never true
125 rpctransport = HTTPTransport(na, int(port))
126 else:
127 rpctransport = HTTPTransport(na)
128 elif 'ncacn_np' == ps: 128 ↛ 135line 128 didn't jump to line 135, because the condition on line 128 was never false
129 named_pipe = sb.get_endpoint()
130 if named_pipe: 130 ↛ 134line 130 didn't jump to line 134, because the condition on line 130 was never false
131 named_pipe = named_pipe[len(r'\pipe'):]
132 rpctransport = SMBTransport(na, filename = named_pipe)
133 else:
134 rpctransport = SMBTransport(na)
135 elif 'ncalocal' == ps:
136 named_pipe = sb.get_endpoint()
137 rpctransport = LOCALTransport(filename = named_pipe)
138 else:
139 raise DCERPCException("Unknown protocol sequence.")
141 rpctransport.set_stringbinding(sb)
142 return rpctransport
144class DCERPCTransport:
146 DCERPC_class = DCERPC_v5
148 def __init__(self, remoteName, dstport):
149 self.__remoteName = remoteName
150 self.__remoteHost = remoteName
151 self.__dstport = dstport
152 self._stringbinding = None
153 self._max_send_frag = None
154 self._max_recv_frag = None
155 self._domain = ''
156 self._lmhash = ''
157 self._nthash = ''
158 self.__connect_timeout = None
159 self._doKerberos = False
160 self._username = ''
161 self._password = ''
162 self._domain = ''
163 self._aesKey = None
164 self._TGT = None
165 self._TGS = None
166 self._kdcHost = None
167 self.set_credentials('','')
168 # Strict host validation - off by default and currently only for
169 # SMBTransport
170 self._strict_hostname_validation = False
171 self._validation_allow_absent = True
172 self._accepted_hostname = ''
174 def connect(self):
175 raise RuntimeError('virtual function')
176 def send(self,data=0, forceWriteAndx = 0, forceRecv = 0):
177 raise RuntimeError('virtual function')
178 def recv(self, forceRecv = 0, count = 0):
179 raise RuntimeError('virtual function')
180 def disconnect(self):
181 raise RuntimeError('virtual function')
182 def get_socket(self):
183 raise RuntimeError('virtual function')
185 def get_connect_timeout(self):
186 return self.__connect_timeout
187 def set_connect_timeout(self, timeout):
188 self.__connect_timeout = timeout
190 def getRemoteName(self):
191 return self.__remoteName
193 def setRemoteName(self, remoteName):
194 """This method only makes sense before connection for most protocols."""
195 self.__remoteName = remoteName
197 def getRemoteHost(self):
198 return self.__remoteHost
200 def setRemoteHost(self, remoteHost):
201 """This method only makes sense before connection for most protocols."""
202 self.__remoteHost = remoteHost
204 def get_dport(self):
205 return self.__dstport
206 def set_dport(self, dport):
207 """This method only makes sense before connection for most protocols."""
208 self.__dstport = dport
210 def get_stringbinding(self):
211 return self._stringbinding
213 def set_stringbinding(self, stringbinding):
214 self._stringbinding = stringbinding
216 def get_addr(self):
217 return self.getRemoteHost(), self.get_dport()
218 def set_addr(self, addr):
219 """This method only makes sense before connection for most protocols."""
220 self.setRemoteHost(addr[0])
221 self.set_dport(addr[1])
223 def set_kerberos(self, flag, kdcHost = None):
224 self._doKerberos = flag
225 self._kdcHost = kdcHost
227 def get_kerberos(self):
228 return self._doKerberos
230 def get_kdcHost(self):
231 return self._kdcHost
233 def set_max_fragment_size(self, send_fragment_size):
234 # -1 is default fragment size: 0 (don't fragment)
235 # 0 is don't fragment
236 # other values are max fragment size
237 if send_fragment_size == -1: 237 ↛ 238line 237 didn't jump to line 238, because the condition on line 237 was never true
238 self.set_default_max_fragment_size()
239 else:
240 self._max_send_frag = send_fragment_size
242 def set_hostname_validation(self, validate, accept_empty, hostname):
243 self._strict_hostname_validation = validate
244 self._validation_allow_absent = accept_empty
245 self._accepted_hostname = hostname
247 def set_default_max_fragment_size(self):
248 # default is 0: don't fragment.
249 # subclasses may override this method
250 self._max_send_frag = 0
252 def get_credentials(self):
253 return (
254 self._username,
255 self._password,
256 self._domain,
257 self._lmhash,
258 self._nthash,
259 self._aesKey,
260 self._TGT,
261 self._TGS)
263 def set_credentials(self, username, password, domain='', lmhash='', nthash='', aesKey='', TGT=None, TGS=None):
264 self._username = username
265 self._password = password
266 self._domain = domain
267 self._aesKey = aesKey
268 self._TGT = TGT
269 self._TGS = TGS
270 if lmhash != '' or nthash != '':
271 if len(lmhash) % 2: 271 ↛ 272line 271 didn't jump to line 272, because the condition on line 271 was never true
272 lmhash = '0%s' % lmhash
273 if len(nthash) % 2: 273 ↛ 274line 273 didn't jump to line 274, because the condition on line 273 was never true
274 nthash = '0%s' % nthash
275 try: # just in case they were converted already
276 self._lmhash = binascii.unhexlify(lmhash)
277 self._nthash = binascii.unhexlify(nthash)
278 except:
279 self._lmhash = lmhash
280 self._nthash = nthash
281 pass
283 def doesSupportNTLMv2(self):
284 # By default we'll be returning the library's default. Only on SMB Transports we might be able to know it beforehand
285 return ntlm.USE_NTLMv2
287 def get_dce_rpc(self):
288 return DCERPC_v5(self)
290class UDPTransport(DCERPCTransport):
291 "Implementation of ncadg_ip_udp protocol sequence"
293 DCERPC_class = DCERPC_v4
295 def __init__(self, remoteName, dstport = 135):
296 DCERPCTransport.__init__(self, remoteName, dstport)
297 self.__socket = 0
298 self.set_connect_timeout(30)
299 self.__recv_addr = ''
301 def connect(self):
302 try:
303 af, socktype, proto, canonname, sa = socket.getaddrinfo(self.getRemoteHost(), self.get_dport(), 0, socket.SOCK_DGRAM)[0]
304 self.__socket = socket.socket(af, socktype, proto)
305 self.__socket.settimeout(self.get_connect_timeout())
306 except socket.error as msg:
307 self.__socket = None
308 raise DCERPCException("Could not connect: %s" % msg)
310 return 1
312 def disconnect(self):
313 try:
314 self.__socket.close()
315 except socket.error:
316 self.__socket = None
317 return 0
318 return 1
320 def send(self,data, forceWriteAndx = 0, forceRecv = 0):
321 self.__socket.sendto(data, (self.getRemoteHost(), self.get_dport()))
323 def recv(self, forceRecv = 0, count = 0):
324 buffer, self.__recv_addr = self.__socket.recvfrom(8192)
325 return buffer
327 def get_recv_addr(self):
328 return self.__recv_addr
330 def get_socket(self):
331 return self.__socket
333class TCPTransport(DCERPCTransport):
334 """Implementation of ncacn_ip_tcp protocol sequence"""
336 def __init__(self, remoteName, dstport = 135):
337 DCERPCTransport.__init__(self, remoteName, dstport)
338 self.__socket = 0
339 self.set_connect_timeout(30)
341 def connect(self):
342 af, socktype, proto, canonname, sa = socket.getaddrinfo(self.getRemoteHost(), self.get_dport(), 0, socket.SOCK_STREAM)[0]
343 self.__socket = socket.socket(af, socktype, proto)
344 try:
345 self.__socket.settimeout(self.get_connect_timeout())
346 self.__socket.connect(sa)
347 except socket.error as msg:
348 self.__socket.close()
349 raise DCERPCException("Could not connect: %s" % msg)
350 return 1
352 def disconnect(self):
353 try:
354 self.__socket.close()
355 except socket.error:
356 self.__socket = None
357 return 0
358 return 1
360 def send(self,data, forceWriteAndx = 0, forceRecv = 0):
361 if self._max_send_frag:
362 offset = 0
363 while 1:
364 toSend = data[offset:offset+self._max_send_frag]
365 if not toSend:
366 break
367 self.__socket.send(toSend)
368 offset += len(toSend)
369 else:
370 self.__socket.send(data)
372 def recv(self, forceRecv = 0, count = 0):
373 if count:
374 buffer = b''
375 while len(buffer) < count:
376 buffer += self.__socket.recv(count-len(buffer))
377 else:
378 buffer = self.__socket.recv(8192)
379 return buffer
381 def get_socket(self):
382 return self.__socket
384class HTTPTransport(TCPTransport, RPCProxyClient):
385 """Implementation of ncacn_http protocol sequence"""
387 def __init__(self, remoteName=None, dstport=593):
388 self._useRpcProxy = False
389 self._rpcProxyUrl = None
390 self._transport = TCPTransport
391 self._version = RPC_OVER_HTTP_v2
393 DCERPCTransport.__init__(self, remoteName, dstport)
394 RPCProxyClient.__init__(self, remoteName, dstport)
395 self.set_connect_timeout(30)
397 def set_credentials(self, username, password, domain='', lmhash='', nthash='', aesKey='', TGT=None, TGS=None):
398 return self._transport.set_credentials(self, username, password,
399 domain, lmhash, nthash, aesKey, TGT, TGS)
401 def rpc_proxy_init(self):
402 self._useRpcProxy = True
403 self._transport = RPCProxyClient
405 def set_rpc_proxy_url(self, url):
406 self.rpc_proxy_init()
407 self._rpcProxyUrl = urlparse(url)
409 def get_rpc_proxy_url(self):
410 return urlunparse(self._rpcProxyUrl)
412 def set_stringbinding(self, set_stringbinding):
413 DCERPCTransport.set_stringbinding(self, set_stringbinding)
415 if self._stringbinding.is_option_set("RpcProxy"): 415 ↛ 416line 415 didn't jump to line 416, because the condition on line 415 was never true
416 self.rpc_proxy_init()
418 rpcproxy = self._stringbinding.get_option("RpcProxy").split(":")
420 if rpcproxy[1] == '443':
421 self.set_rpc_proxy_url('https://%s/rpc/rpcproxy.dll' % rpcproxy[0])
422 elif rpcproxy[1] == '80':
423 self.set_rpc_proxy_url('http://%s/rpc/rpcproxy.dll' % rpcproxy[0])
424 else:
425 # 2.1.2.1
426 # RPC over HTTP always uses port 80 for HTTP traffic and port 443 for HTTPS traffic.
427 # But you can use set_rpc_proxy_url method to set any URL / query you want.
428 raise DCERPCException("RPC Proxy port must be 80 or 443")
430 def connect(self):
431 if self._useRpcProxy == False: 431 ↛ 446line 431 didn't jump to line 446, because the condition on line 431 was never false
432 # Connecting directly to the ncacn_http port
433 #
434 # Here we using RPC over HTTPv1 instead complex RPC over HTTP v2 syntax
435 # RPC over HTTP v2 here can be implemented in the future
436 self._version = RPC_OVER_HTTP_v1
438 TCPTransport.connect(self)
440 # Reading legacy server response
441 data = self.get_socket().recv(8192)
443 if data != b'ncacn_http/1.0': 443 ↛ 444line 443 didn't jump to line 444, because the condition on line 443 was never true
444 raise DCERPCException("%s:%s service is not ncacn_http" % (self.__remoteName, self.__dstport))
445 else:
446 RPCProxyClient.connect(self)
448 def send(self, data, forceWriteAndx=0, forceRecv=0):
449 return self._transport.send(self, data, forceWriteAndx, forceRecv)
451 def recv(self, forceRecv=0, count=0):
452 return self._transport.recv(self, forceRecv, count)
454 def get_socket(self):
455 if self._useRpcProxy == False: 455 ↛ 458line 455 didn't jump to line 458, because the condition on line 455 was never false
456 return TCPTransport.get_socket(self)
457 else:
458 raise DCERPCException("This method is not supported for RPC Proxy connections")
460 def disconnect(self):
461 return self._transport.disconnect(self)
463class SMBTransport(DCERPCTransport):
464 """Implementation of ncacn_np protocol sequence"""
466 def __init__(self, remoteName, dstport=445, filename='', username='', password='', domain='', lmhash='', nthash='',
467 aesKey='', TGT=None, TGS=None, remote_host='', smb_connection=0, doKerberos=False, kdcHost=None):
468 DCERPCTransport.__init__(self, remoteName, dstport)
469 self.__socket = None
470 self.__tid = 0
471 self.__filename = filename
472 self.__handle = 0
473 self.__pending_recv = 0
474 self.set_credentials(username, password, domain, lmhash, nthash, aesKey, TGT, TGS)
475 self._doKerberos = doKerberos
476 self._kdcHost = kdcHost
478 if remote_host != '': 478 ↛ 479line 478 didn't jump to line 479, because the condition on line 478 was never true
479 self.setRemoteHost(remote_host)
481 if smb_connection == 0:
482 self.__existing_smb = False
483 else:
484 self.__existing_smb = True
485 self.set_credentials(*smb_connection.getCredentials())
487 self.__prefDialect = None
488 self.__smb_connection = smb_connection
489 self.set_connect_timeout(30)
491 def preferred_dialect(self, dialect):
492 self.__prefDialect = dialect
494 def setup_smb_connection(self):
495 if not self.__smb_connection: 495 ↛ exitline 495 didn't return from function 'setup_smb_connection', because the condition on line 495 was never false
496 self.__smb_connection = SMBConnection(self.getRemoteName(), self.getRemoteHost(), sess_port=self.get_dport(),
497 preferredDialect=self.__prefDialect, timeout=self.get_connect_timeout())
498 if self._strict_hostname_validation: 498 ↛ 499line 498 didn't jump to line 499, because the condition on line 498 was never true
499 self.__smb_connection.setHostnameValidation(self._strict_hostname_validation, self._validation_allow_absent, self._accepted_hostname)
501 def connect(self):
502 # Check if we have a smb connection already setup
503 if self.__smb_connection == 0:
504 self.setup_smb_connection()
505 if self._doKerberos is False:
506 self.__smb_connection.login(self._username, self._password, self._domain, self._lmhash, self._nthash)
507 else:
508 self.__smb_connection.kerberosLogin(self._username, self._password, self._domain, self._lmhash,
509 self._nthash, self._aesKey, kdcHost=self._kdcHost, TGT=self._TGT,
510 TGS=self._TGS)
511 self.__tid = self.__smb_connection.connectTree('IPC$')
512 self.__handle = self.__smb_connection.openFile(self.__tid, self.__filename)
513 self.__socket = self.__smb_connection.getSMBServer().get_socket()
514 return 1
516 def disconnect(self):
517 self.__smb_connection.disconnectTree(self.__tid)
518 # If we created the SMB connection, we close it, otherwise
519 # that's up for the caller
520 if self.__existing_smb is False:
521 self.__smb_connection.logoff()
522 self.__smb_connection.close()
523 self.__smb_connection = 0
525 def send(self,data, forceWriteAndx = 0, forceRecv = 0):
526 if self._max_send_frag:
527 offset = 0
528 while 1:
529 toSend = data[offset:offset+self._max_send_frag]
530 if not toSend:
531 break
532 self.__smb_connection.writeFile(self.__tid, self.__handle, toSend, offset = offset)
533 offset += len(toSend)
534 else:
535 self.__smb_connection.writeFile(self.__tid, self.__handle, data)
536 if forceRecv:
537 self.__pending_recv += 1
539 def recv(self, forceRecv = 0, count = 0 ):
540 if self._max_send_frag or self.__pending_recv:
541 # _max_send_frag is checked because it's the same condition we checked
542 # to decide whether to use write_andx() or send_trans() in send() above.
543 if self.__pending_recv:
544 self.__pending_recv -= 1
545 return self.__smb_connection.readFile(self.__tid, self.__handle, bytesToRead = self._max_recv_frag)
546 else:
547 return self.__smb_connection.readFile(self.__tid, self.__handle)
549 def get_smb_connection(self):
550 return self.__smb_connection
552 def set_smb_connection(self, smb_connection):
553 self.__smb_connection = smb_connection
554 self.set_credentials(*smb_connection.getCredentials())
555 self.__existing_smb = True
557 def get_smb_server(self):
558 # Raw Access to the SMBServer (whatever type it is)
559 return self.__smb_connection.getSMBServer()
561 def get_socket(self):
562 return self.__socket
564 def doesSupportNTLMv2(self):
565 return self.__smb_connection.doesSupportNTLMv2()
567class LOCALTransport(DCERPCTransport):
568 """
569 Implementation of ncalocal protocol sequence, not the same
570 as ncalrpc (I'm not doing LPC just opening the local pipe)
571 """
573 def __init__(self, filename = ''):
574 DCERPCTransport.__init__(self, '', 0)
575 self.__filename = filename
576 self.__handle = 0
578 def connect(self):
579 if self.__filename.upper().find('PIPE') < 0:
580 self.__filename = '\\PIPE\\%s' % self.__filename
581 self.__handle = os.open('\\\\.\\%s' % self.__filename, os.O_RDWR|os.O_BINARY)
582 return 1
584 def disconnect(self):
585 os.close(self.__handle)
587 def send(self,data, forceWriteAndx = 0, forceRecv = 0):
588 os.write(self.__handle, data)
590 def recv(self, forceRecv = 0, count = 0 ):
591 data = os.read(self.__handle, 65535)
592 return data