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

12from __future__ import division 

13from __future__ import print_function 

14 

15import binascii 

16import os 

17import re 

18import socket 

19 

20try: 

21 from urllib.parse import urlparse, urlunparse 

22except ImportError: 

23 from urlparse import urlparse, urlunparse 

24 

25from impacket import ntlm 

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

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

28from impacket.smbconnection import SMBConnection 

29 

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

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

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

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

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

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

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

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

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