1# Copyright (c) 2003-2016 CORE Security Technologies
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#
12
13import re
14import socket
15import binascii
16import os
17
18from impacket.smbconnection import smb, SMBConnection
19from impacket import nmb
20from impacket import ntlm
21from impacket.dcerpc.v5.rpcrt import DCERPCException, DCERPC_v5, DCERPC_v4
22
23
24class DCERPCStringBinding:
25    parser = re.compile(r'(?:([a-fA-F0-9-]{8}(?:-[a-fA-F0-9-]{4}){3}-[a-fA-F0-9-]{12})@)?' # UUID (opt.)
26                        +'([_a-zA-Z0-9]*):' # Protocol Sequence
27                        +'([^\[]*)' # Network Address (opt.)
28                        +'(?:\[([^\]]*)\])?') # Endpoint and options (opt.)
29
30    def __init__(self, stringbinding):
31        match = DCERPCStringBinding.parser.match(stringbinding)
32        self.__uuid = match.group(1)
33        self.__ps = match.group(2)
34        self.__na = match.group(3)
35        options = match.group(4)
36        if options:
37            options = options.split(',')
38            self.__endpoint = options[0]
39            try:
40                self.__endpoint.index('endpoint=')
41                self.__endpoint = self.__endpoint[len('endpoint='):]
42            except:
43                pass
44            self.__options = options[1:]
45        else:
46            self.__endpoint = ''
47            self.__options = []
48
49    def get_uuid(self):
50        return self.__uuid
51
52    def get_protocol_sequence(self):
53        return self.__ps
54
55    def get_network_address(self):
56        return self.__na
57
58    def get_endpoint(self):
59        return self.__endpoint
60
61    def get_options(self):
62        return self.__options
63
64    def __str__(self):
65        return DCERPCStringBindingCompose(self.__uuid, self.__ps, self.__na, self.__endpoint, self.__options)
66
67def DCERPCStringBindingCompose(uuid=None, protocol_sequence='', network_address='', endpoint='', options=[]):
68    s = ''
69    if uuid: s += uuid + '@'
70    s += protocol_sequence + ':'
71    if network_address: s += network_address
72    if endpoint or options:
73        s += '[' + endpoint
74        if options: s += ',' + ','.join(options)
75        s += ']'
76
77    return s
78
79def DCERPCTransportFactory(stringbinding):
80    sb = DCERPCStringBinding(stringbinding)
81
82    na = sb.get_network_address()
83    ps = sb.get_protocol_sequence()
84    if 'ncadg_ip_udp' == ps:
85        port = sb.get_endpoint()
86        if port:
87            return UDPTransport(na, int(port))
88        else:
89            return UDPTransport(na)
90    elif 'ncacn_ip_tcp' == ps:
91        port = sb.get_endpoint()
92        if port:
93            return TCPTransport(na, int(port))
94        else:
95            return TCPTransport(na)
96    elif 'ncacn_http' == ps:
97        port = sb.get_endpoint()
98        if port:
99            return HTTPTransport(na, int(port))
100        else:
101            return HTTPTransport(na)
102    elif 'ncacn_np' == ps:
103        named_pipe = sb.get_endpoint()
104        if named_pipe:
105            named_pipe = named_pipe[len(r'\pipe'):]
106            return SMBTransport(na, filename = named_pipe)
107        else:
108            return SMBTransport(na)
109    elif 'ncalocal' == ps:
110        named_pipe = sb.get_endpoint()
111        return LOCALTransport(filename = named_pipe)
112    else:
113        raise DCERPCException("Unknown protocol sequence.")
114
115
116class DCERPCTransport:
117
118    DCERPC_class = DCERPC_v5
119
120    def __init__(self, remoteName, dstport):
121        self.__remoteName = remoteName
122        self.__remoteHost = remoteName
123        self.__dstport = dstport
124        self._max_send_frag = None
125        self._max_recv_frag = None
126        self._domain = ''
127        self._lmhash = ''
128        self._nthash = ''
129        self.__connect_timeout = None
130        self._doKerberos = False
131        self._username = ''
132        self._password = ''
133        self._domain   = ''
134        self._aesKey   = None
135        self._TGT      = None
136        self._TGS      = None
137        self._kdcHost  = None
138        self.set_credentials('','')
139
140    def connect(self):
141        raise RuntimeError, 'virtual function'
142    def send(self,data=0, forceWriteAndx = 0, forceRecv = 0):
143        raise RuntimeError, 'virtual function'
144    def recv(self, forceRecv = 0, count = 0):
145        raise RuntimeError, 'virtual function'
146    def disconnect(self):
147        raise RuntimeError, 'virtual function'
148    def get_socket(self):
149        raise RuntimeError, 'virtual function'
150
151    def get_connect_timeout(self):
152        return self.__connect_timeout
153    def set_connect_timeout(self, timeout):
154        self.__connect_timeout = timeout
155
156    def getRemoteName(self):
157        return self.__remoteName
158
159    def setRemoteName(self, remoteName):
160        """This method only makes sense before connection for most protocols."""
161        self.__remoteName = remoteName
162
163    def getRemoteHost(self):
164        return self.__remoteHost
165
166    def setRemoteHost(self, remoteHost):
167        """This method only makes sense before connection for most protocols."""
168        self.__remoteHost = remoteHost
169
170    def get_dport(self):
171        return self.__dstport
172    def set_dport(self, dport):
173        """This method only makes sense before connection for most protocols."""
174        self.__dstport = dport
175
176    def get_addr(self):
177        return self.getRemoteHost(), self.get_dport()
178    def set_addr(self, addr):
179        """This method only makes sense before connection for most protocols."""
180        self.setRemoteHost(addr[0])
181        self.set_dport(addr[1])
182
183    def set_kerberos(self, flag, kdcHost = None):
184        self._doKerberos = flag
185        self._kdcHost = kdcHost
186
187    def get_kerberos(self):
188        return self._doKerberos
189
190    def get_kdcHost(self):
191        return self._kdcHost
192
193    def set_max_fragment_size(self, send_fragment_size):
194        # -1 is default fragment size: 0 (don't fragment)
195        #  0 is don't fragment
196        #    other values are max fragment size
197        if send_fragment_size == -1:
198            self.set_default_max_fragment_size()
199        else:
200            self._max_send_frag = send_fragment_size
201
202    def set_default_max_fragment_size(self):
203        # default is 0: don't fragment.
204        # subclasses may override this method
205        self._max_send_frag = 0
206
207    def get_credentials(self):
208        return (
209            self._username,
210            self._password,
211            self._domain,
212            self._lmhash,
213            self._nthash,
214            self._aesKey,
215            self._TGT,
216            self._TGS)
217
218    def set_credentials(self, username, password, domain='', lmhash='', nthash='', aesKey='', TGT=None, TGS=None):
219        self._username = username
220        self._password = password
221        self._domain   = domain
222        self._aesKey   = aesKey
223        self._TGT      = TGT
224        self._TGS      = TGS
225        if lmhash != '' or nthash != '':
226            if len(lmhash) % 2:     lmhash = '0%s' % lmhash
227            if len(nthash) % 2:     nthash = '0%s' % nthash
228            try: # just in case they were converted already
229               self._lmhash = binascii.unhexlify(lmhash)
230               self._nthash = binascii.unhexlify(nthash)
231            except:
232               self._lmhash = lmhash
233               self._nthash = nthash
234               pass
235
236    def doesSupportNTLMv2(self):
237        # By default we'll be returning the library's default. Only on SMB Transports we might be able to know it beforehand
238        return ntlm.USE_NTLMv2
239
240    def get_dce_rpc(self):
241        return DCERPC_v5(self)
242
243class UDPTransport(DCERPCTransport):
244    "Implementation of ncadg_ip_udp protocol sequence"
245
246    DCERPC_class = DCERPC_v4
247
248    def __init__(self, remoteName, dstport = 135):
249        DCERPCTransport.__init__(self, remoteName, dstport)
250        self.__socket = 0
251        self.set_connect_timeout(30)
252        self.__recv_addr = ''
253
254    def connect(self):
255        try:
256            af, socktype, proto, canonname, sa = socket.getaddrinfo(self.getRemoteHost(), self.get_dport(), 0, socket.SOCK_DGRAM)[0]
257            self.__socket = socket.socket(af, socktype, proto)
258            self.__socket.settimeout(self.get_connect_timeout())
259        except socket.error, msg:
260            self.__socket = None
261            raise DCERPCException("Could not connect: %s" % msg)
262
263        return 1
264
265    def disconnect(self):
266        try:
267            self.__socket.close()
268        except socket.error:
269            self.__socket = None
270            return 0
271        return 1
272
273    def send(self,data, forceWriteAndx = 0, forceRecv = 0):
274        self.__socket.sendto(data, (self.getRemoteHost(), self.get_dport()))
275
276    def recv(self, forceRecv = 0, count = 0):
277        buffer, self.__recv_addr = self.__socket.recvfrom(8192)
278        return buffer
279
280    def get_recv_addr(self):
281        return self.__recv_addr
282
283    def get_socket(self):
284        return self.__socket
285
286class TCPTransport(DCERPCTransport):
287    """Implementation of ncacn_ip_tcp protocol sequence"""
288
289    def __init__(self, remoteName, dstport = 135):
290        DCERPCTransport.__init__(self, remoteName, dstport)
291        self.__socket = 0
292        self.set_connect_timeout(30)
293
294    def connect(self):
295        af, socktype, proto, canonname, sa = socket.getaddrinfo(self.getRemoteHost(), self.get_dport(), 0, socket.SOCK_STREAM)[0]
296        self.__socket = socket.socket(af, socktype, proto)
297        try:
298            self.__socket.settimeout(self.get_connect_timeout())
299            self.__socket.connect(sa)
300        except socket.error, msg:
301            self.__socket.close()
302            raise DCERPCException("Could not connect: %s" % msg)
303        return 1
304
305    def disconnect(self):
306        try:
307            self.__socket.close()
308        except socket.error, msg:
309            self.__socket = None
310            return 0
311        return 1
312
313    def send(self,data, forceWriteAndx = 0, forceRecv = 0):
314        if self._max_send_frag:
315            offset = 0
316            while 1:
317                toSend = data[offset:offset+self._max_send_frag]
318                if not toSend:
319                    break
320                self.__socket.send(toSend)
321                offset += len(toSend)
322        else:
323            self.__socket.send(data)
324
325    def recv(self, forceRecv = 0, count = 0):
326        if count:
327            buffer = ''
328            while len(buffer) < count:
329               buffer += self.__socket.recv(count-len(buffer))
330        else:
331            buffer = self.__socket.recv(8192)
332        return buffer
333
334    def get_socket(self):
335        return self.__socket
336
337class HTTPTransport(TCPTransport):
338    """Implementation of ncacn_http protocol sequence"""
339
340    def connect(self):
341        TCPTransport.connect(self)
342
343        self.get_socket().send('RPC_CONNECT ' + self.getRemoteHost() + ':593 HTTP/1.0\r\n\r\n')
344        data = self.get_socket().recv(8192)
345        if data[10:13] != '200':
346            raise DCERPCException("Service not supported.")
347
348class SMBTransport(DCERPCTransport):
349    """Implementation of ncacn_np protocol sequence"""
350
351    def __init__(self, remoteName, dstport=445, filename='', username='', password='', domain='', lmhash='', nthash='',
352                 aesKey='', TGT=None, TGS=None, remote_host='', smb_connection=0, doKerberos=False, kdcHost=None):
353        DCERPCTransport.__init__(self, remoteName, dstport)
354        self.__socket = None
355        self.__tid = 0
356        self.__filename = filename
357        self.__handle = 0
358        self.__pending_recv = 0
359        self.set_credentials(username, password, domain, lmhash, nthash, aesKey, TGT, TGS)
360        self._doKerberos = doKerberos
361        self._kdcHost = kdcHost
362
363        if remote_host != '':
364            self.setRemoteHost(remote_host)
365
366        if smb_connection == 0:
367            self.__existing_smb = False
368        else:
369            self.__existing_smb = True
370            self.set_credentials(*smb_connection.getCredentials())
371
372        self.__prefDialect = None
373        self.__smb_connection = smb_connection
374
375    def preferred_dialect(self, dialect):
376        self.__prefDialect = dialect
377
378    def setup_smb_connection(self):
379        if not self.__smb_connection:
380            self.__smb_connection = SMBConnection(self.getRemoteName(), self.getRemoteHost(), sess_port=self.get_dport(),
381                                                  preferredDialect=self.__prefDialect)
382
383    def connect(self):
384        # Check if we have a smb connection already setup
385        if self.__smb_connection == 0:
386            self.setup_smb_connection()
387            if self._doKerberos is False:
388                self.__smb_connection.login(self._username, self._password, self._domain, self._lmhash, self._nthash)
389            else:
390                self.__smb_connection.kerberosLogin(self._username, self._password, self._domain, self._lmhash,
391                                                    self._nthash, self._aesKey, kdcHost=self._kdcHost, TGT=self._TGT,
392                                                    TGS=self._TGS)
393        self.__tid = self.__smb_connection.connectTree('IPC$')
394        self.__handle = self.__smb_connection.openFile(self.__tid, self.__filename)
395        self.__socket = self.__smb_connection.getSMBServer().get_socket()
396        return 1
397
398    def disconnect(self):
399        self.__smb_connection.disconnectTree(self.__tid)
400        # If we created the SMB connection, we close it, otherwise
401        # that's up for the caller
402        if self.__existing_smb is False:
403            self.__smb_connection.logoff()
404            self.__smb_connection.close()
405            self.__smb_connection = 0
406
407    def send(self,data, forceWriteAndx = 0, forceRecv = 0):
408        if self._max_send_frag:
409            offset = 0
410            while 1:
411                toSend = data[offset:offset+self._max_send_frag]
412                if not toSend:
413                    break
414                self.__smb_connection.writeFile(self.__tid, self.__handle, toSend, offset = offset)
415                offset += len(toSend)
416        else:
417            self.__smb_connection.writeFile(self.__tid, self.__handle, data)
418        if forceRecv:
419            self.__pending_recv += 1
420
421    def recv(self, forceRecv = 0, count = 0 ):
422        if self._max_send_frag or self.__pending_recv:
423            # _max_send_frag is checked because it's the same condition we checked
424            # to decide whether to use write_andx() or send_trans() in send() above.
425            if self.__pending_recv:
426                self.__pending_recv -= 1
427            return self.__smb_connection.readFile(self.__tid, self.__handle, bytesToRead = self._max_recv_frag)
428        else:
429            return self.__smb_connection.readFile(self.__tid, self.__handle)
430
431    def get_smb_connection(self):
432        return self.__smb_connection
433
434    def set_smb_connection(self, smb_connection):
435        self.__smb_connection = smb_connection
436        self.set_credentials(*smb_connection.getCredentials())
437        self.__existing_smb = True
438
439    def get_smb_server(self):
440        # Raw Access to the SMBServer (whatever type it is)
441        return self.__smb_connection.getSMBServer()
442
443    def get_socket(self):
444        return self.__socket
445
446    def doesSupportNTLMv2(self):
447        return self.__smb_connection.doesSupportNTLMv2()
448
449class LOCALTransport(DCERPCTransport):
450    """
451    Implementation of ncalocal protocol sequence, not the same
452    as ncalrpc (I'm not doing LPC just opening the local pipe)
453    """
454
455    def __init__(self, filename = ''):
456        DCERPCTransport.__init__(self, '', 0)
457        self.__filename = filename
458        self.__handle = 0
459
460    def connect(self):
461        if self.__filename.upper().find('PIPE') < 0:
462            self.__filename = '\\PIPE\\%s' % self.__filename
463        self.__handle = os.open('\\\\.\\%s' % self.__filename, os.O_RDWR|os.O_BINARY)
464        return 1
465
466    def disconnect(self):
467        os.close(self.__handle)
468
469    def send(self,data, forceWriteAndx = 0, forceRecv = 0):
470        os.write(self.__handle, data)
471
472    def recv(self, forceRecv = 0, count = 0 ):
473        data = os.read(self.__handle, 65535)
474        return data
475