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

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

238 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 server as per [MS-RPCE] 

8""" 

9 

10import socket 

11import uuid 

12import threading 

13from collections import deque 

14 

15from scapy.arch import get_if_addr 

16from scapy.config import conf 

17from scapy.data import MTU 

18from scapy.volatile import RandShort 

19 

20from scapy.layers.dcerpc import ( 

21 CommonAuthVerifier, 

22 DCE_RPC_INTERFACES, 

23 DCERPC_Transport, 

24 DceRpc5, 

25 DceRpc5AlterContext, 

26 DceRpc5AlterContextResp, 

27 DceRpc5Auth3, 

28 DceRpc5Bind, 

29 DceRpc5BindAck, 

30 DceRpc5BindNak, 

31 DceRpc5Fault, 

32 DceRpc5PortAny, 

33 DceRpc5Request, 

34 DceRpc5Response, 

35 DceRpc5Result, 

36 DceRpc5TransferSyntax, 

37 DceRpcInterface, 

38 DceRpcSession, 

39 NDRPacket, 

40 RPC_C_AUTHN_LEVEL, 

41) 

42from scapy.layers.gssapi import ( 

43 GSS_S_COMPLETE, 

44 GSS_S_CONTINUE_NEEDED, 

45) 

46 

47# RPC 

48from scapy.layers.msrpce.ept import ( 

49 ept_map_Request, 

50 ept_map_Response, 

51 twr_p_t, 

52 protocol_tower_t, 

53 prot_and_addr_t, 

54) 

55 

56# Typing 

57from typing import ( 

58 Dict, 

59 Callable, 

60 Optional, 

61 Tuple, 

62) 

63 

64 

65class _DCERPC_Server_metaclass(type): 

66 # This value is calculated for each DCE/RPC server, and contains 

67 # the callables sorted by interface+opnum 

68 dcerpc_commands: Dict[Tuple[uuid.UUID, int], Callable] = {} 

69 

70 def __new__(cls, name, bases, dct): 

71 dct.setdefault( 

72 "dcerpc_commands", 

73 {x.dcerpc_command: x for x in dct.values() if hasattr(x, "dcerpc_command")}, 

74 ) 

75 return type.__new__(cls, name, bases, dct) 

76 

77 

78class DCERPC_Fault(Exception): 

79 def __init__(self, status): 

80 self.status = status 

81 

82 

83class DCERPC_Server(metaclass=_DCERPC_Server_metaclass): 

84 """ 

85 DCE/RPC server 

86 

87 :param transport: the DCERPC_Transport to bind this server on 

88 :param ndr64: whether to use NDR64 or not (default: conf.ndr64) 

89 :param verb: verbose mode 

90 

91 Other optional parameters: 

92 

93 :param min_auth_level: the minimum RPC_C_AUTHN_LEVEL to allow. 

94 (NONE allows anonymous access) 

95 """ 

96 

97 def __init__( 

98 self, 

99 transport: DCERPC_Transport, 

100 ndr64: Optional[bool] = None, 

101 verb: bool = True, 

102 min_auth_level: RPC_C_AUTHN_LEVEL = RPC_C_AUTHN_LEVEL.NONE, 

103 # endpoint mapper only 

104 local_ip: str = None, 

105 port: int = None, 

106 portmap: Dict[DceRpcInterface, int] = None, 

107 **kwargs, 

108 ): 

109 self.transport = transport 

110 self.dcerpc_commands = self.dcerpc_commands.copy() 

111 if ndr64 is None: 

112 ndr64 = conf.ndr64 

113 self.ndr64 = ndr64 

114 self.min_auth_level = min_auth_level 

115 

116 # For endpoint mapper. TODO: improve separation/handling of SMB/IP etc 

117 self.local_ip = local_ip 

118 self.port = port 

119 self.portmap = portmap or {} 

120 self.verb = verb 

121 

122 # Session specific 

123 self.session = DceRpcSession(**kwargs) 

124 self.authenticated = False 

125 self.queue = deque() 

126 

127 def loop(self, sock): 

128 while True: 

129 pkt = sock.recv(MTU) 

130 if not pkt: 

131 break 

132 self.recv(pkt) 

133 # send all possible responses 

134 while True: 

135 resp = self.get_response() 

136 if not resp: 

137 break 

138 sock.send(bytes(resp)) 

139 

140 @staticmethod 

141 def answer(reqcls): 

142 """ 

143 A decorator that registers a DCE/RPC responder to a command. 

144 See the DCE/RPC documentation. 

145 

146 :param reqcls: the DCE/RPC packet class to respond to 

147 """ 

148 

149 def deco(func): 

150 if not issubclass(reqcls, NDRPacket): 

151 raise ValueError("Cannot answer a non NDRPacket class !") 

152 try: 

153 func.dcerpc_command = reqcls.intf, reqcls.opnum 

154 except AttributeError: 

155 raise ValueError( 

156 "NDRPacket class isn't registered or isn't a request !" 

157 ) 

158 return func 

159 

160 return deco 

161 

162 def extend(self, server_cls): 

163 """ 

164 Extend a DCE/RPC server into another 

165 """ 

166 self.dcerpc_commands.update(server_cls.dcerpc_commands) 

167 

168 def make_reply(self, req): 

169 """ 

170 Make a response to the DCE/RPC request. 

171 

172 This finds whether a callback has been registered for this particular packet, 

173 and call it if available. 

174 """ 

175 opnum = req[DceRpc5Request].opnum 

176 if self.session.rpc_bind_interface is None: 

177 return None 

178 intf = self.session.rpc_bind_interface.uuid 

179 if (intf, opnum) in self.dcerpc_commands: 

180 # call handler 

181 return self.dcerpc_commands[(intf, opnum)](self, req) 

182 return None 

183 

184 @staticmethod 

185 def _run_client(server, clientsocket, sockets): 

186 try: 

187 server.loop(clientsocket) 

188 finally: 

189 clientsocket.close() 

190 sockets.remove(clientsocket) 

191 

192 @classmethod 

193 def spawn(cls, transport, iface=None, port=135, bg=False, **kwargs): 

194 """ 

195 Spawn a DCE/RPC server 

196 

197 :param transport: one of DCERPC_Transport 

198 :param iface: the interface to spawn it on (default: conf.iface) 

199 :param port: the port to spawn it on (for IP_TCP or the SMB server) 

200 :param bg: background mode? (default: False) 

201 :param ndr64: whether NDR64 is supported or not (default: conf.ndr64). 

202 This attribute will be overwritten if the client doesn't support it. 

203 """ 

204 if transport == DCERPC_Transport.NCACN_IP_TCP: 

205 # IP/TCP case 

206 ssock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 

207 local_ip = get_if_addr(iface or conf.iface) 

208 try: 

209 ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 

210 except OSError: 

211 pass 

212 ssock.bind((local_ip, port)) 

213 ssock.listen(5) 

214 sockets = [] 

215 if kwargs.get("verb", True): 

216 print( 

217 conf.color_theme.green( 

218 "Server %s started. Waiting..." % cls.__name__ 

219 ) 

220 ) 

221 

222 def _run(): 

223 # Wait for clients forever 

224 try: 

225 while True: 

226 clientsocket, address = ssock.accept() 

227 sockets.append(clientsocket) 

228 print( 

229 conf.color_theme.gold( 

230 "\u2503 Connection received from %s" % repr(address) 

231 ) 

232 ) 

233 server = cls( 

234 DCERPC_Transport.NCACN_IP_TCP, 

235 local_ip=local_ip, 

236 port=port, 

237 **kwargs, 

238 ) 

239 threading.Thread( 

240 target=cls._run_client, 

241 args=(server, clientsocket, sockets), 

242 ).start() 

243 except KeyboardInterrupt: 

244 print("X Exiting.") 

245 ssock.shutdown(socket.SHUT_RDWR) 

246 except OSError: 

247 print("X Server closed.") 

248 finally: 

249 for sock in sockets: 

250 try: 

251 sock.shutdown(socket.SHUT_RDWR) 

252 sock.close() 

253 except Exception: 

254 pass 

255 ssock.close() 

256 

257 if bg: 

258 # Background 

259 threading.Thread(target=_run).start() 

260 return ssock 

261 else: 

262 # Non-background 

263 _run() 

264 elif transport == DCERPC_Transport.NCACN_NP: 

265 # SMB case 

266 from scapy.layers.smbserver import SMB_Server 

267 

268 min_auth_level = kwargs.pop( 

269 "min_auth_level", RPC_C_AUTHN_LEVEL.PKT_INTEGRITY 

270 ) 

271 if min_auth_level >= RPC_C_AUTHN_LEVEL.PKT_PRIVACY: 

272 kwargs.setdefault("REQUIRE_ENCRYPTION", True) 

273 elif min_auth_level <= RPC_C_AUTHN_LEVEL.PKT: 

274 kwargs.setdefault("REQUIRE_SIGNATURE", False) 

275 

276 kwargs.setdefault("shares", []) # do not expose files by default 

277 return SMB_Server.spawn( 

278 iface=iface or conf.iface, 

279 port=port, 

280 bg=bg, 

281 # Important: pass the DCE/RPC server 

282 DCERPC_SERVER_CLS=cls, 

283 # SMB parameters 

284 **kwargs, 

285 ) 

286 else: 

287 raise ValueError("Unsupported transport :(") 

288 

289 def _send_fault(self, hdr, req, status): 

290 """ 

291 Internal: return a DCE/RPC Fault 

292 """ 

293 hdr.pfc_flags += "PFC_DID_NOT_EXECUTE" 

294 self.queue.extend( 

295 hdr 

296 / DceRpc5Fault( 

297 status=status, 

298 cont_id=req.cont_id, 

299 ) 

300 ) 

301 

302 def recv(self, data): 

303 if isinstance(data, bytes): 

304 req = DceRpc5(data) 

305 else: 

306 req = data 

307 # If the packet has padding, it contains several fragments 

308 pad = None 

309 if conf.padding_layer in req: 

310 pad = req[conf.padding_layer].load 

311 req[conf.padding_layer].underlayer.remove_payload() 

312 # Ask the DCE/RPC session to process it (match interface, etc.) 

313 req = self.session.in_pkt(req, commit=False) 

314 hdr = DceRpc5( 

315 endian=req.endian, 

316 encoding=req.encoding, 

317 float=req.float, 

318 call_id=req.call_id, 

319 ) 

320 # Now process the packet based on the DCE/RPC type 

321 if DceRpc5Bind in req or DceRpc5AlterContext in req or DceRpc5Auth3 in req: 

322 # Log 

323 if self.verb: 

324 print( 

325 conf.color_theme.opening( 

326 "<< %s" % req.payload.__class__.__name__ 

327 + ( 

328 " (with %s%s)" 

329 % ( 

330 self.session.ssp.__class__.__name__, 

331 ( 

332 f" - {self.session.auth_level.name}" 

333 if self.session.auth_level is not None 

334 else "" 

335 ), 

336 ) 

337 if self.session.ssp 

338 else "" 

339 ) 

340 ) 

341 ) 

342 if ( 

343 not self.session.rpc_bind_interface_commit 

344 and not self.session.rpc_bind_interface 

345 ): 

346 # The session did not find a matching interface ! 

347 self.queue.extend(self.session.out_pkt(hdr / DceRpc5BindNak())) 

348 if self.verb: 

349 print(conf.color_theme.fail("! DceRpc5BindNak (unknown interface)")) 

350 else: 

351 auth_value, status = None, 0 

352 if ( 

353 self.session.ssp 

354 and req.auth_verifier 

355 and req.auth_verifier.auth_value 

356 ): 

357 # SSPI 

358 ( 

359 self.session.sspcontext, 

360 auth_value, 

361 status, 

362 ) = self.session.ssp.GSS_Accept_sec_context( 

363 self.session.sspcontext, req.auth_verifier.auth_value 

364 ) 

365 

366 if DceRpc5Auth3 in req: 

367 # Auth 3 stops here (no server response) ! 

368 if status == GSS_S_COMPLETE: 

369 self.authenticated = True 

370 else: 

371 print(conf.color_theme.fail("! DceRpc5Auth3 failed")) 

372 self.session.auth_level = RPC_C_AUTHN_LEVEL.NONE 

373 if pad is not None: 

374 self.recv(pad) 

375 return 

376 

377 # Check auth status 

378 if status not in [GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED]: 

379 self._send_fault(hdr, req, status=5) 

380 if self.verb: 

381 print( 

382 conf.color_theme.fail( 

383 "! GSS_Accept_sec_context failed: %s" % status 

384 ) 

385 ) 

386 return 

387 

388 # Store session context 

389 self.session.auth_context_id = req.auth_verifier.auth_context_id 

390 self.session.auth_level = RPC_C_AUTHN_LEVEL( 

391 req.auth_verifier.auth_level 

392 ) 

393 if self.session.auth_level < self.min_auth_level: 

394 self._send_fault(hdr, req, status=5) 

395 if self.verb: 

396 print( 

397 conf.color_theme.fail( 

398 "! auth_level %s < min_auth_level %s." 

399 % ( 

400 self.session.auth_level, 

401 self.min_auth_level, 

402 ) 

403 ) 

404 ) 

405 return 

406 

407 # auth_verifier here contains the SSP nego packets 

408 # (whereas it usually contains the verifiers) 

409 if auth_value is not None: 

410 hdr.auth_verifier = CommonAuthVerifier( 

411 auth_type=req.auth_verifier.auth_type, 

412 auth_level=req.auth_verifier.auth_level, 

413 auth_context_id=req.auth_verifier.auth_context_id, 

414 auth_value=auth_value, 

415 ) 

416 

417 # Mark as authenticated if successful. 

418 if status == GSS_S_COMPLETE: 

419 self.authenticated = True 

420 elif not self.authenticated: 

421 # Trying to do unauthenticated bind. 

422 if self.min_auth_level == RPC_C_AUTHN_LEVEL.NONE: 

423 self.session.auth_level = RPC_C_AUTHN_LEVEL.NONE 

424 else: 

425 self._send_fault(hdr, req, status=5) 

426 if self.verb: 

427 print( 

428 conf.color_theme.fail( 

429 "! Anonymous bind is not allowed." 

430 ) 

431 ) 

432 return 

433 

434 # Detect if the client requested NDR64 and the server agrees 

435 self.ndr64 = self.ndr64 and any( 

436 ctx.transfer_syntaxes[0].sprintf("%if_uuid%") == "NDR64" 

437 for ctx in req.context_elem 

438 ) 

439 

440 # Process bind contexts and answer to them 

441 results = [] 

442 for ctx in req.context_elem: 

443 # Get name 

444 name = ctx.transfer_syntaxes[0].sprintf("%if_uuid%") 

445 if ( 

446 # NDR64 

447 (name == "NDR64" and self.ndr64) 

448 or 

449 # NDR 2.0 

450 (name == "NDR 2.0" and not self.ndr64) 

451 ): 

452 # Acceptance 

453 results.append( 

454 DceRpc5Result( 

455 result=0, 

456 reason=0, 

457 transfer_syntax=DceRpc5TransferSyntax( 

458 if_uuid=ctx.transfer_syntaxes[0].if_uuid, 

459 if_version=ctx.transfer_syntaxes[0].if_version, 

460 ), 

461 ) 

462 ) 

463 

464 # Commit session interface selection 

465 self.session.commit_rpc_interface() 

466 elif name == "Bind Time Feature Negotiation": 

467 # Handle Bind Time Feature 

468 results.append( 

469 DceRpc5Result( 

470 result=3, 

471 reason=3, 

472 transfer_syntax=DceRpc5TransferSyntax( 

473 if_uuid="NULL", 

474 if_version=0, 

475 ), 

476 ) 

477 ) 

478 else: 

479 # Reject 

480 results.append( 

481 DceRpc5Result( 

482 result=2, 

483 reason=2, 

484 transfer_syntax=DceRpc5TransferSyntax( 

485 if_uuid="NULL", 

486 if_version=0, 

487 ), 

488 ) 

489 ) 

490 

491 if self.port is None: 

492 # Piped 

493 port_spec = ( 

494 b"\\\\PIPE\\\\%s\0" 

495 % self.session.rpc_bind_interface.name.encode() 

496 ) 

497 else: 

498 # IP 

499 port_spec = str(self.port).encode() + b"\x00" 

500 if DceRpc5Bind in req: 

501 cls = DceRpc5BindAck 

502 else: 

503 cls = DceRpc5AlterContextResp 

504 self.queue.extend( 

505 self.session.out_pkt( 

506 hdr 

507 / cls( 

508 assoc_group_id=int(RandShort()), 

509 sec_addr=DceRpc5PortAny( 

510 port_spec=port_spec, 

511 ), 

512 results=results, 

513 ), 

514 ) 

515 ) 

516 if self.verb: 

517 print( 

518 conf.color_theme.success( 

519 f">> {cls.__name__} {self.session.rpc_bind_interface.name}" 

520 f" is on port '{port_spec.decode()}' using " 

521 + ("NDR64" if self.ndr64 else "NDR32") 

522 ) 

523 ) 

524 elif DceRpc5Request in req: 

525 if self.verb: 

526 print( 

527 conf.color_theme.opening( 

528 "<< REQUEST: %s" 

529 % req[DceRpc5Request].payload.__class__.__name__ 

530 ) 

531 ) 

532 

533 # Check auth 

534 if not self.authenticated and self.min_auth_level != RPC_C_AUTHN_LEVEL.NONE: 

535 self._send_fault(hdr, req, status=5) 

536 return 

537 

538 # Can be any RPC request ! 

539 try: 

540 resp = self.make_reply(req) 

541 if not resp: 

542 # nca_s_op_rng_error 

543 raise DCERPC_Fault(0x1C010002) 

544 

545 # Send response 

546 self.queue.extend( 

547 self.session.out_pkt( 

548 hdr 

549 / DceRpc5Response( 

550 alloc_hint=len(resp), 

551 cont_id=req.cont_id, 

552 ) 

553 / resp, 

554 ) 

555 ) 

556 if self.verb: 

557 print( 

558 conf.color_theme.success( 

559 ">> RESPONSE: %s" % (resp.__class__.__name__) 

560 ) 

561 ) 

562 except DCERPC_Fault as ex: 

563 if self.verb: 

564 print(conf.color_theme.fail("! %s" % ex.status)) 

565 

566 # Return a Fault 

567 self._send_fault(hdr, req, status=ex.status) 

568 

569 # If there was padding, process the second frag 

570 if pad is not None: 

571 self.recv(pad) 

572 

573 def get_response(self): 

574 try: 

575 return self.queue.popleft() 

576 except IndexError: 

577 return None 

578 

579 # Endpoint mapper 

580 

581 @answer.__func__(ept_map_Request) # hack for Python <= 3.9 

582 def ept_map(self, req): 

583 """ 

584 Answer to ept_map_Request. 

585 """ 

586 if self.transport != DCERPC_Transport.NCACN_IP_TCP: 

587 raise ValueError("Unimplemented") 

588 

589 tower = protocol_tower_t( 

590 req[ept_map_Request].valueof("map_tower.tower_octet_string") 

591 ) 

592 uuid = tower.floors[0].uuid 

593 if_version = (tower.floors[0].rhs << 16) | tower.floors[0].version 

594 

595 # Check for results in our portmap 

596 port = None 

597 if (uuid, if_version) in DCE_RPC_INTERFACES: 

598 interface = DCE_RPC_INTERFACES[(uuid, if_version)] 

599 if interface in self.portmap: 

600 port = self.portmap[interface] 

601 

602 if port is not None: 

603 # Found result 

604 resp_tower = twr_p_t( 

605 tower_octet_string=bytes( 

606 protocol_tower_t( 

607 floors=[ 

608 tower.floors[0], # UUID 

609 tower.floors[1], # NDR version 

610 tower.floors[2], # RPC version 

611 prot_and_addr_t( 

612 lhs_length=1, 

613 protocol_identifier="NCACN_IP_TCP", 

614 rhs_length=2, 

615 rhs=port, 

616 ), 

617 prot_and_addr_t( 

618 lhs_length=1, 

619 protocol_identifier="IP", 

620 rhs_length=4, 

621 rhs=self.local_ip or "0.0.0.0", 

622 ), 

623 ] 

624 ) 

625 ) 

626 ) 

627 resp = ept_map_Response(ITowers=[resp_tower], ndr64=self.ndr64) 

628 resp.ITowers.max_count = req.max_towers # ugh 

629 else: 

630 # No result found: nca_s_unk_if 

631 raise DCERPC_Fault(0x1C010003) 

632 return resp