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

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

## 361 statements

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#

15

16 import re

17 import binascii

18 from struct import unpack

19

20 from impacket import uuid, ntlm, system_errors, nt_errors, LOG

21 from impacket.dcerpc.v5.rpcrt import DCERPCException

22

23 from impacket.uuid import EMPTY_UUID

24 from impacket.http import HTTPClientSecurityProvider, AUTH_BASIC

25 from impacket.structure import Structure

26 from impacket.dcerpc.v5.rpcrt import MSRPCHeader, \

27 MSRPC_RTS, PFC_FIRST_FRAG, PFC_LAST_FRAG

28

29 class RPCProxyClientException(DCERPCException):

30 parser = re.compile(r'RPC Error: ([a-fA-F0-9]{1,8})')

31

32 def __init__(self, error_string=None, proxy_error=None):

33 rpc_error_code = None

34

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

41

42 DCERPCException.__init__(self, error_string, rpc_error_code)

43

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

57

58################################################################################

59# CONSTANTS

60################################################################################

61

62 RPC_OVER_HTTP_v1 = 1

63 RPC_OVER_HTTP_v2 = 2

64

65# Errors which might need handling

66

67# RPCProxyClient internal errors

68 RPC_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.'

70

71# Errors below contain a part of server responses

72 RPC_PROXY_INVALID_RPC_PORT_ERR = 'Invalid RPC Port'

73 RPC_PROXY_CONN_A1_0X6BA_ERR = 'RPC Proxy CONN/A1 request failed, code: 0x6ba'

74 RPC_PROXY_CONN_A1_404_ERR = 'CONN/A1 request failed: HTTP/1.1 404 Not Found'

75 RPC_PROXY_RPC_OUT_DATA_404_ERR = 'RPC_OUT_DATA channel: HTTP/1.1 404 Not Found'

76 RPC_PROXY_CONN_A1_401_ERR = 'CONN/A1 request failed: HTTP/1.1 401 Unauthorized'

77 RPC_PROXY_HTTP_IN_DATA_401_ERR = 'RPC_IN_DATA channel: HTTP/1.1 401 Unauthorized'

78

79

80# 2.2.3.3 Forward Destinations

81 FDClient = 0x00000000

82 FDInProxy = 0x00000001

83 FDServer = 0x00000002

84 FDOutProxy = 0x00000003

85

86 RTS_FLAG_NONE = 0x0000

87 RTS_FLAG_PING = 0x0001

88 RTS_FLAG_OTHER_CMD = 0x0002

89 RTS_FLAG_RECYCLE_CHANNEL = 0x0004

90 RTS_FLAG_IN_CHANNEL = 0x0008

91 RTS_FLAG_OUT_CHANNEL = 0x0010

92 RTS_FLAG_EOF = 0x0020

93 RTS_FLAG_ECHO = 0x0040

94

95# 2.2.3.5 RTS Commands

96 RTS_CMD_RECEIVE_WINDOW_SIZE = 0x00000000

97 RTS_CMD_FLOW_CONTROL_ACK = 0x00000001

98 RTS_CMD_CONNECTION_TIMEOUT = 0x00000002

99 RTS_CMD_COOKIE = 0x00000003

100 RTS_CMD_CHANNEL_LIFETIME = 0x00000004

101 RTS_CMD_CLIENT_KEEPALIVE = 0x00000005

102 RTS_CMD_VERSION = 0x00000006

103 RTS_CMD_EMPTY = 0x00000007

104 RTS_CMD_PADDING = 0x00000008

105 RTS_CMD_NEGATIVE_ANCE = 0x00000009

106 RTS_CMD_ANCE = 0x0000000A

107 RTS_CMD_CLIENT_ADDRESS = 0x0000000B

108 RTS_CMD_ASSOCIATION_GROUP_ID = 0x0000000C

109 RTS_CMD_DESTINATION = 0x0000000D

110 RTS_CMD_PING_TRAFFIC_SENT_NOTIFY = 0x0000000E

111

112################################################################################

113# STRUCTURES

114################################################################################

115

116# 2.2.3.1 RTS Cookie

117 class RTSCookie(Structure):

118 structure = (

119 ('Cookie','16s=b"\\x00"*16'),

120 )

121

122# 2.2.3.2 Client Address

123 class 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 )

130

131# 2.2.3.4 Flow Control Acknowledgment

132 class Ack(Structure):

133 structure = (

134 ('BytesReceived','<L=0'),

135 ('AvailableWindow','<L=0'),

136 ('ChannelCookie',':',RTSCookie),

137 )

138

139# 2.2.3.5.1 ReceiveWindowSize

140 class ReceiveWindowSize(Structure):

141 structure = (

142 ('CommandType','<L=0'),

143 ('ReceiveWindowSize','<L=262144'),

144 )

145

146# 2.2.3.5.2 FlowControlAck

147 class FlowControlAck(Structure):

148 structure = (

149 ('CommandType','<L=1'),

150 ('Ack',':',Ack),

151 )

152

153# 2.2.3.5.3 ConnectionTimeout

154 class ConnectionTimeout(Structure):

155 structure = (

156 ('CommandType','<L=2'),

157 ('ConnectionTimeout','<L=120000'),

158 )

159

160# 2.2.3.5.4 Cookie

161 class Cookie(Structure):

162 structure = (

163 ('CommandType','<L=3'),

164 ('Cookie',':',RTSCookie),

165 )

166

167# 2.2.3.5.5 ChannelLifetime

168 class ChannelLifetime(Structure):

169 structure = (

170 ('CommandType','<L=4'),

171 ('ChannelLifetime','<L=1073741824'),

172 )

173

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.

181 class ClientKeepalive(Structure):

182 structure = (

183 ('CommandType','<L=5'),

184 ('ClientKeepalive','<L=300000'),

185 )

186

187# 2.2.3.5.7 Version

188 class Version(Structure):

189 structure = (

190 ('CommandType','<L=6'),

191 ('Version','<L=1'),

192 )

193

194# 2.2.3.5.8 Empty

195 class Empty(Structure):

196 structure = (

197 ('CommandType','<L=7'),

198 )

199

200# 2.2.3.5.9 Padding

201 class Padding(Structure):

202 structure = (

203 ('CommandType','<L=8'),

204 ('ConformanceCount','<L=len(Padding)'),

205 ('Padding','*ConformanceCount'),

206 )

207

208# 2.2.3.5.10 NegativeANCE

209 class NegativeANCE(Structure):

210 structure = (

211 ('CommandType','<L=9'),

212 )

213

214# 2.2.3.5.11 ANCE

215 class ANCE(Structure):

216 structure = (

217 ('CommandType','<L=0xA'),

218 )

219

220# 2.2.3.5.12 ClientAddress

221 class ClientAddress(Structure):

222 structure = (

223 ('CommandType','<L=0xB'),

224 ('ClientAddress',':',EncodedClientAddress),

225 )

226

227# 2.2.3.5.13 AssociationGroupId

228 class AssociationGroupId(Structure):

229 structure = (

230 ('CommandType','<L=0xC'),

231 ('AssociationGroupId',':',RTSCookie),

232 )

233

234# 2.2.3.5.14 Destination

235 class Destination(Structure):

236 structure = (

237 ('CommandType','<L=0xD'),

238 ('Destination','<L'),

239 )

240

241# 2.2.3.5.15 PingTrafficSentNotify

242 class PingTrafficSentNotify(Structure):

243 structure = (

244 ('CommandType','<L=0xE'),

245 ('PingTrafficSent','<L'),

246 )

247

248 COMMANDS = {

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}

265

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.

270 class RTSHeader(MSRPCHeader):

271 _SIZE = 20

272 commonHdr = MSRPCHeader.commonHdr + (

273 ('Flags','<H=0'), # 16

274 ('NumberOfCommands','<H=0'), # 18

275 )

276

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

283

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.

288 class CONN_A1_RTS_PDU(Structure):

289 structure = (

290 ('Version',':',Version),

291 ('VirtualConnectionCookie',':',Cookie),

292 ('OutChannelCookie',':',Cookie),

293 ('ReceiveWindowSize',':',ReceiveWindowSize),

294 )

295

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.

300 class 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 )

309

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.

314 class CONN_A3_RTS_PDU(Structure):

315 structure = (

316 ('ConnectionTimeout',':',ConnectionTimeout),

317 )

318

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.

323 class CONN_C2_RTS_PDU(Structure):

324 structure = (

325 ('Version',':',Version),

326 ('ReceiveWindowSize',':',ReceiveWindowSize),

327 ('ConnectionTimeout',':',ConnectionTimeout),

328 )

329

330# 2.2.4.51 FlowControlAckWithDestination RTS PDU

331 class FlowControlAckWithDestination_RTS_PDU(Structure):

332 structure = (

333 ('Destination',':',Destination),

334 ('FlowControlAck',':',FlowControlAck),

335 )

336

337################################################################################

338# HELPERS

339################################################################################

340 def 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

349

350 packet = RTSHeader()

351 packet['Flags'] = RTS_FLAG_NONE

352 packet['NumberOfCommands'] = len(conn_a1.structure)

353 packet['pduData'] = conn_a1.getData()

354

355 return packet.getData()

356

357 def 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

369

370 packet = RTSHeader()

371 packet['Flags'] = RTS_FLAG_NONE

372 packet['NumberOfCommands'] = len(conn_b1.structure)

373 packet['pduData'] = conn_b1.getData()

374

375 return packet.getData()

376

377 def 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

385

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

389

390 packet = RTSHeader()

391 packet['Flags'] = RTS_FLAG_OTHER_CMD

392 packet['NumberOfCommands'] = len(rts_pdu.structure)

393 packet['pduData'] = rts_pdu.getData()

394

395 return packet.getData()

396

397 def hPing():

398 packet = RTSHeader()

399 packet['Flags'] = RTS_FLAG_PING

400

401 return packet.getData()

402

403################################################################################

404# CLASSES

405################################################################################

406 class 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 }

415

416 def __init__(self, remoteName=None, dstport=593):

417 HTTPClientSecurityProvider.__init__(self)

418 self.__remoteName = remoteName

419 self.__dstport = dstport

420

421 # Chosen auth type

422 self.__auth_type = None

423

424 self.init_state()

425

426 def init_state(self):

427 self.__channels = {}

428

429 self.__inChannelCookie = uuid.generate()

430 self.__outChannelCookie = uuid.generate()

431 self.__associationGroupId = uuid.generate()

432 self.__virtualConnectionCookie = uuid.generate()

433

434 self.__serverConnectionTimeout = None

435 self.__serverReceiveWindowSize = None

436 self.__availableWindowAdvertised = 262144 # 256k

437 self.__receiverAvailableWindow = self.__availableWindowAdvertised

438 self.__bytesReceived = 0

439

440 self.__serverChunked = False

441 self.__readBuffer = b''

442 self.__chunkLeft = 0

443

444 self.rts_ping_received = False

445

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)

449

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)

453

454 def create_rpc_in_channel(self):

455 headers = self.default_headers.copy()

456 headers['Content-Length'] = '1073741824'

457

458 self.create_channel('RPC_IN_DATA', headers)

459

460 def create_rpc_out_channel(self):

461 headers = self.default_headers.copy()

462 headers['Content-Length'] = '76'

463

464 self.create_channel('RPC_OUT_DATA', headers)

465

466 def create_channel(self, method, headers):

467 self.__channels[method] = HTTPClientSecurityProvider.connect(self, self._rpcProxyUrl.scheme,

468 self._rpcProxyUrl.netloc)

469

470 auth_headers = HTTPClientSecurityProvider.get_auth_headers(self, self.__channels[method],

471 method, self._rpcProxyUrl.path, headers)[0]

472

473 headers_final = {}

474 headers_final.update(headers)

475 headers_final.update(auth_headers)

476

477 self.__auth_type = HTTPClientSecurityProvider.get_auth_type(self)

478

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)

551

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)

557

558 if not self._rpcProxyUrl.query:

559 query = self.__remoteName + ':' + str(self.__dstport)

560 self._rpcProxyUrl = self._rpcProxyUrl._replace(query=query)

561

562 path = self._rpcProxyUrl.path + '?' + self._rpcProxyUrl.query

563

564 self.__channels[method].request(method, path, headers=headers_final)

565 self._read_100_continue(method)

566

567 def _read_100_continue(self, method):

568 resp = self.__channels[method].sock.recv(self.RECV_SIZE)

569

570 while resp.find(b'\r\n\r\n') == -1:

571 resp += self.__channels[method].sock.recv(self.RECV_SIZE)

572

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')

584

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))

590

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)

595

596 packet = hCONN_B1(self.__virtualConnectionCookie, self.__inChannelCookie, self.__associationGroupId)

597 self.get_socket_in().send(packet)

598

599 resp = self.get_socket_out().recv(self.RECV_SIZE)

600

601 while resp.find(b'\r\n\r\n') == -1:

602 resp += self.get_socket_out().recv(self.RECV_SIZE)

603

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')

609

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')

613

614 resp_ascii = resp.decode("ASCII", errors='replace')

615 if "transfer-encoding: chunked" in resp_ascii.lower():

616 self.__serverChunked = True

617

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:]

620

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']

626

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']

632

633 def get_socket_in(self):

634 return self.__channels['RPC_IN_DATA'].sock

635

636 def get_socket_out(self):

637 return self.__channels['RPC_OUT_DATA'].sock

638

639 def close_rpc_in_channel(self):

640 return self.__channels['RPC_IN_DATA'].close()

641

642 def close_rpc_out_channel(self):

643 return self.__channels['RPC_OUT_DATA'].close()

644

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)

648

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()

656

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)

666

667 self.check_http_error(buffer)

668

669 if len(buffer) <= amt:

670 return buffer

671

672 # We received more than we need

673 self.__readBuffer = buffer[amt:]

674 return buffer[:amt]

675

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

685

686 return buffer

687 else:

688 buffer = self.__readBuffer[:amt]

689 self.__readBuffer = self.__readBuffer[amt:]

690 self.__chunkLeft -= amt

691

692 return buffer

693

694 # Let's start to process a new chunk

695 buffer = self.__readBuffer

696 self.__readBuffer = b''

697

698 self.check_http_error(buffer)

699

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)

705

706 chunksize = int(buffer[:buffer.find(b'\r\n')], 16)

707 buffer = buffer[buffer.find(b'\r\n') + 2:]

708

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)

712

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]

719

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

726

727 return buffer[:amt]

728 else:

729 # Removing CRLF

730 return buffer[:-2]

731

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)

737

738 def rpc_out_read_pkt(self, handle_rts=False):

739 while True:

740 response_data = b''

741

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))

754

755 response_header = MSRPCHeader(response_data)

756

757 # frag_len contains the full length of the packet for both

758 # MSRPC and RTS

759 frag_len = response_header['frag_len']

760

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))

764

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)

772

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

777

778 def recv(self, forceRecv=0, count=0):

779 return self.rpc_out_read_pkt(handle_rts=True)

780

781 def handle_out_of_sequence_rts(self, response_data):

782 packet = RTSHeader(response_data)

783

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 ===========")

803

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.

812

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?")

817

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

830

831 def flow_control(self, frag_len):

832 self.__bytesReceived += frag_len

833 self.__receiverAvailableWindow -= frag_len

834

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)

840

841 def connect(self):

842 self.create_rpc_in_channel()

843 self.create_rpc_out_channel()

844 self.create_tunnel()

845

846 def disconnect(self):

847 self.close_rpc_in_channel()

848 self.close_rpc_out_channel()

849 self.init_state()