Hide keyboard shortcuts

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# 

15from __future__ import division 

16from __future__ import print_function 

17 

18import binascii 

19import os 

20import re 

21import socket 

22 

23try: 

24 from urllib.parse import urlparse, urlunparse 

25except ImportError: 

26 from urlparse import urlparse, urlunparse 

27 

28from impacket import ntlm 

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

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

31from impacket.smbconnection import SMBConnection 

32 

33 

34class 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 

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

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

150class 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 

296class 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 

339class 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 

390class 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 

469class 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 

573class 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