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# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.

2#

3# This software is provided under under a slightly modified version

4# of the Apache Software License. See the accompanying LICENSE file

5# for more information.

6#

7# Author: Alberto Solino (@agsolino)

8#

9# Description:

10# Transport implementations for the DCE/RPC protocol.

11#

12 from __future__ import division

13 from __future__ import print_function

14

15 import binascii

16 import os

17 import re

18 import socket

19

20 try:

21 from urllib.parse import urlparse, urlunparse

22 except ImportError:

23 from urlparse import urlparse, urlunparse

24

25 from impacket import ntlm

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

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

28 from impacket.smbconnection import SMBConnection

29

30 class DCERPCStringBinding:

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

32 +'([_a-zA-Z0-9]*):' # Protocol Sequence

33 +'([^\[]*)' # Network Address (opt.)

34 +'(?:\[([^\]]*)\])?') # Endpoint and options (opt.)

35

36 def __init__(self, stringbinding):

37 match = DCERPCStringBinding.parser.match(stringbinding)

38 self.__uuid = match.group(1)

39 self.__ps = match.group(2)

40 self.__na = match.group(3)

41 options = match.group(4)

42 if options:

43 options = options.split(',')

44

45 self.__endpoint = options[0]

46 try:

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

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

49 except:

50 pass

51

52 self.__options = {}

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

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

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

56 else:

57 self.__endpoint = ''

58 self.__options = {}

59

60 def get_uuid(self):

61 return self.__uuid

62

63 def get_protocol_sequence(self):

64 return self.__ps

65

66 def get_network_address(self):

67 return self.__na

68

69 def set_network_address(self, addr):

70 self.__na = addr

71

72 def get_endpoint(self):

73 return self.__endpoint

74

75 def get_options(self):

76 return self.__options

77

78 def get_option(self, option_name):

79 return self.__options[option_name]

80

81 def is_option_set(self, option_name):

82 return option_name in self.__options

83

84 def unset_option(self, option_name):

85 del self.__options[option_name]

86

87 def __str__(self):

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

89

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

91 s = ''

92 if uuid:

93 s += uuid + '@'

94 s += protocol_sequence + ':'

95 if network_address:

96 s += network_address

97 if endpoint or options:

98 s += '[' + endpoint

99 if options:

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

101 s += ']'

102

103 return s

104

105 def DCERPCTransportFactory(stringbinding):

106 sb = DCERPCStringBinding(stringbinding)

107

108 na = sb.get_network_address()

109 ps = sb.get_protocol_sequence()

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

111 port = sb.get_endpoint()

112 if port:

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

114 else:

115 rpctransport = UDPTransport(na)

116 elif 'ncacn_ip_tcp' == ps:

117 port = sb.get_endpoint()

118 if port:

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

120 else:

121 rpctransport = TCPTransport(na)

122 elif 'ncacn_http' == ps:

123 port = sb.get_endpoint()

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

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

126 else:

127 rpctransport = HTTPTransport(na)

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

129 named_pipe = sb.get_endpoint()

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

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

132 rpctransport = SMBTransport(na, filename = named_pipe)

133 else:

134 rpctransport = SMBTransport(na)

135 elif 'ncalocal' == ps:

136 named_pipe = sb.get_endpoint()

137 rpctransport = LOCALTransport(filename = named_pipe)

138 else:

139 raise DCERPCException("Unknown protocol sequence.")

140

141 rpctransport.set_stringbinding(sb)

142 return rpctransport

143

144 class DCERPCTransport:

145

146 DCERPC_class = DCERPC_v5

147

148 def __init__(self, remoteName, dstport):

149 self.__remoteName = remoteName

150 self.__remoteHost = remoteName

151 self.__dstport = dstport

152 self._stringbinding = None

153 self._max_send_frag = None

154 self._max_recv_frag = None

155 self._domain = ''

156 self._lmhash = ''

157 self._nthash = ''

158 self.__connect_timeout = None

159 self._doKerberos = False

160 self._username = ''

161 self._password = ''

162 self._domain = ''

163 self._aesKey = None

164 self._TGT = None

165 self._TGS = None

166 self._kdcHost = None

167 self.set_credentials('','')

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

169 # SMBTransport

170 self._strict_hostname_validation = False

171 self._validation_allow_absent = True

172 self._accepted_hostname = ''

173

174 def connect(self):

175 raise RuntimeError('virtual function')

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

177 raise RuntimeError('virtual function')

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

179 raise RuntimeError('virtual function')

180 def disconnect(self):

181 raise RuntimeError('virtual function')

182 def get_socket(self):

183 raise RuntimeError('virtual function')

184

185 def get_connect_timeout(self):

186 return self.__connect_timeout

187 def set_connect_timeout(self, timeout):

188 self.__connect_timeout = timeout

189

190 def getRemoteName(self):

191 return self.__remoteName

192

193 def setRemoteName(self, remoteName):

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

195 self.__remoteName = remoteName

196

197 def getRemoteHost(self):

198 return self.__remoteHost

199

200 def setRemoteHost(self, remoteHost):

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

202 self.__remoteHost = remoteHost

203

204 def get_dport(self):

205 return self.__dstport

206 def set_dport(self, dport):

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

208 self.__dstport = dport

209

210 def get_stringbinding(self):

211 return self._stringbinding

212

213 def set_stringbinding(self, stringbinding):

214 self._stringbinding = stringbinding

215

216 def get_addr(self):

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

218 def set_addr(self, addr):

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

220 self.setRemoteHost(addr[0])

221 self.set_dport(addr[1])

222

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

224 self._doKerberos = flag

225 self._kdcHost = kdcHost

226

227 def get_kerberos(self):

228 return self._doKerberos

229

230 def get_kdcHost(self):

231 return self._kdcHost

232

233 def set_max_fragment_size(self, send_fragment_size):

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

235 # 0 is don't fragment

236 # other values are max fragment size

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

238 self.set_default_max_fragment_size()

239 else:

240 self._max_send_frag = send_fragment_size

241

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

243 self._strict_hostname_validation = validate

244 self._validation_allow_absent = accept_empty

245 self._accepted_hostname = hostname

246

247 def set_default_max_fragment_size(self):

248 # default is 0: don't fragment.

249 # subclasses may override this method

250 self._max_send_frag = 0

251

252 def get_credentials(self):

253 return (

254 self._username,

255 self._password,

256 self._domain,

257 self._lmhash,

258 self._nthash,

259 self._aesKey,

260 self._TGT,

261 self._TGS)

262

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

264 self._username = username

265 self._password = password

266 self._domain = domain

267 self._aesKey = aesKey

268 self._TGT = TGT

269 self._TGS = TGS

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

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

272 lmhash = '0%s' % lmhash

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

274 nthash = '0%s' % nthash

275 try: # just in case they were converted already

276 self._lmhash = binascii.unhexlify(lmhash)

277 self._nthash = binascii.unhexlify(nthash)

278 except:

279 self._lmhash = lmhash

280 self._nthash = nthash

281 pass

282

283 def doesSupportNTLMv2(self):

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

285 return ntlm.USE_NTLMv2

286

287 def get_dce_rpc(self):

288 return DCERPC_v5(self)

289

290 class UDPTransport(DCERPCTransport):

291 "Implementation of ncadg_ip_udp protocol sequence"

292

293 DCERPC_class = DCERPC_v4

294

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

296 DCERPCTransport.__init__(self, remoteName, dstport)

297 self.__socket = 0

298 self.set_connect_timeout(30)

299 self.__recv_addr = ''

300

301 def connect(self):

302 try:

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

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

305 self.__socket.settimeout(self.get_connect_timeout())

306 except socket.error as msg:

307 self.__socket = None

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

309

310 return 1

311

312 def disconnect(self):

313 try:

314 self.__socket.close()

315 except socket.error:

316 self.__socket = None

317 return 0

318 return 1

319

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

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

322

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

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

325 return buffer

326

327 def get_recv_addr(self):

328 return self.__recv_addr

329

330 def get_socket(self):

331 return self.__socket

332

333 class TCPTransport(DCERPCTransport):

334 """Implementation of ncacn_ip_tcp protocol sequence"""

335

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

337 DCERPCTransport.__init__(self, remoteName, dstport)

338 self.__socket = 0

339 self.set_connect_timeout(30)

340

341 def connect(self):

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

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

344 try:

345 self.__socket.settimeout(self.get_connect_timeout())

346 self.__socket.connect(sa)

347 except socket.error as msg:

348 self.__socket.close()

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

350 return 1

351

352 def disconnect(self):

353 try:

354 self.__socket.close()

355 except socket.error:

356 self.__socket = None

357 return 0

358 return 1

359

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

361 if self._max_send_frag:

362 offset = 0

363 while 1:

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

365 if not toSend:

366 break

367 self.__socket.send(toSend)

368 offset += len(toSend)

369 else:

370 self.__socket.send(data)

371

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

373 if count:

374 buffer = b''

375 while len(buffer) < count:

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

377 else:

378 buffer = self.__socket.recv(8192)

379 return buffer

380

381 def get_socket(self):

382 return self.__socket

383

384 class HTTPTransport(TCPTransport, RPCProxyClient):

385 """Implementation of ncacn_http protocol sequence"""

386

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

388 self._useRpcProxy = False

389 self._rpcProxyUrl = None

390 self._transport = TCPTransport

391 self._version = RPC_OVER_HTTP_v2

392

393 DCERPCTransport.__init__(self, remoteName, dstport)

394 RPCProxyClient.__init__(self, remoteName, dstport)

395 self.set_connect_timeout(30)

396

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

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

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

400

401 def rpc_proxy_init(self):

402 self._useRpcProxy = True

403 self._transport = RPCProxyClient

404

405 def set_rpc_proxy_url(self, url):

406 self.rpc_proxy_init()

407 self._rpcProxyUrl = urlparse(url)

408

409 def get_rpc_proxy_url(self):

410 return urlunparse(self._rpcProxyUrl)

411

412 def set_stringbinding(self, set_stringbinding):

413 DCERPCTransport.set_stringbinding(self, set_stringbinding)

414

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

416 self.rpc_proxy_init()

417

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

419

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

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

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

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

424 else:

425 # 2.1.2.1

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

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

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

429

430 def connect(self):

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

432 # Connecting directly to the ncacn_http port

433 #

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

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

436 self._version = RPC_OVER_HTTP_v1

437

438 TCPTransport.connect(self)

439

440 # Reading legacy server response

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

442

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

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

445 else:

446 RPCProxyClient.connect(self)

447

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

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

450

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

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

453

454 def get_socket(self):

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

456 return TCPTransport.get_socket(self)

457 else:

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

459

460 def disconnect(self):

461 return self._transport.disconnect(self)

462

463 class SMBTransport(DCERPCTransport):

464 """Implementation of ncacn_np protocol sequence"""

465

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

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

468 DCERPCTransport.__init__(self, remoteName, dstport)

469 self.__socket = None

470 self.__tid = 0

471 self.__filename = filename

472 self.__handle = 0

473 self.__pending_recv = 0

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

475 self._doKerberos = doKerberos

476 self._kdcHost = kdcHost

477

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

479 self.setRemoteHost(remote_host)

480

481 if smb_connection == 0:

482 self.__existing_smb = False

483 else:

484 self.__existing_smb = True

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

486

487 self.__prefDialect = None

488 self.__smb_connection = smb_connection

489 self.set_connect_timeout(30)

490

491 def preferred_dialect(self, dialect):

492 self.__prefDialect = dialect

493

494 def setup_smb_connection(self):

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

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

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

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

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

500

501 def connect(self):

502 # Check if we have a smb connection already setup

503 if self.__smb_connection == 0:

504 self.setup_smb_connection()

505 if self._doKerberos is False:

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

507 else:

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

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

510 TGS=self._TGS)

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

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

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

514 return 1

515

516 def disconnect(self):

517 self.__smb_connection.disconnectTree(self.__tid)

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

519 # that's up for the caller

520 if self.__existing_smb is False:

521 self.__smb_connection.logoff()

522 self.__smb_connection.close()

523 self.__smb_connection = 0

524

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

526 if self._max_send_frag:

527 offset = 0

528 while 1:

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

530 if not toSend:

531 break

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

533 offset += len(toSend)

534 else:

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

536 if forceRecv:

537 self.__pending_recv += 1

538

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

540 if self._max_send_frag or self.__pending_recv:

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

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

543 if self.__pending_recv:

544 self.__pending_recv -= 1

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

546 else:

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

548

549 def get_smb_connection(self):

550 return self.__smb_connection

551

552 def set_smb_connection(self, smb_connection):

553 self.__smb_connection = smb_connection

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

555 self.__existing_smb = True

556

557 def get_smb_server(self):

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

559 return self.__smb_connection.getSMBServer()

560

561 def get_socket(self):

562 return self.__socket

563

564 def doesSupportNTLMv2(self):

565 return self.__smb_connection.doesSupportNTLMv2()

566

567 class LOCALTransport(DCERPCTransport):

568 """

569 Implementation of ncalocal protocol sequence, not the same

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

571 """

572

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

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

575 self.__filename = filename

576 self.__handle = 0

577

578 def connect(self):

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

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

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

582 return 1

583

584 def disconnect(self):

585 os.close(self.__handle)

586

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

588 os.write(self.__handle, data)

589

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

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

592 return data