Coverage for /root/GitHubProjects/impacket/impacket/dcerpc/v5/rpch.py : 37%

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# Initial [MS-RCPH] Interface implementation
11#
12# Author:
13# Arseniy Sharoglazov <mohemiv@gmail.com> / Positive Technologies (https://www.ptsecurity.com/)
14#
16import re
17import binascii
18from struct import unpack
20from impacket import uuid, ntlm, system_errors, nt_errors, LOG
21from impacket.dcerpc.v5.rpcrt import DCERPCException
23from impacket.uuid import EMPTY_UUID
24from impacket.http import HTTPClientSecurityProvider, AUTH_BASIC
25from impacket.structure import Structure
26from impacket.dcerpc.v5.rpcrt import MSRPCHeader, \
27 MSRPC_RTS, PFC_FIRST_FRAG, PFC_LAST_FRAG
29class RPCProxyClientException(DCERPCException):
30 parser = re.compile(r'RPC Error: ([a-fA-F0-9]{1,8})')
32 def __init__(self, error_string=None, proxy_error=None):
33 rpc_error_code = None
35 if proxy_error is not None:
36 try:
37 search = self.parser.search(proxy_error)
38 rpc_error_code = int(search.group(1), 16)
39 except:
40 error_string += ': ' + proxy_error
42 DCERPCException.__init__(self, error_string, rpc_error_code)
44 def __str__(self):
45 if self.error_code is not None:
46 key = self.error_code
47 if key in system_errors.ERROR_MESSAGES:
48 error_msg_short = system_errors.ERROR_MESSAGES[key][0]
49 return '%s, code: 0x%x - %s' % (self.error_string, self.error_code, error_msg_short)
50 elif key in nt_errors.ERROR_MESSAGES:
51 error_msg_short = nt_errors.ERROR_MESSAGES[key][0]
52 return '%s, code: 0x%x - %s' % (self.error_string, self.error_code, error_msg_short)
53 else:
54 return '%s: unknown code: 0x%x' % (self.error_string, self.error_code)
55 else:
56 return self.error_string
58################################################################################
59# CONSTANTS
60################################################################################
62RPC_OVER_HTTP_v1 = 1
63RPC_OVER_HTTP_v2 = 2
65# Errors which might need handling
67# RPCProxyClient internal errors
68RPC_PROXY_REMOTE_NAME_NEEDED_ERR = 'Basic authentication in RPC proxy is used, ' \
69 'so coudn\'t obtain a target NetBIOS name from NTLMSSP to connect.'
71# Errors below contain a part of server responses
72RPC_PROXY_INVALID_RPC_PORT_ERR = 'Invalid RPC Port'
73RPC_PROXY_CONN_A1_0X6BA_ERR = 'RPC Proxy CONN/A1 request failed, code: 0x6ba'
74RPC_PROXY_CONN_A1_404_ERR = 'CONN/A1 request failed: HTTP/1.1 404 Not Found'
75RPC_PROXY_RPC_OUT_DATA_404_ERR = 'RPC_OUT_DATA channel: HTTP/1.1 404 Not Found'
76RPC_PROXY_CONN_A1_401_ERR = 'CONN/A1 request failed: HTTP/1.1 401 Unauthorized'
77RPC_PROXY_HTTP_IN_DATA_401_ERR = 'RPC_IN_DATA channel: HTTP/1.1 401 Unauthorized'
80# 2.2.3.3 Forward Destinations
81FDClient = 0x00000000
82FDInProxy = 0x00000001
83FDServer = 0x00000002
84FDOutProxy = 0x00000003
86RTS_FLAG_NONE = 0x0000
87RTS_FLAG_PING = 0x0001
88RTS_FLAG_OTHER_CMD = 0x0002
89RTS_FLAG_RECYCLE_CHANNEL = 0x0004
90RTS_FLAG_IN_CHANNEL = 0x0008
91RTS_FLAG_OUT_CHANNEL = 0x0010
92RTS_FLAG_EOF = 0x0020
93RTS_FLAG_ECHO = 0x0040
95# 2.2.3.5 RTS Commands
96RTS_CMD_RECEIVE_WINDOW_SIZE = 0x00000000
97RTS_CMD_FLOW_CONTROL_ACK = 0x00000001
98RTS_CMD_CONNECTION_TIMEOUT = 0x00000002
99RTS_CMD_COOKIE = 0x00000003
100RTS_CMD_CHANNEL_LIFETIME = 0x00000004
101RTS_CMD_CLIENT_KEEPALIVE = 0x00000005
102RTS_CMD_VERSION = 0x00000006
103RTS_CMD_EMPTY = 0x00000007
104RTS_CMD_PADDING = 0x00000008
105RTS_CMD_NEGATIVE_ANCE = 0x00000009
106RTS_CMD_ANCE = 0x0000000A
107RTS_CMD_CLIENT_ADDRESS = 0x0000000B
108RTS_CMD_ASSOCIATION_GROUP_ID = 0x0000000C
109RTS_CMD_DESTINATION = 0x0000000D
110RTS_CMD_PING_TRAFFIC_SENT_NOTIFY = 0x0000000E
112################################################################################
113# STRUCTURES
114################################################################################
116# 2.2.3.1 RTS Cookie
117class RTSCookie(Structure):
118 structure = (
119 ('Cookie','16s=b"\\x00"*16'),
120 )
122# 2.2.3.2 Client Address
123class EncodedClientAddress(Structure):
124 structure = (
125 ('AddressType','<L=(0 if len(ClientAddress) == 4 else 1)'),
126 ('_ClientAddress','_-ClientAddress','4 if AddressType == 0 else 16'),
127 ('ClientAddress',':'),
128 ('Padding','12s=b"\\x00"*12'),
129 )
131# 2.2.3.4 Flow Control Acknowledgment
132class Ack(Structure):
133 structure = (
134 ('BytesReceived','<L=0'),
135 ('AvailableWindow','<L=0'),
136 ('ChannelCookie',':',RTSCookie),
137 )
139# 2.2.3.5.1 ReceiveWindowSize
140class ReceiveWindowSize(Structure):
141 structure = (
142 ('CommandType','<L=0'),
143 ('ReceiveWindowSize','<L=262144'),
144 )
146# 2.2.3.5.2 FlowControlAck
147class FlowControlAck(Structure):
148 structure = (
149 ('CommandType','<L=1'),
150 ('Ack',':',Ack),
151 )
153# 2.2.3.5.3 ConnectionTimeout
154class ConnectionTimeout(Structure):
155 structure = (
156 ('CommandType','<L=2'),
157 ('ConnectionTimeout','<L=120000'),
158 )
160# 2.2.3.5.4 Cookie
161class Cookie(Structure):
162 structure = (
163 ('CommandType','<L=3'),
164 ('Cookie',':',RTSCookie),
165 )
167# 2.2.3.5.5 ChannelLifetime
168class ChannelLifetime(Structure):
169 structure = (
170 ('CommandType','<L=4'),
171 ('ChannelLifetime','<L=1073741824'),
172 )
174# 2.2.3.5.6 ClientKeepalive
175#
176# By the spec, ClientKeepalive value can be 0 or in the inclusive
177# range of 60,000 through 4,294,967,295.
178# If it is 0, it MUST be interpreted as 300,000.
179#
180# But do not set it to 0, it will cause 0x6c0 rpc error.
181class ClientKeepalive(Structure):
182 structure = (
183 ('CommandType','<L=5'),
184 ('ClientKeepalive','<L=300000'),
185 )
187# 2.2.3.5.7 Version
188class Version(Structure):
189 structure = (
190 ('CommandType','<L=6'),
191 ('Version','<L=1'),
192 )
194# 2.2.3.5.8 Empty
195class Empty(Structure):
196 structure = (
197 ('CommandType','<L=7'),
198 )
200# 2.2.3.5.9 Padding
201class Padding(Structure):
202 structure = (
203 ('CommandType','<L=8'),
204 ('ConformanceCount','<L=len(Padding)'),
205 ('Padding','*ConformanceCount'),
206 )
208# 2.2.3.5.10 NegativeANCE
209class NegativeANCE(Structure):
210 structure = (
211 ('CommandType','<L=9'),
212 )
214# 2.2.3.5.11 ANCE
215class ANCE(Structure):
216 structure = (
217 ('CommandType','<L=0xA'),
218 )
220# 2.2.3.5.12 ClientAddress
221class ClientAddress(Structure):
222 structure = (
223 ('CommandType','<L=0xB'),
224 ('ClientAddress',':',EncodedClientAddress),
225 )
227# 2.2.3.5.13 AssociationGroupId
228class AssociationGroupId(Structure):
229 structure = (
230 ('CommandType','<L=0xC'),
231 ('AssociationGroupId',':',RTSCookie),
232 )
234# 2.2.3.5.14 Destination
235class Destination(Structure):
236 structure = (
237 ('CommandType','<L=0xD'),
238 ('Destination','<L'),
239 )
241# 2.2.3.5.15 PingTrafficSentNotify
242class PingTrafficSentNotify(Structure):
243 structure = (
244 ('CommandType','<L=0xE'),
245 ('PingTrafficSent','<L'),
246 )
248COMMANDS = {
249 0x0: ReceiveWindowSize,
250 0x1: FlowControlAck,
251 0x2: ConnectionTimeout,
252 0x3: Cookie,
253 0x4: ChannelLifetime,
254 0x5: ClientKeepalive,
255 0x6: Version,
256 0x7: Empty,
257 0x8: Padding,
258 0x9: NegativeANCE,
259 0xA: ANCE,
260 0xB: ClientAddress,
261 0xC: AssociationGroupId,
262 0xD: Destination,
263 0xE: PingTrafficSentNotify,
264}
266# 2.2.3.6.1 RTS PDU Header
267# The RTS PDU Header has the same layout as the common header of
268# the connection-oriented RPC PDU as specified in [C706] section 12.6.1,
269# with a few additional requirements around the contents of the header fields.
270class RTSHeader(MSRPCHeader):
271 _SIZE = 20
272 commonHdr = MSRPCHeader.commonHdr + (
273 ('Flags','<H=0'), # 16
274 ('NumberOfCommands','<H=0'), # 18
275 )
277 def __init__(self, data=None, alignment=0):
278 MSRPCHeader.__init__(self, data, alignment)
279 self['type'] = MSRPC_RTS
280 self['flags'] = PFC_FIRST_FRAG | PFC_LAST_FRAG
281 self['auth_length'] = 0
282 self['call_id'] = 0
284# 2.2.4.2 CONN/A1 RTS PDU
285#
286# The CONN/A1 RTS PDU MUST be sent from the client to the outbound proxy on the OUT channel to
287# initiate the establishment of a virtual connection.
288class CONN_A1_RTS_PDU(Structure):
289 structure = (
290 ('Version',':',Version),
291 ('VirtualConnectionCookie',':',Cookie),
292 ('OutChannelCookie',':',Cookie),
293 ('ReceiveWindowSize',':',ReceiveWindowSize),
294 )
296# 2.2.4.5 CONN/B1 RTS PDU
297#
298# The CONN/B1 RTS PDU MUST be sent from the client to the inbound proxy on the IN channel to
299# initiate the establishment of a virtual connection.
300class CONN_B1_RTS_PDU(Structure):
301 structure = (
302 ('Version',':',Version),
303 ('VirtualConnectionCookie',':',Cookie),
304 ('INChannelCookie',':',Cookie),
305 ('ChannelLifetime',':',ChannelLifetime),
306 ('ClientKeepalive',':',ClientKeepalive),
307 ('AssociationGroupId',':',AssociationGroupId),
308 )
310# 2.2.4.4 CONN/A3 RTS PDU
311#
312# The CONN/A3 RTS PDU MUST be sent from the outbound proxy to the client on the OUT channel to
313# continue the establishment of the virtual connection.
314class CONN_A3_RTS_PDU(Structure):
315 structure = (
316 ('ConnectionTimeout',':',ConnectionTimeout),
317 )
319# 2.2.4.9 CONN/C2 RTS PDU
320#
321# The CONN/C2 RTS PDU MUST be sent from the outbound proxy to the client on the OUT channel to
322# notify it that a virtual connection has been established.
323class CONN_C2_RTS_PDU(Structure):
324 structure = (
325 ('Version',':',Version),
326 ('ReceiveWindowSize',':',ReceiveWindowSize),
327 ('ConnectionTimeout',':',ConnectionTimeout),
328 )
330# 2.2.4.51 FlowControlAckWithDestination RTS PDU
331class FlowControlAckWithDestination_RTS_PDU(Structure):
332 structure = (
333 ('Destination',':',Destination),
334 ('FlowControlAck',':',FlowControlAck),
335 )
337################################################################################
338# HELPERS
339################################################################################
340def hCONN_A1(virtualConnectionCookie=EMPTY_UUID, outChannelCookie=EMPTY_UUID, receiveWindowSize=262144):
341 conn_a1 = CONN_A1_RTS_PDU()
342 conn_a1['Version'] = Version()
343 conn_a1['VirtualConnectionCookie'] = Cookie()
344 conn_a1['VirtualConnectionCookie']['Cookie'] = virtualConnectionCookie
345 conn_a1['OutChannelCookie'] = Cookie()
346 conn_a1['OutChannelCookie']['Cookie'] = outChannelCookie
347 conn_a1['ReceiveWindowSize'] = ReceiveWindowSize()
348 conn_a1['ReceiveWindowSize']['ReceiveWindowSize'] = receiveWindowSize
350 packet = RTSHeader()
351 packet['Flags'] = RTS_FLAG_NONE
352 packet['NumberOfCommands'] = len(conn_a1.structure)
353 packet['pduData'] = conn_a1.getData()
355 return packet.getData()
357def hCONN_B1(virtualConnectionCookie=EMPTY_UUID, inChannelCookie=EMPTY_UUID, associationGroupId=EMPTY_UUID):
358 conn_b1 = CONN_B1_RTS_PDU()
359 conn_b1['Version'] = Version()
360 conn_b1['VirtualConnectionCookie'] = Cookie()
361 conn_b1['VirtualConnectionCookie']['Cookie'] = virtualConnectionCookie
362 conn_b1['INChannelCookie'] = Cookie()
363 conn_b1['INChannelCookie']['Cookie'] = inChannelCookie
364 conn_b1['ChannelLifetime'] = ChannelLifetime()
365 conn_b1['ClientKeepalive'] = ClientKeepalive()
366 conn_b1['AssociationGroupId'] = AssociationGroupId()
367 conn_b1['AssociationGroupId']['AssociationGroupId'] = RTSCookie()
368 conn_b1['AssociationGroupId']['AssociationGroupId']['Cookie'] = associationGroupId
370 packet = RTSHeader()
371 packet['Flags'] = RTS_FLAG_NONE
372 packet['NumberOfCommands'] = len(conn_b1.structure)
373 packet['pduData'] = conn_b1.getData()
375 return packet.getData()
377def hFlowControlAckWithDestination(destination, bytesReceived, availableWindow, channelCookie):
378 rts_pdu = FlowControlAckWithDestination_RTS_PDU()
379 rts_pdu['Destination'] = Destination()
380 rts_pdu['Destination']['Destination'] = destination
381 rts_pdu['FlowControlAck'] = FlowControlAck()
382 rts_pdu['FlowControlAck']['Ack'] = Ack()
383 rts_pdu['FlowControlAck']['Ack']['BytesReceived'] = bytesReceived
384 rts_pdu['FlowControlAck']['Ack']['AvailableWindow'] = availableWindow
386 # Cookie of the channel for which the traffic received is being acknowledged
387 rts_pdu['FlowControlAck']['Ack']['ChannelCookie'] = RTSCookie()
388 rts_pdu['FlowControlAck']['Ack']['ChannelCookie']['Cookie'] = channelCookie
390 packet = RTSHeader()
391 packet['Flags'] = RTS_FLAG_OTHER_CMD
392 packet['NumberOfCommands'] = len(rts_pdu.structure)
393 packet['pduData'] = rts_pdu.getData()
395 return packet.getData()
397def hPing():
398 packet = RTSHeader()
399 packet['Flags'] = RTS_FLAG_PING
401 return packet.getData()
403################################################################################
404# CLASSES
405################################################################################
406class RPCProxyClient(HTTPClientSecurityProvider):
407 RECV_SIZE = 8192
408 default_headers = {'User-Agent' : 'MSRPC',
409 'Cache-Control': 'no-cache',
410 'Connection' : 'Keep-Alive',
411 'Expect' : '100-continue',
412 'Accept' : 'application/rpc',
413 'Pragma' : 'No-cache'
414 }
416 def __init__(self, remoteName=None, dstport=593):
417 HTTPClientSecurityProvider.__init__(self)
418 self.__remoteName = remoteName
419 self.__dstport = dstport
421 # Chosen auth type
422 self.__auth_type = None
424 self.init_state()
426 def init_state(self):
427 self.__channels = {}
429 self.__inChannelCookie = uuid.generate()
430 self.__outChannelCookie = uuid.generate()
431 self.__associationGroupId = uuid.generate()
432 self.__virtualConnectionCookie = uuid.generate()
434 self.__serverConnectionTimeout = None
435 self.__serverReceiveWindowSize = None
436 self.__availableWindowAdvertised = 262144 # 256k
437 self.__receiverAvailableWindow = self.__availableWindowAdvertised
438 self.__bytesReceived = 0
440 self.__serverChunked = False
441 self.__readBuffer = b''
442 self.__chunkLeft = 0
444 self.rts_ping_received = False
446 def set_proxy_credentials(self, username, password, domain='', lmhash='', nthash=''):
447 LOG.error("DeprecationWarning: Call to deprecated method set_proxy_credentials (use set_credentials).")
448 self.set_credentials(username, password, domain, lmhash, nthash)
450 def set_credentials(self, username, password, domain='', lmhash='', nthash='', aesKey='', TGT=None, TGS=None):
451 HTTPClientSecurityProvider.set_credentials(self, username, password,
452 domain, lmhash, nthash, aesKey, TGT, TGS)
454 def create_rpc_in_channel(self):
455 headers = self.default_headers.copy()
456 headers['Content-Length'] = '1073741824'
458 self.create_channel('RPC_IN_DATA', headers)
460 def create_rpc_out_channel(self):
461 headers = self.default_headers.copy()
462 headers['Content-Length'] = '76'
464 self.create_channel('RPC_OUT_DATA', headers)
466 def create_channel(self, method, headers):
467 self.__channels[method] = HTTPClientSecurityProvider.connect(self, self._rpcProxyUrl.scheme,
468 self._rpcProxyUrl.netloc)
470 auth_headers = HTTPClientSecurityProvider.get_auth_headers(self, self.__channels[method],
471 method, self._rpcProxyUrl.path, headers)[0]
473 headers_final = {}
474 headers_final.update(headers)
475 headers_final.update(auth_headers)
477 self.__auth_type = HTTPClientSecurityProvider.get_auth_type(self)
479 # To connect to an RPC Server, we need to let the RPC Proxy know
480 # where to connect. The target RPC Server name and its port are passed
481 # in the query of the HTTP request. The target RPC Server must be the ncacn_http
482 # service.
483 #
484 # The utilized format: /rpc/rpcproxy.dll?RemoteName:RemotePort
485 #
486 # For RDG servers, you can specify localhost:3388, but in other cases you cannot
487 # use localhost as there will be no ACL for it.
488 #
489 # To know what RemoteName to use, we rely on Default ACL. It's specified
490 # in the HKLM\SOFTWARE\Microsoft\Rpc\RpcProxy key:
491 #
492 # ValidPorts REG_SZ COMPANYSERVER04:593;COMPANYSERVER04:49152-65535
493 #
494 # In this way, we can at least connect to the endpoint mapper on port 593.
495 # So, if the caller set remoteName to an empty string, we assume the target
496 # is the RPC Proxy server itself, and get its NetBIOS name from the NTLMSSP.
497 #
498 # Interestingly, if the administrator renames the server after RPC Proxy installation
499 # or joins the server to the domain after RPC Proxy installation, the ACL will remain
500 # the original. So, sometimes the ValidPorts values have the format WIN-JCKEDQVDOQU, and
501 # we are not able to use them.
502 #
503 # For Exchange servers, the value of the default ACL doesn't matter as they
504 # allow connections by their own mechanisms:
505 # - Exchange 2003 / 2007 / 2010 servers add their own ACL, which includes
506 # NetBIOS names of all Exchange servers (and some other servers).
507 # This ACL is regularly and automatically updated on each server.
508 # Allowed ports: 6001-6004
509 #
510 # 6001 is used for MS-OXCRPC
511 # 6002 is used for MS-OXABREF
512 # 6003 is not used
513 # 6004 is used for MS-OXNSPI
514 #
515 # Tests on Exchange 2010 show that MS-OXNSPI and MS-OXABREF are available
516 # on both 6002 and 6004.
517 #
518 # - Exchange 2013 / 2016 / 2019 servers process RemoteName on their own
519 # (via RpcProxyShim.dll), and the NetBIOS name format is supported only for
520 # backward compatibility.
521 #
522 # ! Default ACL is never used, so there is no way to connect to the endpoint mapper!
523 #
524 # Allowed ports: 6001-6004
525 #
526 # 6001 is used for MS-OXCRPC
527 # 6002 is used for MS-OXABREF
528 # 6003 is not used
529 # 6004 is used for MS-OXNSPI
530 #
531 # Tests show that all protocols are available on the 6001 / 6002 / 6004 ports via
532 # RPC over HTTP v2, and the separation is only used for backward compatibility.
533 #
534 # The pure ncacn_http endpoint is available only on the 6001 TCP/IP port.
535 #
536 # RpcProxyShim.dll allows you to skip authentication on the RPC level to get
537 # a faster connection, and it makes Exchange 2013 / 2016 / 2019 RPC over HTTP v2
538 # endpoints vulnerable to NTLM-Relaying attacks.
539 #
540 # If the target is Exchange behind Microsoft TMG, you most likely need to specify
541 # the remote name manually using the value from /autodiscover/autodiscover.xml.
542 # Note that /autodiscover/autodiscover.xml might not be available with
543 # a non-outlook User-Agent.
544 #
545 # There may be multiple RPC Proxy servers with different NetBIOS names on
546 # a single external IP. We store the first one's NetBIOS name and use it for all
547 # the following channels.
548 # It's acceptable to assume all RPC Proxies have the same ACLs (true for Exchange).
549 if not self.__remoteName and self.__auth_type == AUTH_BASIC:
550 raise RPCProxyClientException(RPC_PROXY_REMOTE_NAME_NEEDED_ERR)
552 if not self.__remoteName:
553 ntlmssp = self.get_ntlmssp_info()
554 self.__remoteName = ntlmssp[ntlm.NTLMSSP_AV_HOSTNAME][1].decode('utf-16le')
555 self._stringbinding.set_network_address(self.__remoteName)
556 LOG.debug('StringBinding has been changed to %s' % self._stringbinding)
558 if not self._rpcProxyUrl.query:
559 query = self.__remoteName + ':' + str(self.__dstport)
560 self._rpcProxyUrl = self._rpcProxyUrl._replace(query=query)
562 path = self._rpcProxyUrl.path + '?' + self._rpcProxyUrl.query
564 self.__channels[method].request(method, path, headers=headers_final)
565 self._read_100_continue(method)
567 def _read_100_continue(self, method):
568 resp = self.__channels[method].sock.recv(self.RECV_SIZE)
570 while resp.find(b'\r\n\r\n') == -1:
571 resp += self.__channels[method].sock.recv(self.RECV_SIZE)
573 # Continue responses can have multiple lines, for example:
574 #
575 # HTTP/1.1 100 Continue
576 # Via: 1.1 FIREWALL1
577 #
578 # Don't expect the response to contain "100 Continue\r\n\r\n"
579 if resp[9:23] != b'100 Continue\r\n':
580 try:
581 # The server (IIS) may return localized error messages in
582 # the first line. Tests shown they are in UTF-8.
583 resp = resp.split(b'\r\n')[0].decode("UTF-8", errors='replace')
585 raise RPCProxyClientException('RPC Proxy Client: %s authentication failed in %s channel' %
586 (self.__auth_type, method), proxy_error=resp)
587 except (IndexError, KeyError, AttributeError):
588 raise RPCProxyClientException('RPC Proxy Client: %s authentication failed in %s channel' %
589 (self.__auth_type, method))
591 def create_tunnel(self):
592 # 3.2.1.5.3.1 Connection Establishment
593 packet = hCONN_A1(self.__virtualConnectionCookie, self.__outChannelCookie, self.__availableWindowAdvertised)
594 self.get_socket_out().send(packet)
596 packet = hCONN_B1(self.__virtualConnectionCookie, self.__inChannelCookie, self.__associationGroupId)
597 self.get_socket_in().send(packet)
599 resp = self.get_socket_out().recv(self.RECV_SIZE)
601 while resp.find(b'\r\n\r\n') == -1:
602 resp += self.get_socket_out().recv(self.RECV_SIZE)
604 if resp[9:12] != b'200':
605 try:
606 # The server (IIS) may return localized error messages in
607 # the first line. Tests shown they are in UTF-8.
608 resp = resp.split(b'\r\n')[0].decode("UTF-8", errors='replace')
610 raise RPCProxyClientException('RPC Proxy CONN/A1 request failed', proxy_error=resp)
611 except (IndexError, KeyError, AttributeError):
612 raise RPCProxyClientException('RPC Proxy CONN/A1 request failed')
614 resp_ascii = resp.decode("ASCII", errors='replace')
615 if "transfer-encoding: chunked" in resp_ascii.lower():
616 self.__serverChunked = True
618 # If the body is here, let's send it to rpc_out_recv1()
619 self.__readBuffer = resp[resp.find(b'\r\n\r\n') + 4:]
621 # Recieving and parsing CONN/A3
622 conn_a3_rpc = self.rpc_out_read_pkt()
623 conn_a3_pdu = RTSHeader(conn_a3_rpc)['pduData']
624 conn_a3 = CONN_A3_RTS_PDU(conn_a3_pdu)
625 self.__serverConnectionTimeout = conn_a3['ConnectionTimeout']['ConnectionTimeout']
627 # Recieving and parsing CONN/C2
628 conn_c2_rpc = self.rpc_out_read_pkt()
629 conn_c2_pdu = RTSHeader(conn_c2_rpc)['pduData']
630 conn_c2 = CONN_C2_RTS_PDU(conn_c2_pdu)
631 self.__serverReceiveWindowSize = conn_c2['ReceiveWindowSize']['ReceiveWindowSize']
633 def get_socket_in(self):
634 return self.__channels['RPC_IN_DATA'].sock
636 def get_socket_out(self):
637 return self.__channels['RPC_OUT_DATA'].sock
639 def close_rpc_in_channel(self):
640 return self.__channels['RPC_IN_DATA'].close()
642 def close_rpc_out_channel(self):
643 return self.__channels['RPC_OUT_DATA'].close()
645 def check_http_error(self, buffer):
646 if buffer[:22] == b'HTTP/1.0 503 RPC Error':
647 raise RPCProxyClientException('RPC Proxy request failed', proxy_error=buffer)
649 def rpc_out_recv1(self, amt=None):
650 # Read with at most one underlying system call.
651 # The function MUST return the maximum amt bytes.
652 #
653 # Strictly speaking, it may cause more than one read,
654 # but that is ok, since that is to satisfy the chunked protocol.
655 sock = self.get_socket_out()
657 if self.__serverChunked is False:
658 if len(self.__readBuffer) > 0:
659 buffer = self.__readBuffer
660 self.__readBuffer = b''
661 else:
662 # Let's read RECV_SIZE bytes and not amt bytes.
663 # We would need to check the answer for HTTP errors, as
664 # they can just appear in the middle of the stream.
665 buffer = sock.recv(self.RECV_SIZE)
667 self.check_http_error(buffer)
669 if len(buffer) <= amt:
670 return buffer
672 # We received more than we need
673 self.__readBuffer = buffer[amt:]
674 return buffer[:amt]
676 # Check if the previous chunk is still there
677 if self.__chunkLeft > 0:
678 # If the previous chunk is still there,
679 # just give the caller what we already have
680 if amt >= self.__chunkLeft:
681 buffer = self.__readBuffer[:self.__chunkLeft]
682 # We may have recieved a part of a new chunk
683 self.__readBuffer = self.__readBuffer[self.__chunkLeft + 2:]
684 self.__chunkLeft = 0
686 return buffer
687 else:
688 buffer = self.__readBuffer[:amt]
689 self.__readBuffer = self.__readBuffer[amt:]
690 self.__chunkLeft -= amt
692 return buffer
694 # Let's start to process a new chunk
695 buffer = self.__readBuffer
696 self.__readBuffer = b''
698 self.check_http_error(buffer)
700 # Let's receive a chunk size field which ends with CRLF
701 # For Microsoft TMG 2010 it can cause more than one read
702 while buffer.find(b'\r\n') == -1:
703 buffer += sock.recv(self.RECV_SIZE)
704 self.check_http_error(buffer)
706 chunksize = int(buffer[:buffer.find(b'\r\n')], 16)
707 buffer = buffer[buffer.find(b'\r\n') + 2:]
709 # Let's read at least our chunk including final CRLF
710 while len(buffer) - 2 < chunksize:
711 buffer += sock.recv(chunksize - len(buffer) + 2)
713 # We should not be using any information from
714 # the TCP level to determine HTTP boundaries.
715 # So, we may have received more than we need.
716 if len(buffer) - 2 > chunksize:
717 self.__readBuffer = buffer[chunksize + 2:]
718 buffer = buffer[:chunksize + 2]
720 # Checking the amt
721 if len(buffer) - 2 > amt:
722 self.__chunkLeft = chunksize - amt
723 # We may have recieved a part of a new chunk before,
724 # so the concatenation is crucual
725 self.__readBuffer = buffer[amt:] + self.__readBuffer
727 return buffer[:amt]
728 else:
729 # Removing CRLF
730 return buffer[:-2]
732 def send(self, data, forceWriteAndx=0, forceRecv=0):
733 # We don't use chunked encoding for IN channel as
734 # Microsoft software is developed this way.
735 # If you do this, it may fail.
736 self.get_socket_in().send(data)
738 def rpc_out_read_pkt(self, handle_rts=False):
739 while True:
740 response_data = b''
742 # Let's receive common RPC header and no more
743 #
744 # C706
745 # 12.4 Common Fields
746 # Header encodings differ between connectionless and connection-oriented PDUs.
747 # However, certain fields use common sets of values with a consistent
748 # interpretation across the two protocols.
749 #
750 # This MUST recv MSRPCHeader._SIZE bytes, and not MSRPCRespHeader._SIZE bytes!
751 #
752 while len(response_data) < MSRPCHeader._SIZE:
753 response_data += self.rpc_out_recv1(MSRPCHeader._SIZE - len(response_data))
755 response_header = MSRPCHeader(response_data)
757 # frag_len contains the full length of the packet for both
758 # MSRPC and RTS
759 frag_len = response_header['frag_len']
761 # Receiving the full pkt and no more
762 while len(response_data) < frag_len:
763 response_data += self.rpc_out_recv1(frag_len - len(response_data))
765 # We need to do the Flow Control procedures
766 #
767 # 3.2.1.1.4
768 # This protocol specifies that only RPC PDUs are subject to the flow control abstract data
769 # model. RTS PDUs and the HTTP request and response headers are not subject to flow control.
770 if response_header['type'] != MSRPC_RTS:
771 self.flow_control(frag_len)
773 if handle_rts is True and response_header['type'] == MSRPC_RTS:
774 self.handle_out_of_sequence_rts(response_data)
775 else:
776 return response_data
778 def recv(self, forceRecv=0, count=0):
779 return self.rpc_out_read_pkt(handle_rts=True)
781 def handle_out_of_sequence_rts(self, response_data):
782 packet = RTSHeader(response_data)
784 #print("=========== RTS PKT ===========")
785 #print("RAW: %s" % binascii.hexlify(response_data))
786 #packet.dump()
787 #
788 #pduData = packet['pduData']
789 #numberOfCommands = packet['NumberOfCommands']
790 #
791 #server_cmds = []
792 #while numberOfCommands > 0:
793 # numberOfCommands -= 1
794 #
795 # cmd_type = unpack('<L', pduData[:4])[0]
796 # cmd = COMMANDS[cmd_type](pduData)
797 # server_cmds.append(cmd)
798 # pduData = pduData[len(cmd):]
799 #
800 #for cmd in server_cmds:
801 # cmd.dump()
802 #print("=========== / RTS PKT ===========")
804 # 2.2.4.49 Ping RTS PDU
805 if packet['Flags'] == RTS_FLAG_PING:
806 # 3.2.1.2.1 PingTimer
807 #
808 # If the SendingChannel is part of a Virtual Connection in the Outbound Proxy or Client roles, the
809 # SendingChannel maintains a PingTimer that on expiration indicates a PING PDU must be sent to the
810 # receiving channel. The PING PDU is sent to the receiving channel when no data has been sent within
811 # half of the value of the KeepAliveInterval.
813 # As we do not do long-term connections with no data transfer,
814 # it means something on the server-side is going wrong.
815 self.rts_ping_received = True
816 LOG.error("Ping RTS PDU packet received. Is the RPC Server alive?")
818 # Just in case it's a long operation, let's send PING PDU to IN Channel like in xfreerdp
819 # It's better to send more than one PING packet as it only 20 bytes long
820 packet = hPing()
821 self.send(packet)
822 self.send(packet)
823 # 2.2.4.24 OUT_R1/A2 RTS PDU
824 elif packet['Flags'] == RTS_FLAG_RECYCLE_CHANNEL:
825 raise RPCProxyClientException("The server requested recycling of a virtual OUT channel, " \
826 "but this function is not supported!")
827 # Ignore all other messages, most probably flow control acknowledgments
828 else:
829 pass
831 def flow_control(self, frag_len):
832 self.__bytesReceived += frag_len
833 self.__receiverAvailableWindow -= frag_len
835 if (self.__receiverAvailableWindow < self.__availableWindowAdvertised // 2):
836 self.__receiverAvailableWindow = self.__availableWindowAdvertised
837 packet = hFlowControlAckWithDestination(FDOutProxy, self.__bytesReceived,
838 self.__availableWindowAdvertised, self.__outChannelCookie)
839 self.send(packet)
841 def connect(self):
842 self.create_rpc_in_channel()
843 self.create_rpc_out_channel()
844 self.create_tunnel()
846 def disconnect(self):
847 self.close_rpc_in_channel()
848 self.close_rpc_out_channel()
849 self.init_state()