Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/scapy/layers/msrpce/rpcclient.py: 12%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

291 statements  

1# SPDX-License-Identifier: GPL-2.0-or-later 

2# This file is part of Scapy 

3# See https://scapy.net/ for more information 

4# Copyright (C) Gabriel Potter 

5 

6""" 

7DCE/RPC client as per [MS-RPCE] 

8""" 

9 

10import collections 

11import uuid 

12import socket 

13 

14from scapy.config import conf 

15from scapy.error import log_runtime 

16 

17from scapy.layers.dcerpc import ( 

18 _DCE_RPC_ERROR_CODES, 

19 ComInterface, 

20 CommonAuthVerifier, 

21 DCE_C_AUTHN_LEVEL, 

22 DCERPC_Transport, 

23 DceRpc5, 

24 DceRpc5AbstractSyntax, 

25 DceRpc5AlterContext, 

26 DceRpc5AlterContextResp, 

27 DceRpc5Auth3, 

28 DceRpc5Bind, 

29 DceRpc5BindAck, 

30 DceRpc5BindNak, 

31 DceRpc5Context, 

32 DceRpc5Fault, 

33 DceRpc5Request, 

34 DceRpc5Response, 

35 DceRpc5TransferSyntax, 

36 DceRpcInterface, 

37 DceRpcSecVT, 

38 DceRpcSecVTCommand, 

39 DceRpcSecVTPcontext, 

40 DceRpcSession, 

41 DceRpcSocket, 

42 find_dcerpc_interface, 

43 NDRContextHandle, 

44 NDRPointer, 

45 RPC_C_IMP_LEVEL, 

46) 

47from scapy.layers.gssapi import ( 

48 SSP, 

49 GSS_S_FAILURE, 

50 GSS_S_COMPLETE, 

51 GSS_S_CONTINUE_NEEDED, 

52 GSS_C_FLAGS, 

53) 

54from scapy.layers.smbclient import ( 

55 SMB_RPC_SOCKET, 

56) 

57from scapy.layers.windows.erref import STATUS_ERREF 

58 

59# RPC 

60from scapy.layers.msrpce.ept import ( 

61 ept_map_Request, 

62 ept_map_Response, 

63 twr_p_t, 

64 protocol_tower_t, 

65 prot_and_addr_t, 

66 UUID, 

67) 

68 

69# Typing 

70from typing import ( 

71 Optional, 

72 Union, 

73) 

74 

75 

76class DCERPC_Client(object): 

77 """ 

78 A basic DCE/RPC client 

79 

80 :param transport: the transport to use. 

81 :param ndr64: should ask for NDR64 when binding (default conf.ndr64) 

82 :param ndrendian: the endianness to use (default little) 

83 :param verb: enable verbose logging (default True) 

84 :param auth_level: the DCE_C_AUTHN_LEVEL to use 

85 :param impersonation_type: the RPC_C_IMP_LEVEL to use 

86 """ 

87 

88 def __init__( 

89 self, 

90 transport: DCERPC_Transport, 

91 ndr64: Optional[bool] = None, 

92 ndrendian: str = "little", 

93 verb: bool = True, 

94 auth_level: Optional[DCE_C_AUTHN_LEVEL] = None, 

95 impersonation_type: RPC_C_IMP_LEVEL = RPC_C_IMP_LEVEL.DEFAULT, 

96 **kwargs, 

97 ): 

98 self.sock = None 

99 self.transport = transport 

100 assert isinstance( 

101 transport, DCERPC_Transport 

102 ), "transport must be from DCERPC_Transport" 

103 

104 # Counters 

105 self.call_ids = collections.defaultdict(lambda: 0) # by assoc_group_id 

106 self.next_cont_id = 0 # next available context id 

107 self.next_auth_contex_id = 0 # next available auth context id 

108 

109 # Session parameters 

110 if ndr64 is None: 

111 ndr64 = conf.ndr64 

112 self.ndr64: bool = ndr64 

113 self.ndrendian = ndrendian 

114 self.verb = verb 

115 self.host: str = None 

116 self.port: int = -1 

117 self.ssp = kwargs.pop("ssp", None) # type: SSP 

118 self.sspcontext = None 

119 if auth_level is not None: 

120 self.auth_level = auth_level 

121 elif self.ssp is not None: 

122 self.auth_level = DCE_C_AUTHN_LEVEL.CONNECT 

123 else: 

124 self.auth_level = DCE_C_AUTHN_LEVEL.NONE 

125 if impersonation_type == RPC_C_IMP_LEVEL.DEFAULT: 

126 # Same default as windows 

127 impersonation_type = RPC_C_IMP_LEVEL.IDENTIFY 

128 self.impersonation_type = impersonation_type 

129 self._first_time_on_interface = True 

130 self.contexts = {} 

131 self.dcesockargs = kwargs 

132 self.dcesockargs["transport"] = self.transport 

133 

134 @classmethod 

135 def from_smblink(cls, smbcli, smb_kwargs={}, **kwargs): 

136 """ 

137 Build a DCERPC_Client from a SMB_Client.smblink directly 

138 """ 

139 client = DCERPC_Client(DCERPC_Transport.NCACN_NP, **kwargs) 

140 sock = client.smbrpcsock = SMB_RPC_SOCKET(smbcli, **smb_kwargs) 

141 client.sock = DceRpcSocket( 

142 sock, 

143 DceRpc5, 

144 ssp=client.ssp, 

145 auth_level=client.auth_level, 

146 **client.dcesockargs, 

147 ) 

148 return client 

149 

150 @property 

151 def session(self) -> DceRpcSession: 

152 try: 

153 return self.sock.session 

154 except AttributeError: 

155 raise ValueError("Client is not connected ! Please connect()") 

156 

157 def connect( 

158 self, 

159 host, 

160 endpoint: Union[int, str] = None, 

161 port: Optional[int] = None, 

162 interface=None, 

163 timeout=5, 

164 smb_kwargs={}, 

165 ): 

166 """ 

167 Initiate a connection. 

168 

169 :param host: the host to connect to 

170 :param endpoint: (optional) the port/smb pipe to connect to 

171 :param interface: (optional) if endpoint isn't provided, uses the endpoint 

172 mapper to find the appropriate endpoint for that interface. 

173 :param timeout: (optional) the connection timeout (default 5) 

174 :param port: (optional) the port to connect to. (useful for SMB) 

175 """ 

176 smb_kwargs.setdefault("HOST", host) 

177 if endpoint is None and interface is not None: 

178 # Figure out the endpoint using the endpoint mapper 

179 

180 if self.transport == DCERPC_Transport.NCACN_IP_TCP and port is None: 

181 # IP/TCP 

182 # ask the endpoint mapper (port 135) for the IP:PORT 

183 endpoints = get_endpoint( 

184 host, 

185 interface, 

186 ndrendian=self.ndrendian, 

187 verb=self.verb, 

188 ) 

189 if endpoints: 

190 _, endpoint = endpoints[0] 

191 else: 

192 raise ValueError( 

193 "Could not find an available endpoint for that interface !" 

194 ) 

195 elif self.transport == DCERPC_Transport.NCACN_NP: 

196 # SMB 

197 # ask the endpoint mapper (over SMB) for the namedpipe 

198 endpoints = get_endpoint( 

199 host, 

200 interface, 

201 transport=self.transport, 

202 ndrendian=self.ndrendian, 

203 verb=self.verb, 

204 ssp=self.ssp, 

205 smb_kwargs=smb_kwargs, 

206 ) 

207 if endpoints: 

208 endpoint = endpoints[0].lstrip("\\pipe\\") 

209 else: 

210 return 

211 

212 # Assign the default port if no port is provided 

213 if port is None: 

214 if self.transport == DCERPC_Transport.NCACN_IP_TCP: # IP/TCP 

215 port = endpoint or 135 

216 elif self.transport == DCERPC_Transport.NCACN_NP: # SMB 

217 port = 445 

218 else: 

219 raise ValueError( 

220 "Can't guess the port for transport: %s" % self.transport 

221 ) 

222 

223 # Start socket and connect 

224 self.host = host 

225 self.port = port 

226 sock = socket.socket() 

227 sock.settimeout(timeout) 

228 if self.verb: 

229 print( 

230 "\u2503 Connecting to %s on port %s via %s..." 

231 % (host, port, repr(self.transport)) 

232 ) 

233 sock.connect((host, port)) 

234 if self.verb: 

235 print( 

236 conf.color_theme.green( 

237 "\u2514 Connected from %s" % repr(sock.getsockname()) 

238 ) 

239 ) 

240 

241 if self.transport == DCERPC_Transport.NCACN_NP: # SMB 

242 # We pack the socket into a SMB_RPC_SOCKET 

243 sock = self.smbrpcsock = SMB_RPC_SOCKET.from_tcpsock( 

244 sock, ssp=self.ssp, **smb_kwargs 

245 ) 

246 

247 # If the endpoint is provided, connect to it. 

248 if endpoint is not None: 

249 self.open_smbpipe(endpoint) 

250 

251 self.sock = DceRpcSocket(sock, DceRpc5, **self.dcesockargs) 

252 elif self.transport == DCERPC_Transport.NCACN_IP_TCP: 

253 self.sock = DceRpcSocket( 

254 sock, 

255 DceRpc5, 

256 ssp=self.ssp, 

257 auth_level=self.auth_level, 

258 **self.dcesockargs, 

259 ) 

260 

261 def close(self): 

262 """ 

263 Close the DCE/RPC client. 

264 """ 

265 if self.verb: 

266 print("X Connection closed\n") 

267 self.sock.close() 

268 

269 def sr1(self, pkt, **kwargs): 

270 """ 

271 Send/Receive a DCE/RPC message. 

272 

273 The DCE/RPC header is added automatically. 

274 """ 

275 self.call_ids[self.session.assoc_group_id] += 1 

276 pkt = ( 

277 DceRpc5( 

278 call_id=self.call_ids[self.session.assoc_group_id], 

279 pfc_flags="PFC_FIRST_FRAG+PFC_LAST_FRAG", 

280 endian=self.ndrendian, 

281 auth_verifier=kwargs.pop("auth_verifier", None), 

282 vt_trailer=kwargs.pop("vt_trailer", None), 

283 ) 

284 / pkt 

285 ) 

286 if "pfc_flags" in kwargs: 

287 pkt.pfc_flags = kwargs.pop("pfc_flags") 

288 if "objectuuid" in kwargs: 

289 pkt.pfc_flags += "PFC_OBJECT_UUID" 

290 pkt.object = kwargs.pop("objectuuid") 

291 return self.sock.sr1(pkt, verbose=0, **kwargs) 

292 

293 def send(self, pkt, **kwargs): 

294 """ 

295 Send a DCE/RPC message. 

296 

297 The DCE/RPC header is added automatically. 

298 """ 

299 self.call_ids[self.session.assoc_group_id] += 1 

300 pkt = ( 

301 DceRpc5( 

302 call_id=self.call_ids[self.session.assoc_group_id], 

303 pfc_flags="PFC_FIRST_FRAG+PFC_LAST_FRAG", 

304 endian=self.ndrendian, 

305 auth_verifier=kwargs.pop("auth_verifier", None), 

306 vt_trailer=kwargs.pop("vt_trailer", None), 

307 ) 

308 / pkt 

309 ) 

310 if "pfc_flags" in kwargs: 

311 pkt.pfc_flags = kwargs.pop("pfc_flags") 

312 if "objectuuid" in kwargs: 

313 pkt.pfc_flags += "PFC_OBJECT_UUID" 

314 pkt.object = kwargs.pop("objectuuid") 

315 return self.sock.send(pkt, **kwargs) 

316 

317 def sr1_req(self, pkt, **kwargs): 

318 """ 

319 Send/Receive a DCE/RPC request. 

320 

321 :param pkt: the inner DCE/RPC message, without any header. 

322 """ 

323 if self.verb: 

324 if "objectuuid" in kwargs: 

325 # COM 

326 print( 

327 conf.color_theme.opening( 

328 ">> REQUEST (COM): %s" % pkt.payload.__class__.__name__ 

329 ) 

330 ) 

331 else: 

332 print( 

333 conf.color_theme.opening(">> REQUEST: %s" % pkt.__class__.__name__) 

334 ) 

335 

336 # Add sectrailer if first time talking on this interface 

337 vt_trailer = b"" 

338 if ( 

339 self._first_time_on_interface 

340 and self.transport != DCERPC_Transport.NCACN_NP 

341 ): 

342 # In the first request after a bind, Windows sends a trailer to verify 

343 # that the negotiated transfer/interface wasn't altered. 

344 self._first_time_on_interface = False 

345 vt_trailer = DceRpcSecVT( 

346 commands=[ 

347 DceRpcSecVTCommand(SEC_VT_COMMAND_END=1) 

348 / DceRpcSecVTPcontext( 

349 InterfaceId=self.session.rpc_bind_interface.uuid, 

350 Version=self.session.rpc_bind_interface.if_version, 

351 TransferSyntax="NDR64" if self.ndr64 else "NDR 2.0", 

352 TransferVersion=1 if self.ndr64 else 2, 

353 ) 

354 ] 

355 ) 

356 

357 # Optional: force opnum 

358 opnum = {} 

359 if "opnum" in kwargs: 

360 opnum["opnum"] = kwargs.pop("opnum") 

361 

362 # Set NDR64 

363 pkt.ndr64 = self.ndr64 

364 

365 # Send/receive 

366 resp = self.sr1( 

367 DceRpc5Request( 

368 cont_id=self.session.cont_id, 

369 alloc_hint=len(pkt) + len(vt_trailer), 

370 **opnum, 

371 ) 

372 / pkt, 

373 vt_trailer=vt_trailer, 

374 **kwargs, 

375 ) 

376 

377 # Parse result 

378 result = None 

379 if DceRpc5Response in resp: 

380 if self.verb: 

381 if "objectuuid" in kwargs: 

382 # COM 

383 print( 

384 conf.color_theme.success( 

385 "<< RESPONSE (COM): %s" 

386 % (resp[DceRpc5Response].payload.payload.__class__.__name__) 

387 ) 

388 ) 

389 else: 

390 print( 

391 conf.color_theme.success( 

392 "<< RESPONSE: %s" 

393 % (resp[DceRpc5Response].payload.__class__.__name__) 

394 ) 

395 ) 

396 result = resp[DceRpc5Response].payload 

397 elif DceRpc5Fault in resp: 

398 if self.verb: 

399 print(conf.color_theme.success("<< FAULT")) 

400 # If [MS-EERR] is loaded, show the extended info 

401 if resp[DceRpc5Fault].payload and not isinstance( 

402 resp[DceRpc5Fault].payload, conf.raw_layer 

403 ): 

404 resp[DceRpc5Fault].payload.show() 

405 result = resp 

406 

407 if self.verb and getattr(resp, "status", 0) != 0: 

408 if resp.status in _DCE_RPC_ERROR_CODES: 

409 print(conf.color_theme.fail(f"! {_DCE_RPC_ERROR_CODES[resp.status]}")) 

410 elif resp.status in STATUS_ERREF: 

411 print(conf.color_theme.fail(f"! {STATUS_ERREF[resp.status]}")) 

412 else: 

413 print(conf.color_theme.fail("! Failure")) 

414 resp.show() 

415 return result 

416 

417 def _get_bind_context(self, interface): 

418 """ 

419 Internal: get the bind DCE/RPC context. 

420 """ 

421 if interface in self.contexts: 

422 # We have already found acceptable contexts for this interface, 

423 # reuse that. 

424 return self.contexts[interface] 

425 

426 # NDR 2.0 

427 contexts = [ 

428 DceRpc5Context( 

429 cont_id=self.next_cont_id, 

430 abstract_syntax=DceRpc5AbstractSyntax( 

431 if_uuid=interface.uuid, 

432 if_version=interface.if_version, 

433 ), 

434 transfer_syntaxes=[ 

435 DceRpc5TransferSyntax( 

436 # NDR 2.0 32-bit 

437 if_uuid="NDR 2.0", 

438 if_version=2, 

439 ) 

440 ], 

441 ), 

442 ] 

443 self.next_cont_id += 1 

444 

445 # NDR64 

446 if self.ndr64: 

447 contexts.append( 

448 DceRpc5Context( 

449 cont_id=self.next_cont_id, 

450 abstract_syntax=DceRpc5AbstractSyntax( 

451 if_uuid=interface.uuid, 

452 if_version=interface.if_version, 

453 ), 

454 transfer_syntaxes=[ 

455 DceRpc5TransferSyntax( 

456 # NDR64 

457 if_uuid="NDR64", 

458 if_version=1, 

459 ) 

460 ], 

461 ) 

462 ) 

463 self.next_cont_id += 1 

464 

465 # BindTimeFeatureNegotiationBitmask 

466 contexts.append( 

467 DceRpc5Context( 

468 cont_id=self.next_cont_id, 

469 abstract_syntax=DceRpc5AbstractSyntax( 

470 if_uuid=interface.uuid, 

471 if_version=interface.if_version, 

472 ), 

473 transfer_syntaxes=[ 

474 DceRpc5TransferSyntax( 

475 if_uuid=uuid.UUID("6cb71c2c-9812-4540-0300-000000000000"), 

476 if_version=1, 

477 ) 

478 ], 

479 ) 

480 ) 

481 self.next_cont_id += 1 

482 

483 # Store contexts for this interface 

484 self.contexts[interface] = contexts 

485 

486 return contexts 

487 

488 def _check_bind_context(self, interface, contexts) -> bool: 

489 """ 

490 Internal: check the answer DCE/RPC bind context, and update them. 

491 """ 

492 for i, ctx in enumerate(contexts): 

493 if ctx.result == 0: 

494 # Context was accepted. Remove all others from cache 

495 if len(self.contexts[interface]) != 1: 

496 self.contexts[interface] = [self.contexts[interface][i]] 

497 return True 

498 

499 return False 

500 

501 def _bind( 

502 self, 

503 interface: Union[DceRpcInterface, ComInterface], 

504 reqcls, 

505 respcls, 

506 target_name: Optional[str] = None, 

507 ) -> bool: 

508 """ 

509 Internal: used to send a bind/alter request 

510 """ 

511 # Build a security context: [MS-RPCE] 3.3.1.5.2 

512 if self.verb: 

513 print( 

514 conf.color_theme.opening( 

515 ">> %s on %s" % (reqcls.__name__, interface) 

516 + (" (with %s)" % self.ssp.__class__.__name__ if self.ssp else "") 

517 ) 

518 ) 

519 

520 # Do we need an authenticated bind 

521 if not self.ssp or ( 

522 self.sspcontext is not None 

523 or self.transport == DCERPC_Transport.NCACN_NP 

524 and self.auth_level < DCE_C_AUTHN_LEVEL.PKT_INTEGRITY 

525 ): 

526 # NCACN_NP = SMB without INTEGRITY/PRIVACY does not bind the RPC securely, 

527 # again as it has already authenticated during the SMB Session Setup 

528 resp = self.sr1( 

529 reqcls(context_elem=self._get_bind_context(interface)), 

530 auth_verifier=None, 

531 ) 

532 status = GSS_S_COMPLETE 

533 else: 

534 # Perform authentication 

535 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context( 

536 self.sspcontext, 

537 req_flags=( 

538 # SSPs need to be instantiated with some special flags 

539 # for DCE/RPC usages. 

540 GSS_C_FLAGS.GSS_C_DCE_STYLE 

541 | GSS_C_FLAGS.GSS_C_REPLAY_FLAG 

542 | GSS_C_FLAGS.GSS_C_SEQUENCE_FLAG 

543 | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG 

544 | ( 

545 GSS_C_FLAGS.GSS_C_INTEG_FLAG 

546 if self.auth_level >= DCE_C_AUTHN_LEVEL.PKT_INTEGRITY 

547 else 0 

548 ) 

549 | ( 

550 GSS_C_FLAGS.GSS_C_CONF_FLAG 

551 if self.auth_level >= DCE_C_AUTHN_LEVEL.PKT_PRIVACY 

552 else 0 

553 ) 

554 | ( 

555 GSS_C_FLAGS.GSS_C_IDENTIFY_FLAG 

556 if self.impersonation_type <= RPC_C_IMP_LEVEL.IDENTIFY 

557 else 0 

558 ) 

559 | ( 

560 GSS_C_FLAGS.GSS_C_DELEG_FLAG 

561 if self.impersonation_type == RPC_C_IMP_LEVEL.DELEGATE 

562 else 0 

563 ) 

564 ), 

565 target_name=target_name or ("host/" + self.host), 

566 ) 

567 

568 if status not in [GSS_S_CONTINUE_NEEDED, GSS_S_COMPLETE]: 

569 # Authentication failed. 

570 self.sspcontext.clifailure() 

571 return False 

572 

573 resp = self.sr1( 

574 reqcls(context_elem=self._get_bind_context(interface)), 

575 auth_verifier=( 

576 None 

577 if not self.sspcontext 

578 else CommonAuthVerifier( 

579 auth_type=self.ssp.auth_type, 

580 auth_level=self.auth_level, 

581 auth_context_id=self.session.auth_context_id, 

582 auth_value=token, 

583 ) 

584 ), 

585 pfc_flags=( 

586 "PFC_FIRST_FRAG+PFC_LAST_FRAG" 

587 + ( 

588 # If the SSP supports "Header Signing", advertise it 

589 "+PFC_SUPPORT_HEADER_SIGN" 

590 if self.ssp is not None and self.session.support_header_signing 

591 else "" 

592 ) 

593 ), 

594 ) 

595 

596 # Check that the answer looks valid and contexts were accepted 

597 if respcls not in resp or not self._check_bind_context( 

598 interface, resp.results 

599 ): 

600 token = None 

601 status = GSS_S_FAILURE 

602 else: 

603 # Call the underlying SSP 

604 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context( 

605 self.sspcontext, 

606 input_token=resp.auth_verifier.auth_value, 

607 target_name=target_name or ("host/" + self.host), 

608 ) 

609 

610 if status in [GSS_S_CONTINUE_NEEDED, GSS_S_COMPLETE]: 

611 # Authentication should continue, in two ways: 

612 # - through DceRpc5Auth3 (e.g. NTLM) 

613 # - through DceRpc5AlterContext (e.g. Kerberos) 

614 if token and self.ssp.LegsAmount(self.sspcontext) % 2 == 1: 

615 # AUTH 3 for certain SSPs (e.g. NTLM) 

616 # "The server MUST NOT respond to an rpc_auth_3 PDU" 

617 self.send( 

618 DceRpc5Auth3(), 

619 auth_verifier=CommonAuthVerifier( 

620 auth_type=self.ssp.auth_type, 

621 auth_level=self.auth_level, 

622 auth_context_id=self.session.auth_context_id, 

623 auth_value=token, 

624 ), 

625 ) 

626 status = GSS_S_COMPLETE 

627 else: 

628 while token: 

629 respcls = DceRpc5AlterContextResp 

630 resp = self.sr1( 

631 DceRpc5AlterContext( 

632 context_elem=self._get_bind_context(interface) 

633 ), 

634 auth_verifier=CommonAuthVerifier( 

635 auth_type=self.ssp.auth_type, 

636 auth_level=self.auth_level, 

637 auth_context_id=self.session.auth_context_id, 

638 auth_value=token, 

639 ), 

640 ) 

641 if respcls not in resp: 

642 status = GSS_S_FAILURE 

643 break 

644 if resp.auth_verifier is None: 

645 status = GSS_S_COMPLETE 

646 break 

647 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context( 

648 self.sspcontext, 

649 input_token=resp.auth_verifier.auth_value, 

650 target_name=target_name or ("host/" + self.host), 

651 ) 

652 else: 

653 log_runtime.error("GSS_Init_sec_context failed with %s !" % status) 

654 

655 # Check context acceptance 

656 if ( 

657 status == GSS_S_COMPLETE 

658 and respcls in resp 

659 and self._check_bind_context(interface, resp.results) 

660 ): 

661 port = resp.sec_addr.port_spec.decode() 

662 ndr = self.session.ndr64 and "NDR64" or "NDR32" 

663 self.ndr64 = self.session.ndr64 

664 if self.verb: 

665 print( 

666 conf.color_theme.success( 

667 f"<< {respcls.__name__} port '{port}' using {ndr}" 

668 ) 

669 ) 

670 self.session.sspcontext = self.sspcontext 

671 self._first_time_on_interface = True 

672 return True 

673 else: 

674 if self.verb: 

675 if DceRpc5BindNak in resp: 

676 err_msg = resp.sprintf( 

677 "reject_reason: %DceRpc5BindNak.provider_reject_reason%" 

678 ) 

679 print(conf.color_theme.fail("! Bind_nak (%s)" % err_msg)) 

680 if DceRpc5BindNak in resp: 

681 if resp[DceRpc5BindNak].payload and not isinstance( 

682 resp[DceRpc5BindNak].payload, conf.raw_layer 

683 ): 

684 resp[DceRpc5BindNak].payload.show() 

685 elif DceRpc5Fault in resp: 

686 if getattr(resp, "status", 0) != 0: 

687 if resp.status in _DCE_RPC_ERROR_CODES: 

688 print( 

689 conf.color_theme.fail( 

690 f"! {_DCE_RPC_ERROR_CODES[resp.status]}" 

691 ) 

692 ) 

693 elif resp.status in STATUS_ERREF: 

694 print( 

695 conf.color_theme.fail(f"! {STATUS_ERREF[resp.status]}") 

696 ) 

697 else: 

698 print(conf.color_theme.fail("! Failure")) 

699 resp.show() 

700 if resp[DceRpc5Fault].payload and not isinstance( 

701 resp[DceRpc5Fault].payload, conf.raw_layer 

702 ): 

703 resp[DceRpc5Fault].payload.show() 

704 else: 

705 print(conf.color_theme.fail("! Failure")) 

706 resp.show() 

707 return False 

708 

709 def bind( 

710 self, 

711 interface: Union[DceRpcInterface, ComInterface], 

712 target_name: Optional[str] = None, 

713 ) -> bool: 

714 """ 

715 Bind the client to an interface 

716 

717 :param interface: the DceRpcInterface object 

718 """ 

719 return self._bind( 

720 interface, 

721 DceRpc5Bind, 

722 DceRpc5BindAck, 

723 target_name=target_name, 

724 ) 

725 

726 def alter_context( 

727 self, 

728 interface: Union[DceRpcInterface, ComInterface], 

729 target_name: Optional[str] = None, 

730 ) -> bool: 

731 """ 

732 Alter context: post-bind context negotiation 

733 

734 :param interface: the DceRpcInterface object 

735 """ 

736 return self._bind( 

737 interface, 

738 DceRpc5AlterContext, 

739 DceRpc5AlterContextResp, 

740 target_name=target_name, 

741 ) 

742 

743 def bind_or_alter( 

744 self, 

745 interface: Union[DceRpcInterface, ComInterface], 

746 target_name: Optional[str] = None, 

747 ) -> bool: 

748 """ 

749 Bind the client to an interface or alter the context if already bound 

750 

751 :param interface: the DceRpcInterface object 

752 """ 

753 if not self.session.rpc_bind_interface: 

754 # No interface is bound 

755 return self.bind(interface, target_name=target_name) 

756 elif self.session.rpc_bind_interface != interface: 

757 # An interface is already bound 

758 return self.alter_context(interface, target_name=target_name) 

759 return True 

760 

761 def open_smbpipe(self, name: str): 

762 """ 

763 Open a certain filehandle with the SMB automaton. 

764 

765 :param name: the name of the pipe 

766 """ 

767 self.ipc_tid = self.smbrpcsock.tree_connect("IPC$") 

768 self.smbrpcsock.open_pipe(name) 

769 

770 def close_smbpipe(self): 

771 """ 

772 Close the previously opened pipe 

773 """ 

774 self.smbrpcsock.set_TID(self.ipc_tid) 

775 self.smbrpcsock.close_pipe() 

776 self.smbrpcsock.tree_disconnect() 

777 

778 def connect_and_bind( 

779 self, 

780 host: str, 

781 interface: DceRpcInterface, 

782 port: Optional[int] = None, 

783 timeout: int = 5, 

784 smb_kwargs={}, 

785 ): 

786 """ 

787 Asks the Endpoint Mapper what address to use to connect to the interface, 

788 then uses connect() followed by a bind() 

789 

790 :param host: the host to connect to 

791 :param interface: the DceRpcInterface object 

792 :param port: (optional, NCACN_NP only) the port to connect to 

793 :param timeout: (optional) the connection timeout (default 5) 

794 """ 

795 # Connect to the interface using the endpoint mapper 

796 self.connect( 

797 host=host, 

798 interface=interface, 

799 port=port, 

800 timeout=timeout, 

801 smb_kwargs=smb_kwargs, 

802 ) 

803 

804 # Bind in RPC 

805 self.bind(interface) 

806 

807 def epm_map(self, interface): 

808 """ 

809 Calls ept_map (the EndPoint Manager) 

810 """ 

811 if self.ndr64: 

812 ndr_uuid = "NDR64" 

813 ndr_version = 1 

814 else: 

815 ndr_uuid = "NDR 2.0" 

816 ndr_version = 2 

817 pkt = self.sr1_req( 

818 ept_map_Request( 

819 obj=NDRPointer( 

820 referent_id=1, 

821 value=UUID( 

822 Data1=0, 

823 Data2=0, 

824 Data3=0, 

825 Data4=None, 

826 ), 

827 ), 

828 map_tower=NDRPointer( 

829 referent_id=2, 

830 value=twr_p_t( 

831 tower_octet_string=bytes( 

832 protocol_tower_t( 

833 floors=[ 

834 prot_and_addr_t( 

835 lhs_length=19, 

836 protocol_identifier=0xD, 

837 uuid=interface.uuid, 

838 version=interface.major_version, 

839 rhs_length=2, 

840 rhs=interface.minor_version, 

841 ), 

842 prot_and_addr_t( 

843 lhs_length=19, 

844 protocol_identifier=0xD, 

845 uuid=ndr_uuid, 

846 version=ndr_version, 

847 rhs_length=2, 

848 rhs=0, 

849 ), 

850 prot_and_addr_t( 

851 lhs_length=1, 

852 protocol_identifier="RPC connection-oriented protocol", # noqa: E501 

853 rhs_length=2, 

854 rhs=0, 

855 ), 

856 { 

857 DCERPC_Transport.NCACN_IP_TCP: ( 

858 prot_and_addr_t( 

859 lhs_length=1, 

860 protocol_identifier="NCACN_IP_TCP", 

861 rhs_length=2, 

862 rhs=135, 

863 ) 

864 ), 

865 DCERPC_Transport.NCACN_NP: ( 

866 prot_and_addr_t( 

867 lhs_length=1, 

868 protocol_identifier="NCACN_NP", 

869 rhs_length=2, 

870 rhs=b"0\x00", 

871 ) 

872 ), 

873 }[self.transport], 

874 { 

875 DCERPC_Transport.NCACN_IP_TCP: ( 

876 prot_and_addr_t( 

877 lhs_length=1, 

878 protocol_identifier="IP", 

879 rhs_length=4, 

880 rhs="0.0.0.0", 

881 ) 

882 ), 

883 DCERPC_Transport.NCACN_NP: ( 

884 prot_and_addr_t( 

885 lhs_length=1, 

886 protocol_identifier="NCACN_NB", 

887 rhs_length=10, 

888 rhs=b"127.0.0.1\x00", 

889 ) 

890 ), 

891 }[self.transport], 

892 ], 

893 ) 

894 ), 

895 ), 

896 ), 

897 entry_handle=NDRContextHandle( 

898 attributes=0, 

899 uuid=b"\x00" * 16, 

900 ), 

901 max_towers=500, 

902 ndr64=self.ndr64, 

903 ndrendian=self.ndrendian, 

904 ) 

905 ) 

906 if pkt and ept_map_Response in pkt: 

907 status = pkt[ept_map_Response].status 

908 # [MS-RPCE] sect 2.2.1.2.5 

909 if status == 0x00000000: 

910 towers = [ 

911 protocol_tower_t(x.value.tower_octet_string) 

912 for x in pkt[ept_map_Response].ITowers.value[0].value 

913 ] 

914 # Let's do some checks to know we know what we're doing 

915 endpoints = [] 

916 for t in towers: 

917 if t.floors[0].uuid != interface.uuid: 

918 if self.verb: 

919 print( 

920 conf.color_theme.fail( 

921 "! Server answered with a different interface." 

922 ) 

923 ) 

924 raise ValueError 

925 if t.floors[1].sprintf("%uuid%") != ndr_uuid: 

926 if self.verb: 

927 print( 

928 conf.color_theme.fail( 

929 "! Server answered with a different NDR version." 

930 ) 

931 ) 

932 raise ValueError 

933 if self.transport == DCERPC_Transport.NCACN_IP_TCP: 

934 endpoints.append((t.floors[4].rhs, t.floors[3].rhs)) 

935 elif self.transport == DCERPC_Transport.NCACN_NP: 

936 endpoints.append(t.floors[3].rhs.rstrip(b"\x00").decode()) 

937 return endpoints 

938 elif status == 0x16C9A0D6: 

939 if self.verb: 

940 print( 

941 conf.color_theme.fail( 

942 "! Server errored: 'There are no elements that satisfy" 

943 " the specified search criteria'." 

944 ) 

945 ) 

946 raise ValueError 

947 print(conf.color_theme.fail("! Failure.")) 

948 if pkt: 

949 pkt.show() 

950 raise ValueError("EPM Map failed") 

951 

952 

953def get_endpoint( 

954 ip, 

955 interface, 

956 transport=DCERPC_Transport.NCACN_IP_TCP, 

957 ndrendian="little", 

958 verb=True, 

959 ssp=None, 

960 smb_kwargs={}, 

961): 

962 """ 

963 Call the endpoint mapper on a remote IP to find an interface 

964 

965 :param ip: 

966 :param interface: 

967 :param mode: 

968 :param verb: 

969 :param ssp: 

970 

971 :return: a list of connection tuples for this interface 

972 """ 

973 client = DCERPC_Client( 

974 transport, 

975 # EPM only works with NDR32 

976 ndr64=False, 

977 ndrendian=ndrendian, 

978 verb=verb, 

979 ssp=ssp, 

980 ) 

981 

982 if transport == DCERPC_Transport.NCACN_IP_TCP: 

983 endpoint = 135 

984 elif transport == DCERPC_Transport.NCACN_NP: 

985 endpoint = "epmapper" 

986 else: 

987 raise ValueError("Unknown transport value !") 

988 

989 client.connect(ip, endpoint=endpoint, smb_kwargs=smb_kwargs) 

990 

991 client.bind(find_dcerpc_interface("ept")) 

992 try: 

993 endpoints = client.epm_map(interface) 

994 finally: 

995 client.close() 

996 

997 return endpoints