Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/scapy/layers/smbserver.py: 21%

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

706 statements  

1# SPDX-License-Identifier: GPL-2.0-only 

2# This file is part of Scapy 

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

4# Copyright (C) Gabriel Potter 

5 

6""" 

7SMB 2 Server Automaton 

8 

9This provides a [MS-SMB2] server that can: 

10- serve files 

11- host a DCE/RPC server 

12 

13This is a Scapy Automaton that is supposedly easily extendable. 

14 

15.. note:: 

16 You will find more complete documentation for this layer over at 

17 `SMB <https://scapy.readthedocs.io/en/latest/layers/smb.html#server>`_ 

18""" 

19 

20import hashlib 

21import pathlib 

22import socket 

23import struct 

24import time 

25 

26from scapy.arch import get_if_addr 

27from scapy.automaton import ATMT, Automaton 

28from scapy.config import conf 

29from scapy.error import log_runtime, log_interactive 

30from scapy.volatile import RandUUID 

31 

32from scapy.layers.dcerpc import ( 

33 DCERPC_Transport, 

34 NDRUnion, 

35) 

36from scapy.layers.gssapi import ( 

37 GSS_S_COMPLETE, 

38 GSS_S_CONTINUE_NEEDED, 

39 GSS_S_CREDENTIALS_EXPIRED, 

40) 

41from scapy.layers.msrpce.rpcserver import DCERPC_Server 

42from scapy.layers.ntlm import ( 

43 NTLMSSP, 

44) 

45from scapy.layers.smb import ( 

46 SMBNegotiate_Request, 

47 SMBNegotiate_Response_Extended_Security, 

48 SMBNegotiate_Response_Security, 

49 SMBSession_Null, 

50 SMBSession_Setup_AndX_Request, 

51 SMBSession_Setup_AndX_Request_Extended_Security, 

52 SMBSession_Setup_AndX_Response, 

53 SMBSession_Setup_AndX_Response_Extended_Security, 

54 SMBTree_Connect_AndX, 

55 SMB_Header, 

56) 

57from scapy.layers.smb2 import ( 

58 DFS_REFERRAL_ENTRY1, 

59 DFS_REFERRAL_V3, 

60 DirectTCP, 

61 FILE_BOTH_DIR_INFORMATION, 

62 FILE_FULL_DIR_INFORMATION, 

63 FILE_ID_BOTH_DIR_INFORMATION, 

64 FILE_NAME_INFORMATION, 

65 FileAllInformation, 

66 FileAlternateNameInformation, 

67 FileBasicInformation, 

68 FileEaInformation, 

69 FileFsAttributeInformation, 

70 FileFsSizeInformation, 

71 FileFsVolumeInformation, 

72 FileIdBothDirectoryInformation, 

73 FileInternalInformation, 

74 FileNetworkOpenInformation, 

75 FileStandardInformation, 

76 FileStreamInformation, 

77 NETWORK_INTERFACE_INFO, 

78 SECURITY_DESCRIPTOR, 

79 SMB2_Cancel_Request, 

80 SMB2_Change_Notify_Request, 

81 SMB2_Change_Notify_Response, 

82 SMB2_Close_Request, 

83 SMB2_Close_Response, 

84 SMB2_Create_Context, 

85 SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2, 

86 SMB2_CREATE_QUERY_MAXIMAL_ACCESS_RESPONSE, 

87 SMB2_CREATE_QUERY_ON_DISK_ID, 

88 SMB2_Create_Request, 

89 SMB2_Create_Response, 

90 SMB2_Echo_Request, 

91 SMB2_Echo_Response, 

92 SMB2_Encryption_Capabilities, 

93 SMB2_Error_Response, 

94 SMB2_FILEID, 

95 SMB2_Header, 

96 SMB2_IOCTL_Network_Interface_Info, 

97 SMB2_IOCTL_Request, 

98 SMB2_IOCTL_RESP_GET_DFS_Referral, 

99 SMB2_IOCTL_Response, 

100 SMB2_IOCTL_Validate_Negotiate_Info_Response, 

101 SMB2_Negotiate_Context, 

102 SMB2_Negotiate_Protocol_Request, 

103 SMB2_Negotiate_Protocol_Response, 

104 SMB2_Preauth_Integrity_Capabilities, 

105 SMB2_Query_Directory_Request, 

106 SMB2_Query_Directory_Response, 

107 SMB2_Query_Info_Request, 

108 SMB2_Query_Info_Response, 

109 SMB2_Read_Request, 

110 SMB2_Read_Response, 

111 SMB2_Session_Logoff_Request, 

112 SMB2_Session_Logoff_Response, 

113 SMB2_Session_Setup_Request, 

114 SMB2_Session_Setup_Response, 

115 SMB2_Set_Info_Request, 

116 SMB2_Set_Info_Response, 

117 SMB2_Signing_Capabilities, 

118 SMB2_Tree_Connect_Request, 

119 SMB2_Tree_Connect_Response, 

120 SMB2_Tree_Disconnect_Request, 

121 SMB2_Tree_Disconnect_Response, 

122 SMB2_Write_Request, 

123 SMB2_Write_Response, 

124 SMBStreamSocket, 

125 SOCKADDR_STORAGE, 

126 SRVSVC_SHARE_TYPES, 

127) 

128from scapy.layers.spnego import SPNEGOSSP 

129 

130# Import DCE/RPC 

131from scapy.layers.msrpce.raw.ms_srvs import ( 

132 LPSERVER_INFO_101, 

133 LPSHARE_ENUM_STRUCT, 

134 LPSHARE_INFO_1, 

135 NetrServerGetInfo_Request, 

136 NetrServerGetInfo_Response, 

137 NetrShareEnum_Request, 

138 NetrShareEnum_Response, 

139 NetrShareGetInfo_Request, 

140 NetrShareGetInfo_Response, 

141 SHARE_INFO_1_CONTAINER, 

142) 

143from scapy.layers.msrpce.raw.ms_wkst import ( 

144 LPWKSTA_INFO_100, 

145 NetrWkstaGetInfo_Request, 

146 NetrWkstaGetInfo_Response, 

147) 

148 

149 

150class SMBShare: 

151 """ 

152 A class used to define a share, used by SMB_Server 

153 

154 :param name: the share name 

155 :param path: the path the the folder hosted by the share 

156 :param type: (optional) share type per [MS-SRVS] sect 2.2.2.4 

157 :param remark: (optional) a description of the share 

158 """ 

159 

160 def __init__(self, name, path=".", type=None, remark=""): 

161 # Set the default type 

162 if type is None: 

163 type = 0 # DISKTREE 

164 if name.endswith("$"): 

165 type &= 0x80000000 # SPECIAL 

166 # Lower case the name for resolution 

167 self._name = name.lower() 

168 # Resolve path 

169 self.path = pathlib.Path(path).resolve() 

170 # props 

171 self.name = name 

172 self.type = type 

173 self.remark = remark 

174 

175 def __repr__(self): 

176 type = SRVSVC_SHARE_TYPES[self.type & 0x0FFFFFFF] 

177 if self.type & 0x80000000: 

178 type = "SPECIAL+" + type 

179 if self.type & 0x40000000: 

180 type = "TEMPORARY+" + type 

181 return "<SMBShare %s [%s]%s = %s>" % ( 

182 self.name, 

183 type, 

184 self.remark and (" '%s'" % self.remark) or "", 

185 str(self.path), 

186 ) 

187 

188 

189# The SMB Automaton 

190 

191 

192class SMB_Server(Automaton): 

193 """ 

194 SMB server automaton 

195 

196 :param shares: the shares to serve. By default, share nothing. 

197 Note that IPC$ is appended. 

198 :param ssp: the SSP to use 

199 

200 All other options (in caps) are optional, and SMB specific: 

201 

202 :param ANONYMOUS_LOGIN: mark the clients as anonymous 

203 :param GUEST_LOGIN: mark the clients as guest 

204 :param REQUIRE_SIGNATURE: set 'Require Signature' 

205 :param MAX_DIALECT: maximum SMB dialect. Defaults to 0x0311 (3.1.1) 

206 :param TREE_SHARE_FLAGS: flags to announce on Tree_Connect_Response 

207 :param TREE_CAPABILITIES: capabilities to announce on Tree_Connect_Response 

208 :param TREE_MAXIMAL_ACCESS: maximal access to announce on Tree_Connect_Response 

209 :param FILE_MAXIMAL_ACCESS: maximal access to announce in MxAc Create Context 

210 """ 

211 

212 pkt_cls = DirectTCP 

213 socketcls = SMBStreamSocket 

214 

215 def __init__(self, shares=[], ssp=None, verb=True, readonly=True, *args, **kwargs): 

216 self.verb = verb 

217 if "sock" not in kwargs: 

218 raise ValueError( 

219 "SMB_Server cannot be started directly ! Use SMB_Server.spawn" 

220 ) 

221 # Various SMB server arguments 

222 self.ANONYMOUS_LOGIN = kwargs.pop("ANONYMOUS_LOGIN", False) 

223 self.GUEST_LOGIN = kwargs.pop("GUEST_LOGIN", None) 

224 self.EXTENDED_SECURITY = kwargs.pop("EXTENDED_SECURITY", True) 

225 self.USE_SMB1 = kwargs.pop("USE_SMB1", False) 

226 self.REQUIRE_SIGNATURE = kwargs.pop("REQUIRE_SIGNATURE", False) 

227 self.MAX_DIALECT = kwargs.pop("MAX_DIALECT", 0x0311) 

228 self.TREE_SHARE_FLAGS = kwargs.pop( 

229 "TREE_SHARE_FLAGS", "FORCE_LEVELII_OPLOCK+RESTRICT_EXCLUSIVE_OPENS" 

230 ) 

231 self.TREE_CAPABILITIES = kwargs.pop("TREE_CAPABILITIES", 0) 

232 self.TREE_MAXIMAL_ACCESS = kwargs.pop( 

233 "TREE_MAXIMAL_ACCESS", 

234 "+".join( 

235 [ 

236 "FILE_READ_DATA", 

237 "FILE_WRITE_DATA", 

238 "FILE_APPEND_DATA", 

239 "FILE_READ_EA", 

240 "FILE_WRITE_EA", 

241 "FILE_EXECUTE", 

242 "FILE_DELETE_CHILD", 

243 "FILE_READ_ATTRIBUTES", 

244 "FILE_WRITE_ATTRIBUTES", 

245 "DELETE", 

246 "READ_CONTROL", 

247 "WRITE_DAC", 

248 "WRITE_OWNER", 

249 "SYNCHRONIZE", 

250 ] 

251 ), 

252 ) 

253 self.FILE_MAXIMAL_ACCESS = kwargs.pop( 

254 # Read-only 

255 "FILE_MAXIMAL_ACCESS", 

256 "+".join( 

257 [ 

258 "FILE_READ_DATA", 

259 "FILE_READ_EA", 

260 "FILE_EXECUTE", 

261 "FILE_READ_ATTRIBUTES", 

262 "READ_CONTROL", 

263 "SYNCHRONIZE", 

264 ] 

265 ), 

266 ) 

267 self.LOCAL_IPS = kwargs.pop( 

268 "LOCAL_IPS", [get_if_addr(kwargs.get("iface", conf.iface) or conf.iface)] 

269 ) 

270 self.DOMAIN_REFERRALS = kwargs.pop("DOMAIN_REFERRALS", []) 

271 if self.USE_SMB1: 

272 log_runtime.warning("Serving SMB1 is not supported :/") 

273 self.readonly = readonly 

274 # We don't want to update the parent shares argument 

275 self.shares = shares.copy() 

276 # Append the IPC$ share 

277 self.shares.append( 

278 SMBShare( 

279 name="IPC$", 

280 type=0x80000003, # SPECIAL+IPC 

281 remark="Remote IPC", 

282 ) 

283 ) 

284 # Initialize the DCE/RPC server for SMB 

285 self.rpc_server = SMB_DCERPC_Server( 

286 DCERPC_Transport.NCACN_NP, 

287 shares=self.shares, 

288 verb=self.verb, 

289 ) 

290 # Extend it if another DCE/RPC server is provided 

291 if "DCERPC_SERVER_CLS" in kwargs: 

292 self.rpc_server.extend(kwargs.pop("DCERPC_SERVER_CLS")) 

293 # Internal Session information 

294 self.SMB2 = False 

295 self.NegotiateCapabilities = None 

296 self.GUID = RandUUID()._fix() 

297 # Compounds are handled on receiving by the StreamSocket, 

298 # and on aggregated in a CompoundQueue to be sent in one go 

299 self.NextCompound = False 

300 self.CompoundedHandle = None 

301 # SSP provider 

302 if ssp is None: 

303 # No SSP => fallback on NTLM with guest 

304 ssp = SPNEGOSSP( 

305 [ 

306 NTLMSSP( 

307 USE_MIC=False, 

308 DO_NOT_CHECK_LOGIN=True, 

309 ), 

310 ] 

311 ) 

312 if self.GUEST_LOGIN is None: 

313 self.GUEST_LOGIN = True 

314 # Initialize 

315 Automaton.__init__(self, *args, **kwargs) 

316 # Set session options 

317 self.session.ssp = ssp 

318 self.session.SecurityMode = kwargs.pop( 

319 "SECURITY_MODE", 

320 3 if self.REQUIRE_SIGNATURE else bool(ssp), 

321 ) 

322 

323 @property 

324 def session(self): 

325 # session shorthand 

326 return self.sock.session 

327 

328 def vprint(self, s=""): 

329 """ 

330 Verbose print (if enabled) 

331 """ 

332 if self.verb: 

333 if conf.interactive: 

334 log_interactive.info("> %s", s) 

335 else: 

336 print("> %s" % s) 

337 

338 def send(self, pkt): 

339 return super(SMB_Server, self).send(pkt, Compounded=self.NextCompound) 

340 

341 @ATMT.state(initial=1) 

342 def BEGIN(self): 

343 self.authenticated = False 

344 

345 @ATMT.receive_condition(BEGIN) 

346 def received_negotiate(self, pkt): 

347 if SMBNegotiate_Request in pkt: 

348 raise self.NEGOTIATED().action_parameters(pkt) 

349 

350 @ATMT.receive_condition(BEGIN) 

351 def received_negotiate_smb2_begin(self, pkt): 

352 if SMB2_Negotiate_Protocol_Request in pkt: 

353 self.SMB2 = True 

354 raise self.NEGOTIATED().action_parameters(pkt) 

355 

356 @ATMT.action(received_negotiate_smb2_begin) 

357 def on_negotiate_smb2_begin(self, pkt): 

358 self.on_negotiate(pkt) 

359 

360 @ATMT.action(received_negotiate) 

361 def on_negotiate(self, pkt): 

362 self.session.sspcontext, spnego_token = self.session.ssp.NegTokenInit2() 

363 # Build negotiate response 

364 DialectIndex = None 

365 DialectRevision = None 

366 if SMB2_Negotiate_Protocol_Request in pkt: 

367 # SMB2 

368 DialectRevisions = pkt[SMB2_Negotiate_Protocol_Request].Dialects 

369 DialectRevisions = [x for x in DialectRevisions if x <= self.MAX_DIALECT] 

370 DialectRevisions.sort(reverse=True) 

371 if DialectRevisions: 

372 DialectRevision = DialectRevisions[0] 

373 else: 

374 # SMB1 

375 DialectIndexes = [ 

376 x.DialectString for x in pkt[SMBNegotiate_Request].Dialects 

377 ] 

378 if self.USE_SMB1: 

379 # Enforce SMB1 

380 DialectIndex = DialectIndexes.index(b"NT LM 0.12") 

381 else: 

382 # Find a value matching SMB2, fallback to SMB1 

383 for key, rev in [(b"SMB 2.???", 0x02FF), (b"SMB 2.002", 0x0202)]: 

384 try: 

385 DialectIndex = DialectIndexes.index(key) 

386 DialectRevision = rev 

387 self.SMB2 = True 

388 break 

389 except ValueError: 

390 pass 

391 else: 

392 DialectIndex = DialectIndexes.index(b"NT LM 0.12") 

393 if DialectRevision and DialectRevision & 0xFF != 0xFF: 

394 # Version isn't SMB X.??? 

395 self.session.Dialect = DialectRevision 

396 cls = None 

397 if self.SMB2: 

398 # SMB2 

399 cls = SMB2_Negotiate_Protocol_Response 

400 self.smb_header = DirectTCP() / SMB2_Header( 

401 Flags="SMB2_FLAGS_SERVER_TO_REDIR", 

402 CreditRequest=1, 

403 CreditCharge=1, 

404 ) 

405 if SMB2_Negotiate_Protocol_Request in pkt: 

406 self.update_smbheader(pkt) 

407 else: 

408 # SMB1 

409 self.smb_header = DirectTCP() / SMB_Header( 

410 Flags="REPLY+CASE_INSENSITIVE+CANONICALIZED_PATHS", 

411 Flags2=( 

412 "LONG_NAMES+EAS+NT_STATUS+SMB_SECURITY_SIGNATURE+" 

413 "UNICODE+EXTENDED_SECURITY" 

414 ), 

415 TID=pkt.TID, 

416 MID=pkt.MID, 

417 UID=pkt.UID, 

418 PIDLow=pkt.PIDLow, 

419 ) 

420 if self.EXTENDED_SECURITY: 

421 cls = SMBNegotiate_Response_Extended_Security 

422 else: 

423 cls = SMBNegotiate_Response_Security 

424 if DialectRevision is None and DialectIndex is None: 

425 # No common dialect found. 

426 if self.SMB2: 

427 resp = self.smb_header.copy() / SMB2_Error_Response() 

428 resp.Command = "SMB2_NEGOTIATE" 

429 else: 

430 resp = self.smb_header.copy() / SMBSession_Null() 

431 resp.Command = "SMB_COM_NEGOTIATE" 

432 resp.Status = "STATUS_NOT_SUPPORTED" 

433 self.send(resp) 

434 return 

435 if self.SMB2: # SMB2 

436 # Capabilities: [MS-SMB2] 3.3.5.4 

437 self.NegotiateCapabilities = "+".join( 

438 [ 

439 "DFS", 

440 "LEASING", 

441 "LARGE_MTU", 

442 ] 

443 ) 

444 if DialectRevision >= 0x0300: 

445 # "if Connection.Dialect belongs to the SMB 3.x dialect family, 

446 # the server supports..." 

447 self.NegotiateCapabilities += "+" + "+".join( 

448 [ 

449 "MULTI_CHANNEL", 

450 "PERSISTENT_HANDLES", 

451 "DIRECTORY_LEASING", 

452 ] 

453 ) 

454 if DialectRevision in [0x0300, 0x0302]: 

455 # "if Connection.Dialect is "3.0" or "3.0.2""... 

456 # Note: 3.1.1 uses the ENCRYPT_DATA flag in Tree Connect Response 

457 self.NegotiateCapabilities += "+ENCRYPTION" 

458 # Build response 

459 resp = self.smb_header.copy() / cls( 

460 DialectRevision=DialectRevision, 

461 SecurityMode=self.session.SecurityMode, 

462 ServerTime=(time.time() + 11644473600) * 1e7, 

463 ServerStartTime=0, 

464 MaxTransactionSize=65536, 

465 MaxReadSize=65536, 

466 MaxWriteSize=65536, 

467 Capabilities=self.NegotiateCapabilities, 

468 ) 

469 # SMB >= 3.0.0 

470 if DialectRevision >= 0x0300: 

471 # [MS-SMB2] sect 3.3.5.3.1 note 253 

472 resp.MaxTransactionSize = 0x800000 

473 resp.MaxReadSize = 0x800000 

474 resp.MaxWriteSize = 0x800000 

475 # SMB 3.1.1 

476 if DialectRevision >= 0x0311: 

477 resp.NegotiateContexts = [ 

478 # Preauth capabilities 

479 SMB2_Negotiate_Context() 

480 / SMB2_Preauth_Integrity_Capabilities( 

481 # SHA-512 by default 

482 HashAlgorithms=[self.session.PreauthIntegrityHashId], 

483 Salt=self.session.Salt, 

484 ), 

485 # Encryption capabilities 

486 SMB2_Negotiate_Context() 

487 / SMB2_Encryption_Capabilities( 

488 # AES-128-CCM by default 

489 Ciphers=[self.session.CipherId], 

490 ), 

491 # Signing capabilities 

492 SMB2_Negotiate_Context() 

493 / SMB2_Signing_Capabilities( 

494 # AES-128-CCM by default 

495 SigningAlgorithms=[self.session.SigningAlgorithmId], 

496 ), 

497 ] 

498 else: 

499 # SMB1 

500 resp = self.smb_header.copy() / cls( 

501 DialectIndex=DialectIndex, 

502 ServerCapabilities=( 

503 "UNICODE+LARGE_FILES+NT_SMBS+RPC_REMOTE_APIS+STATUS32+" 

504 "LEVEL_II_OPLOCKS+LOCK_AND_READ+NT_FIND+" 

505 "LWIO+INFOLEVEL_PASSTHRU+LARGE_READX+LARGE_WRITEX" 

506 ), 

507 SecurityMode=self.session.SecurityMode, 

508 ServerTime=(time.time() + 11644473600) * 1e7, 

509 ServerTimeZone=0x3C, 

510 ) 

511 if self.EXTENDED_SECURITY: 

512 resp.ServerCapabilities += "EXTENDED_SECURITY" 

513 if self.EXTENDED_SECURITY or self.SMB2: 

514 # Extended SMB1 / SMB2 

515 resp.GUID = self.GUID 

516 # Add security blob 

517 resp.SecurityBlob = spnego_token 

518 else: 

519 # Non-extended SMB1 

520 # FIXME never tested. 

521 resp.SecurityBlob = spnego_token 

522 resp.Flags2 -= "EXTENDED_SECURITY" 

523 if not self.SMB2: 

524 resp[SMB_Header].Flags2 = ( 

525 resp[SMB_Header].Flags2 

526 - "SMB_SECURITY_SIGNATURE" 

527 + "SMB_SECURITY_SIGNATURE_REQUIRED+IS_LONG_NAME" 

528 ) 

529 if SMB2_Header in pkt: 

530 # If required, compute sessions 

531 self.session.computeSMBConnectionPreauth( 

532 bytes(pkt[SMB2_Header]), # nego request 

533 bytes(resp[SMB2_Header]), # nego response 

534 ) 

535 self.send(resp) 

536 

537 @ATMT.state() 

538 def NEGOTIATED(self): 

539 pass 

540 

541 def update_smbheader(self, pkt): 

542 """ 

543 Called when receiving a SMB2 packet to update the current smb_header 

544 """ 

545 # [MS-SMB2] sect 3.2.5.1.4 - always grant client its credits 

546 self.smb_header.CreditRequest = pkt.CreditRequest 

547 # [MS-SMB2] sect 3.3.4.1 

548 # "the server SHOULD set the CreditCharge field in the SMB2 header 

549 # of the response to the CreditCharge value in the SMB2 header of the request." 

550 self.smb_header.CreditCharge = pkt.CreditCharge 

551 # If the packet has a NextCommand, set NextCompound to True 

552 self.NextCompound = bool(pkt.NextCommand) 

553 # [MS-SMB2] sect 3.3.5.2.7.2 

554 # Add SMB2_FLAGS_RELATED_OPERATIONS to the response if present 

555 if pkt.Flags.SMB2_FLAGS_RELATED_OPERATIONS: 

556 self.smb_header.Flags += "SMB2_FLAGS_RELATED_OPERATIONS" 

557 else: 

558 self.smb_header.Flags -= "SMB2_FLAGS_RELATED_OPERATIONS" 

559 # [MS-SMB2] sect 2.2.1.2 - Priority 

560 if (self.session.Dialect or 0) >= 0x0311: 

561 self.smb_header.Flags &= 0xFF8F 

562 self.smb_header.Flags |= int(pkt.Flags) & 0x70 

563 # Update IDs 

564 self.smb_header.SessionId = pkt.SessionId 

565 self.smb_header.TID = pkt.TID 

566 self.smb_header.MID = pkt.MID 

567 self.smb_header.PID = pkt.PID 

568 

569 @ATMT.receive_condition(NEGOTIATED) 

570 def received_negotiate_smb2(self, pkt): 

571 if SMB2_Negotiate_Protocol_Request in pkt: 

572 raise self.NEGOTIATED().action_parameters(pkt) 

573 

574 @ATMT.action(received_negotiate_smb2) 

575 def on_negotiate_smb2(self, pkt): 

576 self.on_negotiate(pkt) 

577 

578 @ATMT.receive_condition(NEGOTIATED) 

579 def receive_setup_andx_request(self, pkt): 

580 if ( 

581 SMBSession_Setup_AndX_Request_Extended_Security in pkt 

582 or SMBSession_Setup_AndX_Request in pkt 

583 ): 

584 # SMB1 

585 if SMBSession_Setup_AndX_Request_Extended_Security in pkt: 

586 # Extended 

587 ssp_blob = pkt.SecurityBlob 

588 else: 

589 # Non-extended 

590 ssp_blob = pkt[SMBSession_Setup_AndX_Request].UnicodePassword 

591 raise self.RECEIVED_SETUP_ANDX_REQUEST().action_parameters(pkt, ssp_blob) 

592 elif SMB2_Session_Setup_Request in pkt: 

593 # SMB2 

594 ssp_blob = pkt.SecurityBlob 

595 raise self.RECEIVED_SETUP_ANDX_REQUEST().action_parameters(pkt, ssp_blob) 

596 

597 @ATMT.state() 

598 def RECEIVED_SETUP_ANDX_REQUEST(self): 

599 pass 

600 

601 @ATMT.action(receive_setup_andx_request) 

602 def on_setup_andx_request(self, pkt, ssp_blob): 

603 self.session.sspcontext, tok, status = self.session.ssp.GSS_Accept_sec_context( 

604 self.session.sspcontext, ssp_blob 

605 ) 

606 self.update_smbheader(pkt) 

607 if SMB2_Session_Setup_Request in pkt: 

608 # SMB2 

609 self.smb_header.SessionId = 0x0001000000000015 

610 if status not in [GSS_S_CONTINUE_NEEDED, GSS_S_COMPLETE]: 

611 # Error 

612 if SMB2_Session_Setup_Request in pkt: 

613 # SMB2 

614 resp = self.smb_header.copy() / SMB2_Session_Setup_Response() 

615 # Set security blob (if any) 

616 resp.SecurityBlob = tok 

617 else: 

618 # SMB1 

619 resp = self.smb_header.copy() / SMBSession_Null() 

620 # Map some GSS return codes to NTStatus 

621 if status == GSS_S_CREDENTIALS_EXPIRED: 

622 resp.Status = "STATUS_PASSWORD_EXPIRED" 

623 else: 

624 resp.Status = "STATUS_LOGON_FAILURE" 

625 # Reset Session preauth (SMB 3.1.1) 

626 self.session.SessionPreauthIntegrityHashValue = None 

627 else: 

628 # Negotiation 

629 if ( 

630 SMBSession_Setup_AndX_Request_Extended_Security in pkt 

631 or SMB2_Session_Setup_Request in pkt 

632 ): 

633 # SMB1 extended / SMB2 

634 if SMB2_Session_Setup_Request in pkt: 

635 # SMB2 

636 resp = self.smb_header.copy() / SMB2_Session_Setup_Response() 

637 if self.GUEST_LOGIN: 

638 resp.SessionFlags = "IS_GUEST" 

639 if self.ANONYMOUS_LOGIN: 

640 resp.SessionFlags = "IS_NULL" 

641 else: 

642 # SMB1 extended 

643 resp = ( 

644 self.smb_header.copy() 

645 / SMBSession_Setup_AndX_Response_Extended_Security( 

646 NativeOS="Windows 4.0", 

647 NativeLanMan="Windows 4.0", 

648 ) 

649 ) 

650 if self.GUEST_LOGIN: 

651 resp.Action = "SMB_SETUP_GUEST" 

652 # Set security blob 

653 resp.SecurityBlob = tok 

654 elif SMBSession_Setup_AndX_Request in pkt: 

655 # Non-extended 

656 resp = self.smb_header.copy() / SMBSession_Setup_AndX_Response( 

657 NativeOS="Windows 4.0", 

658 NativeLanMan="Windows 4.0", 

659 ) 

660 resp.Status = 0x0 if (status == GSS_S_COMPLETE) else 0xC0000016 

661 # We have a response. If required, compute sessions 

662 if status == GSS_S_CONTINUE_NEEDED: 

663 # the setup session response is used in hash 

664 self.session.computeSMBSessionPreauth( 

665 bytes(pkt[SMB2_Header]), # session setup request 

666 bytes(resp[SMB2_Header]), # session setup response 

667 ) 

668 else: 

669 # the setup session response is not used in hash 

670 self.session.computeSMBSessionPreauth( 

671 bytes(pkt[SMB2_Header]), # session setup request 

672 ) 

673 if status == GSS_S_COMPLETE: 

674 # Authentication was successful 

675 self.session.computeSMBSessionKey() 

676 self.authenticated = True 

677 # and send 

678 self.send(resp) 

679 

680 @ATMT.condition(RECEIVED_SETUP_ANDX_REQUEST) 

681 def wait_for_next_request(self): 

682 if self.authenticated: 

683 self.vprint( 

684 "User authenticated %s!" % (self.GUEST_LOGIN and " as guest" or "") 

685 ) 

686 raise self.AUTHENTICATED() 

687 else: 

688 raise self.NEGOTIATED() 

689 

690 @ATMT.state() 

691 def AUTHENTICATED(self): 

692 """Dev: overload this""" 

693 pass 

694 

695 # DEV: add a condition on AUTHENTICATED with prio=0 

696 

697 @ATMT.condition(AUTHENTICATED, prio=1) 

698 def should_serve(self): 

699 # Serve files 

700 self.current_trees = {} 

701 self.current_handles = {} 

702 self.enumerate_index = {} # used for query directory enumeration 

703 self.tree_id = 0 

704 self.base_time_t = self.current_smb_time() 

705 raise self.SERVING() 

706 

707 def _ioctl_error(self, Status="STATUS_NOT_SUPPORTED"): 

708 pkt = self.smb_header.copy() / SMB2_Error_Response(ErrorData=b"\xff") 

709 pkt.Status = Status 

710 pkt.Command = "SMB2_IOCTL" 

711 self.send(pkt) 

712 

713 @ATMT.state(final=1) 

714 def END(self): 

715 self.end() 

716 

717 # SERVE FILES 

718 

719 def current_tree(self): 

720 """ 

721 Return the current tree name 

722 """ 

723 return self.current_trees[self.smb_header.TID] 

724 

725 def root_path(self): 

726 """ 

727 Return the root path of the current tree 

728 """ 

729 curtree = self.current_tree() 

730 try: 

731 share_path = next(x.path for x in self.shares if x._name == curtree.lower()) 

732 except StopIteration: 

733 return None 

734 return pathlib.Path(share_path).resolve() 

735 

736 @ATMT.state() 

737 def SERVING(self): 

738 """ 

739 Main state when serving files 

740 """ 

741 pass 

742 

743 @ATMT.receive_condition(SERVING) 

744 def receive_logoff_request(self, pkt): 

745 if SMB2_Session_Logoff_Request in pkt: 

746 raise self.NEGOTIATED().action_parameters(pkt) 

747 

748 @ATMT.action(receive_logoff_request) 

749 def send_logoff_response(self, pkt): 

750 self.update_smbheader(pkt) 

751 self.send(self.smb_header.copy() / SMB2_Session_Logoff_Response()) 

752 

753 @ATMT.receive_condition(SERVING) 

754 def receive_setup_andx_request_in_serving(self, pkt): 

755 self.receive_setup_andx_request(pkt) 

756 

757 @ATMT.receive_condition(SERVING) 

758 def is_smb1_tree(self, pkt): 

759 if SMBTree_Connect_AndX in pkt: 

760 # Unsupported 

761 log_runtime.warning("Tree request in SMB1: unimplemented. Quit") 

762 raise self.END() 

763 

764 @ATMT.receive_condition(SERVING) 

765 def receive_tree_connect(self, pkt): 

766 if SMB2_Tree_Connect_Request in pkt: 

767 tree_name = pkt[SMB2_Tree_Connect_Request].Path.split("\\")[-1] 

768 raise self.SERVING().action_parameters(pkt, tree_name) 

769 

770 @ATMT.action(receive_tree_connect) 

771 def send_tree_connect_response(self, pkt, tree_name): 

772 self.update_smbheader(pkt) 

773 # Check the tree name against the shares we're serving 

774 if not any(x._name == tree_name.lower() for x in self.shares): 

775 # Unknown tree 

776 resp = self.smb_header.copy() / SMB2_Error_Response() 

777 resp.Command = "SMB2_TREE_CONNECT" 

778 resp.Status = "STATUS_BAD_NETWORK_NAME" 

779 self.send(resp) 

780 return 

781 # Add tree to current trees 

782 if tree_name not in self.current_trees: 

783 self.tree_id += 1 

784 self.smb_header.TID = self.tree_id 

785 self.current_trees[self.smb_header.TID] = tree_name 

786 self.vprint("Tree Connect on: %s" % tree_name) 

787 self.send( 

788 self.smb_header 

789 / SMB2_Tree_Connect_Response( 

790 ShareType="PIPE" if self.current_tree() == "IPC$" else "DISK", 

791 ShareFlags="AUTO_CACHING+NO_CACHING" 

792 if self.current_tree() == "IPC$" 

793 else self.TREE_SHARE_FLAGS, 

794 Capabilities=0 

795 if self.current_tree() == "IPC$" 

796 else self.TREE_CAPABILITIES, 

797 MaximalAccess=self.TREE_MAXIMAL_ACCESS, 

798 ) 

799 ) 

800 

801 @ATMT.receive_condition(SERVING) 

802 def receive_ioctl(self, pkt): 

803 if SMB2_IOCTL_Request in pkt: 

804 raise self.SERVING().action_parameters(pkt) 

805 

806 @ATMT.action(receive_ioctl) 

807 def send_ioctl_response(self, pkt): 

808 self.update_smbheader(pkt) 

809 if pkt.CtlCode == 0x11C017: 

810 # FSCTL_PIPE_TRANSCEIVE 

811 self.rpc_server.recv(pkt.Input.load) 

812 self.send( 

813 self.smb_header.copy() 

814 / SMB2_IOCTL_Response( 

815 CtlCode=0x11C017, 

816 FileId=pkt[SMB2_IOCTL_Request].FileId, 

817 Buffer=[("Output", self.rpc_server.get_response())], 

818 ) 

819 ) 

820 elif pkt.CtlCode == 0x00140204 and self.session.sspcontext.SessionKey: 

821 # FSCTL_VALIDATE_NEGOTIATE_INFO 

822 # This is a security measure asking the server to validate 

823 # what flags were negotiated during the SMBNegotiate exchange. 

824 # This packet is ALWAYS signed, and expects a signed response. 

825 

826 # https://docs.microsoft.com/en-us/archive/blogs/openspecification/smb3-secure-dialect-negotiation 

827 # > "Down-level servers (pre-Windows 2012) will return 

828 # > STATUS_NOT_SUPPORTED or STATUS_INVALID_DEVICE_REQUEST 

829 # > since they do not allow or implement 

830 # > FSCTL_VALIDATE_NEGOTIATE_INFO. 

831 # > The client should accept the 

832 # > response provided it's properly signed". 

833 

834 if (self.session.Dialect or 0) < 0x0300: 

835 # SMB < 3 isn't supposed to support FSCTL_VALIDATE_NEGOTIATE_INFO 

836 self._ioctl_error(Status="STATUS_FILE_CLOSED") 

837 return 

838 

839 # SMB3 

840 self.send( 

841 self.smb_header.copy() 

842 / SMB2_IOCTL_Response( 

843 CtlCode=0x00140204, 

844 FileId=pkt[SMB2_IOCTL_Request].FileId, 

845 Buffer=[ 

846 ( 

847 "Output", 

848 SMB2_IOCTL_Validate_Negotiate_Info_Response( 

849 GUID=self.GUID, 

850 DialectRevision=self.session.Dialect, 

851 SecurityMode=self.session.SecurityMode, 

852 Capabilities=self.NegotiateCapabilities, 

853 ), 

854 ) 

855 ], 

856 ) 

857 ) 

858 elif pkt.CtlCode == 0x001401FC: 

859 # FSCTL_QUERY_NETWORK_INTERFACE_INFO 

860 self.send( 

861 self.smb_header.copy() 

862 / SMB2_IOCTL_Response( 

863 CtlCode=0x001401FC, 

864 FileId=pkt[SMB2_IOCTL_Request].FileId, 

865 Output=SMB2_IOCTL_Network_Interface_Info( 

866 interfaces=[ 

867 NETWORK_INTERFACE_INFO( 

868 SockAddr_Storage=SOCKADDR_STORAGE( 

869 Family=0x0002, 

870 IPv4Adddress=x, 

871 ) 

872 ) 

873 for x in self.LOCAL_IPS 

874 ] 

875 ), 

876 ) 

877 ) 

878 elif pkt.CtlCode == 0x00060194: 

879 # FSCTL_DFS_GET_REFERRALS 

880 if ( 

881 self.DOMAIN_REFERRALS 

882 and not pkt[SMB2_IOCTL_Request].Input.RequestFileName 

883 ): 

884 # Requesting domain referrals 

885 self.send( 

886 self.smb_header.copy() 

887 / SMB2_IOCTL_Response( 

888 CtlCode=0x00060194, 

889 FileId=pkt[SMB2_IOCTL_Request].FileId, 

890 Output=SMB2_IOCTL_RESP_GET_DFS_Referral( 

891 ReferralEntries=[ 

892 DFS_REFERRAL_V3( 

893 ReferralEntryFlags="NameListReferral", 

894 TimeToLive=600, 

895 ) 

896 for _ in self.DOMAIN_REFERRALS 

897 ], 

898 ReferralBuffer=[ 

899 DFS_REFERRAL_ENTRY1(SpecialName=name) 

900 for name in self.DOMAIN_REFERRALS 

901 ], 

902 ), 

903 ) 

904 ) 

905 return 

906 resp = self.smb_header.copy() / SMB2_Error_Response() 

907 resp.Command = "SMB2_IOCTL" 

908 resp.Status = "STATUS_FS_DRIVER_REQUIRED" 

909 self.send(resp) 

910 else: 

911 # Among other things, FSCTL_VALIDATE_NEGOTIATE_INFO 

912 self._ioctl_error(Status="STATUS_NOT_SUPPORTED") 

913 

914 @ATMT.receive_condition(SERVING) 

915 def receive_create_file(self, pkt): 

916 if SMB2_Create_Request in pkt: 

917 raise self.SERVING().action_parameters(pkt) 

918 

919 PIPES_TABLE = { 

920 "srvsvc": SMB2_FILEID(Persistent=0x4000000012, Volatile=0x4000000001), 

921 "wkssvc": SMB2_FILEID(Persistent=0x4000000013, Volatile=0x4000000002), 

922 "NETLOGON": SMB2_FILEID(Persistent=0x4000000014, Volatile=0x4000000003), 

923 } 

924 

925 # special handle in case of compounded requests ([MS-SMB2] 3.2.4.1.4) 

926 # that points to the chained opened file handle 

927 LAST_HANDLE = SMB2_FILEID( 

928 Persistent=0xFFFFFFFFFFFFFFFF, Volatile=0xFFFFFFFFFFFFFFFF 

929 ) 

930 

931 def current_smb_time(self): 

932 return ( 

933 FileNetworkOpenInformation().get_field("CreationTime").i2m(None, None) 

934 - 864000000000 # one day ago 

935 ) 

936 

937 def make_file_id(self, fname): 

938 """ 

939 Generate deterministic FileId based on the fname 

940 """ 

941 hash = hashlib.md5((fname or "").encode()).digest() 

942 return 0x4000000000 | struct.unpack("<I", hash[:4])[0] 

943 

944 def lookup_file(self, fname, durable_handle=None, create=False, createOptions=None): 

945 """ 

946 Lookup the file and build it's SMB2_FILEID 

947 """ 

948 root = self.root_path() 

949 if isinstance(fname, pathlib.Path): 

950 path = fname 

951 fname = path.name 

952 else: 

953 path = root / (fname or "").replace("\\", "/") 

954 path = path.resolve() 

955 # Word of caution: this check ONLY works because root and path have been 

956 # resolve(). Be careful 

957 # Note: symbolic links are currently unsupported. 

958 if root not in path.parents and path != root: 

959 raise FileNotFoundError 

960 if path.is_reserved(): 

961 raise FileNotFoundError 

962 if not path.exists(): 

963 if create and createOptions: 

964 if createOptions.FILE_DIRECTORY_FILE: 

965 # Folder creation 

966 path.mkdir() 

967 self.vprint("Created folder:" + fname) 

968 else: 

969 # File creation 

970 path.touch() 

971 self.vprint("Created file:" + fname) 

972 else: 

973 raise FileNotFoundError 

974 if durable_handle is None: 

975 handle = SMB2_FILEID( 

976 Persistent=self.make_file_id(fname) + self.smb_header.MID, 

977 ) 

978 else: 

979 # We were given a durable handle. Use it 

980 handle = durable_handle 

981 attrs = { 

982 "CreationTime": self.base_time_t, 

983 "LastAccessTime": self.base_time_t, 

984 "LastWriteTime": self.base_time_t, 

985 "ChangeTime": self.base_time_t, 

986 "EndOfFile": 0, 

987 "AllocationSize": 0, 

988 } 

989 path_stat = path.stat() 

990 attrs["EndOfFile"] = attrs["AllocationSize"] = path_stat.st_size 

991 if fname is None: 

992 # special case 

993 attrs["FileAttributes"] = "+".join( 

994 [ 

995 "FILE_ATTRIBUTE_HIDDEN", 

996 "FILE_ATTRIBUTE_SYSTEM", 

997 "FILE_ATTRIBUTE_DIRECTORY", 

998 ] 

999 ) 

1000 elif path.is_dir(): 

1001 attrs["FileAttributes"] = "FILE_ATTRIBUTE_DIRECTORY" 

1002 else: 

1003 attrs["FileAttributes"] = "FILE_ATTRIBUTE_ARCHIVE" 

1004 self.current_handles[handle] = ( 

1005 path, # file path 

1006 attrs, # file attributes 

1007 ) 

1008 self.enumerate_index[handle] = 0 

1009 return handle 

1010 

1011 def set_compounded_handle(self, handle): 

1012 """ 

1013 Mark a handle as the current one being compounded. 

1014 """ 

1015 self.CompoundedHandle = handle 

1016 

1017 def get_file_id(self, pkt): 

1018 """ 

1019 Return the FileId attribute of pkt, accounting for compounded requests. 

1020 """ 

1021 fid = pkt.FileId 

1022 if fid == self.LAST_HANDLE: 

1023 return self.CompoundedHandle 

1024 return fid 

1025 

1026 def lookup_folder(self, handle, filter, offset, cls): 

1027 """ 

1028 Lookup a folder handle 

1029 """ 

1030 path = self.current_handles[handle][0] 

1031 self.vprint("Query directory: " + str(path)) 

1032 self.current_handles[handle][1]["LastAccessTime"] = self.current_smb_time() 

1033 if not path.is_dir(): 

1034 raise NotADirectoryError 

1035 return sorted( 

1036 [ 

1037 cls(FileName=x.name, **self.current_handles[self.lookup_file(x)][1]) 

1038 for x in path.glob(filter) 

1039 # Note: symbolic links are unsupported because it's hard to check 

1040 # for path traversal on them. 

1041 if not x.is_symlink() 

1042 ] 

1043 + [ 

1044 cls( 

1045 FileAttributes=("FILE_ATTRIBUTE_DIRECTORY"), 

1046 FileName=".", 

1047 ) 

1048 ] 

1049 + ( 

1050 [ 

1051 cls( 

1052 FileAttributes=("FILE_ATTRIBUTE_DIRECTORY"), 

1053 FileName="..", 

1054 ) 

1055 ] 

1056 if path.resolve() != self.root_path() 

1057 else [] 

1058 ), 

1059 key=lambda x: x.FileName, 

1060 )[offset:] 

1061 

1062 @ATMT.action(receive_create_file) 

1063 def send_create_file_response(self, pkt): 

1064 """ 

1065 Handle CreateFile request 

1066 

1067 See [MS-SMB2] 3.3.5.9 () 

1068 """ 

1069 self.update_smbheader(pkt) 

1070 if pkt[SMB2_Create_Request].NameLen: 

1071 fname = pkt[SMB2_Create_Request].Name 

1072 else: 

1073 fname = None 

1074 if fname: 

1075 self.vprint("Opened: " + fname) 

1076 if self.current_tree() == "IPC$": 

1077 # Special IPC$ case: opening a pipe 

1078 FILE_ID = self.PIPES_TABLE.get(fname, None) 

1079 if FILE_ID: 

1080 attrs = { 

1081 "CreationTime": 0, 

1082 "LastAccessTime": 0, 

1083 "LastWriteTime": 0, 

1084 "ChangeTime": 0, 

1085 "EndOfFile": 0, 

1086 "AllocationSize": 4096, 

1087 } 

1088 self.current_handles[FILE_ID] = ( 

1089 fname, 

1090 attrs, 

1091 ) 

1092 self.send( 

1093 self.smb_header.copy() 

1094 / SMB2_Create_Response( 

1095 OplockLevel=pkt.RequestedOplockLevel, 

1096 FileId=FILE_ID, 

1097 **attrs, 

1098 ) 

1099 ) 

1100 else: 

1101 # NOT_FOUND 

1102 resp = self.smb_header.copy() / SMB2_Error_Response() 

1103 resp.Command = "SMB2_CREATE" 

1104 resp.Status = "STATUS_OBJECT_NAME_NOT_FOUND" 

1105 self.send(resp) 

1106 return 

1107 else: 

1108 # Check if there is a Durable Handle Reconnect Request 

1109 durable_handle = None 

1110 if pkt[SMB2_Create_Request].CreateContextsLen: 

1111 try: 

1112 durable_handle = next( 

1113 x.Data.FileId 

1114 for x in pkt[SMB2_Create_Request].CreateContexts 

1115 if x.Name == b"DH2C" 

1116 ) 

1117 except StopIteration: 

1118 pass 

1119 # Lookup file handle 

1120 try: 

1121 handle = self.lookup_file(fname, durable_handle=durable_handle) 

1122 except FileNotFoundError: 

1123 # NOT_FOUND 

1124 if pkt[SMB2_Create_Request].CreateDisposition in [ 

1125 0x00000002, # FILE_CREATE 

1126 0x00000005, # FILE_OVERWRITE_IF 

1127 ]: 

1128 if self.readonly: 

1129 resp = self.smb_header.copy() / SMB2_Error_Response() 

1130 resp.Command = "SMB2_CREATE" 

1131 resp.Status = "STATUS_ACCESS_DENIED" 

1132 self.send(resp) 

1133 return 

1134 else: 

1135 # Create file 

1136 handle = self.lookup_file( 

1137 fname, 

1138 durable_handle=durable_handle, 

1139 create=True, 

1140 createOptions=pkt[SMB2_Create_Request].CreateOptions, 

1141 ) 

1142 else: 

1143 resp = self.smb_header.copy() / SMB2_Error_Response() 

1144 resp.Command = "SMB2_CREATE" 

1145 resp.Status = "STATUS_OBJECT_NAME_NOT_FOUND" 

1146 self.send(resp) 

1147 return 

1148 # Store compounded handle 

1149 self.set_compounded_handle(handle) 

1150 # Build response 

1151 attrs = self.current_handles[handle][1] 

1152 resp = self.smb_header.copy() / SMB2_Create_Response( 

1153 OplockLevel=pkt.RequestedOplockLevel, 

1154 FileId=handle, 

1155 **attrs, 

1156 ) 

1157 # Handle the various chain elements 

1158 if pkt[SMB2_Create_Request].CreateContextsLen: 

1159 CreateContexts = [] 

1160 # Note: failing to provide context elements when the client asks for 

1161 # them will make the windows implementation fall into a weird 

1162 # "the-server-is-dumb" mode. So provide them 'quoi qu'il en coûte'. 

1163 for elt in pkt[SMB2_Create_Request].CreateContexts: 

1164 if elt.Name == b"QFid": 

1165 # [MS-SMB2] sect 3.3.5.9.9 

1166 CreateContexts.append( 

1167 SMB2_Create_Context( 

1168 Name=b"QFid", 

1169 Data=SMB2_CREATE_QUERY_ON_DISK_ID( 

1170 DiskFileId=self.make_file_id(fname), 

1171 VolumeId=0xBA39CD11, 

1172 ), 

1173 ) 

1174 ) 

1175 elif elt.Name == b"MxAc": 

1176 # [MS-SMB2] sect 3.3.5.9.5 

1177 CreateContexts.append( 

1178 SMB2_Create_Context( 

1179 Name=b"MxAc", 

1180 Data=SMB2_CREATE_QUERY_MAXIMAL_ACCESS_RESPONSE( 

1181 QueryStatus=0, 

1182 MaximalAccess=self.FILE_MAXIMAL_ACCESS, 

1183 ), 

1184 ) 

1185 ) 

1186 elif elt.Name == b"DH2Q": 

1187 # [MS-SMB2] sect 3.3.5.9.10 

1188 if "FILE_ATTRIBUTE_DIRECTORY" in attrs["FileAttributes"]: 

1189 continue 

1190 CreateContexts.append( 

1191 SMB2_Create_Context( 

1192 Name=b"DH2Q", 

1193 Data=SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2( 

1194 Timeout=180000 

1195 ), 

1196 ) 

1197 ) 

1198 elif elt.Name == b"RqLs": 

1199 # [MS-SMB2] sect 3.3.5.9.11 

1200 # TODO: hmm, we are probably supposed to do something here 

1201 CreateContexts.append( 

1202 SMB2_Create_Context( 

1203 Name=b"RqLs", 

1204 Data=elt.Data, 

1205 ) 

1206 ) 

1207 resp.CreateContexts = CreateContexts 

1208 self.send(resp) 

1209 

1210 @ATMT.receive_condition(SERVING) 

1211 def receive_change_notify_info(self, pkt): 

1212 if SMB2_Change_Notify_Request in pkt: 

1213 raise self.SERVING().action_parameters(pkt) 

1214 

1215 @ATMT.action(receive_change_notify_info) 

1216 def send_change_notify_info_response(self, pkt): 

1217 # [MS-SMB2] sect 3.3.5.19 

1218 # "If the underlying object store does not support change notifications, the 

1219 # server MUST fail this request with STATUS_NOT_SUPPORTED." 

1220 self.update_smbheader(pkt) 

1221 resp = self.smb_header.copy() / SMB2_Error_Response() 

1222 resp.Command = "SMB2_CHANGE_NOTIFY" 

1223 # ScapyFS doesn't support notifications 

1224 resp.Status = "STATUS_NOT_SUPPORTED" 

1225 self.send(resp) 

1226 

1227 @ATMT.receive_condition(SERVING) 

1228 def receive_query_directory_info(self, pkt): 

1229 if SMB2_Query_Directory_Request in pkt: 

1230 raise self.SERVING().action_parameters(pkt) 

1231 

1232 @ATMT.action(receive_query_directory_info) 

1233 def send_query_directory_response(self, pkt): 

1234 self.update_smbheader(pkt) 

1235 if not pkt.FileNameLen: 

1236 # this is broken. 

1237 return 

1238 query = pkt.FileName 

1239 fid = self.get_file_id(pkt) 

1240 # Check for handled FileInformationClass 

1241 # 0x02: FileFullDirectoryInformation 

1242 # 0x03: FileBothDirectoryInformation 

1243 # 0x25: FileIdBothDirectoryInformation 

1244 if pkt.FileInformationClass not in [0x02, 0x03, 0x25]: 

1245 # Unknown FileInformationClass 

1246 resp = self.smb_header.copy() / SMB2_Error_Response() 

1247 resp.Command = "SMB2_QUERY_DIRECTORY" 

1248 resp.Status = "STATUS_INVALID_INFO_CLASS" 

1249 self.send(resp) 

1250 return 

1251 # Handle SMB2_RESTART_SCANS 

1252 if pkt[SMB2_Query_Directory_Request].Flags.SMB2_RESTART_SCANS: 

1253 self.enumerate_index[fid] = 0 

1254 # Lookup the files 

1255 try: 

1256 files = self.lookup_folder( 

1257 fid, 

1258 query, 

1259 self.enumerate_index[fid], 

1260 { 

1261 0x02: FILE_FULL_DIR_INFORMATION, 

1262 0x03: FILE_BOTH_DIR_INFORMATION, 

1263 0x25: FILE_ID_BOTH_DIR_INFORMATION, 

1264 }[pkt.FileInformationClass], 

1265 ) 

1266 except NotADirectoryError: 

1267 resp = self.smb_header.copy() / SMB2_Error_Response() 

1268 resp.Command = "SMB2_QUERY_DIRECTORY" 

1269 resp.Status = "STATUS_INVALID_PARAMETER" 

1270 self.send(resp) 

1271 return 

1272 if not files: 

1273 # No more files ! 

1274 self.enumerate_index[fid] = 0 

1275 resp = self.smb_header.copy() / SMB2_Error_Response() 

1276 resp.Command = "SMB2_QUERY_DIRECTORY" 

1277 resp.Status = "STATUS_NO_MORE_FILES" 

1278 self.send(resp) 

1279 return 

1280 # Handle SMB2_RETURN_SINGLE_ENTRY 

1281 if pkt[SMB2_Query_Directory_Request].Flags.SMB2_RETURN_SINGLE_ENTRY: 

1282 files = files[:1] 

1283 # Increment index 

1284 self.enumerate_index[fid] += len(files) 

1285 # Build response based on the FileInformationClass 

1286 fileinfo = FileIdBothDirectoryInformation( 

1287 files=files, 

1288 ) 

1289 self.send( 

1290 self.smb_header.copy() 

1291 / SMB2_Query_Directory_Response(Buffer=[("Output", fileinfo)]) 

1292 ) 

1293 

1294 @ATMT.receive_condition(SERVING) 

1295 def receive_query_info(self, pkt): 

1296 if SMB2_Query_Info_Request in pkt: 

1297 raise self.SERVING().action_parameters(pkt) 

1298 

1299 @ATMT.action(receive_query_info) 

1300 def send_query_info_response(self, pkt): 

1301 self.update_smbheader(pkt) 

1302 # [MS-FSCC] + [MS-SMB2] sect 2.2.37 / 3.3.5.20.1 

1303 fid = self.get_file_id(pkt) 

1304 if pkt.InfoType == 0x01: # SMB2_0_INFO_FILE 

1305 if pkt.FileInfoClass == 0x05: # FileStandardInformation 

1306 attrs = self.current_handles[fid][1] 

1307 fileinfo = FileStandardInformation( 

1308 EndOfFile=attrs["EndOfFile"], 

1309 AllocationSize=attrs["AllocationSize"], 

1310 ) 

1311 elif pkt.FileInfoClass == 0x06: # FileInternalInformation 

1312 pth = self.current_handles[fid][0] 

1313 fileinfo = FileInternalInformation( 

1314 IndexNumber=hash(pth) & 0xFFFFFFFFFFFFFFFF, 

1315 ) 

1316 elif pkt.FileInfoClass == 0x07: # FileEaInformation 

1317 fileinfo = FileEaInformation() 

1318 elif pkt.FileInfoClass == 0x12: # FileAllInformation 

1319 attrs = self.current_handles[fid][1] 

1320 fileinfo = FileAllInformation( 

1321 BasicInformation=FileBasicInformation( 

1322 CreationTime=attrs["CreationTime"], 

1323 LastAccessTime=attrs["LastAccessTime"], 

1324 LastWriteTime=attrs["LastWriteTime"], 

1325 ChangeTime=attrs["ChangeTime"], 

1326 FileAttributes=attrs["FileAttributes"], 

1327 ), 

1328 StandardInformation=FileStandardInformation( 

1329 EndOfFile=attrs["EndOfFile"], 

1330 AllocationSize=attrs["AllocationSize"], 

1331 ), 

1332 ) 

1333 elif pkt.FileInfoClass == 0x15: # FileAlternateNameInformation 

1334 pth = self.current_handles[fid][0] 

1335 fileinfo = FileAlternateNameInformation( 

1336 FileName=pth.name, 

1337 ) 

1338 elif pkt.FileInfoClass == 0x16: # FileStreamInformation 

1339 attrs = self.current_handles[fid][1] 

1340 fileinfo = FileStreamInformation( 

1341 StreamSize=attrs["EndOfFile"], 

1342 StreamAllocationSize=attrs["AllocationSize"], 

1343 ) 

1344 elif pkt.FileInfoClass == 0x22: # FileNetworkOpenInformation 

1345 attrs = self.current_handles[fid][1] 

1346 fileinfo = FileNetworkOpenInformation( 

1347 **attrs, 

1348 ) 

1349 elif pkt.FileInfoClass == 0x30: # FileNormalizedNameInformation 

1350 pth = self.current_handles[fid][0] 

1351 fileinfo = FILE_NAME_INFORMATION( 

1352 FileName=pth.name, 

1353 ) 

1354 else: 

1355 log_runtime.warning( 

1356 "Unimplemented: %s" 

1357 % pkt[SMB2_Query_Info_Request].sprintf("%InfoType% %FileInfoClass%") 

1358 ) 

1359 return 

1360 elif pkt.InfoType == 0x02: # SMB2_0_INFO_FILESYSTEM 

1361 # [MS-FSCC] sect 2.5 

1362 if pkt.FileInfoClass == 0x01: # FileFsVolumeInformation 

1363 fileinfo = FileFsVolumeInformation() 

1364 elif pkt.FileInfoClass == 0x03: # FileFsSizeInformation 

1365 fileinfo = FileFsSizeInformation() 

1366 elif pkt.FileInfoClass == 0x05: # FileFsAttributeInformation 

1367 fileinfo = FileFsAttributeInformation( 

1368 FileSystemAttributes=0x88000F, 

1369 ) 

1370 elif pkt.FileInfoClass == 0x07: # FileEaInformation 

1371 fileinfo = FileEaInformation() 

1372 else: 

1373 log_runtime.warning( 

1374 "Unimplemented: %s" 

1375 % pkt[SMB2_Query_Info_Request].sprintf("%InfoType% %FileInfoClass%") 

1376 ) 

1377 return 

1378 elif pkt.InfoType == 0x03: # SMB2_0_INFO_SECURITY 

1379 # [MS-FSCC] 2.4.6 

1380 fileinfo = SECURITY_DESCRIPTOR() 

1381 # TODO: fill it 

1382 if pkt.AdditionalInformation.OWNER_SECURITY_INFORMATION: 

1383 pass 

1384 if pkt.AdditionalInformation.GROUP_SECURITY_INFORMATION: 

1385 pass 

1386 if pkt.AdditionalInformation.DACL_SECURITY_INFORMATION: 

1387 pass 

1388 if pkt.AdditionalInformation.SACL_SECURITY_INFORMATION: 

1389 pass 

1390 # Observed: 

1391 if ( 

1392 pkt.AdditionalInformation.OWNER_SECURITY_INFORMATION 

1393 or pkt.AdditionalInformation.SACL_SECURITY_INFORMATION 

1394 or pkt.AdditionalInformation.GROUP_SECURITY_INFORMATION 

1395 or pkt.AdditionalInformation.DACL_SECURITY_INFORMATION 

1396 ): 

1397 pkt = self.smb_header.copy() / SMB2_Error_Response(ErrorData=b"\xff") 

1398 pkt.Status = "STATUS_ACCESS_DENIED" 

1399 pkt.Command = "SMB2_QUERY_INFO" 

1400 self.send(pkt) 

1401 return 

1402 if pkt.AdditionalInformation.ATTRIBUTE_SECURITY_INFORMATION: 

1403 fileinfo.Control = 0x8800 

1404 self.send( 

1405 self.smb_header.copy() 

1406 / SMB2_Query_Info_Response(Buffer=[("Output", fileinfo)]) 

1407 ) 

1408 

1409 @ATMT.receive_condition(SERVING) 

1410 def receive_set_info_request(self, pkt): 

1411 if SMB2_Set_Info_Request in pkt: 

1412 raise self.SERVING().action_parameters(pkt) 

1413 

1414 @ATMT.action(receive_set_info_request) 

1415 def send_set_info_response(self, pkt): 

1416 self.update_smbheader(pkt) 

1417 self.send(self.smb_header.copy() / SMB2_Set_Info_Response()) 

1418 

1419 @ATMT.receive_condition(SERVING) 

1420 def receive_write_request(self, pkt): 

1421 if SMB2_Write_Request in pkt: 

1422 raise self.SERVING().action_parameters(pkt) 

1423 

1424 @ATMT.action(receive_write_request) 

1425 def send_write_response(self, pkt): 

1426 self.update_smbheader(pkt) 

1427 resp = SMB2_Write_Response(Count=len(pkt.Data)) 

1428 fid = self.get_file_id(pkt) 

1429 if self.current_tree() == "IPC$": 

1430 if fid in self.PIPES_TABLE.values(): 

1431 # A pipe 

1432 self.rpc_server.recv(pkt.Data) 

1433 else: 

1434 if self.readonly: 

1435 # Read only ! 

1436 resp = SMB2_Error_Response() 

1437 resp.Command = "SMB2_WRITE" 

1438 resp.Status = "ERROR_FILE_READ_ONLY" 

1439 else: 

1440 # Write file 

1441 pth, _ = self.current_handles[fid] 

1442 length = pkt[SMB2_Write_Request].DataLen 

1443 off = pkt[SMB2_Write_Request].Offset 

1444 self.vprint("Writing %s bytes at %s" % (length, off)) 

1445 with open(pth, "r+b") as fd: 

1446 fd.seek(off) 

1447 resp.Count = fd.write(pkt[SMB2_Write_Request].Data) 

1448 self.send(self.smb_header.copy() / resp) 

1449 

1450 @ATMT.receive_condition(SERVING) 

1451 def receive_read_request(self, pkt): 

1452 if SMB2_Read_Request in pkt: 

1453 raise self.SERVING().action_parameters(pkt) 

1454 

1455 @ATMT.action(receive_read_request) 

1456 def send_read_response(self, pkt): 

1457 self.update_smbheader(pkt) 

1458 resp = SMB2_Read_Response() 

1459 fid = self.get_file_id(pkt) 

1460 if self.current_tree() == "IPC$": 

1461 # Read output from DCE/RPC server 

1462 r = self.rpc_server.get_response() 

1463 resp.Data = bytes(r) 

1464 else: 

1465 # Read file and send content 

1466 pth, _ = self.current_handles[fid] 

1467 length = pkt[SMB2_Read_Request].Length 

1468 off = pkt[SMB2_Read_Request].Offset 

1469 self.vprint("Reading %s bytes at %s" % (length, off)) 

1470 with open(pth, "rb") as fd: 

1471 fd.seek(off) 

1472 resp.Data = fd.read(length) 

1473 self.send(self.smb_header.copy() / resp) 

1474 

1475 @ATMT.receive_condition(SERVING) 

1476 def receive_close_request(self, pkt): 

1477 if SMB2_Close_Request in pkt: 

1478 raise self.SERVING().action_parameters(pkt) 

1479 

1480 @ATMT.action(receive_close_request) 

1481 def send_close_response(self, pkt): 

1482 self.update_smbheader(pkt) 

1483 if self.current_tree() != "IPC$": 

1484 fid = self.get_file_id(pkt) 

1485 pth, attrs = self.current_handles[fid] 

1486 if pth: 

1487 self.vprint("Closed: " + str(pth)) 

1488 del self.current_handles[fid] 

1489 del self.enumerate_index[fid] 

1490 self.send( 

1491 self.smb_header.copy() 

1492 / SMB2_Close_Response( 

1493 Flags=pkt[SMB2_Close_Request].Flags, 

1494 **attrs, 

1495 ) 

1496 ) 

1497 else: 

1498 self.send(self.smb_header.copy() / SMB2_Close_Response()) 

1499 

1500 @ATMT.receive_condition(SERVING) 

1501 def receive_tree_disconnect_request(self, pkt): 

1502 if SMB2_Tree_Disconnect_Request in pkt: 

1503 raise self.SERVING().action_parameters(pkt) 

1504 

1505 @ATMT.action(receive_tree_disconnect_request) 

1506 def send_tree_disconnect_response(self, pkt): 

1507 self.update_smbheader(pkt) 

1508 try: 

1509 del self.current_trees[self.smb_header.TID] # clear tree 

1510 resp = self.smb_header.copy() / SMB2_Tree_Disconnect_Response() 

1511 except KeyError: 

1512 resp = self.smb_header.copy() / SMB2_Error_Response() 

1513 resp.Command = "SMB2_TREE_DISCONNECT" 

1514 resp.Status = "STATUS_NETWORK_NAME_DELETED" 

1515 self.send(resp) 

1516 

1517 @ATMT.receive_condition(SERVING) 

1518 def receive_cancel_request(self, pkt): 

1519 if SMB2_Cancel_Request in pkt: 

1520 raise self.SERVING().action_parameters(pkt) 

1521 

1522 @ATMT.action(receive_cancel_request) 

1523 def send_notify_cancel_response(self, pkt): 

1524 self.update_smbheader(pkt) 

1525 resp = self.smb_header.copy() / SMB2_Change_Notify_Response() 

1526 resp.Status = "STATUS_CANCELLED" 

1527 self.send(resp) 

1528 

1529 @ATMT.receive_condition(SERVING) 

1530 def receive_echo_request(self, pkt): 

1531 if SMB2_Echo_Request in pkt: 

1532 raise self.SERVING().action_parameters(pkt) 

1533 

1534 @ATMT.action(receive_echo_request) 

1535 def send_echo_reply(self, pkt): 

1536 self.update_smbheader(pkt) 

1537 self.send(self.smb_header.copy() / SMB2_Echo_Response()) 

1538 

1539 

1540# DCE/RPC server for SMB 

1541 

1542 

1543class SMB_DCERPC_Server(DCERPC_Server): 

1544 """ 

1545 DCE/RPC server than handles the minimum RPCs for SMB to work: 

1546 """ 

1547 

1548 def __init__(self, *args, **kwargs): 

1549 self.shares = kwargs.pop("shares") 

1550 super(SMB_DCERPC_Server, self).__init__(*args, **kwargs) 

1551 

1552 @DCERPC_Server.answer(NetrShareEnum_Request) 

1553 def netr_share_enum(self, req): 

1554 """ 

1555 NetrShareEnum [MS-SRVS] 

1556 "retrieves information about each shared resource on a server." 

1557 """ 

1558 nbEntries = len(self.shares) 

1559 return NetrShareEnum_Response( 

1560 InfoStruct=LPSHARE_ENUM_STRUCT( 

1561 Level=1, 

1562 ShareInfo=NDRUnion( 

1563 tag=1, 

1564 value=SHARE_INFO_1_CONTAINER( 

1565 Buffer=[ 

1566 # Add shares 

1567 LPSHARE_INFO_1( 

1568 shi1_netname=x.name, 

1569 shi1_type=x.type, 

1570 shi1_remark=x.remark, 

1571 ) 

1572 for x in self.shares 

1573 ], 

1574 EntriesRead=nbEntries, 

1575 ), 

1576 ), 

1577 ), 

1578 TotalEntries=nbEntries, 

1579 ndr64=self.ndr64, 

1580 ) 

1581 

1582 @DCERPC_Server.answer(NetrWkstaGetInfo_Request) 

1583 def netr_wksta_getinfo(self, req): 

1584 """ 

1585 NetrWkstaGetInfo [MS-SRVS] 

1586 "returns information about the configuration of a workstation." 

1587 """ 

1588 return NetrWkstaGetInfo_Response( 

1589 WkstaInfo=NDRUnion( 

1590 tag=100, 

1591 value=LPWKSTA_INFO_100( 

1592 wki100_platform_id=500, # NT 

1593 wki100_ver_major=5, 

1594 ), 

1595 ), 

1596 ndr64=self.ndr64, 

1597 ) 

1598 

1599 @DCERPC_Server.answer(NetrServerGetInfo_Request) 

1600 def netr_server_getinfo(self, req): 

1601 """ 

1602 NetrServerGetInfo [MS-WKST] 

1603 "retrieves current configuration information for CIFS and 

1604 SMB Version 1.0 servers." 

1605 """ 

1606 return NetrServerGetInfo_Response( 

1607 ServerInfo=NDRUnion( 

1608 tag=101, 

1609 value=LPSERVER_INFO_101( 

1610 sv101_platform_id=500, # NT 

1611 sv101_name=req.ServerName.value.value[0].value, 

1612 sv101_version_major=6, 

1613 sv101_version_minor=1, 

1614 sv101_type=1, # Workstation 

1615 ), 

1616 ), 

1617 ndr64=self.ndr64, 

1618 ) 

1619 

1620 @DCERPC_Server.answer(NetrShareGetInfo_Request) 

1621 def netr_share_getinfo(self, req): 

1622 """ 

1623 NetrShareGetInfo [MS-SRVS] 

1624 "retrieves information about a particular shared resource on a server." 

1625 """ 

1626 return NetrShareGetInfo_Response( 

1627 ShareInfo=NDRUnion( 

1628 tag=1, 

1629 value=LPSHARE_INFO_1( 

1630 shi1_netname=req.NetName.value[0].value, 

1631 shi1_type=0, 

1632 shi1_remark=b"", 

1633 ), 

1634 ), 

1635 ndr64=self.ndr64, 

1636 ) 

1637 

1638 

1639# Util 

1640 

1641 

1642class smbserver: 

1643 r""" 

1644 Spawns a simple smbserver 

1645 

1646 smbserver parameters: 

1647 

1648 :param shares: the list of shares to announce. Note that IPC$ is appended. 

1649 By default, a 'Scapy' share on './' 

1650 :param port: (optional) the port to bind on, default 445 

1651 :param iface: (optional) the interface to bind on, default conf.iface 

1652 :param readonly: (optional) whether the server is read-only or not. default True 

1653 :param ssp: (optional) the SSP to use. See the examples below. 

1654 Default NTLM with guest 

1655 

1656 Many more SMB-specific parameters are available in help(SMB_Server) 

1657 """ 

1658 

1659 def __init__( 

1660 self, 

1661 shares=None, 

1662 iface: str = None, 

1663 port: int = 445, 

1664 verb: int = 2, 

1665 readonly: bool = True, 

1666 # SMB arguments 

1667 ssp=None, 

1668 **kwargs, 

1669 ): 

1670 # Default Share 

1671 if shares is None: 

1672 shares = [ 

1673 SMBShare( 

1674 name="Scapy", path=".", remark="Scapy's SMB server default share" 

1675 ) 

1676 ] 

1677 # Verb 

1678 if verb >= 2: 

1679 log_runtime.info("-- Scapy %s SMB Server --" % conf.version) 

1680 log_runtime.info( 

1681 "SSP: %s. Read-Only: %s. Serving %s shares:" 

1682 % ( 

1683 conf.color_theme.yellow(ssp or "NTLM (guest)"), 

1684 ( 

1685 conf.color_theme.yellow("YES") 

1686 if readonly 

1687 else conf.color_theme.format("NO", "bg_red+white") 

1688 ), 

1689 conf.color_theme.red(len(shares)), 

1690 ) 

1691 ) 

1692 for share in shares: 

1693 log_runtime.info(" * %s" % share) 

1694 # Start SMB Server 

1695 self.srv = SMB_Server.spawn( 

1696 # TCP server 

1697 port=port, 

1698 iface=iface or conf.loopback_name, 

1699 verb=verb, 

1700 # SMB server 

1701 ssp=ssp, 

1702 shares=shares, 

1703 readonly=readonly, 

1704 # SMB arguments 

1705 **kwargs, 

1706 ) 

1707 

1708 def close(self): 

1709 """ 

1710 Close the smbserver if started in background mode (bg=True) 

1711 """ 

1712 if self.srv: 

1713 self.srv.shutdown(socket.SHUT_RDWR) 

1714 self.srv.close() 

1715 

1716 

1717if __name__ == "__main__": 

1718 from scapy.utils import AutoArgparse 

1719 

1720 AutoArgparse(smbserver)