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

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

## 410 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# Transport implementations for the DCE/RPC protocol.

11#

12# Author:

13# Alberto Solino (@agsolino)

14#

15 from __future__ import division

16 from __future__ import print_function

17

18 import binascii

19 import os

20 import re

21 import socket

22

23 try:

24 from urllib.parse import urlparse, urlunparse

25 except ImportError:

26 from urlparse import urlparse, urlunparse

27

28 from impacket import ntlm

29 from impacket.dcerpc.v5.rpcrt import DCERPCException, DCERPC_v5, DCERPC_v4

30 from impacket.dcerpc.v5.rpch import RPCProxyClient, RPCProxyClientException, RPC_OVER_HTTP_v1, RPC_OVER_HTTP_v2

31 from impacket.smbconnection import SMBConnection

32

33

34 class DCERPCStringBinding:

35 parser = re.compile(r"(?:([a-fA-F0-9-]{8}(?:-[a-fA-F0-9-]{4}){3}-[a-fA-F0-9-]{12})@)?" + # UUID (opt.)

36 r"([_a-zA-Z0-9]*):" + # Protocol Sequence

37 r"([^\[]*)" + # Network Address (opt.)

38 r"(?:\[([^]]*)])?") # Endpoint and options (opt.)

39

40 def __init__(self, stringbinding):

41 match = DCERPCStringBinding.parser.match(stringbinding)

42 self.__uuid = match.group(1)

43 self.__ps = match.group(2)

44 self.__na = match.group(3)

45 options = match.group(4)

46 if options:

47 options = options.split(',')

48

49 self.__endpoint = options[0]

50 try:

51 self.__endpoint.index('endpoint=')

52 self.__endpoint = self.__endpoint[len('endpoint='):]

53 except:

54 pass

55

56 self.__options = {}

57 for option in options[1:]: 57 ↛ 58line 57 didn't jump to line 58, because the loop on line 57 never started

58 vv = option.split('=', 1)

59 self.__options[vv[0]] = vv[1] if len(vv) > 1 else ''

60 else:

61 self.__endpoint = ''

62 self.__options = {}

63

64 def get_uuid(self):

65 return self.__uuid

66

67 def get_protocol_sequence(self):

68 return self.__ps

69

70 def get_network_address(self):

71 return self.__na

72

73 def set_network_address(self, addr):

74 self.__na = addr

75

76 def get_endpoint(self):

77 return self.__endpoint

78

79 def get_options(self):

80 return self.__options

81

82 def get_option(self, option_name):

83 return self.__options[option_name]

84

85 def is_option_set(self, option_name):

86 return option_name in self.__options

87

88 def unset_option(self, option_name):

89 del self.__options[option_name]

90

91 def __str__(self):

92 return DCERPCStringBindingCompose(self.__uuid, self.__ps, self.__na, self.__endpoint, self.__options)

93

94

95 def DCERPCStringBindingCompose(uuid=None, protocol_sequence='', network_address='', endpoint='', options={}):

96 s = ''

97 if uuid:

98 s += uuid + '@'

99 s += protocol_sequence + ':'

100 if network_address:

101 s += network_address

102 if endpoint or options:

103 s += '[' + endpoint

104 if options:

105 s += ',' + ','.join([key if str(val) == '' else "=".join([key, str(val)]) for key, val in options.items()])

106 s += ']'

107

108 return s

109

110

111 def DCERPCTransportFactory(stringbinding):

112 sb = DCERPCStringBinding(stringbinding)

113

114 na = sb.get_network_address()

115 ps = sb.get_protocol_sequence()

116 if 'ncadg_ip_udp' == ps: 116 ↛ 117line 116 didn't jump to line 117, because the condition on line 116 was never true

117 port = sb.get_endpoint()

118 if port:

119 rpctransport = UDPTransport(na, int(port))

120 else:

121 rpctransport = UDPTransport(na)

122 elif 'ncacn_ip_tcp' == ps:

123 port = sb.get_endpoint()

124 if port:

125 rpctransport = TCPTransport(na, int(port))

126 else:

127 rpctransport = TCPTransport(na)

128 elif 'ncacn_http' == ps:

129 port = sb.get_endpoint()

130 if port: 130 ↛ 131line 130 didn't jump to line 131, because the condition on line 130 was never true

131 rpctransport = HTTPTransport(na, int(port))

132 else:

133 rpctransport = HTTPTransport(na)

134 elif 'ncacn_np' == ps: 134 ↛ 141line 134 didn't jump to line 141, because the condition on line 134 was never false

135 named_pipe = sb.get_endpoint()

136 if named_pipe: 136 ↛ 140line 136 didn't jump to line 140, because the condition on line 136 was never false

137 named_pipe = named_pipe[len(r'\pipe'):]

138 rpctransport = SMBTransport(na, filename = named_pipe)

139 else:

140 rpctransport = SMBTransport(na)

141 elif 'ncalocal' == ps:

142 named_pipe = sb.get_endpoint()

143 rpctransport = LOCALTransport(filename = named_pipe)

144 else:

145 raise DCERPCException("Unknown protocol sequence.")

146

147 rpctransport.set_stringbinding(sb)

148 return rpctransport

149

150 class DCERPCTransport:

151

152 DCERPC_class = DCERPC_v5

153

154 def __init__(self, remoteName, dstport):

155 self.__remoteName = remoteName

156 self.__remoteHost = remoteName

157 self.__dstport = dstport

158 self._stringbinding = None

159 self._max_send_frag = None

160 self._max_recv_frag = None

161 self._domain = ''

162 self._lmhash = ''

163 self._nthash = ''

164 self.__connect_timeout = None

165 self._doKerberos = False

166 self._username = ''

167 self._password = ''

168 self._domain = ''

169 self._aesKey = None

170 self._TGT = None

171 self._TGS = None

172 self._kdcHost = None

173 self.set_credentials('','')

174 # Strict host validation - off by default and currently only for

175 # SMBTransport

176 self._strict_hostname_validation = False

177 self._validation_allow_absent = True

178 self._accepted_hostname = ''

179

180 def connect(self):

181 raise RuntimeError('virtual function')

182 def send(self,data=0, forceWriteAndx = 0, forceRecv = 0):

183 raise RuntimeError('virtual function')

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

185 raise RuntimeError('virtual function')

186 def disconnect(self):

187 raise RuntimeError('virtual function')

188 def get_socket(self):

189 raise RuntimeError('virtual function')

190

191 def get_connect_timeout(self):

192 return self.__connect_timeout

193 def set_connect_timeout(self, timeout):

194 self.__connect_timeout = timeout

195

196 def getRemoteName(self):

197 return self.__remoteName

198

199 def setRemoteName(self, remoteName):

200 """This method only makes sense before connection for most protocols."""

201 self.__remoteName = remoteName

202

203 def getRemoteHost(self):

204 return self.__remoteHost

205

206 def setRemoteHost(self, remoteHost):

207 """This method only makes sense before connection for most protocols."""

208 self.__remoteHost = remoteHost

209

210 def get_dport(self):

211 return self.__dstport

212 def set_dport(self, dport):

213 """This method only makes sense before connection for most protocols."""

214 self.__dstport = dport

215

216 def get_stringbinding(self):

217 return self._stringbinding

218

219 def set_stringbinding(self, stringbinding):

220 self._stringbinding = stringbinding

221

222 def get_addr(self):

223 return self.getRemoteHost(), self.get_dport()

224 def set_addr(self, addr):

225 """This method only makes sense before connection for most protocols."""

226 self.setRemoteHost(addr[0])

227 self.set_dport(addr[1])

228

229 def set_kerberos(self, flag, kdcHost = None):

230 self._doKerberos = flag

231 self._kdcHost = kdcHost

232

233 def get_kerberos(self):

234 return self._doKerberos

235

236 def get_kdcHost(self):

237 return self._kdcHost

238

239 def set_max_fragment_size(self, send_fragment_size):

240 # -1 is default fragment size: 0 (don't fragment)

241 # 0 is don't fragment

242 # other values are max fragment size

243 if send_fragment_size == -1: 243 ↛ 244line 243 didn't jump to line 244, because the condition on line 243 was never true

244 self.set_default_max_fragment_size()

245 else:

246 self._max_send_frag = send_fragment_size

247

248 def set_hostname_validation(self, validate, accept_empty, hostname):

249 self._strict_hostname_validation = validate

250 self._validation_allow_absent = accept_empty

251 self._accepted_hostname = hostname

252

253 def set_default_max_fragment_size(self):

254 # default is 0: don't fragment.

255 # subclasses may override this method

256 self._max_send_frag = 0

257

258 def get_credentials(self):

259 return (

260 self._username,

261 self._password,

262 self._domain,

263 self._lmhash,

264 self._nthash,

265 self._aesKey,

266 self._TGT,

267 self._TGS)

268

269 def set_credentials(self, username, password, domain='', lmhash='', nthash='', aesKey='', TGT=None, TGS=None):

270 self._username = username

271 self._password = password

272 self._domain = domain

273 self._aesKey = aesKey

274 self._TGT = TGT

275 self._TGS = TGS

276 if lmhash != '' or nthash != '':

277 if len(lmhash) % 2: 277 ↛ 278line 277 didn't jump to line 278, because the condition on line 277 was never true

278 lmhash = '0%s' % lmhash

279 if len(nthash) % 2: 279 ↛ 280line 279 didn't jump to line 280, because the condition on line 279 was never true

280 nthash = '0%s' % nthash

281 try: # just in case they were converted already

282 self._lmhash = binascii.unhexlify(lmhash)

283 self._nthash = binascii.unhexlify(nthash)

284 except:

285 self._lmhash = lmhash

286 self._nthash = nthash

287 pass

288

289 def doesSupportNTLMv2(self):

290 # By default we'll be returning the library's default. Only on SMB Transports we might be able to know it beforehand

291 return ntlm.USE_NTLMv2

292

293 def get_dce_rpc(self):

294 return DCERPC_v5(self)

295

296 class UDPTransport(DCERPCTransport):

297 "Implementation of ncadg_ip_udp protocol sequence"

298

299 DCERPC_class = DCERPC_v4

300

301 def __init__(self, remoteName, dstport = 135):

302 DCERPCTransport.__init__(self, remoteName, dstport)

303 self.__socket = 0

304 self.set_connect_timeout(30)

305 self.__recv_addr = ''

306

307 def connect(self):

308 try:

309 af, socktype, proto, canonname, sa = socket.getaddrinfo(self.getRemoteHost(), self.get_dport(), 0, socket.SOCK_DGRAM)[0]

310 self.__socket = socket.socket(af, socktype, proto)

311 self.__socket.settimeout(self.get_connect_timeout())

312 except socket.error as msg:

313 self.__socket = None

314 raise DCERPCException("Could not connect: %s" % msg)

315

316 return 1

317

318 def disconnect(self):

319 try:

320 self.__socket.close()

321 except socket.error:

322 self.__socket = None

323 return 0

324 return 1

325

326 def send(self,data, forceWriteAndx = 0, forceRecv = 0):

327 self.__socket.sendto(data, (self.getRemoteHost(), self.get_dport()))

328

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

330 buffer, self.__recv_addr = self.__socket.recvfrom(8192)

331 return buffer

332

333 def get_recv_addr(self):

334 return self.__recv_addr

335

336 def get_socket(self):

337 return self.__socket

338

339 class TCPTransport(DCERPCTransport):

340 """Implementation of ncacn_ip_tcp protocol sequence"""

341

342 def __init__(self, remoteName, dstport = 135):

343 DCERPCTransport.__init__(self, remoteName, dstport)

344 self.__socket = 0

345 self.set_connect_timeout(30)

346

347 def connect(self):

348 af, socktype, proto, canonname, sa = socket.getaddrinfo(self.getRemoteHost(), self.get_dport(), 0, socket.SOCK_STREAM)[0]

349 self.__socket = socket.socket(af, socktype, proto)

350 try:

351 self.__socket.settimeout(self.get_connect_timeout())

352 self.__socket.connect(sa)

353 except socket.error as msg:

354 self.__socket.close()

355 raise DCERPCException("Could not connect: %s" % msg)

356 return 1

357

358 def disconnect(self):

359 try:

360 self.__socket.close()

361 except socket.error:

362 self.__socket = None

363 return 0

364 return 1

365

366 def send(self,data, forceWriteAndx = 0, forceRecv = 0):

367 if self._max_send_frag:

368 offset = 0

369 while 1:

370 toSend = data[offset: offset+self._max_send_frag]

371 if not toSend:

372 break

373 self.__socket.send(toSend)

374 offset += len(toSend)

375 else:

376 self.__socket.send(data)

377

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

379 if count:

380 buffer = b''

381 while len(buffer) < count:

382 buffer += self.__socket.recv(count-len(buffer))

383 else:

384 buffer = self.__socket.recv(8192)

385 return buffer

386

387 def get_socket(self):

388 return self.__socket

389

390 class HTTPTransport(TCPTransport, RPCProxyClient):

391 """Implementation of ncacn_http protocol sequence"""

392

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

394 self._useRpcProxy = False

395 self._rpcProxyUrl = None

396 self._transport = TCPTransport

397 self._version = RPC_OVER_HTTP_v2

398

399 DCERPCTransport.__init__(self, remoteName, dstport)

400 RPCProxyClient.__init__(self, remoteName, dstport)

401 self.set_connect_timeout(30)

402

403 def set_credentials(self, username, password, domain='', lmhash='', nthash='', aesKey='', TGT=None, TGS=None):

404 return self._transport.set_credentials(self, username, password,

405 domain, lmhash, nthash, aesKey, TGT, TGS)

406

407 def rpc_proxy_init(self):

408 self._useRpcProxy = True

409 self._transport = RPCProxyClient

410

411 def set_rpc_proxy_url(self, url):

412 self.rpc_proxy_init()

413 self._rpcProxyUrl = urlparse(url)

414

415 def get_rpc_proxy_url(self):

416 return urlunparse(self._rpcProxyUrl)

417

418 def set_stringbinding(self, set_stringbinding):

419 DCERPCTransport.set_stringbinding(self, set_stringbinding)

420

421 if self._stringbinding.is_option_set("RpcProxy"): 421 ↛ 422line 421 didn't jump to line 422, because the condition on line 421 was never true

422 self.rpc_proxy_init()

423

424 rpcproxy = self._stringbinding.get_option("RpcProxy").split(":")

425

426 if rpcproxy[1] == '443':

427 self.set_rpc_proxy_url('https://%s/rpc/rpcproxy.dll' % rpcproxy[0])

428 elif rpcproxy[1] == '80':

429 self.set_rpc_proxy_url('http://%s/rpc/rpcproxy.dll' % rpcproxy[0])

430 else:

431 # 2.1.2.1

432 # RPC over HTTP always uses port 80 for HTTP traffic and port 443 for HTTPS traffic.

433 # But you can use set_rpc_proxy_url method to set any URL / query you want.

434 raise DCERPCException("RPC Proxy port must be 80 or 443")

435

436 def connect(self):

437 if self._useRpcProxy == False: 437 ↛ 452line 437 didn't jump to line 452, because the condition on line 437 was never false

438 # Connecting directly to the ncacn_http port

439 #

440 # Here we using RPC over HTTPv1 instead complex RPC over HTTP v2 syntax

441 # RPC over HTTP v2 here can be implemented in the future

442 self._version = RPC_OVER_HTTP_v1

443

444 TCPTransport.connect(self)

445

446 # Reading legacy server response

447 data = self.get_socket().recv(8192)

448

449 if data != b'ncacn_http/1.0': 449 ↛ 450line 449 didn't jump to line 450, because the condition on line 449 was never true

450 raise DCERPCException("%s:%s service is not ncacn_http" % (self.__remoteName, self.__dstport))

451 else:

452 RPCProxyClient.connect(self)

453

454 def send(self, data, forceWriteAndx=0, forceRecv=0):

455 return self._transport.send(self, data, forceWriteAndx, forceRecv)

456

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

458 return self._transport.recv(self, forceRecv, count)

459

460 def get_socket(self):

461 if self._useRpcProxy == False: 461 ↛ 464line 461 didn't jump to line 464, because the condition on line 461 was never false

462 return TCPTransport.get_socket(self)

463 else:

464 raise DCERPCException("This method is not supported for RPC Proxy connections")

465

466 def disconnect(self):

467 return self._transport.disconnect(self)

468

469 class SMBTransport(DCERPCTransport):

470 """Implementation of ncacn_np protocol sequence"""

471

472 def __init__(self, remoteName, dstport=445, filename='', username='', password='', domain='', lmhash='', nthash='',

473 aesKey='', TGT=None, TGS=None, remote_host='', smb_connection=0, doKerberos=False, kdcHost=None):

474 DCERPCTransport.__init__(self, remoteName, dstport)

475 self.__socket = None

476 self.__tid = 0

477 self.__filename = filename

478 self.__handle = 0

479 self.__pending_recv = 0

480 self.set_credentials(username, password, domain, lmhash, nthash, aesKey, TGT, TGS)

481 self._doKerberos = doKerberos

482 self._kdcHost = kdcHost

483

484 if remote_host != '': 484 ↛ 485line 484 didn't jump to line 485, because the condition on line 484 was never true

485 self.setRemoteHost(remote_host)

486

487 if smb_connection == 0:

488 self.__existing_smb = False

489 else:

490 self.__existing_smb = True

491 self.set_credentials(*smb_connection.getCredentials())

492

493 self.__prefDialect = None

494 self.__smb_connection = smb_connection

495 self.set_connect_timeout(30)

496

497 def preferred_dialect(self, dialect):

498 self.__prefDialect = dialect

499

500 def setup_smb_connection(self):

501 if not self.__smb_connection: 501 ↛ exitline 501 didn't return from function 'setup_smb_connection', because the condition on line 501 was never false

502 self.__smb_connection = SMBConnection(self.getRemoteName(), self.getRemoteHost(), sess_port=self.get_dport(),

503 preferredDialect=self.__prefDialect, timeout=self.get_connect_timeout())

504 if self._strict_hostname_validation: 504 ↛ 505line 504 didn't jump to line 505, because the condition on line 504 was never true

505 self.__smb_connection.setHostnameValidation(self._strict_hostname_validation, self._validation_allow_absent, self._accepted_hostname)

506

507 def connect(self):

508 # Check if we have a smb connection already setup

509 if self.__smb_connection == 0:

510 self.setup_smb_connection()

511 if self._doKerberos is False:

512 self.__smb_connection.login(self._username, self._password, self._domain, self._lmhash, self._nthash)

513 else:

514 self.__smb_connection.kerberosLogin(self._username, self._password, self._domain, self._lmhash,

515 self._nthash, self._aesKey, kdcHost=self._kdcHost, TGT=self._TGT,

516 TGS=self._TGS)

517 self.__tid = self.__smb_connection.connectTree('IPC$')

518 self.__handle = self.__smb_connection.openFile(self.__tid, self.__filename)

519 self.__socket = self.__smb_connection.getSMBServer().get_socket()

520 return 1

521

522 def disconnect(self):

523 self.__smb_connection.disconnectTree(self.__tid)

524 # If we created the SMB connection, we close it, otherwise

525 # that's up for the caller

526 if self.__existing_smb is False:

527 self.__smb_connection.logoff()

528 self.__smb_connection.close()

529 self.__smb_connection = 0

530

531 def send(self,data, forceWriteAndx = 0, forceRecv = 0):

532 if self._max_send_frag:

533 offset = 0

534 while 1:

535 toSend = data[offset: offset+self._max_send_frag]

536 if not toSend:

537 break

538 self.__smb_connection.writeFile(self.__tid, self.__handle, toSend, offset = offset)

539 offset += len(toSend)

540 else:

541 self.__smb_connection.writeFile(self.__tid, self.__handle, data)

542 if forceRecv:

543 self.__pending_recv += 1

544

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

546 if self._max_send_frag or self.__pending_recv:

547 # _max_send_frag is checked because it's the same condition we checked

548 # to decide whether to use write_andx() or send_trans() in send() above.

549 if self.__pending_recv:

550 self.__pending_recv -= 1

551 return self.__smb_connection.readFile(self.__tid, self.__handle, bytesToRead = self._max_recv_frag)

552 else:

553 return self.__smb_connection.readFile(self.__tid, self.__handle)

554

555 def get_smb_connection(self):

556 return self.__smb_connection

557

558 def set_smb_connection(self, smb_connection):

559 self.__smb_connection = smb_connection

560 self.set_credentials(*smb_connection.getCredentials())

561 self.__existing_smb = True

562

563 def get_smb_server(self):

564 # Raw Access to the SMBServer (whatever type it is)

565 return self.__smb_connection.getSMBServer()

566

567 def get_socket(self):

568 return self.__socket

569

570 def doesSupportNTLMv2(self):

571 return self.__smb_connection.doesSupportNTLMv2()

572

573 class LOCALTransport(DCERPCTransport):

574 """

575 Implementation of ncalocal protocol sequence, not the same

576 as ncalrpc (I'm not doing LPC just opening the local pipe)

577 """

578

579 def __init__(self, filename = ''):

580 DCERPCTransport.__init__(self, '', 0)

581 self.__filename = filename

582 self.__handle = 0

583

584 def connect(self):

585 if self.__filename.upper().find('PIPE') < 0:

586 self.__filename = '\\PIPE\\%s' % self.__filename

587 self.__handle = os.open('\\\\.\\%s' % self.__filename, os.O_RDWR|os.O_BINARY)

588 return 1

589

590 def disconnect(self):

591 os.close(self.__handle)

592

593 def send(self,data, forceWriteAndx = 0, forceRecv = 0):

594 os.write(self.__handle, data)

595

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

597 data = os.read(self.__handle, 65535)

598 return data