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

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

758 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.consts import WINDOWS 

30from scapy.error import log_runtime, log_interactive 

31from scapy.volatile import RandUUID 

32 

33from scapy.layers.dcerpc import ( 

34 DCERPC_Transport, 

35 NDRUnion, 

36) 

37from scapy.layers.gssapi import ( 

38 GSS_S_COMPLETE, 

39 GSS_S_CONTINUE_NEEDED, 

40 GSS_S_CREDENTIALS_EXPIRED, 

41) 

42from scapy.layers.msrpce.rpcserver import DCERPC_Server 

43from scapy.layers.ntlm import ( 

44 NTLMSSP, 

45) 

46from scapy.layers.smb import ( 

47 SMBNegotiate_Request, 

48 SMBNegotiate_Response_Extended_Security, 

49 SMBNegotiate_Response_Security, 

50 SMBSession_Null, 

51 SMBSession_Setup_AndX_Request, 

52 SMBSession_Setup_AndX_Request_Extended_Security, 

53 SMBSession_Setup_AndX_Response, 

54 SMBSession_Setup_AndX_Response_Extended_Security, 

55 SMBTree_Connect_AndX, 

56 SMB_Header, 

57) 

58from scapy.layers.smb2 import ( 

59 DFS_REFERRAL_ENTRY1, 

60 DFS_REFERRAL_V3, 

61 DirectTCP, 

62 FILE_BOTH_DIR_INFORMATION, 

63 FILE_FULL_DIR_INFORMATION, 

64 FILE_ID_BOTH_DIR_INFORMATION, 

65 FILE_NAME_INFORMATION, 

66 FileAllInformation, 

67 FileAlternateNameInformation, 

68 FileBasicInformation, 

69 FileEaInformation, 

70 FileFsAttributeInformation, 

71 FileFsSizeInformation, 

72 FileFsVolumeInformation, 

73 FileIdBothDirectoryInformation, 

74 FileInternalInformation, 

75 FileNetworkOpenInformation, 

76 FileStandardInformation, 

77 FileStreamInformation, 

78 NETWORK_INTERFACE_INFO, 

79 SECURITY_DESCRIPTOR, 

80 SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2, 

81 SMB2_CREATE_QUERY_MAXIMAL_ACCESS_RESPONSE, 

82 SMB2_CREATE_QUERY_ON_DISK_ID, 

83 SMB2_Cancel_Request, 

84 SMB2_Change_Notify_Request, 

85 SMB2_Change_Notify_Response, 

86 SMB2_Close_Request, 

87 SMB2_Close_Response, 

88 SMB2_Create_Context, 

89 SMB2_Create_Request, 

90 SMB2_Create_Response, 

91 SMB2_ENCRYPTION_CIPHERS, 

92 SMB2_Echo_Request, 

93 SMB2_Echo_Response, 

94 SMB2_Encryption_Capabilities, 

95 SMB2_Error_Response, 

96 SMB2_FILEID, 

97 SMB2_Header, 

98 SMB2_IOCTL_Network_Interface_Info, 

99 SMB2_IOCTL_RESP_GET_DFS_Referral, 

100 SMB2_IOCTL_Request, 

101 SMB2_IOCTL_Response, 

102 SMB2_IOCTL_Validate_Negotiate_Info_Response, 

103 SMB2_Negotiate_Context, 

104 SMB2_Negotiate_Protocol_Request, 

105 SMB2_Negotiate_Protocol_Response, 

106 SMB2_Preauth_Integrity_Capabilities, 

107 SMB2_Query_Directory_Request, 

108 SMB2_Query_Directory_Response, 

109 SMB2_Query_Info_Request, 

110 SMB2_Query_Info_Response, 

111 SMB2_Read_Request, 

112 SMB2_Read_Response, 

113 SMB2_SIGNING_ALGORITHMS, 

114 SMB2_Session_Logoff_Request, 

115 SMB2_Session_Logoff_Response, 

116 SMB2_Session_Setup_Request, 

117 SMB2_Session_Setup_Response, 

118 SMB2_Set_Info_Request, 

119 SMB2_Set_Info_Response, 

120 SMB2_Signing_Capabilities, 

121 SMB2_Tree_Connect_Request, 

122 SMB2_Tree_Connect_Response, 

123 SMB2_Tree_Disconnect_Request, 

124 SMB2_Tree_Disconnect_Response, 

125 SMB2_Write_Request, 

126 SMB2_Write_Response, 

127 SMBStreamSocket, 

128 SOCKADDR_STORAGE, 

129 SRVSVC_SHARE_TYPES, 

130) 

131from scapy.layers.spnego import SPNEGOSSP 

132 

133# Import DCE/RPC 

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

135 LPSERVER_INFO_101, 

136 LPSHARE_ENUM_STRUCT, 

137 LPSHARE_INFO_1, 

138 NetrServerGetInfo_Request, 

139 NetrServerGetInfo_Response, 

140 NetrShareEnum_Request, 

141 NetrShareEnum_Response, 

142 NetrShareGetInfo_Request, 

143 NetrShareGetInfo_Response, 

144 SHARE_INFO_1_CONTAINER, 

145) 

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

147 LPWKSTA_INFO_100, 

148 NetrWkstaGetInfo_Request, 

149 NetrWkstaGetInfo_Response, 

150) 

151 

152 

153class SMBShare: 

154 """ 

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

156 

157 :param name: the share name 

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

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

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

161 :param encryptdata: (optional) whether encryption should be used for this 

162 share. This only applies to SMB 3.1.1. 

163 """ 

164 

165 def __init__(self, name, path=".", type=None, remark="", encryptdata=False): 

166 # Set the default type 

167 if type is None: 

168 type = 0 # DISKTREE 

169 if name.endswith("$"): 

170 type &= 0x80000000 # SPECIAL 

171 # Lower case the name for resolution 

172 self._name = name.lower() 

173 # Resolve path 

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

175 # props 

176 self.name = name 

177 self.type = type 

178 self.remark = remark 

179 self.encryptdata = encryptdata 

180 

181 def __repr__(self): 

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

183 if self.type & 0x80000000: 

184 type = "SPECIAL+" + type 

185 if self.type & 0x40000000: 

186 type = "TEMPORARY+" + type 

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

188 self.name, 

189 type, 

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

191 str(self.path), 

192 ) 

193 

194 

195# The SMB Automaton 

196 

197 

198class SMB_Server(Automaton): 

199 """ 

200 SMB server automaton 

201 

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

203 Note that IPC$ is appended. 

204 :param ssp: the SSP to use 

205 

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

207 

208 :param ANONYMOUS_LOGIN: mark the clients as anonymous 

209 :param GUEST_LOGIN: mark the clients as guest 

210 :param REQUIRE_SIGNATURE: set 'Require Signature' 

211 :param REQUIRE_ENCRYPTION: globally require encryption. 

212 You could also make it share-specific on 3.1.1. 

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

214 :param TREE_SHARE_FLAGS: flags to announce on Tree_Connect_Response 

215 :param TREE_CAPABILITIES: capabilities to announce on Tree_Connect_Response 

216 :param TREE_MAXIMAL_ACCESS: maximal access to announce on Tree_Connect_Response 

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

218 """ 

219 

220 pkt_cls = DirectTCP 

221 socketcls = SMBStreamSocket 

222 

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

224 self.verb = verb 

225 if "sock" not in kwargs: 

226 raise ValueError( 

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

228 ) 

229 # Various SMB server arguments 

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

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

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

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

234 self.REQUIRE_SIGNATURE = kwargs.pop("REQUIRE_SIGNATURE", None) 

235 self.REQUIRE_ENCRYPTION = kwargs.pop("REQUIRE_ENCRYPTION", False) 

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

237 self.TREE_SHARE_FLAGS = kwargs.pop( 

238 "TREE_SHARE_FLAGS", "FORCE_LEVELII_OPLOCK+RESTRICT_EXCLUSIVE_OPENS" 

239 ) 

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

241 self.TREE_MAXIMAL_ACCESS = kwargs.pop( 

242 "TREE_MAXIMAL_ACCESS", 

243 "+".join( 

244 [ 

245 "FILE_READ_DATA", 

246 "FILE_WRITE_DATA", 

247 "FILE_APPEND_DATA", 

248 "FILE_READ_EA", 

249 "FILE_WRITE_EA", 

250 "FILE_EXECUTE", 

251 "FILE_DELETE_CHILD", 

252 "FILE_READ_ATTRIBUTES", 

253 "FILE_WRITE_ATTRIBUTES", 

254 "DELETE", 

255 "READ_CONTROL", 

256 "WRITE_DAC", 

257 "WRITE_OWNER", 

258 "SYNCHRONIZE", 

259 ] 

260 ), 

261 ) 

262 self.FILE_MAXIMAL_ACCESS = kwargs.pop( 

263 # Read-only 

264 "FILE_MAXIMAL_ACCESS", 

265 "+".join( 

266 [ 

267 "FILE_READ_DATA", 

268 "FILE_READ_EA", 

269 "FILE_EXECUTE", 

270 "FILE_READ_ATTRIBUTES", 

271 "READ_CONTROL", 

272 "SYNCHRONIZE", 

273 ] 

274 ), 

275 ) 

276 self.LOCAL_IPS = kwargs.pop( 

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

278 ) 

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

280 if self.USE_SMB1: 

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

282 self.readonly = readonly 

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

284 self.shares = shares.copy() 

285 # Append the IPC$ share 

286 self.shares.append( 

287 SMBShare( 

288 name="IPC$", 

289 type=0x80000003, # SPECIAL+IPC 

290 remark="Remote IPC", 

291 ) 

292 ) 

293 # Initialize the DCE/RPC server for SMB 

294 self.rpc_server = SMB_DCERPC_Server( 

295 DCERPC_Transport.NCACN_NP, 

296 shares=self.shares, 

297 verb=self.verb, 

298 ) 

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

300 if "DCERPC_SERVER_CLS" in kwargs: 

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

302 # Internal Session information 

303 self.SMB2 = False 

304 self.NegotiateCapabilities = None 

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

306 self.NextForceSign = False 

307 self.NextForceEncrypt = False 

308 # Compounds are handled on receiving by the StreamSocket, 

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

310 self.NextCompound = False 

311 self.CompoundedHandle = None 

312 # SSP provider 

313 if ssp is None: 

314 # No SSP => fallback on NTLM with guest 

315 ssp = SPNEGOSSP( 

316 [ 

317 NTLMSSP( 

318 USE_MIC=False, 

319 DO_NOT_CHECK_LOGIN=True, 

320 ), 

321 ] 

322 ) 

323 if self.GUEST_LOGIN is None: 

324 self.GUEST_LOGIN = True 

325 # Initialize 

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

327 # Set session options 

328 self.session.ssp = ssp 

329 self.session.SigningRequired = ( 

330 self.REQUIRE_SIGNATURE if self.REQUIRE_SIGNATURE is not None else bool(ssp) 

331 ) 

332 

333 @property 

334 def session(self): 

335 # session shorthand 

336 return self.sock.session 

337 

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

339 """ 

340 Verbose print (if enabled) 

341 """ 

342 if self.verb: 

343 if conf.interactive: 

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

345 else: 

346 print("> %s" % s) 

347 

348 def send(self, pkt): 

349 ForceSign, ForceEncrypt = self.NextForceSign, self.NextForceEncrypt 

350 self.NextForceSign = self.NextForceEncrypt = False 

351 return super(SMB_Server, self).send( 

352 pkt, 

353 Compounded=self.NextCompound, 

354 ForceSign=ForceSign, 

355 ForceEncrypt=ForceEncrypt, 

356 ) 

357 

358 @ATMT.state(initial=1) 

359 def BEGIN(self): 

360 self.authenticated = False 

361 

362 @ATMT.receive_condition(BEGIN) 

363 def received_negotiate(self, pkt): 

364 if SMBNegotiate_Request in pkt: 

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

366 

367 @ATMT.receive_condition(BEGIN) 

368 def received_negotiate_smb2_begin(self, pkt): 

369 if SMB2_Negotiate_Protocol_Request in pkt: 

370 self.SMB2 = True 

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

372 

373 @ATMT.action(received_negotiate_smb2_begin) 

374 def on_negotiate_smb2_begin(self, pkt): 

375 self.on_negotiate(pkt) 

376 

377 @ATMT.action(received_negotiate) 

378 def on_negotiate(self, pkt): 

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

380 # Build negotiate response 

381 DialectIndex = None 

382 DialectRevision = None 

383 if SMB2_Negotiate_Protocol_Request in pkt: 

384 # SMB2 

385 DialectRevisions = pkt[SMB2_Negotiate_Protocol_Request].Dialects 

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

387 DialectRevisions.sort(reverse=True) 

388 if DialectRevisions: 

389 DialectRevision = DialectRevisions[0] 

390 else: 

391 # SMB1 

392 DialectIndexes = [ 

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

394 ] 

395 if self.USE_SMB1: 

396 # Enforce SMB1 

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

398 else: 

399 # Find a value matching SMB2, fallback to SMB1 

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

401 try: 

402 DialectIndex = DialectIndexes.index(key) 

403 DialectRevision = rev 

404 self.SMB2 = True 

405 break 

406 except ValueError: 

407 pass 

408 else: 

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

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

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

412 self.session.Dialect = DialectRevision 

413 cls = None 

414 if self.SMB2: 

415 # SMB2 

416 cls = SMB2_Negotiate_Protocol_Response 

417 self.smb_header = DirectTCP() / SMB2_Header( 

418 Flags="SMB2_FLAGS_SERVER_TO_REDIR", 

419 CreditRequest=1, 

420 CreditCharge=1, 

421 ) 

422 if SMB2_Negotiate_Protocol_Request in pkt: 

423 self.update_smbheader(pkt) 

424 else: 

425 # SMB1 

426 self.smb_header = DirectTCP() / SMB_Header( 

427 Flags="REPLY+CASE_INSENSITIVE+CANONICALIZED_PATHS", 

428 Flags2=( 

429 "LONG_NAMES+EAS+NT_STATUS+SMB_SECURITY_SIGNATURE+" 

430 "UNICODE+EXTENDED_SECURITY" 

431 ), 

432 TID=pkt.TID, 

433 MID=pkt.MID, 

434 UID=pkt.UID, 

435 PIDLow=pkt.PIDLow, 

436 ) 

437 if self.EXTENDED_SECURITY: 

438 cls = SMBNegotiate_Response_Extended_Security 

439 else: 

440 cls = SMBNegotiate_Response_Security 

441 if DialectRevision is None and DialectIndex is None: 

442 # No common dialect found. 

443 if self.SMB2: 

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

445 resp.Command = "SMB2_NEGOTIATE" 

446 else: 

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

448 resp.Command = "SMB_COM_NEGOTIATE" 

449 resp.Status = "STATUS_NOT_SUPPORTED" 

450 self.send(resp) 

451 return 

452 if self.SMB2: # SMB2 

453 # SecurityMode 

454 if SMB2_Header in pkt and pkt.SecurityMode.SIGNING_REQUIRED: 

455 self.session.SigningRequired = True 

456 # Capabilities: [MS-SMB2] 3.3.5.4 

457 self.NegotiateCapabilities = "+".join( 

458 [ 

459 "DFS", 

460 "LEASING", 

461 "LARGE_MTU", 

462 ] 

463 ) 

464 if DialectRevision >= 0x0300: 

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

466 # the server supports..." 

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

468 [ 

469 "MULTI_CHANNEL", 

470 "PERSISTENT_HANDLES", 

471 "DIRECTORY_LEASING", 

472 "ENCRYPTION", 

473 ] 

474 ) 

475 # Build response 

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

477 DialectRevision=DialectRevision, 

478 SecurityMode=( 

479 "SIGNING_ENABLED+SIGNING_REQUIRED" 

480 if self.session.SigningRequired 

481 else "SIGNING_ENABLED" 

482 ), 

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

484 ServerStartTime=0, 

485 MaxTransactionSize=65536, 

486 MaxReadSize=65536, 

487 MaxWriteSize=65536, 

488 Capabilities=self.NegotiateCapabilities, 

489 ) 

490 # SMB >= 3.0.0 

491 if DialectRevision >= 0x0300: 

492 # [MS-SMB2] sect 3.3.5.3.1 note 253 

493 resp.MaxTransactionSize = 0x800000 

494 resp.MaxReadSize = 0x800000 

495 resp.MaxWriteSize = 0x800000 

496 # SMB 3.1.1 

497 if DialectRevision >= 0x0311 and pkt.NegotiateContextsCount: 

498 # Negotiate context-capabilities 

499 for ngctx in pkt.NegotiateContexts: 

500 if ngctx.ContextType == 0x0002: 

501 # SMB2_ENCRYPTION_CAPABILITIES 

502 for ciph in ngctx.Ciphers: 

503 tciph = SMB2_ENCRYPTION_CIPHERS.get(ciph, None) 

504 if tciph in self.session.SupportedCipherIds: 

505 # Common ! 

506 self.session.CipherId = tciph 

507 self.session.SupportsEncryption = True 

508 break 

509 elif ngctx.ContextType == 0x0008: 

510 # SMB2_SIGNING_CAPABILITIES 

511 for signalg in ngctx.SigningAlgorithms: 

512 tsignalg = SMB2_SIGNING_ALGORITHMS.get(signalg, None) 

513 if tsignalg in self.session.SupportedSigningAlgorithmIds: 

514 # Common ! 

515 self.session.SigningAlgorithmId = tsignalg 

516 break 

517 # Send back the negotiated algorithms 

518 resp.NegotiateContexts = [ 

519 # Preauth capabilities 

520 SMB2_Negotiate_Context() 

521 / SMB2_Preauth_Integrity_Capabilities( 

522 # SHA-512 by default 

523 HashAlgorithms=[self.session.PreauthIntegrityHashId], 

524 Salt=self.session.Salt, 

525 ), 

526 # Encryption capabilities 

527 SMB2_Negotiate_Context() 

528 / SMB2_Encryption_Capabilities( 

529 # AES-128-CCM by default 

530 Ciphers=[self.session.CipherId], 

531 ), 

532 # Signing capabilities 

533 SMB2_Negotiate_Context() 

534 / SMB2_Signing_Capabilities( 

535 # AES-128-CCM by default 

536 SigningAlgorithms=[self.session.SigningAlgorithmId], 

537 ), 

538 ] 

539 else: 

540 # SMB1 

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

542 DialectIndex=DialectIndex, 

543 ServerCapabilities=( 

544 "UNICODE+LARGE_FILES+NT_SMBS+RPC_REMOTE_APIS+STATUS32+" 

545 "LEVEL_II_OPLOCKS+LOCK_AND_READ+NT_FIND+" 

546 "LWIO+INFOLEVEL_PASSTHRU+LARGE_READX+LARGE_WRITEX" 

547 ), 

548 SecurityMode=( 

549 "SIGNING_ENABLED+SIGNING_REQUIRED" 

550 if self.session.SigningRequired 

551 else "SIGNING_ENABLED" 

552 ), 

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

554 ServerTimeZone=0x3C, 

555 ) 

556 if self.EXTENDED_SECURITY: 

557 resp.ServerCapabilities += "EXTENDED_SECURITY" 

558 if self.EXTENDED_SECURITY or self.SMB2: 

559 # Extended SMB1 / SMB2 

560 resp.GUID = self.GUID 

561 # Add security blob 

562 resp.SecurityBlob = spnego_token 

563 else: 

564 # Non-extended SMB1 

565 # FIXME never tested. 

566 resp.SecurityBlob = spnego_token 

567 resp.Flags2 -= "EXTENDED_SECURITY" 

568 if not self.SMB2: 

569 resp[SMB_Header].Flags2 = ( 

570 resp[SMB_Header].Flags2 

571 - "SMB_SECURITY_SIGNATURE" 

572 + "SMB_SECURITY_SIGNATURE_REQUIRED+IS_LONG_NAME" 

573 ) 

574 if SMB2_Header in pkt: 

575 # If required, compute sessions 

576 self.session.computeSMBConnectionPreauth( 

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

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

579 ) 

580 self.send(resp) 

581 

582 @ATMT.state(final=1) 

583 def NEGO_FAILED(self): 

584 self.vprint("SMB Negotiate failed: encryption was not negotiated.") 

585 self.end() 

586 

587 @ATMT.state() 

588 def NEGOTIATED(self): 

589 pass 

590 

591 def update_smbheader(self, pkt): 

592 """ 

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

594 """ 

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

596 self.smb_header.CreditRequest = pkt.CreditRequest 

597 # [MS-SMB2] sect 3.3.4.1 

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

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

600 self.smb_header.CreditCharge = pkt.CreditCharge 

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

602 self.NextCompound = bool(pkt.NextCommand) 

603 # [MS-SMB2] sect 3.3.4.1.1 - "If the request was signed by the client..." 

604 # If the packet was signed, note we must answer with a signed packet. 

605 if ( 

606 not self.session.SigningRequired 

607 and pkt.SecuritySignature != b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" 

608 ): 

609 self.NextForceSign = True 

610 # [MS-SMB2] sect 3.3.4.1.4 - "If the message being sent is any response to a 

611 # client request for which Request.IsEncrypted is TRUE" 

612 if pkt[SMB2_Header]._decrypted: 

613 self.NextForceEncrypt = True 

614 # [MS-SMB2] sect 3.3.5.2.7.2 

615 # Add SMB2_FLAGS_RELATED_OPERATIONS to the response if present 

616 if pkt.Flags.SMB2_FLAGS_RELATED_OPERATIONS: 

617 self.smb_header.Flags += "SMB2_FLAGS_RELATED_OPERATIONS" 

618 else: 

619 self.smb_header.Flags -= "SMB2_FLAGS_RELATED_OPERATIONS" 

620 # [MS-SMB2] sect 2.2.1.2 - Priority 

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

622 self.smb_header.Flags &= 0xFF8F 

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

624 # Update IDs 

625 self.smb_header.SessionId = pkt.SessionId 

626 self.smb_header.TID = pkt.TID 

627 self.smb_header.MID = pkt.MID 

628 self.smb_header.PID = pkt.PID 

629 

630 @ATMT.receive_condition(NEGOTIATED) 

631 def received_negotiate_smb2(self, pkt): 

632 if SMB2_Negotiate_Protocol_Request in pkt: 

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

634 

635 @ATMT.action(received_negotiate_smb2) 

636 def on_negotiate_smb2(self, pkt): 

637 self.on_negotiate(pkt) 

638 

639 @ATMT.receive_condition(NEGOTIATED) 

640 def receive_setup_andx_request(self, pkt): 

641 if ( 

642 SMBSession_Setup_AndX_Request_Extended_Security in pkt 

643 or SMBSession_Setup_AndX_Request in pkt 

644 ): 

645 # SMB1 

646 if SMBSession_Setup_AndX_Request_Extended_Security in pkt: 

647 # Extended 

648 ssp_blob = pkt.SecurityBlob 

649 else: 

650 # Non-extended 

651 ssp_blob = pkt[SMBSession_Setup_AndX_Request].UnicodePassword 

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

653 elif SMB2_Session_Setup_Request in pkt: 

654 # SMB2 

655 ssp_blob = pkt.SecurityBlob 

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

657 

658 @ATMT.state() 

659 def RECEIVED_SETUP_ANDX_REQUEST(self): 

660 pass 

661 

662 @ATMT.action(receive_setup_andx_request) 

663 def on_setup_andx_request(self, pkt, ssp_blob): 

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

665 self.session.sspcontext, 

666 ssp_blob, 

667 ) 

668 self.update_smbheader(pkt) 

669 if SMB2_Session_Setup_Request in pkt: 

670 # SMB2 

671 self.smb_header.SessionId = 0x0001000000000015 

672 if status not in [GSS_S_CONTINUE_NEEDED, GSS_S_COMPLETE]: 

673 # Error 

674 if SMB2_Session_Setup_Request in pkt: 

675 # SMB2 

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

677 # Set security blob (if any) 

678 resp.SecurityBlob = tok 

679 else: 

680 # SMB1 

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

682 # Map some GSS return codes to NTStatus 

683 if status == GSS_S_CREDENTIALS_EXPIRED: 

684 resp.Status = "STATUS_PASSWORD_EXPIRED" 

685 else: 

686 resp.Status = "STATUS_LOGON_FAILURE" 

687 # Reset Session preauth (SMB 3.1.1) 

688 self.session.SessionPreauthIntegrityHashValue = None 

689 else: 

690 # Negotiation 

691 if ( 

692 SMBSession_Setup_AndX_Request_Extended_Security in pkt 

693 or SMB2_Session_Setup_Request in pkt 

694 ): 

695 # SMB1 extended / SMB2 

696 if SMB2_Session_Setup_Request in pkt: 

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

698 if self.GUEST_LOGIN: 

699 # "If the security subsystem indicates that the session 

700 # was established by a guest user, Session.SigningRequired 

701 # MUST be set to FALSE and Session.IsGuest MUST be set to TRUE." 

702 resp.SessionFlags = "IS_GUEST" 

703 self.session.IsGuest = True 

704 self.session.SigningRequired = False 

705 if self.ANONYMOUS_LOGIN: 

706 resp.SessionFlags = "IS_NULL" 

707 # [MS-SMB2] sect 3.3.5.5.3 

708 if self.session.Dialect >= 0x0300 and self.REQUIRE_ENCRYPTION: 

709 resp.SessionFlags += "ENCRYPT_DATA" 

710 else: 

711 # SMB1 extended 

712 resp = ( 

713 self.smb_header.copy() 

714 / SMBSession_Setup_AndX_Response_Extended_Security( 

715 NativeOS="Windows 4.0", 

716 NativeLanMan="Windows 4.0", 

717 ) 

718 ) 

719 if self.GUEST_LOGIN: 

720 resp.Action = "SMB_SETUP_GUEST" 

721 # Set security blob 

722 resp.SecurityBlob = tok 

723 elif SMBSession_Setup_AndX_Request in pkt: 

724 # Non-extended 

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

726 NativeOS="Windows 4.0", 

727 NativeLanMan="Windows 4.0", 

728 ) 

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

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

731 if status == GSS_S_CONTINUE_NEEDED: 

732 # the setup session response is used in hash 

733 self.session.computeSMBSessionPreauth( 

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

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

736 ) 

737 else: 

738 # the setup session response is not used in hash 

739 self.session.computeSMBSessionPreauth( 

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

741 ) 

742 if status == GSS_S_COMPLETE: 

743 # Authentication was successful 

744 self.session.computeSMBSessionKeys(IsClient=False) 

745 self.authenticated = True 

746 # [MS-SMB2] Note: "Windows-based servers always sign the final session setup 

747 # response when the user is neither anonymous nor guest." 

748 # If not available, it will still be ignored. 

749 self.NextForceSign = True 

750 self.send(resp) 

751 # Check whether we must enable encryption from now on 

752 if ( 

753 self.authenticated 

754 and not self.session.IsGuest 

755 and self.session.Dialect >= 0x0300 

756 and self.REQUIRE_ENCRYPTION 

757 ): 

758 # [MS-SMB2] sect 3.3.5.5.3: from now on, turn encryption on ! 

759 self.session.EncryptData = True 

760 self.session.SigningRequired = False 

761 

762 @ATMT.condition(RECEIVED_SETUP_ANDX_REQUEST) 

763 def wait_for_next_request(self): 

764 if self.authenticated: 

765 self.vprint( 

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

767 ) 

768 raise self.AUTHENTICATED() 

769 else: 

770 raise self.NEGOTIATED() 

771 

772 @ATMT.state() 

773 def AUTHENTICATED(self): 

774 """Dev: overload this""" 

775 pass 

776 

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

778 

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

780 def should_serve(self): 

781 # Serve files 

782 self.current_trees = {} 

783 self.current_handles = {} 

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

785 self.tree_id = 0 

786 self.base_time_t = self.current_smb_time() 

787 raise self.SERVING() 

788 

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

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

791 pkt.Status = Status 

792 pkt.Command = "SMB2_IOCTL" 

793 self.send(pkt) 

794 

795 @ATMT.state(final=1) 

796 def END(self): 

797 self.end() 

798 

799 # SERVE FILES 

800 

801 def current_tree(self): 

802 """ 

803 Return the current tree name 

804 """ 

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

806 

807 def root_path(self): 

808 """ 

809 Return the root path of the current tree 

810 """ 

811 curtree = self.current_tree() 

812 try: 

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

814 except StopIteration: 

815 return None 

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

817 

818 @ATMT.state() 

819 def SERVING(self): 

820 """ 

821 Main state when serving files 

822 """ 

823 pass 

824 

825 @ATMT.receive_condition(SERVING) 

826 def receive_logoff_request(self, pkt): 

827 if SMB2_Session_Logoff_Request in pkt: 

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

829 

830 @ATMT.action(receive_logoff_request) 

831 def send_logoff_response(self, pkt): 

832 self.update_smbheader(pkt) 

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

834 

835 @ATMT.receive_condition(SERVING) 

836 def receive_setup_andx_request_in_serving(self, pkt): 

837 self.receive_setup_andx_request(pkt) 

838 

839 @ATMT.receive_condition(SERVING) 

840 def is_smb1_tree(self, pkt): 

841 if SMBTree_Connect_AndX in pkt: 

842 # Unsupported 

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

844 raise self.END() 

845 

846 @ATMT.receive_condition(SERVING) 

847 def receive_tree_connect(self, pkt): 

848 if SMB2_Tree_Connect_Request in pkt: 

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

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

851 

852 @ATMT.action(receive_tree_connect) 

853 def send_tree_connect_response(self, pkt, tree_name): 

854 self.update_smbheader(pkt) 

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

856 try: 

857 share = next(x for x in self.shares if x._name == tree_name.lower()) 

858 except StopIteration: 

859 # Unknown tree 

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

861 resp.Command = "SMB2_TREE_CONNECT" 

862 resp.Status = "STATUS_BAD_NETWORK_NAME" 

863 self.send(resp) 

864 return 

865 # Add tree to current trees 

866 if tree_name not in self.current_trees: 

867 self.tree_id += 1 

868 self.smb_header.TID = self.tree_id 

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

870 

871 # Construct ShareFlags 

872 ShareFlags = ( 

873 "AUTO_CACHING+NO_CACHING" 

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

875 else self.TREE_SHARE_FLAGS 

876 ) 

877 # [MS-SMB2] sect 3.3.5.7 

878 if ( 

879 self.session.Dialect >= 0x0311 

880 and not self.session.EncryptData 

881 and share.encryptdata 

882 ): 

883 if not self.session.SupportsEncryption: 

884 raise Exception("Peer asked for encryption but doesn't support it !") 

885 ShareFlags += "+ENCRYPT_DATA" 

886 

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

888 self.send( 

889 self.smb_header.copy() 

890 / SMB2_Tree_Connect_Response( 

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

892 ShareFlags=ShareFlags, 

893 Capabilities=( 

894 0 if self.current_tree() == "IPC$" else self.TREE_CAPABILITIES 

895 ), 

896 MaximalAccess=self.TREE_MAXIMAL_ACCESS, 

897 ) 

898 ) 

899 

900 @ATMT.receive_condition(SERVING) 

901 def receive_ioctl(self, pkt): 

902 if SMB2_IOCTL_Request in pkt: 

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

904 

905 @ATMT.action(receive_ioctl) 

906 def send_ioctl_response(self, pkt): 

907 self.update_smbheader(pkt) 

908 if pkt.CtlCode == 0x11C017: 

909 # FSCTL_PIPE_TRANSCEIVE 

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

911 self.send( 

912 self.smb_header.copy() 

913 / SMB2_IOCTL_Response( 

914 CtlCode=0x11C017, 

915 FileId=pkt[SMB2_IOCTL_Request].FileId, 

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

917 ) 

918 ) 

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

920 # FSCTL_VALIDATE_NEGOTIATE_INFO 

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

922 # what flags were negotiated during the SMBNegotiate exchange. 

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

924 

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

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

927 # > STATUS_NOT_SUPPORTED or STATUS_INVALID_DEVICE_REQUEST 

928 # > since they do not allow or implement 

929 # > FSCTL_VALIDATE_NEGOTIATE_INFO. 

930 # > The client should accept the 

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

932 

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

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

935 self._ioctl_error(Status="STATUS_FILE_CLOSED") 

936 return 

937 

938 # SMB3 

939 self.send( 

940 self.smb_header.copy() 

941 / SMB2_IOCTL_Response( 

942 CtlCode=0x00140204, 

943 FileId=pkt[SMB2_IOCTL_Request].FileId, 

944 Buffer=[ 

945 ( 

946 "Output", 

947 SMB2_IOCTL_Validate_Negotiate_Info_Response( 

948 GUID=self.GUID, 

949 DialectRevision=self.session.Dialect, 

950 SecurityMode=( 

951 "SIGNING_ENABLED+SIGNING_REQUIRED" 

952 if self.session.SigningRequired 

953 else "SIGNING_ENABLED" 

954 ), 

955 Capabilities=self.NegotiateCapabilities, 

956 ), 

957 ) 

958 ], 

959 ) 

960 ) 

961 elif pkt.CtlCode == 0x001401FC: 

962 # FSCTL_QUERY_NETWORK_INTERFACE_INFO 

963 self.send( 

964 self.smb_header.copy() 

965 / SMB2_IOCTL_Response( 

966 CtlCode=0x001401FC, 

967 FileId=pkt[SMB2_IOCTL_Request].FileId, 

968 Output=SMB2_IOCTL_Network_Interface_Info( 

969 interfaces=[ 

970 NETWORK_INTERFACE_INFO( 

971 SockAddr_Storage=SOCKADDR_STORAGE( 

972 Family=0x0002, 

973 IPv4Adddress=x, 

974 ) 

975 ) 

976 for x in self.LOCAL_IPS 

977 ] 

978 ), 

979 ) 

980 ) 

981 elif pkt.CtlCode == 0x00060194: 

982 # FSCTL_DFS_GET_REFERRALS 

983 if ( 

984 self.DOMAIN_REFERRALS 

985 and not pkt[SMB2_IOCTL_Request].Input.RequestFileName 

986 ): 

987 # Requesting domain referrals 

988 self.send( 

989 self.smb_header.copy() 

990 / SMB2_IOCTL_Response( 

991 CtlCode=0x00060194, 

992 FileId=pkt[SMB2_IOCTL_Request].FileId, 

993 Output=SMB2_IOCTL_RESP_GET_DFS_Referral( 

994 ReferralEntries=[ 

995 DFS_REFERRAL_V3( 

996 ReferralEntryFlags="NameListReferral", 

997 TimeToLive=600, 

998 ) 

999 for _ in self.DOMAIN_REFERRALS 

1000 ], 

1001 ReferralBuffer=[ 

1002 DFS_REFERRAL_ENTRY1(SpecialName=name) 

1003 for name in self.DOMAIN_REFERRALS 

1004 ], 

1005 ), 

1006 ) 

1007 ) 

1008 return 

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

1010 resp.Command = "SMB2_IOCTL" 

1011 resp.Status = "STATUS_FS_DRIVER_REQUIRED" 

1012 self.send(resp) 

1013 else: 

1014 # Among other things, FSCTL_VALIDATE_NEGOTIATE_INFO 

1015 self._ioctl_error(Status="STATUS_NOT_SUPPORTED") 

1016 

1017 @ATMT.receive_condition(SERVING) 

1018 def receive_create_file(self, pkt): 

1019 if SMB2_Create_Request in pkt: 

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

1021 

1022 PIPES_TABLE = { 

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

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

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

1026 } 

1027 

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

1029 # that points to the chained opened file handle 

1030 LAST_HANDLE = SMB2_FILEID( 

1031 Persistent=0xFFFFFFFFFFFFFFFF, Volatile=0xFFFFFFFFFFFFFFFF 

1032 ) 

1033 

1034 def current_smb_time(self): 

1035 return ( 

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

1037 - 864000000000 # one day ago 

1038 ) 

1039 

1040 def make_file_id(self, fname): 

1041 """ 

1042 Generate deterministic FileId based on the fname 

1043 """ 

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

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

1046 

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

1048 """ 

1049 Lookup the file and build it's SMB2_FILEID 

1050 """ 

1051 root = self.root_path() 

1052 if isinstance(fname, pathlib.Path): 

1053 path = fname 

1054 fname = path.name 

1055 else: 

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

1057 path = path.resolve() 

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

1059 # resolve(). Be careful 

1060 # Note: symbolic links are currently unsupported. 

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

1062 raise FileNotFoundError 

1063 if WINDOWS and path.is_reserved(): 

1064 raise FileNotFoundError 

1065 if not path.exists(): 

1066 if create and createOptions: 

1067 if createOptions.FILE_DIRECTORY_FILE: 

1068 # Folder creation 

1069 path.mkdir() 

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

1071 else: 

1072 # File creation 

1073 path.touch() 

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

1075 else: 

1076 raise FileNotFoundError 

1077 if durable_handle is None: 

1078 handle = SMB2_FILEID( 

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

1080 ) 

1081 else: 

1082 # We were given a durable handle. Use it 

1083 handle = durable_handle 

1084 attrs = { 

1085 "CreationTime": self.base_time_t, 

1086 "LastAccessTime": self.base_time_t, 

1087 "LastWriteTime": self.base_time_t, 

1088 "ChangeTime": self.base_time_t, 

1089 "EndOfFile": 0, 

1090 "AllocationSize": 0, 

1091 } 

1092 path_stat = path.stat() 

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

1094 if fname is None: 

1095 # special case 

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

1097 [ 

1098 "FILE_ATTRIBUTE_HIDDEN", 

1099 "FILE_ATTRIBUTE_SYSTEM", 

1100 "FILE_ATTRIBUTE_DIRECTORY", 

1101 ] 

1102 ) 

1103 elif path.is_dir(): 

1104 attrs["FileAttributes"] = "FILE_ATTRIBUTE_DIRECTORY" 

1105 else: 

1106 attrs["FileAttributes"] = "FILE_ATTRIBUTE_ARCHIVE" 

1107 self.current_handles[handle] = ( 

1108 path, # file path 

1109 attrs, # file attributes 

1110 ) 

1111 self.enumerate_index[handle] = 0 

1112 return handle 

1113 

1114 def set_compounded_handle(self, handle): 

1115 """ 

1116 Mark a handle as the current one being compounded. 

1117 """ 

1118 self.CompoundedHandle = handle 

1119 

1120 def get_file_id(self, pkt): 

1121 """ 

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

1123 """ 

1124 fid = pkt.FileId 

1125 if fid == self.LAST_HANDLE: 

1126 return self.CompoundedHandle 

1127 return fid 

1128 

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

1130 """ 

1131 Lookup a folder handle 

1132 """ 

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

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

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

1136 if not path.is_dir(): 

1137 raise NotADirectoryError 

1138 return sorted( 

1139 [ 

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

1141 for x in path.glob(filter) 

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

1143 # for path traversal on them. 

1144 if not x.is_symlink() 

1145 ] 

1146 + [ 

1147 cls( 

1148 FileAttributes=("FILE_ATTRIBUTE_DIRECTORY"), 

1149 FileName=".", 

1150 ) 

1151 ] 

1152 + ( 

1153 [ 

1154 cls( 

1155 FileAttributes=("FILE_ATTRIBUTE_DIRECTORY"), 

1156 FileName="..", 

1157 ) 

1158 ] 

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

1160 else [] 

1161 ), 

1162 key=lambda x: x.FileName, 

1163 )[offset:] 

1164 

1165 @ATMT.action(receive_create_file) 

1166 def send_create_file_response(self, pkt): 

1167 """ 

1168 Handle CreateFile request 

1169 

1170 See [MS-SMB2] 3.3.5.9 () 

1171 """ 

1172 self.update_smbheader(pkt) 

1173 if pkt[SMB2_Create_Request].NameLen: 

1174 fname = pkt[SMB2_Create_Request].Name 

1175 else: 

1176 fname = None 

1177 if fname: 

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

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

1180 # Special IPC$ case: opening a pipe 

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

1182 if FILE_ID: 

1183 attrs = { 

1184 "CreationTime": 0, 

1185 "LastAccessTime": 0, 

1186 "LastWriteTime": 0, 

1187 "ChangeTime": 0, 

1188 "EndOfFile": 0, 

1189 "AllocationSize": 4096, 

1190 } 

1191 self.current_handles[FILE_ID] = ( 

1192 fname, 

1193 attrs, 

1194 ) 

1195 self.send( 

1196 self.smb_header.copy() 

1197 / SMB2_Create_Response( 

1198 OplockLevel=pkt.RequestedOplockLevel, 

1199 FileId=FILE_ID, 

1200 **attrs, 

1201 ) 

1202 ) 

1203 else: 

1204 # NOT_FOUND 

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

1206 resp.Command = "SMB2_CREATE" 

1207 resp.Status = "STATUS_OBJECT_NAME_NOT_FOUND" 

1208 self.send(resp) 

1209 return 

1210 else: 

1211 # Check if there is a Durable Handle Reconnect Request 

1212 durable_handle = None 

1213 if pkt[SMB2_Create_Request].CreateContextsLen: 

1214 try: 

1215 durable_handle = next( 

1216 x.Data.FileId 

1217 for x in pkt[SMB2_Create_Request].CreateContexts 

1218 if x.Name == b"DH2C" 

1219 ) 

1220 except StopIteration: 

1221 pass 

1222 # Lookup file handle 

1223 try: 

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

1225 except FileNotFoundError: 

1226 # NOT_FOUND 

1227 if pkt[SMB2_Create_Request].CreateDisposition in [ 

1228 0x00000002, # FILE_CREATE 

1229 0x00000005, # FILE_OVERWRITE_IF 

1230 ]: 

1231 if self.readonly: 

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

1233 resp.Command = "SMB2_CREATE" 

1234 resp.Status = "STATUS_ACCESS_DENIED" 

1235 self.send(resp) 

1236 return 

1237 else: 

1238 # Create file 

1239 handle = self.lookup_file( 

1240 fname, 

1241 durable_handle=durable_handle, 

1242 create=True, 

1243 createOptions=pkt[SMB2_Create_Request].CreateOptions, 

1244 ) 

1245 else: 

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

1247 resp.Command = "SMB2_CREATE" 

1248 resp.Status = "STATUS_OBJECT_NAME_NOT_FOUND" 

1249 self.send(resp) 

1250 return 

1251 # Store compounded handle 

1252 self.set_compounded_handle(handle) 

1253 # Build response 

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

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

1256 OplockLevel=pkt.RequestedOplockLevel, 

1257 FileId=handle, 

1258 **attrs, 

1259 ) 

1260 # Handle the various chain elements 

1261 if pkt[SMB2_Create_Request].CreateContextsLen: 

1262 CreateContexts = [] 

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

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

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

1266 for elt in pkt[SMB2_Create_Request].CreateContexts: 

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

1268 # [MS-SMB2] sect 3.3.5.9.9 

1269 CreateContexts.append( 

1270 SMB2_Create_Context( 

1271 Name=b"QFid", 

1272 Data=SMB2_CREATE_QUERY_ON_DISK_ID( 

1273 DiskFileId=self.make_file_id(fname), 

1274 VolumeId=0xBA39CD11, 

1275 ), 

1276 ) 

1277 ) 

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

1279 # [MS-SMB2] sect 3.3.5.9.5 

1280 CreateContexts.append( 

1281 SMB2_Create_Context( 

1282 Name=b"MxAc", 

1283 Data=SMB2_CREATE_QUERY_MAXIMAL_ACCESS_RESPONSE( 

1284 QueryStatus=0, 

1285 MaximalAccess=self.FILE_MAXIMAL_ACCESS, 

1286 ), 

1287 ) 

1288 ) 

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

1290 # [MS-SMB2] sect 3.3.5.9.10 

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

1292 continue 

1293 CreateContexts.append( 

1294 SMB2_Create_Context( 

1295 Name=b"DH2Q", 

1296 Data=SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2( 

1297 Timeout=180000 

1298 ), 

1299 ) 

1300 ) 

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

1302 # [MS-SMB2] sect 3.3.5.9.11 

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

1304 CreateContexts.append( 

1305 SMB2_Create_Context( 

1306 Name=b"RqLs", 

1307 Data=elt.Data, 

1308 ) 

1309 ) 

1310 resp.CreateContexts = CreateContexts 

1311 self.send(resp) 

1312 

1313 @ATMT.receive_condition(SERVING) 

1314 def receive_change_notify_info(self, pkt): 

1315 if SMB2_Change_Notify_Request in pkt: 

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

1317 

1318 @ATMT.action(receive_change_notify_info) 

1319 def send_change_notify_info_response(self, pkt): 

1320 # [MS-SMB2] sect 3.3.5.19 

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

1322 # server MUST fail this request with STATUS_NOT_SUPPORTED." 

1323 self.update_smbheader(pkt) 

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

1325 resp.Command = "SMB2_CHANGE_NOTIFY" 

1326 # ScapyFS doesn't support notifications 

1327 resp.Status = "STATUS_NOT_SUPPORTED" 

1328 self.send(resp) 

1329 

1330 @ATMT.receive_condition(SERVING) 

1331 def receive_query_directory_info(self, pkt): 

1332 if SMB2_Query_Directory_Request in pkt: 

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

1334 

1335 @ATMT.action(receive_query_directory_info) 

1336 def send_query_directory_response(self, pkt): 

1337 self.update_smbheader(pkt) 

1338 if not pkt.FileNameLen: 

1339 # this is broken. 

1340 return 

1341 query = pkt.FileName 

1342 fid = self.get_file_id(pkt) 

1343 # Check for handled FileInformationClass 

1344 # 0x02: FileFullDirectoryInformation 

1345 # 0x03: FileBothDirectoryInformation 

1346 # 0x25: FileIdBothDirectoryInformation 

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

1348 # Unknown FileInformationClass 

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

1350 resp.Command = "SMB2_QUERY_DIRECTORY" 

1351 resp.Status = "STATUS_INVALID_INFO_CLASS" 

1352 self.send(resp) 

1353 return 

1354 # Handle SMB2_RESTART_SCANS 

1355 if pkt[SMB2_Query_Directory_Request].Flags.SMB2_RESTART_SCANS: 

1356 self.enumerate_index[fid] = 0 

1357 # Lookup the files 

1358 try: 

1359 files = self.lookup_folder( 

1360 fid, 

1361 query, 

1362 self.enumerate_index[fid], 

1363 { 

1364 0x02: FILE_FULL_DIR_INFORMATION, 

1365 0x03: FILE_BOTH_DIR_INFORMATION, 

1366 0x25: FILE_ID_BOTH_DIR_INFORMATION, 

1367 }[pkt.FileInformationClass], 

1368 ) 

1369 except NotADirectoryError: 

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

1371 resp.Command = "SMB2_QUERY_DIRECTORY" 

1372 resp.Status = "STATUS_INVALID_PARAMETER" 

1373 self.send(resp) 

1374 return 

1375 if not files: 

1376 # No more files ! 

1377 self.enumerate_index[fid] = 0 

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

1379 resp.Command = "SMB2_QUERY_DIRECTORY" 

1380 resp.Status = "STATUS_NO_MORE_FILES" 

1381 self.send(resp) 

1382 return 

1383 # Handle SMB2_RETURN_SINGLE_ENTRY 

1384 if pkt[SMB2_Query_Directory_Request].Flags.SMB2_RETURN_SINGLE_ENTRY: 

1385 files = files[:1] 

1386 # Increment index 

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

1388 # Build response based on the FileInformationClass 

1389 fileinfo = FileIdBothDirectoryInformation( 

1390 files=files, 

1391 ) 

1392 self.send( 

1393 self.smb_header.copy() 

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

1395 ) 

1396 

1397 @ATMT.receive_condition(SERVING) 

1398 def receive_query_info(self, pkt): 

1399 if SMB2_Query_Info_Request in pkt: 

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

1401 

1402 @ATMT.action(receive_query_info) 

1403 def send_query_info_response(self, pkt): 

1404 self.update_smbheader(pkt) 

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

1406 fid = self.get_file_id(pkt) 

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

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

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

1410 fileinfo = FileStandardInformation( 

1411 EndOfFile=attrs["EndOfFile"], 

1412 AllocationSize=attrs["AllocationSize"], 

1413 ) 

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

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

1416 fileinfo = FileInternalInformation( 

1417 IndexNumber=hash(pth) & 0xFFFFFFFFFFFFFFFF, 

1418 ) 

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

1420 fileinfo = FileEaInformation() 

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

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

1423 fileinfo = FileAllInformation( 

1424 BasicInformation=FileBasicInformation( 

1425 CreationTime=attrs["CreationTime"], 

1426 LastAccessTime=attrs["LastAccessTime"], 

1427 LastWriteTime=attrs["LastWriteTime"], 

1428 ChangeTime=attrs["ChangeTime"], 

1429 FileAttributes=attrs["FileAttributes"], 

1430 ), 

1431 StandardInformation=FileStandardInformation( 

1432 EndOfFile=attrs["EndOfFile"], 

1433 AllocationSize=attrs["AllocationSize"], 

1434 ), 

1435 ) 

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

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

1438 fileinfo = FileAlternateNameInformation( 

1439 FileName=pth.name, 

1440 ) 

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

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

1443 fileinfo = FileStreamInformation( 

1444 StreamSize=attrs["EndOfFile"], 

1445 StreamAllocationSize=attrs["AllocationSize"], 

1446 ) 

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

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

1449 fileinfo = FileNetworkOpenInformation( 

1450 **attrs, 

1451 ) 

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

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

1454 fileinfo = FILE_NAME_INFORMATION( 

1455 FileName=pth.name, 

1456 ) 

1457 else: 

1458 log_runtime.warning( 

1459 "Unimplemented: %s" 

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

1461 ) 

1462 return 

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

1464 # [MS-FSCC] sect 2.5 

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

1466 fileinfo = FileFsVolumeInformation() 

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

1468 fileinfo = FileFsSizeInformation() 

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

1470 fileinfo = FileFsAttributeInformation( 

1471 FileSystemAttributes=0x88000F, 

1472 ) 

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

1474 fileinfo = FileEaInformation() 

1475 else: 

1476 log_runtime.warning( 

1477 "Unimplemented: %s" 

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

1479 ) 

1480 return 

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

1482 # [MS-FSCC] 2.4.6 

1483 fileinfo = SECURITY_DESCRIPTOR() 

1484 # TODO: fill it 

1485 if pkt.AdditionalInformation.OWNER_SECURITY_INFORMATION: 

1486 pass 

1487 if pkt.AdditionalInformation.GROUP_SECURITY_INFORMATION: 

1488 pass 

1489 if pkt.AdditionalInformation.DACL_SECURITY_INFORMATION: 

1490 pass 

1491 if pkt.AdditionalInformation.SACL_SECURITY_INFORMATION: 

1492 pass 

1493 # Observed: 

1494 if ( 

1495 pkt.AdditionalInformation.OWNER_SECURITY_INFORMATION 

1496 or pkt.AdditionalInformation.SACL_SECURITY_INFORMATION 

1497 or pkt.AdditionalInformation.GROUP_SECURITY_INFORMATION 

1498 or pkt.AdditionalInformation.DACL_SECURITY_INFORMATION 

1499 ): 

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

1501 pkt.Status = "STATUS_ACCESS_DENIED" 

1502 pkt.Command = "SMB2_QUERY_INFO" 

1503 self.send(pkt) 

1504 return 

1505 if pkt.AdditionalInformation.ATTRIBUTE_SECURITY_INFORMATION: 

1506 fileinfo.Control = 0x8800 

1507 self.send( 

1508 self.smb_header.copy() 

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

1510 ) 

1511 

1512 @ATMT.receive_condition(SERVING) 

1513 def receive_set_info_request(self, pkt): 

1514 if SMB2_Set_Info_Request in pkt: 

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

1516 

1517 @ATMT.action(receive_set_info_request) 

1518 def send_set_info_response(self, pkt): 

1519 self.update_smbheader(pkt) 

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

1521 

1522 @ATMT.receive_condition(SERVING) 

1523 def receive_write_request(self, pkt): 

1524 if SMB2_Write_Request in pkt: 

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

1526 

1527 @ATMT.action(receive_write_request) 

1528 def send_write_response(self, pkt): 

1529 self.update_smbheader(pkt) 

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

1531 fid = self.get_file_id(pkt) 

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

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

1534 # A pipe 

1535 self.rpc_server.recv(pkt.Data) 

1536 else: 

1537 if self.readonly: 

1538 # Read only ! 

1539 resp = SMB2_Error_Response() 

1540 resp.Command = "SMB2_WRITE" 

1541 resp.Status = "ERROR_FILE_READ_ONLY" 

1542 else: 

1543 # Write file 

1544 pth, _ = self.current_handles[fid] 

1545 length = pkt[SMB2_Write_Request].DataLen 

1546 off = pkt[SMB2_Write_Request].Offset 

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

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

1549 fd.seek(off) 

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

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

1552 

1553 @ATMT.receive_condition(SERVING) 

1554 def receive_read_request(self, pkt): 

1555 if SMB2_Read_Request in pkt: 

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

1557 

1558 @ATMT.action(receive_read_request) 

1559 def send_read_response(self, pkt): 

1560 self.update_smbheader(pkt) 

1561 resp = SMB2_Read_Response() 

1562 fid = self.get_file_id(pkt) 

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

1564 # Read output from DCE/RPC server 

1565 r = self.rpc_server.get_response() 

1566 resp.Data = bytes(r) 

1567 else: 

1568 # Read file and send content 

1569 pth, _ = self.current_handles[fid] 

1570 length = pkt[SMB2_Read_Request].Length 

1571 off = pkt[SMB2_Read_Request].Offset 

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

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

1574 fd.seek(off) 

1575 resp.Data = fd.read(length) 

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

1577 

1578 @ATMT.receive_condition(SERVING) 

1579 def receive_close_request(self, pkt): 

1580 if SMB2_Close_Request in pkt: 

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

1582 

1583 @ATMT.action(receive_close_request) 

1584 def send_close_response(self, pkt): 

1585 self.update_smbheader(pkt) 

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

1587 fid = self.get_file_id(pkt) 

1588 pth, attrs = self.current_handles[fid] 

1589 if pth: 

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

1591 del self.current_handles[fid] 

1592 del self.enumerate_index[fid] 

1593 self.send( 

1594 self.smb_header.copy() 

1595 / SMB2_Close_Response( 

1596 Flags=pkt[SMB2_Close_Request].Flags, 

1597 **attrs, 

1598 ) 

1599 ) 

1600 else: 

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

1602 

1603 @ATMT.receive_condition(SERVING) 

1604 def receive_tree_disconnect_request(self, pkt): 

1605 if SMB2_Tree_Disconnect_Request in pkt: 

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

1607 

1608 @ATMT.action(receive_tree_disconnect_request) 

1609 def send_tree_disconnect_response(self, pkt): 

1610 self.update_smbheader(pkt) 

1611 try: 

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

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

1614 except KeyError: 

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

1616 resp.Command = "SMB2_TREE_DISCONNECT" 

1617 resp.Status = "STATUS_NETWORK_NAME_DELETED" 

1618 self.send(resp) 

1619 

1620 @ATMT.receive_condition(SERVING) 

1621 def receive_cancel_request(self, pkt): 

1622 if SMB2_Cancel_Request in pkt: 

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

1624 

1625 @ATMT.action(receive_cancel_request) 

1626 def send_notify_cancel_response(self, pkt): 

1627 self.update_smbheader(pkt) 

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

1629 resp.Status = "STATUS_CANCELLED" 

1630 self.send(resp) 

1631 

1632 @ATMT.receive_condition(SERVING) 

1633 def receive_echo_request(self, pkt): 

1634 if SMB2_Echo_Request in pkt: 

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

1636 

1637 @ATMT.action(receive_echo_request) 

1638 def send_echo_reply(self, pkt): 

1639 self.update_smbheader(pkt) 

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

1641 

1642 

1643# DCE/RPC server for SMB 

1644 

1645 

1646class SMB_DCERPC_Server(DCERPC_Server): 

1647 """ 

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

1649 """ 

1650 

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

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

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

1654 

1655 @DCERPC_Server.answer(NetrShareEnum_Request) 

1656 def netr_share_enum(self, req): 

1657 """ 

1658 NetrShareEnum [MS-SRVS] 

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

1660 """ 

1661 nbEntries = len(self.shares) 

1662 return NetrShareEnum_Response( 

1663 InfoStruct=LPSHARE_ENUM_STRUCT( 

1664 Level=1, 

1665 ShareInfo=NDRUnion( 

1666 tag=1, 

1667 value=SHARE_INFO_1_CONTAINER( 

1668 Buffer=[ 

1669 # Add shares 

1670 LPSHARE_INFO_1( 

1671 shi1_netname=x.name, 

1672 shi1_type=x.type, 

1673 shi1_remark=x.remark, 

1674 ) 

1675 for x in self.shares 

1676 ], 

1677 EntriesRead=nbEntries, 

1678 ), 

1679 ), 

1680 ), 

1681 TotalEntries=nbEntries, 

1682 ndr64=self.ndr64, 

1683 ) 

1684 

1685 @DCERPC_Server.answer(NetrWkstaGetInfo_Request) 

1686 def netr_wksta_getinfo(self, req): 

1687 """ 

1688 NetrWkstaGetInfo [MS-SRVS] 

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

1690 """ 

1691 return NetrWkstaGetInfo_Response( 

1692 WkstaInfo=NDRUnion( 

1693 tag=100, 

1694 value=LPWKSTA_INFO_100( 

1695 wki100_platform_id=500, # NT 

1696 wki100_ver_major=5, 

1697 ), 

1698 ), 

1699 ndr64=self.ndr64, 

1700 ) 

1701 

1702 @DCERPC_Server.answer(NetrServerGetInfo_Request) 

1703 def netr_server_getinfo(self, req): 

1704 """ 

1705 NetrServerGetInfo [MS-WKST] 

1706 "retrieves current configuration information for CIFS and 

1707 SMB Version 1.0 servers." 

1708 """ 

1709 return NetrServerGetInfo_Response( 

1710 ServerInfo=NDRUnion( 

1711 tag=101, 

1712 value=LPSERVER_INFO_101( 

1713 sv101_platform_id=500, # NT 

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

1715 sv101_version_major=6, 

1716 sv101_version_minor=1, 

1717 sv101_type=1, # Workstation 

1718 ), 

1719 ), 

1720 ndr64=self.ndr64, 

1721 ) 

1722 

1723 @DCERPC_Server.answer(NetrShareGetInfo_Request) 

1724 def netr_share_getinfo(self, req): 

1725 """ 

1726 NetrShareGetInfo [MS-SRVS] 

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

1728 """ 

1729 return NetrShareGetInfo_Response( 

1730 ShareInfo=NDRUnion( 

1731 tag=1, 

1732 value=LPSHARE_INFO_1( 

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

1734 shi1_type=0, 

1735 shi1_remark=b"", 

1736 ), 

1737 ), 

1738 ndr64=self.ndr64, 

1739 ) 

1740 

1741 

1742# Util 

1743 

1744 

1745class smbserver: 

1746 r""" 

1747 Spawns a simple smbserver 

1748 

1749 smbserver parameters: 

1750 

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

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

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

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

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

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

1757 Default NTLM with guest 

1758 

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

1760 """ 

1761 

1762 def __init__( 

1763 self, 

1764 shares=None, 

1765 iface: str = None, 

1766 port: int = 445, 

1767 verb: int = 2, 

1768 readonly: bool = True, 

1769 # SMB arguments 

1770 ssp=None, 

1771 **kwargs, 

1772 ): 

1773 # Default Share 

1774 if shares is None: 

1775 shares = [ 

1776 SMBShare( 

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

1778 ) 

1779 ] 

1780 # Verb 

1781 if verb >= 2: 

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

1783 log_runtime.info( 

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

1785 % ( 

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

1787 ( 

1788 conf.color_theme.yellow("YES") 

1789 if readonly 

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

1791 ), 

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

1793 ) 

1794 ) 

1795 for share in shares: 

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

1797 # Start SMB Server 

1798 self.srv = SMB_Server.spawn( 

1799 # TCP server 

1800 port=port, 

1801 iface=iface or conf.loopback_name, 

1802 verb=verb, 

1803 # SMB server 

1804 ssp=ssp, 

1805 shares=shares, 

1806 readonly=readonly, 

1807 # SMB arguments 

1808 **kwargs, 

1809 ) 

1810 

1811 def close(self): 

1812 """ 

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

1814 """ 

1815 if self.srv: 

1816 try: 

1817 self.srv.shutdown(socket.SHUT_RDWR) 

1818 except OSError: 

1819 pass 

1820 self.srv.close() 

1821 

1822 

1823if __name__ == "__main__": 

1824 from scapy.utils import AutoArgparse 

1825 

1826 AutoArgparse(smbserver)