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

175 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 threading 

12from collections import deque 

13 

14from scapy.arch import get_if_addr 

15from scapy.config import conf 

16from scapy.data import MTU 

17from scapy.volatile import RandShort 

18 

19from scapy.layers.dcerpc import ( 

20 DceRpc5, 

21 DceRpcSession, 

22 DceRpc5Bind, 

23 DceRpc5BindAck, 

24 DceRpc5BindNak, 

25 DceRpc5Auth3, 

26 DceRpc5AlterContext, 

27 DceRpc5AlterContextResp, 

28 DceRpc5Result, 

29 DceRpc5Request, 

30 DceRpc5Response, 

31 DceRpc5TransferSyntax, 

32 DceRpc5PortAny, 

33 CommonAuthVerifier, 

34 DCE_RPC_INTERFACES, 

35 DCERPC_Transport, 

36 RPC_C_AUTHN_LEVEL, 

37) 

38 

39# RPC 

40from scapy.layers.msrpce.ept import ( 

41 ept_map_Request, 

42 ept_map_Response, 

43 twr_p_t, 

44 protocol_tower_t, 

45 prot_and_addr_t, 

46) 

47 

48 

49class _DCERPC_Server_metaclass(type): 

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

51 dct.setdefault( 

52 "dcerpc_commands", 

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

54 ) 

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

56 

57 

58class DCERPC_Server(metaclass=_DCERPC_Server_metaclass): 

59 def __init__( 

60 self, 

61 transport, 

62 ndr64=False, 

63 verb=True, 

64 local_ip=None, 

65 port=None, 

66 portmap=None, 

67 **kwargs, 

68 ): 

69 self.transport = transport 

70 self.session = DceRpcSession(**kwargs) 

71 self.queue = deque() 

72 self.ndr64 = ndr64 

73 if ndr64: 

74 self.ndr_name = "NDR64" 

75 else: 

76 self.ndr_name = "NDR 2.0" 

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

78 self.local_ip = local_ip 

79 self.port = port 

80 self.portmap = portmap or {} 

81 self.verb = verb 

82 

83 def loop(self, sock): 

84 while True: 

85 pkt = sock.recv(MTU) 

86 if not pkt: 

87 break 

88 self.recv(pkt) 

89 # send all possible responses 

90 while True: 

91 resp = self.get_response() 

92 if not resp: 

93 break 

94 sock.send(bytes(resp)) 

95 

96 @staticmethod 

97 def answer(reqcls): 

98 """ 

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

100 See the DCE/RPC documentation. 

101 

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

103 """ 

104 

105 def deco(func): 

106 func.dcerpc_command = reqcls 

107 return func 

108 

109 return deco 

110 

111 def extend(self, server_cls): 

112 """ 

113 Extend a DCE/RPC server into another 

114 """ 

115 self.dcerpc_commands.update(server_cls.dcerpc_commands) 

116 

117 def make_reply(self, req): 

118 cls = req[DceRpc5Request].payload.__class__ 

119 if cls in self.dcerpc_commands: 

120 # call handler 

121 return self.dcerpc_commands[cls](self, req) 

122 return None 

123 

124 @classmethod 

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

126 """ 

127 Spawn a DCE/RPC server 

128 

129 :param transport: one of DCERPC_Transport 

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

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

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

133 """ 

134 if transport == DCERPC_Transport.NCACN_IP_TCP: 

135 # IP/TCP case 

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

137 local_ip = get_if_addr(iface or conf.iface) 

138 try: 

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

140 except OSError: 

141 pass 

142 ssock.bind((local_ip, port)) 

143 ssock.listen(5) 

144 sockets = [] 

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

146 print( 

147 conf.color_theme.green( 

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

149 ) 

150 ) 

151 

152 def _run(): 

153 # Wait for clients forever 

154 try: 

155 while True: 

156 clientsocket, address = ssock.accept() 

157 sockets.append(clientsocket) 

158 print( 

159 conf.color_theme.gold( 

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

161 ) 

162 ) 

163 server = cls( 

164 DCERPC_Transport.NCACN_IP_TCP, 

165 local_ip=local_ip, 

166 port=port, 

167 **kwargs, 

168 ) 

169 threading.Thread( 

170 target=server.loop, args=(clientsocket,) 

171 ).start() 

172 except KeyboardInterrupt: 

173 print("X Exiting.") 

174 ssock.shutdown(socket.SHUT_RDWR) 

175 except OSError: 

176 print("X Server closed.") 

177 finally: 

178 for sock in sockets: 

179 try: 

180 sock.shutdown(socket.SHUT_RDWR) 

181 sock.close() 

182 except Exception: 

183 pass 

184 ssock.close() 

185 

186 if bg: 

187 # Background 

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

189 return ssock 

190 else: 

191 # Non-background 

192 _run() 

193 elif transport == DCERPC_Transport.NCACN_NP: 

194 # SMB case 

195 from scapy.layers.smbserver import SMB_Server 

196 

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

198 return SMB_Server.spawn( 

199 iface=iface or conf.iface, 

200 port=port, 

201 bg=bg, 

202 # Important: pass the DCE/RPC server 

203 DCERPC_SERVER_CLS=cls, 

204 # SMB parameters 

205 **kwargs, 

206 ) 

207 else: 

208 raise ValueError("Unsupported transport :(") 

209 

210 def recv(self, data): 

211 if isinstance(data, bytes): 

212 req = DceRpc5(data) 

213 else: 

214 req = data 

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

216 pad = None 

217 if conf.padding_layer in req: 

218 pad = req[conf.padding_layer].load 

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

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

221 req = self.session.in_pkt(req) 

222 hdr = DceRpc5( 

223 endian=req.endian, 

224 encoding=req.encoding, 

225 float=req.float, 

226 call_id=req.call_id, 

227 ) 

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

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

230 # Log 

231 if self.verb: 

232 print( 

233 conf.color_theme.opening( 

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

235 + ( 

236 " (with %s%s)" 

237 % ( 

238 self.session.ssp.__class__.__name__, 

239 ( 

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

241 if self.session.auth_level is not None 

242 else "" 

243 ), 

244 ) 

245 if self.session.ssp 

246 else "" 

247 ) 

248 ) 

249 ) 

250 if not self.session.rpc_bind_interface: 

251 # The session did not find a matching interface ! 

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

253 if self.verb: 

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

255 else: 

256 auth_value, status = None, 0 

257 if ( 

258 self.session.ssp 

259 and req.auth_verifier 

260 and req.auth_verifier.auth_value 

261 ): 

262 ( 

263 self.session.sspcontext, 

264 auth_value, 

265 status, 

266 ) = self.session.ssp.GSS_Accept_sec_context( 

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

268 ) 

269 self.session.auth_level = RPC_C_AUTHN_LEVEL( 

270 req.auth_verifier.auth_level 

271 ) 

272 self.session.auth_context_id = req.auth_verifier.auth_context_id 

273 if DceRpc5Auth3 in req: 

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

275 if status != 0: 

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

277 if pad is not None: 

278 self.recv(pad) 

279 return 

280 # auth_verifier here contains the SSP nego packets 

281 # (whereas it usually contains the verifiers) 

282 if auth_value is not None: 

283 hdr.auth_verifier = CommonAuthVerifier( 

284 auth_type=req.auth_verifier.auth_type, 

285 auth_level=req.auth_verifier.auth_level, 

286 auth_context_id=req.auth_verifier.auth_context_id, 

287 auth_value=auth_value, 

288 ) 

289 

290 def get_result(ctx): 

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

292 if name == self.ndr_name: 

293 # Acceptance 

294 return DceRpc5Result( 

295 result=0, 

296 reason=0, 

297 transfer_syntax=DceRpc5TransferSyntax( 

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

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

300 ), 

301 ) 

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

303 return DceRpc5Result( 

304 result=3, 

305 reason=3, 

306 transfer_syntax=DceRpc5TransferSyntax( 

307 if_uuid="NULL", 

308 if_version=0, 

309 ), 

310 ) 

311 else: 

312 # Reject 

313 return DceRpc5Result( 

314 result=2, 

315 reason=2, 

316 transfer_syntax=DceRpc5TransferSyntax( 

317 if_uuid="NULL", 

318 if_version=0, 

319 ), 

320 ) 

321 

322 results = [get_result(x) for x in req.context_elem] 

323 if self.port is None: 

324 # Piped 

325 port_spec = ( 

326 b"\\\\PIPE\\\\%s\0" 

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

328 ) 

329 else: 

330 # IP 

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

332 if DceRpc5Bind in req: 

333 cls = DceRpc5BindAck 

334 else: 

335 cls = DceRpc5AlterContextResp 

336 self.queue.extend( 

337 self.session.out_pkt( 

338 hdr 

339 / cls( 

340 assoc_group_id=RandShort(), 

341 sec_addr=DceRpc5PortAny( 

342 port_spec=port_spec, 

343 ), 

344 results=results, 

345 ), 

346 ) 

347 ) 

348 if self.verb: 

349 print( 

350 conf.color_theme.success( 

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

352 f" is on port '{port_spec.decode()}' using {self.ndr_name}" 

353 ) 

354 ) 

355 elif DceRpc5Request in req: 

356 if self.verb: 

357 print( 

358 conf.color_theme.opening( 

359 "<< REQUEST: %s" 

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

361 ) 

362 ) 

363 # Can be any RPC request ! 

364 resp = self.make_reply(req) 

365 if resp: 

366 self.queue.extend( 

367 self.session.out_pkt( 

368 hdr 

369 / DceRpc5Response( 

370 alloc_hint=len(resp), 

371 cont_id=req.cont_id, 

372 ) 

373 / resp, 

374 ) 

375 ) 

376 if self.verb: 

377 print( 

378 conf.color_theme.success( 

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

380 ) 

381 ) 

382 # If there was padding, process the second frag 

383 if pad is not None: 

384 self.recv(pad) 

385 

386 def get_response(self): 

387 try: 

388 return self.queue.popleft() 

389 except IndexError: 

390 return None 

391 

392 # Endpoint mapper 

393 

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

395 def ept_map(self, req): 

396 """ 

397 Answer to ept_map_Request. 

398 """ 

399 if self.transport != DCERPC_Transport.NCACN_IP_TCP: 

400 raise ValueError("Unimplemented") 

401 

402 tower = protocol_tower_t( 

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

404 ) 

405 uuid = tower.floors[0].uuid 

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

407 

408 # Check for results in our portmap 

409 port = None 

410 if (uuid, if_version) in DCE_RPC_INTERFACES: 

411 interface = DCE_RPC_INTERFACES[(uuid, if_version)] 

412 if interface in self.portmap: 

413 port = self.portmap[interface] 

414 

415 if port is not None: 

416 # Found result 

417 resp_tower = twr_p_t( 

418 tower_octet_string=bytes( 

419 protocol_tower_t( 

420 floors=[ 

421 tower.floors[0], # UUID 

422 tower.floors[1], # NDR version 

423 tower.floors[2], # RPC version 

424 prot_and_addr_t( 

425 lhs_length=1, 

426 protocol_identifier="NCACN_IP_TCP", 

427 rhs_length=2, 

428 rhs=port, 

429 ), 

430 prot_and_addr_t( 

431 lhs_length=1, 

432 protocol_identifier="IP", 

433 rhs_length=4, 

434 rhs=self.local_ip or "0.0.0.0", 

435 ), 

436 ] 

437 ) 

438 ) 

439 ) 

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

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

442 else: 

443 # No result found 

444 pass 

445 return resp