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

776 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 NDRPointer, 

37) 

38from scapy.layers.gssapi import ( 

39 GSS_S_COMPLETE, 

40 GSS_S_CONTINUE_NEEDED, 

41 GSS_S_CREDENTIALS_EXPIRED, 

42) 

43from scapy.layers.msrpce.rpcserver import DCERPC_Server 

44from scapy.layers.ntlm import ( 

45 NTLMSSP, 

46) 

47from scapy.layers.smb import ( 

48 SMBNegotiate_Request, 

49 SMBSession_Null, 

50 SMBSession_Setup_AndX_Request, 

51 SMBSession_Setup_AndX_Request_Extended_Security, 

52 SMB_Header, 

53) 

54from scapy.layers.windows.security import SECURITY_DESCRIPTOR 

55from scapy.layers.smb2 import ( 

56 DFS_REFERRAL_ENTRY1, 

57 DFS_REFERRAL_V3, 

58 DirectTCP, 

59 FILE_BOTH_DIR_INFORMATION, 

60 FILE_FULL_DIR_INFORMATION, 

61 FILE_ID_BOTH_DIR_INFORMATION, 

62 FILE_NAME_INFORMATION, 

63 FileAllInformation, 

64 FileAlternateNameInformation, 

65 FileBasicInformation, 

66 FileEaInformation, 

67 FileFsAttributeInformation, 

68 FileFsSizeInformation, 

69 FileFsVolumeInformation, 

70 FileIdBothDirectoryInformation, 

71 FileInternalInformation, 

72 FileNetworkOpenInformation, 

73 FileStandardInformation, 

74 FileStreamInformation, 

75 NETWORK_INTERFACE_INFO, 

76 SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2, 

77 SMB2_CREATE_QUERY_MAXIMAL_ACCESS_RESPONSE, 

78 SMB2_CREATE_QUERY_ON_DISK_ID, 

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_Request, 

86 SMB2_Create_Response, 

87 SMB2_ENCRYPTION_CIPHERS, 

88 SMB2_Echo_Request, 

89 SMB2_Echo_Response, 

90 SMB2_Encryption_Capabilities, 

91 SMB2_Error_Response, 

92 SMB2_FILEID, 

93 SMB2_Header, 

94 SMB2_IOCTL_Network_Interface_Info, 

95 SMB2_IOCTL_RESP_GET_DFS_Referral, 

96 SMB2_IOCTL_Request, 

97 SMB2_IOCTL_Response, 

98 SMB2_IOCTL_Validate_Negotiate_Info_Response, 

99 SMB2_Negotiate_Context, 

100 SMB2_Negotiate_Protocol_Request, 

101 SMB2_Negotiate_Protocol_Response, 

102 SMB2_Preauth_Integrity_Capabilities, 

103 SMB2_Query_Directory_Request, 

104 SMB2_Query_Directory_Response, 

105 SMB2_Query_Info_Request, 

106 SMB2_Query_Info_Response, 

107 SMB2_Read_Request, 

108 SMB2_Read_Response, 

109 SMB2_SIGNING_ALGORITHMS, 

110 SMB2_Session_Logoff_Request, 

111 SMB2_Session_Logoff_Response, 

112 SMB2_Session_Setup_Request, 

113 SMB2_Session_Setup_Response, 

114 SMB2_Set_Info_Request, 

115 SMB2_Set_Info_Response, 

116 SMB2_Signing_Capabilities, 

117 SMB2_Tree_Connect_Request, 

118 SMB2_Tree_Connect_Response, 

119 SMB2_Tree_Disconnect_Request, 

120 SMB2_Tree_Disconnect_Response, 

121 SMB2_Write_Request, 

122 SMB2_Write_Response, 

123 SMBStreamSocket, 

124 SOCKADDR_STORAGE, 

125 SRVSVC_SHARE_TYPES, 

126) 

127from scapy.layers.spnego import SPNEGOSSP 

128 

129# Import DCE/RPC 

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

131 LPSERVER_INFO_101, 

132 LPSHARE_ENUM_STRUCT, 

133 LPSHARE_INFO_1, 

134 NetrServerGetInfo_Request, 

135 NetrServerGetInfo_Response, 

136 NetrShareEnum_Request, 

137 NetrShareEnum_Response, 

138 NetrShareGetInfo_Request, 

139 NetrShareGetInfo_Response, 

140 SHARE_INFO_1_CONTAINER, 

141) 

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

143 LPWKSTA_INFO_100, 

144 NetrWkstaGetInfo_Request, 

145 NetrWkstaGetInfo_Response, 

146) 

147 

148 

149class SMBShare: 

150 """ 

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

152 

153 :param name: the share name 

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

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

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

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

158 share. This only applies to SMB 3.1.1. 

159 """ 

160 

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

162 # Set the default type 

163 if type is None: 

164 type = 0 # DISKTREE 

165 if name.endswith("$"): 

166 type &= 0x80000000 # SPECIAL 

167 # Lower case the name for resolution 

168 self._name = name.lower() 

169 # Resolve path 

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

171 # props 

172 self.name = name 

173 self.type = type 

174 self.remark = remark 

175 self.encryptdata = encryptdata 

176 

177 def __repr__(self): 

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

179 if self.type & 0x80000000: 

180 type = "SPECIAL+" + type 

181 if self.type & 0x40000000: 

182 type = "TEMPORARY+" + type 

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

184 self.name, 

185 type, 

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

187 str(self.path), 

188 ) 

189 

190 

191# The SMB Automaton 

192 

193 

194class SMB_Server(Automaton): 

195 """ 

196 SMB server automaton 

197 

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

199 Note that IPC$ is appended. 

200 :param ssp: the SSP to use 

201 

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

203 

204 :param ANONYMOUS_LOGIN: mark the clients as anonymous 

205 :param GUEST_LOGIN: mark the clients as guest 

206 :param REQUIRE_SIGNATURE: set 'Require Signature' 

207 :param REQUIRE_ENCRYPTION: globally require encryption. 

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

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

210 :param TREE_SHARE_FLAGS: flags to announce on Tree_Connect_Response 

211 :param TREE_CAPABILITIES: capabilities to announce on Tree_Connect_Response 

212 :param TREE_MAXIMAL_ACCESS: maximal access to announce on Tree_Connect_Response 

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

214 """ 

215 

216 pkt_cls = DirectTCP 

217 socketcls = SMBStreamSocket 

218 

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

220 self.verb = verb 

221 if "sock" not in kwargs: 

222 raise ValueError( 

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

224 ) 

225 # Various SMB server arguments 

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

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

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

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

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

231 self.TREE_SHARE_FLAGS = kwargs.pop( 

232 "TREE_SHARE_FLAGS", "FORCE_LEVELII_OPLOCK+RESTRICT_EXCLUSIVE_OPENS" 

233 ) 

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

235 self.TREE_MAXIMAL_ACCESS = kwargs.pop( 

236 "TREE_MAXIMAL_ACCESS", 

237 "+".join( 

238 [ 

239 "FILE_READ_DATA", 

240 "FILE_WRITE_DATA", 

241 "FILE_APPEND_DATA", 

242 "FILE_READ_EA", 

243 "FILE_WRITE_EA", 

244 "FILE_EXECUTE", 

245 "FILE_DELETE_CHILD", 

246 "FILE_READ_ATTRIBUTES", 

247 "FILE_WRITE_ATTRIBUTES", 

248 "DELETE", 

249 "READ_CONTROL", 

250 "WRITE_DAC", 

251 "WRITE_OWNER", 

252 "SYNCHRONIZE", 

253 ] 

254 ), 

255 ) 

256 self.FILE_MAXIMAL_ACCESS = kwargs.pop( 

257 # Read-only 

258 "FILE_MAXIMAL_ACCESS", 

259 "+".join( 

260 [ 

261 "FILE_READ_DATA", 

262 "FILE_READ_EA", 

263 "FILE_EXECUTE", 

264 "FILE_READ_ATTRIBUTES", 

265 "READ_CONTROL", 

266 "SYNCHRONIZE", 

267 ] 

268 ), 

269 ) 

270 self.LOCAL_IPS = kwargs.pop( 

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

272 ) 

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

274 self.readonly = readonly 

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

276 self.shares = shares.copy() 

277 # Append the IPC$ share 

278 self.shares.append( 

279 SMBShare( 

280 name="IPC$", 

281 type=0x80000003, # SPECIAL+IPC 

282 remark="Remote IPC", 

283 ) 

284 ) 

285 # Initialize the DCE/RPC server for SMB 

286 self.rpc_server = SMB_DCERPC_Server( 

287 DCERPC_Transport.NCACN_NP, 

288 shares=self.shares, 

289 verb=self.verb, 

290 ) 

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

292 if "DCERPC_SERVER_CLS" in kwargs: 

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

294 # Internal Session information 

295 self.NegotiateCapabilities = None 

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

297 self.NextForceSign = False 

298 self.NextForceEncrypt = False 

299 

300 # Credits/Sequence handling 

301 self.MaxCreditCount = 2048 # [MS-SMB2] note: windows max default 

302 self.ClientCreditCount = 1 

303 self.SequenceWindow = (0, 0) 

304 self.SequenceNumbers = set([0]) # keep track of allowed MIDs 

305 

306 # Compounds are handled on receiving by the StreamSocket, 

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

308 self.NextCompound = False 

309 self.CompoundedHandle = None 

310 # SSP provider 

311 if ssp is None: 

312 # No SSP => fallback on NTLM with guest 

313 ssp = SPNEGOSSP( 

314 [ 

315 NTLMSSP( 

316 USE_MIC=False, 

317 DO_NOT_CHECK_LOGIN=True, 

318 ), 

319 ] 

320 ) 

321 if self.GUEST_LOGIN is None: 

322 self.GUEST_LOGIN = True 

323 # Initialize 

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

325 # Set session options 

326 self.session.ssp = ssp 

327 self.session.SigningRequired = ( 

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

329 ) 

330 

331 @property 

332 def session(self): 

333 # session shorthand 

334 return self.sock.session 

335 

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

337 """ 

338 Verbose print (if enabled) 

339 """ 

340 if self.verb: 

341 if conf.interactive: 

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

343 else: 

344 print("> %s" % s) 

345 

346 def send(self, pkt): 

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

348 self.NextForceSign = self.NextForceEncrypt = False 

349 return super(SMB_Server, self).send( 

350 pkt, 

351 Compounded=self.NextCompound, 

352 ForceSign=ForceSign, 

353 ForceEncrypt=ForceEncrypt, 

354 ) 

355 

356 @ATMT.state(initial=1) 

357 def BEGIN(self): 

358 self.authenticated = False 

359 

360 @ATMT.receive_condition(BEGIN) 

361 def received_negotiate_smb1(self, pkt): 

362 if SMBNegotiate_Request in pkt: 

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

364 

365 @ATMT.receive_condition(BEGIN) 

366 def received_negotiate_smb2_begin(self, pkt): 

367 if SMB2_Negotiate_Protocol_Request in pkt: 

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

369 

370 @ATMT.action(received_negotiate_smb2_begin) 

371 def on_negotiate_smb2_begin(self, pkt): 

372 self.on_negotiate(pkt) 

373 

374 @ATMT.action(received_negotiate_smb1) 

375 def on_negotiate(self, pkt): 

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

377 

378 # Build SMB2 header 

379 self.smb_header = DirectTCP() / SMB2_Header( 

380 Flags="SMB2_FLAGS_SERVER_TO_REDIR", 

381 CreditRequest=1, 

382 CreditCharge=1, 

383 ) 

384 

385 if SMBNegotiate_Request in pkt: 

386 # SMB1. We don't support anything else than a Multi-Protocol Negotiate. 

387 # [MS-SMB2] 3.2.4.2.2.1 Multi-Protocol Negotiate 

388 

389 # Find a dialect matching SMB2, or error. 

390 DialectIndexes = [ 

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

392 ] 

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

394 if key in DialectIndexes: 

395 DialectRevision = rev 

396 break 

397 else: 

398 # Error: client only supports SMB1. We must answer in SMB1. 

399 resp = DirectTCP() / SMB_Header( 

400 Command="SMB_COM_NEGOTIATE", 

401 Status="STATUS_NOT_SUPPORTED", 

402 Flags="REPLY+CASE_INSENSITIVE+CANONICALIZED_PATHS", 

403 Flags2=( 

404 "LONG_NAMES+EAS+NT_STATUS+SMB_SECURITY_SIGNATURE+" 

405 "UNICODE+IS_LONG_NAME+EXTENDED_SECURITY" 

406 ), 

407 TID=pkt.TID, 

408 MID=pkt.MID, 

409 UID=pkt.UID, 

410 PIDLow=pkt.PIDLow, 

411 ) 

412 resp /= SMBSession_Null() 

413 self.send(resp) 

414 return 

415 

416 # Multi-Protocol Negotiate: allocate credit 1 

417 self.SequenceWindow = (1, 1) 

418 self.SequenceNumbers = set([1]) 

419 

420 elif SMB2_Negotiate_Protocol_Request in pkt: 

421 # SMB2 

422 self.update_smbheader(pkt) 

423 

424 # Find common revisions 

425 DialectRevisions = pkt[SMB2_Negotiate_Protocol_Request].Dialects 

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

427 DialectRevisions.sort(reverse=True) 

428 if DialectRevisions: 

429 DialectRevision = DialectRevisions[0] 

430 if DialectRevision & 0xFF != 0xFF: 

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

432 self.session.Dialect = DialectRevision 

433 

434 # SecurityMode 

435 if pkt.SecurityMode.SIGNING_REQUIRED: 

436 self.session.SigningRequired = True 

437 

438 if DialectRevision is None: 

439 # No common dialect found. 

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

441 resp.Command = "SMB2_NEGOTIATE" 

442 resp.Status = "STATUS_NOT_SUPPORTED" 

443 self.send(resp) 

444 return 

445 

446 # Capabilities: [MS-SMB2] 3.3.5.4 

447 self.NegotiateCapabilities = "+".join( 

448 [ 

449 "DFS", 

450 "LEASING", 

451 "LARGE_MTU", 

452 ] 

453 ) 

454 if DialectRevision >= 0x0300: 

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

456 # the server supports..." 

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

458 [ 

459 "MULTI_CHANNEL", 

460 "PERSISTENT_HANDLES", 

461 "DIRECTORY_LEASING", 

462 "ENCRYPTION", 

463 ] 

464 ) 

465 

466 # Build response 

467 resp = self.smb_header.copy() / SMB2_Negotiate_Protocol_Response( 

468 DialectRevision=DialectRevision, 

469 SecurityMode=( 

470 "SIGNING_ENABLED+SIGNING_REQUIRED" 

471 if self.session.SigningRequired 

472 else "SIGNING_ENABLED" 

473 ), 

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

475 ServerStartTime=0, 

476 MaxTransactionSize=65536, 

477 MaxReadSize=65536, 

478 MaxWriteSize=65536, 

479 Capabilities=self.NegotiateCapabilities, 

480 GUID=self.GUID, 

481 ) 

482 # SMB >= 3.0.0 

483 if DialectRevision >= 0x0300: 

484 # [MS-SMB2] sect 3.3.5.3.1 note 253 

485 resp.MaxTransactionSize = 0x800000 

486 resp.MaxReadSize = 0x800000 

487 resp.MaxWriteSize = 0x800000 

488 # SMB 3.1.1 

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

490 # Negotiate context-capabilities 

491 for ngctx in pkt.NegotiateContexts: 

492 if ngctx.ContextType == 0x0002: 

493 # SMB2_ENCRYPTION_CAPABILITIES 

494 for ciph in ngctx.Ciphers: 

495 tciph = SMB2_ENCRYPTION_CIPHERS.get(ciph, None) 

496 if tciph in self.session.SupportedCipherIds: 

497 # Common ! 

498 self.session.CipherId = tciph 

499 self.session.SupportsEncryption = True 

500 break 

501 elif ngctx.ContextType == 0x0008: 

502 # SMB2_SIGNING_CAPABILITIES 

503 for signalg in ngctx.SigningAlgorithms: 

504 tsignalg = SMB2_SIGNING_ALGORITHMS.get(signalg, None) 

505 if tsignalg in self.session.SupportedSigningAlgorithmIds: 

506 # Common ! 

507 self.session.SigningAlgorithmId = tsignalg 

508 break 

509 # Send back the negotiated algorithms 

510 resp.NegotiateContexts = [ 

511 # Preauth capabilities 

512 SMB2_Negotiate_Context() 

513 / SMB2_Preauth_Integrity_Capabilities( 

514 # SHA-512 by default 

515 HashAlgorithms=[self.session.PreauthIntegrityHashId], 

516 Salt=self.session.Salt, 

517 ), 

518 # Encryption capabilities 

519 SMB2_Negotiate_Context() 

520 / SMB2_Encryption_Capabilities( 

521 # AES-128-CCM by default 

522 Ciphers=[self.session.CipherId], 

523 ), 

524 # Signing capabilities 

525 SMB2_Negotiate_Context() 

526 / SMB2_Signing_Capabilities( 

527 # AES-128-CCM by default 

528 SigningAlgorithms=[self.session.SigningAlgorithmId], 

529 ), 

530 ] 

531 

532 # Add security blob 

533 resp.SecurityBlob = spnego_token 

534 

535 if SMB2_Negotiate_Protocol_Request in pkt: 

536 # If required, compute sessions 

537 self.session.computeSMBConnectionPreauth( 

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

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

540 ) 

541 

542 self.send(resp) 

543 

544 @ATMT.state(error=1) 

545 def NEGO_FAILED(self): 

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

547 self.end() 

548 

549 @ATMT.state() 

550 def NEGOTIATED(self): 

551 pass 

552 

553 @ATMT.state(error=1) 

554 def INVALID_SEQUENCE_WINDOW(self, MID, CreditCharge): 

555 self.vprint( 

556 "SMB: Invalid sequence [%s:%s] in %s. (%s)" 

557 % ( 

558 MID, 

559 MID + CreditCharge - 1, 

560 repr(self.SequenceWindow), 

561 repr(self.SequenceNumbers), 

562 ) 

563 ) 

564 self.end() 

565 

566 @ATMT.state(error=1) 

567 def INSUFFICIENT_CREDITS(self): 

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

569 self.end() 

570 

571 def update_smbheader(self, pkt): 

572 """ 

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

574 """ 

575 # [MS-SMB2] sect 3.3.1.1 - Algorithm for Handling MIDs 

576 MID = pkt.MID 

577 if self.session.Dialect >= 0x0210: 

578 # SMB 2.1.0+ - Credits 

579 CreditCharge = pkt.CreditCharge 

580 Consumed = range(MID, MID + CreditCharge) 

581 if ( 

582 ( 

583 CreditCharge <= 0 

584 and not isinstance( 

585 pkt.payload.payload, SMB2_Negotiate_Protocol_Request 

586 ) 

587 ) 

588 or CreditCharge >= self.MaxCreditCount 

589 or not (self.SequenceWindow[0] <= MID <= self.SequenceWindow[1]) 

590 or not self.SequenceNumbers.issuperset(Consumed) 

591 ): 

592 raise self.INVALID_SEQUENCE_WINDOW(MID, CreditCharge) 

593 

594 # [MS-SMB2] sect 3.3.1.2 - Algorithm for the Granting of Credits 

595 CreditGranted = min( 

596 pkt.CreditRequest, self.MaxCreditCount - self.ClientCreditCount 

597 ) 

598 if ( 

599 self.SequenceWindow[1] - self.SequenceWindow[0] 

600 >= 2 * self.ClientCreditCount 

601 ): 

602 # "the server will not allow the difference between the smallest 

603 # available sequence number and the largest available sequence number 

604 # to exceed 2*[Credit Granted]" 

605 CreditGranted = 0 

606 self.smb_header.CreditRequest = CreditGranted 

607 self.ClientCreditCount += CreditGranted 

608 self.ClientCreditCount -= CreditCharge 

609 if self.ClientCreditCount <= 0: 

610 raise self.INSUFFICIENT_CREDITS() 

611 

612 else: 

613 # SMB 2.0.2 - No credits 

614 if not (self.SequenceWindow[0] <= MID <= self.SequenceWindow[1]): 

615 raise self.INVALID_SEQUENCE_WINDOW() 

616 CreditGranted = 1 

617 Consumed = [MID] 

618 

619 # Update allowed sequence numbers (protect against replay) 

620 self.SequenceNumbers -= set(Consumed) 

621 

622 # Store granted sequence numbers 

623 self.SequenceNumbers |= set( 

624 range( 

625 self.SequenceWindow[1] + 1, 

626 self.SequenceWindow[1] + 1 + CreditGranted, 

627 ) 

628 ) 

629 # And update sequence window 

630 self.SequenceWindow = ( 

631 min(self.SequenceNumbers), 

632 self.SequenceWindow[1] + CreditGranted, 

633 ) 

634 

635 # [MS-SMB2] sect 3.3.4.1 

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

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

638 self.smb_header.CreditCharge = pkt.CreditCharge 

639 # TODO: check that the announced CreditCharge is correct for the message. 

640 # Right now we trust the client. 

641 

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

643 self.NextCompound = bool(pkt.NextCommand) 

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

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

646 if ( 

647 not self.session.SigningRequired 

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

649 ): 

650 self.NextForceSign = True 

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

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

653 if pkt[SMB2_Header]._decrypted: 

654 self.NextForceEncrypt = True 

655 

656 # [MS-SMB2] sect 3.3.5.2.7.2 

657 # Add SMB2_FLAGS_RELATED_OPERATIONS to the response if present 

658 if pkt.Flags.SMB2_FLAGS_RELATED_OPERATIONS: 

659 self.smb_header.Flags += "SMB2_FLAGS_RELATED_OPERATIONS" 

660 else: 

661 self.smb_header.Flags -= "SMB2_FLAGS_RELATED_OPERATIONS" 

662 

663 # [MS-SMB2] sect 2.2.1.2 - Priority 

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

665 self.smb_header.Flags &= 0xFF8F 

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

667 

668 # Update IDs 

669 self.smb_header.SessionId = pkt.SessionId 

670 self.smb_header.TID = pkt.TID 

671 self.smb_header.MID = pkt.MID 

672 self.smb_header.PID = pkt.PID 

673 

674 @ATMT.receive_condition(NEGOTIATED) 

675 def received_negotiate_smb2(self, pkt): 

676 if SMB2_Negotiate_Protocol_Request in pkt: 

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

678 

679 @ATMT.action(received_negotiate_smb2) 

680 def on_negotiate_smb2(self, pkt): 

681 self.on_negotiate(pkt) 

682 

683 @ATMT.state(error=1) 

684 def INVALID_SMB_VERSION(self): 

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

686 self.end() 

687 

688 @ATMT.receive_condition(NEGOTIATED) 

689 def receive_setup_andx_request(self, pkt): 

690 if SMB2_Session_Setup_Request in pkt: 

691 # SMB2 

692 ssp_blob = pkt.SecurityBlob 

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

694 if ( 

695 SMBSession_Setup_AndX_Request_Extended_Security in pkt 

696 or SMBSession_Setup_AndX_Request in pkt 

697 ): 

698 raise self.INVALID_SMB_VERSION() 

699 

700 @ATMT.state() 

701 def RECEIVED_SETUP_ANDX_REQUEST(self): 

702 pass 

703 

704 @ATMT.action(receive_setup_andx_request) 

705 def on_setup_andx_request(self, pkt, ssp_blob): 

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

707 self.session.sspcontext, 

708 ssp_blob, 

709 ) 

710 self.update_smbheader(pkt) 

711 self.smb_header.SessionId = 0x0001000000000015 

712 if status not in [GSS_S_CONTINUE_NEEDED, GSS_S_COMPLETE]: 

713 # Error 

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

715 # Map some GSS return codes to NTStatus 

716 if status == GSS_S_CREDENTIALS_EXPIRED: 

717 resp.Status = "STATUS_PASSWORD_EXPIRED" 

718 else: 

719 resp.Status = "STATUS_LOGON_FAILURE" 

720 # Reset Session preauth (SMB 3.1.1) 

721 self.session.SessionPreauthIntegrityHashValue = None 

722 else: 

723 # Negotiation 

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

725 if self.GUEST_LOGIN: 

726 # "If the security subsystem indicates that the session 

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

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

729 resp.SessionFlags = "IS_GUEST" 

730 self.session.IsGuest = True 

731 self.session.SigningRequired = False 

732 if self.ANONYMOUS_LOGIN: 

733 resp.SessionFlags = "IS_NULL" 

734 # [MS-SMB2] sect 3.3.5.5.3 

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

736 resp.SessionFlags += "ENCRYPT_DATA" 

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

738 

739 # Set security blob (if any) 

740 resp.SecurityBlob = tok 

741 

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

743 if status == GSS_S_CONTINUE_NEEDED: 

744 # the setup session response is used in hash 

745 self.session.computeSMBSessionPreauth( 

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

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

748 ) 

749 else: 

750 # the setup session response is not used in hash 

751 self.session.computeSMBSessionPreauth( 

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

753 ) 

754 if status == GSS_S_COMPLETE: 

755 # Authentication was successful 

756 self.session.computeSMBSessionKeys(IsClient=False) 

757 self.authenticated = True 

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

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

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

761 self.NextForceSign = True 

762 self.send(resp) 

763 # Check whether we must enable encryption from now on 

764 if ( 

765 self.authenticated 

766 and not self.session.IsGuest 

767 and self.session.Dialect >= 0x0300 

768 and self.REQUIRE_ENCRYPTION 

769 ): 

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

771 self.session.EncryptData = True 

772 self.session.EncryptionRequired = True 

773 self.session.SigningRequired = False 

774 

775 @ATMT.condition(RECEIVED_SETUP_ANDX_REQUEST) 

776 def wait_for_next_request(self): 

777 if self.authenticated: 

778 self.vprint( 

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

780 ) 

781 raise self.AUTHENTICATED() 

782 else: 

783 raise self.NEGOTIATED() 

784 

785 @ATMT.state() 

786 def AUTHENTICATED(self): 

787 """Dev: overload this""" 

788 pass 

789 

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

791 

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

793 def should_serve(self): 

794 # Serve files 

795 self.current_trees = {} 

796 self.current_handles = {} 

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

798 self.tree_id = 0 

799 self.base_time_t = self.current_smb_time() 

800 raise self.SERVING() 

801 

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

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

804 pkt.Status = Status 

805 pkt.Command = "SMB2_IOCTL" 

806 self.send(pkt) 

807 

808 @ATMT.state(final=1) 

809 def END(self): 

810 self.end() 

811 

812 # SERVE FILES 

813 

814 def current_tree(self): 

815 """ 

816 Return the current tree name 

817 """ 

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

819 

820 def root_path(self): 

821 """ 

822 Return the root path of the current tree 

823 """ 

824 curtree = self.current_tree() 

825 try: 

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

827 except StopIteration: 

828 return None 

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

830 

831 @ATMT.state() 

832 def SERVING(self): 

833 """ 

834 Main state when serving files 

835 """ 

836 pass 

837 

838 @ATMT.receive_condition(SERVING) 

839 def receive_logoff_request(self, pkt): 

840 if SMB2_Session_Logoff_Request in pkt: 

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

842 

843 @ATMT.action(receive_logoff_request) 

844 def send_logoff_response(self, pkt): 

845 self.update_smbheader(pkt) 

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

847 

848 @ATMT.receive_condition(SERVING) 

849 def receive_setup_andx_request_in_serving(self, pkt): 

850 self.receive_setup_andx_request(pkt) 

851 

852 @ATMT.receive_condition(SERVING) 

853 def receive_tree_connect(self, pkt): 

854 if SMB2_Tree_Connect_Request in pkt: 

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

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

857 

858 @ATMT.action(receive_tree_connect) 

859 def send_tree_connect_response(self, pkt, tree_name): 

860 self.update_smbheader(pkt) 

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

862 try: 

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

864 tree_name = share.name # normalize 

865 except StopIteration: 

866 # Unknown tree 

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

868 resp.Command = "SMB2_TREE_CONNECT" 

869 resp.Status = "STATUS_BAD_NETWORK_NAME" 

870 self.send(resp) 

871 return 

872 # Add tree to current trees 

873 if tree_name not in self.current_trees: 

874 self.tree_id += 1 

875 self.smb_header.TID = self.tree_id 

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

877 

878 # Construct ShareFlags 

879 ShareFlags = ( 

880 "AUTO_CACHING+NO_CACHING" 

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

882 else self.TREE_SHARE_FLAGS 

883 ) 

884 # [MS-SMB2] sect 3.3.5.7 

885 if ( 

886 self.session.Dialect >= 0x0311 

887 and not self.session.EncryptData 

888 and share.encryptdata 

889 ): 

890 if not self.session.SupportsEncryption: 

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

892 ShareFlags += "+ENCRYPT_DATA" 

893 

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

895 self.send( 

896 self.smb_header.copy() 

897 / SMB2_Tree_Connect_Response( 

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

899 ShareFlags=ShareFlags, 

900 Capabilities=( 

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

902 ), 

903 MaximalAccess=self.TREE_MAXIMAL_ACCESS, 

904 ) 

905 ) 

906 

907 @ATMT.receive_condition(SERVING) 

908 def receive_ioctl(self, pkt): 

909 if SMB2_IOCTL_Request in pkt: 

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

911 

912 @ATMT.action(receive_ioctl) 

913 def send_ioctl_response(self, pkt): 

914 self.update_smbheader(pkt) 

915 if pkt.CtlCode == 0x11C017: 

916 # FSCTL_PIPE_TRANSCEIVE 

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

918 self.send( 

919 self.smb_header.copy() 

920 / SMB2_IOCTL_Response( 

921 CtlCode=0x11C017, 

922 FileId=pkt[SMB2_IOCTL_Request].FileId, 

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

924 ) 

925 ) 

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

927 # FSCTL_VALIDATE_NEGOTIATE_INFO 

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

929 # what flags were negotiated during the SMBNegotiate exchange. 

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

931 

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

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

934 # > STATUS_NOT_SUPPORTED or STATUS_INVALID_DEVICE_REQUEST 

935 # > since they do not allow or implement 

936 # > FSCTL_VALIDATE_NEGOTIATE_INFO. 

937 # > The client should accept the 

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

939 

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

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

942 self._ioctl_error(Status="STATUS_FILE_CLOSED") 

943 return 

944 

945 # SMB3 

946 self.send( 

947 self.smb_header.copy() 

948 / SMB2_IOCTL_Response( 

949 CtlCode=0x00140204, 

950 FileId=pkt[SMB2_IOCTL_Request].FileId, 

951 Buffer=[ 

952 ( 

953 "Output", 

954 SMB2_IOCTL_Validate_Negotiate_Info_Response( 

955 GUID=self.GUID, 

956 DialectRevision=self.session.Dialect, 

957 SecurityMode=( 

958 "SIGNING_ENABLED+SIGNING_REQUIRED" 

959 if self.session.SigningRequired 

960 else "SIGNING_ENABLED" 

961 ), 

962 Capabilities=self.NegotiateCapabilities, 

963 ), 

964 ) 

965 ], 

966 ) 

967 ) 

968 elif pkt.CtlCode == 0x001401FC: 

969 # FSCTL_QUERY_NETWORK_INTERFACE_INFO 

970 self.send( 

971 self.smb_header.copy() 

972 / SMB2_IOCTL_Response( 

973 CtlCode=0x001401FC, 

974 FileId=pkt[SMB2_IOCTL_Request].FileId, 

975 Output=SMB2_IOCTL_Network_Interface_Info( 

976 interfaces=[ 

977 NETWORK_INTERFACE_INFO( 

978 SockAddr_Storage=SOCKADDR_STORAGE( 

979 Family=0x0002, 

980 IPv4Adddress=x, 

981 ) 

982 ) 

983 for x in self.LOCAL_IPS 

984 ] 

985 ), 

986 ) 

987 ) 

988 elif pkt.CtlCode == 0x00060194: 

989 # FSCTL_DFS_GET_REFERRALS 

990 if ( 

991 self.DOMAIN_REFERRALS 

992 and not pkt[SMB2_IOCTL_Request].Input.RequestFileName 

993 ): 

994 # Requesting domain referrals 

995 self.send( 

996 self.smb_header.copy() 

997 / SMB2_IOCTL_Response( 

998 CtlCode=0x00060194, 

999 FileId=pkt[SMB2_IOCTL_Request].FileId, 

1000 Output=SMB2_IOCTL_RESP_GET_DFS_Referral( 

1001 ReferralEntries=[ 

1002 DFS_REFERRAL_V3( 

1003 ReferralEntryFlags="NameListReferral", 

1004 TimeToLive=600, 

1005 ) 

1006 for _ in self.DOMAIN_REFERRALS 

1007 ], 

1008 ReferralBuffer=[ 

1009 DFS_REFERRAL_ENTRY1(SpecialName=name) 

1010 for name in self.DOMAIN_REFERRALS 

1011 ], 

1012 ), 

1013 ) 

1014 ) 

1015 return 

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

1017 resp.Command = "SMB2_IOCTL" 

1018 resp.Status = "STATUS_FS_DRIVER_REQUIRED" 

1019 self.send(resp) 

1020 else: 

1021 # Among other things, FSCTL_VALIDATE_NEGOTIATE_INFO 

1022 self._ioctl_error(Status="STATUS_NOT_SUPPORTED") 

1023 

1024 @ATMT.receive_condition(SERVING) 

1025 def receive_create_file(self, pkt): 

1026 if SMB2_Create_Request in pkt: 

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

1028 

1029 PIPES_TABLE = { 

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

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

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

1033 } 

1034 

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

1036 # that points to the chained opened file handle 

1037 LAST_HANDLE = SMB2_FILEID( 

1038 Persistent=0xFFFFFFFFFFFFFFFF, Volatile=0xFFFFFFFFFFFFFFFF 

1039 ) 

1040 

1041 def current_smb_time(self): 

1042 return ( 

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

1044 - 864000000000 # one day ago 

1045 ) 

1046 

1047 def make_file_id(self, fname): 

1048 """ 

1049 Generate deterministic FileId based on the fname 

1050 """ 

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

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

1053 

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

1055 """ 

1056 Lookup the file and build it's SMB2_FILEID 

1057 """ 

1058 root = self.root_path() 

1059 if isinstance(fname, pathlib.Path): 

1060 path = fname 

1061 fname = path.name 

1062 else: 

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

1064 path = path.resolve() 

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

1066 # resolve(). Be careful 

1067 # Note: symbolic links are currently unsupported. 

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

1069 raise FileNotFoundError 

1070 if WINDOWS and path.is_reserved(): 

1071 raise FileNotFoundError 

1072 if not path.exists(): 

1073 if create and createOptions: 

1074 if createOptions.FILE_DIRECTORY_FILE: 

1075 # Folder creation 

1076 path.mkdir() 

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

1078 else: 

1079 # File creation 

1080 path.touch() 

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

1082 else: 

1083 raise FileNotFoundError 

1084 if durable_handle is None: 

1085 handle = SMB2_FILEID( 

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

1087 ) 

1088 else: 

1089 # We were given a durable handle. Use it 

1090 handle = durable_handle 

1091 attrs = { 

1092 "CreationTime": self.base_time_t, 

1093 "LastAccessTime": self.base_time_t, 

1094 "LastWriteTime": self.base_time_t, 

1095 "ChangeTime": self.base_time_t, 

1096 "EndOfFile": 0, 

1097 "AllocationSize": 0, 

1098 } 

1099 path_stat = path.stat() 

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

1101 if fname is None: 

1102 # special case 

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

1104 [ 

1105 "FILE_ATTRIBUTE_HIDDEN", 

1106 "FILE_ATTRIBUTE_SYSTEM", 

1107 "FILE_ATTRIBUTE_DIRECTORY", 

1108 ] 

1109 ) 

1110 elif path.is_dir(): 

1111 attrs["FileAttributes"] = "FILE_ATTRIBUTE_DIRECTORY" 

1112 else: 

1113 attrs["FileAttributes"] = "FILE_ATTRIBUTE_ARCHIVE" 

1114 self.current_handles[handle] = ( 

1115 path, # file path 

1116 attrs, # file attributes 

1117 ) 

1118 self.enumerate_index[handle] = 0 

1119 return handle 

1120 

1121 def set_compounded_handle(self, handle): 

1122 """ 

1123 Mark a handle as the current one being compounded. 

1124 """ 

1125 self.CompoundedHandle = handle 

1126 

1127 def get_file_id(self, pkt): 

1128 """ 

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

1130 """ 

1131 fid = pkt.FileId 

1132 if fid == self.LAST_HANDLE: 

1133 return self.CompoundedHandle 

1134 return fid 

1135 

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

1137 """ 

1138 Lookup a folder handle 

1139 """ 

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

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

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

1143 if not path.is_dir(): 

1144 raise NotADirectoryError 

1145 return sorted( 

1146 [ 

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

1148 for x in path.glob(filter) 

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

1150 # for path traversal on them. 

1151 if not x.is_symlink() 

1152 ] 

1153 + [ 

1154 cls( 

1155 FileAttributes=("FILE_ATTRIBUTE_DIRECTORY"), 

1156 FileName=".", 

1157 ) 

1158 ] 

1159 + ( 

1160 [ 

1161 cls( 

1162 FileAttributes=("FILE_ATTRIBUTE_DIRECTORY"), 

1163 FileName="..", 

1164 ) 

1165 ] 

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

1167 else [] 

1168 ), 

1169 key=lambda x: x.FileName, 

1170 )[offset:] 

1171 

1172 @ATMT.action(receive_create_file) 

1173 def send_create_file_response(self, pkt): 

1174 """ 

1175 Handle CreateFile request 

1176 

1177 See [MS-SMB2] 3.3.5.9 () 

1178 """ 

1179 self.update_smbheader(pkt) 

1180 if pkt[SMB2_Create_Request].NameLen: 

1181 fname = pkt[SMB2_Create_Request].Name 

1182 else: 

1183 fname = None 

1184 if fname: 

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

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

1187 # Special IPC$ case: opening a pipe 

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

1189 if FILE_ID: 

1190 attrs = { 

1191 "CreationTime": 0, 

1192 "LastAccessTime": 0, 

1193 "LastWriteTime": 0, 

1194 "ChangeTime": 0, 

1195 "EndOfFile": 0, 

1196 "AllocationSize": 4096, 

1197 } 

1198 self.current_handles[FILE_ID] = ( 

1199 fname, 

1200 attrs, 

1201 ) 

1202 self.send( 

1203 self.smb_header.copy() 

1204 / SMB2_Create_Response( 

1205 OplockLevel=pkt.RequestedOplockLevel, 

1206 FileId=FILE_ID, 

1207 **attrs, 

1208 ) 

1209 ) 

1210 else: 

1211 # NOT_FOUND 

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

1213 resp.Command = "SMB2_CREATE" 

1214 resp.Status = "STATUS_OBJECT_NAME_NOT_FOUND" 

1215 self.send(resp) 

1216 return 

1217 else: 

1218 # Check if there is a Durable Handle Reconnect Request 

1219 durable_handle = None 

1220 if pkt[SMB2_Create_Request].CreateContextsLen: 

1221 try: 

1222 durable_handle = next( 

1223 x.Data.FileId 

1224 for x in pkt[SMB2_Create_Request].CreateContexts 

1225 if x.Name == b"DH2C" 

1226 ) 

1227 except StopIteration: 

1228 pass 

1229 # Lookup file handle 

1230 try: 

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

1232 except FileNotFoundError: 

1233 # NOT_FOUND 

1234 if pkt[SMB2_Create_Request].CreateDisposition in [ 

1235 0x00000002, # FILE_CREATE 

1236 0x00000005, # FILE_OVERWRITE_IF 

1237 ]: 

1238 if self.readonly: 

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

1240 resp.Command = "SMB2_CREATE" 

1241 resp.Status = "STATUS_ACCESS_DENIED" 

1242 self.send(resp) 

1243 return 

1244 else: 

1245 # Create file 

1246 handle = self.lookup_file( 

1247 fname, 

1248 durable_handle=durable_handle, 

1249 create=True, 

1250 createOptions=pkt[SMB2_Create_Request].CreateOptions, 

1251 ) 

1252 else: 

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

1254 resp.Command = "SMB2_CREATE" 

1255 resp.Status = "STATUS_OBJECT_NAME_NOT_FOUND" 

1256 self.send(resp) 

1257 return 

1258 # Store compounded handle 

1259 self.set_compounded_handle(handle) 

1260 # Build response 

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

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

1263 OplockLevel=pkt.RequestedOplockLevel, 

1264 FileId=handle, 

1265 **attrs, 

1266 ) 

1267 # Handle the various chain elements 

1268 if pkt[SMB2_Create_Request].CreateContextsLen: 

1269 CreateContexts = [] 

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

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

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

1273 for elt in pkt[SMB2_Create_Request].CreateContexts: 

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

1275 # [MS-SMB2] sect 3.3.5.9.9 

1276 CreateContexts.append( 

1277 SMB2_Create_Context( 

1278 Name=b"QFid", 

1279 Data=SMB2_CREATE_QUERY_ON_DISK_ID( 

1280 DiskFileId=self.make_file_id(fname), 

1281 VolumeId=0xBA39CD11, 

1282 ), 

1283 ) 

1284 ) 

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

1286 # [MS-SMB2] sect 3.3.5.9.5 

1287 CreateContexts.append( 

1288 SMB2_Create_Context( 

1289 Name=b"MxAc", 

1290 Data=SMB2_CREATE_QUERY_MAXIMAL_ACCESS_RESPONSE( 

1291 QueryStatus=0, 

1292 MaximalAccess=self.FILE_MAXIMAL_ACCESS, 

1293 ), 

1294 ) 

1295 ) 

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

1297 # [MS-SMB2] sect 3.3.5.9.10 

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

1299 continue 

1300 CreateContexts.append( 

1301 SMB2_Create_Context( 

1302 Name=b"DH2Q", 

1303 Data=SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2( 

1304 Timeout=180000 

1305 ), 

1306 ) 

1307 ) 

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

1309 # [MS-SMB2] sect 3.3.5.9.11 

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

1311 CreateContexts.append( 

1312 SMB2_Create_Context( 

1313 Name=b"RqLs", 

1314 Data=elt.Data, 

1315 ) 

1316 ) 

1317 resp.CreateContexts = CreateContexts 

1318 self.send(resp) 

1319 

1320 @ATMT.receive_condition(SERVING) 

1321 def receive_change_notify_info(self, pkt): 

1322 if SMB2_Change_Notify_Request in pkt: 

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

1324 

1325 @ATMT.action(receive_change_notify_info) 

1326 def send_change_notify_info_response(self, pkt): 

1327 # [MS-SMB2] sect 3.3.5.19 

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

1329 # server MUST fail this request with STATUS_NOT_SUPPORTED." 

1330 self.update_smbheader(pkt) 

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

1332 resp.Command = "SMB2_CHANGE_NOTIFY" 

1333 # ScapyFS doesn't support notifications 

1334 resp.Status = "STATUS_NOT_SUPPORTED" 

1335 self.send(resp) 

1336 

1337 @ATMT.receive_condition(SERVING) 

1338 def receive_query_directory_info(self, pkt): 

1339 if SMB2_Query_Directory_Request in pkt: 

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

1341 

1342 @ATMT.action(receive_query_directory_info) 

1343 def send_query_directory_response(self, pkt): 

1344 self.update_smbheader(pkt) 

1345 if not pkt.FileNameLen: 

1346 # this is broken. 

1347 return 

1348 query = pkt.FileName 

1349 fid = self.get_file_id(pkt) 

1350 # Check for handled FileInformationClass 

1351 # 0x02: FileFullDirectoryInformation 

1352 # 0x03: FileBothDirectoryInformation 

1353 # 0x25: FileIdBothDirectoryInformation 

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

1355 # Unknown FileInformationClass 

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

1357 resp.Command = "SMB2_QUERY_DIRECTORY" 

1358 resp.Status = "STATUS_INVALID_INFO_CLASS" 

1359 self.send(resp) 

1360 return 

1361 # Handle SMB2_RESTART_SCANS 

1362 if pkt[SMB2_Query_Directory_Request].Flags.SMB2_RESTART_SCANS: 

1363 self.enumerate_index[fid] = 0 

1364 # Lookup the files 

1365 try: 

1366 files = self.lookup_folder( 

1367 fid, 

1368 query, 

1369 self.enumerate_index[fid], 

1370 { 

1371 0x02: FILE_FULL_DIR_INFORMATION, 

1372 0x03: FILE_BOTH_DIR_INFORMATION, 

1373 0x25: FILE_ID_BOTH_DIR_INFORMATION, 

1374 }[pkt.FileInformationClass], 

1375 ) 

1376 except NotADirectoryError: 

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

1378 resp.Command = "SMB2_QUERY_DIRECTORY" 

1379 resp.Status = "STATUS_INVALID_PARAMETER" 

1380 self.send(resp) 

1381 return 

1382 if not files: 

1383 # No more files ! 

1384 self.enumerate_index[fid] = 0 

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

1386 resp.Command = "SMB2_QUERY_DIRECTORY" 

1387 resp.Status = "STATUS_NO_MORE_FILES" 

1388 self.send(resp) 

1389 return 

1390 # Handle SMB2_RETURN_SINGLE_ENTRY 

1391 if pkt[SMB2_Query_Directory_Request].Flags.SMB2_RETURN_SINGLE_ENTRY: 

1392 files = files[:1] 

1393 # Increment index 

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

1395 # Build response based on the FileInformationClass 

1396 fileinfo = FileIdBothDirectoryInformation( 

1397 files=files, 

1398 ) 

1399 self.send( 

1400 self.smb_header.copy() 

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

1402 ) 

1403 

1404 @ATMT.receive_condition(SERVING) 

1405 def receive_query_info(self, pkt): 

1406 if SMB2_Query_Info_Request in pkt: 

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

1408 

1409 @ATMT.action(receive_query_info) 

1410 def send_query_info_response(self, pkt): 

1411 self.update_smbheader(pkt) 

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

1413 fid = self.get_file_id(pkt) 

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

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

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

1417 fileinfo = FileStandardInformation( 

1418 EndOfFile=attrs["EndOfFile"], 

1419 AllocationSize=attrs["AllocationSize"], 

1420 ) 

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

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

1423 fileinfo = FileInternalInformation( 

1424 IndexNumber=hash(pth) & 0xFFFFFFFFFFFFFFFF, 

1425 ) 

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

1427 fileinfo = FileEaInformation() 

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

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

1430 fileinfo = FileAllInformation( 

1431 BasicInformation=FileBasicInformation( 

1432 CreationTime=attrs["CreationTime"], 

1433 LastAccessTime=attrs["LastAccessTime"], 

1434 LastWriteTime=attrs["LastWriteTime"], 

1435 ChangeTime=attrs["ChangeTime"], 

1436 FileAttributes=attrs["FileAttributes"], 

1437 ), 

1438 StandardInformation=FileStandardInformation( 

1439 EndOfFile=attrs["EndOfFile"], 

1440 AllocationSize=attrs["AllocationSize"], 

1441 ), 

1442 ) 

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

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

1445 fileinfo = FileAlternateNameInformation( 

1446 FileName=pth.name, 

1447 ) 

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

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

1450 fileinfo = FileStreamInformation( 

1451 StreamSize=attrs["EndOfFile"], 

1452 StreamAllocationSize=attrs["AllocationSize"], 

1453 ) 

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

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

1456 fileinfo = FileNetworkOpenInformation( 

1457 **attrs, 

1458 ) 

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

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

1461 fileinfo = FILE_NAME_INFORMATION( 

1462 FileName=pth.name, 

1463 ) 

1464 else: 

1465 log_runtime.warning( 

1466 "Unimplemented: %s" 

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

1468 ) 

1469 return 

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

1471 # [MS-FSCC] sect 2.5 

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

1473 fileinfo = FileFsVolumeInformation() 

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

1475 fileinfo = FileFsSizeInformation() 

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

1477 fileinfo = FileFsAttributeInformation( 

1478 FileSystemAttributes=0x88000F, 

1479 ) 

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

1481 fileinfo = FileEaInformation() 

1482 else: 

1483 log_runtime.warning( 

1484 "Unimplemented: %s" 

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

1486 ) 

1487 return 

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

1489 # [MS-FSCC] 2.4.6 

1490 fileinfo = SECURITY_DESCRIPTOR() 

1491 # TODO: fill it 

1492 if pkt.AdditionalInformation.OWNER_SECURITY_INFORMATION: 

1493 pass 

1494 if pkt.AdditionalInformation.GROUP_SECURITY_INFORMATION: 

1495 pass 

1496 if pkt.AdditionalInformation.DACL_SECURITY_INFORMATION: 

1497 pass 

1498 if pkt.AdditionalInformation.SACL_SECURITY_INFORMATION: 

1499 pass 

1500 # Observed: 

1501 if ( 

1502 pkt.AdditionalInformation.OWNER_SECURITY_INFORMATION 

1503 or pkt.AdditionalInformation.SACL_SECURITY_INFORMATION 

1504 or pkt.AdditionalInformation.GROUP_SECURITY_INFORMATION 

1505 or pkt.AdditionalInformation.DACL_SECURITY_INFORMATION 

1506 ): 

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

1508 pkt.Status = "STATUS_ACCESS_DENIED" 

1509 pkt.Command = "SMB2_QUERY_INFO" 

1510 self.send(pkt) 

1511 return 

1512 if pkt.AdditionalInformation.ATTRIBUTE_SECURITY_INFORMATION: 

1513 fileinfo.Control = 0x8800 

1514 self.send( 

1515 self.smb_header.copy() 

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

1517 ) 

1518 

1519 @ATMT.receive_condition(SERVING) 

1520 def receive_set_info_request(self, pkt): 

1521 if SMB2_Set_Info_Request in pkt: 

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

1523 

1524 @ATMT.action(receive_set_info_request) 

1525 def send_set_info_response(self, pkt): 

1526 self.update_smbheader(pkt) 

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

1528 

1529 @ATMT.receive_condition(SERVING) 

1530 def receive_write_request(self, pkt): 

1531 if SMB2_Write_Request in pkt: 

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

1533 

1534 @ATMT.action(receive_write_request) 

1535 def send_write_response(self, pkt): 

1536 self.update_smbheader(pkt) 

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

1538 fid = self.get_file_id(pkt) 

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

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

1541 # A pipe 

1542 self.rpc_server.recv(pkt.Data) 

1543 else: 

1544 if self.readonly: 

1545 # Read only ! 

1546 resp = SMB2_Error_Response() 

1547 resp.Command = "SMB2_WRITE" 

1548 resp.Status = "ERROR_FILE_READ_ONLY" 

1549 else: 

1550 # Write file 

1551 pth, _ = self.current_handles[fid] 

1552 length = pkt[SMB2_Write_Request].DataLen 

1553 off = pkt[SMB2_Write_Request].Offset 

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

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

1556 fd.seek(off) 

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

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

1559 

1560 @ATMT.receive_condition(SERVING) 

1561 def receive_read_request(self, pkt): 

1562 if SMB2_Read_Request in pkt: 

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

1564 

1565 @ATMT.action(receive_read_request) 

1566 def send_read_response(self, pkt): 

1567 self.update_smbheader(pkt) 

1568 resp = SMB2_Read_Response() 

1569 fid = self.get_file_id(pkt) 

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

1571 # Read output from DCE/RPC server 

1572 r = self.rpc_server.get_response() 

1573 resp.Data = bytes(r) 

1574 else: 

1575 # Read file and send content 

1576 pth, _ = self.current_handles[fid] 

1577 length = pkt[SMB2_Read_Request].Length 

1578 off = pkt[SMB2_Read_Request].Offset 

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

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

1581 fd.seek(off) 

1582 resp.Data = fd.read(length) 

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

1584 

1585 @ATMT.receive_condition(SERVING) 

1586 def receive_close_request(self, pkt): 

1587 if SMB2_Close_Request in pkt: 

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

1589 

1590 @ATMT.action(receive_close_request) 

1591 def send_close_response(self, pkt): 

1592 self.update_smbheader(pkt) 

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

1594 fid = self.get_file_id(pkt) 

1595 pth, attrs = self.current_handles[fid] 

1596 if pth: 

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

1598 del self.current_handles[fid] 

1599 del self.enumerate_index[fid] 

1600 self.send( 

1601 self.smb_header.copy() 

1602 / SMB2_Close_Response( 

1603 Flags=pkt[SMB2_Close_Request].Flags, 

1604 **attrs, 

1605 ) 

1606 ) 

1607 else: 

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

1609 

1610 @ATMT.receive_condition(SERVING) 

1611 def receive_tree_disconnect_request(self, pkt): 

1612 if SMB2_Tree_Disconnect_Request in pkt: 

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

1614 

1615 @ATMT.action(receive_tree_disconnect_request) 

1616 def send_tree_disconnect_response(self, pkt): 

1617 self.update_smbheader(pkt) 

1618 try: 

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

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

1621 except KeyError: 

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

1623 resp.Command = "SMB2_TREE_DISCONNECT" 

1624 resp.Status = "STATUS_NETWORK_NAME_DELETED" 

1625 self.send(resp) 

1626 

1627 @ATMT.receive_condition(SERVING) 

1628 def receive_cancel_request(self, pkt): 

1629 if SMB2_Cancel_Request in pkt: 

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

1631 

1632 @ATMT.action(receive_cancel_request) 

1633 def send_notify_cancel_response(self, pkt): 

1634 self.update_smbheader(pkt) 

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

1636 resp.Status = "STATUS_CANCELLED" 

1637 self.send(resp) 

1638 

1639 @ATMT.receive_condition(SERVING) 

1640 def receive_echo_request(self, pkt): 

1641 if SMB2_Echo_Request in pkt: 

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

1643 

1644 @ATMT.action(receive_echo_request) 

1645 def send_echo_reply(self, pkt): 

1646 self.update_smbheader(pkt) 

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

1648 

1649 

1650# DCE/RPC server for SMB 

1651 

1652 

1653class SMB_DCERPC_Server(DCERPC_Server): 

1654 """ 

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

1656 """ 

1657 

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

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

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

1661 

1662 @DCERPC_Server.answer(NetrShareEnum_Request) 

1663 def netr_share_enum(self, req): 

1664 """ 

1665 NetrShareEnum [MS-SRVS] 

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

1667 """ 

1668 level = req.InfoStruct.Level 

1669 

1670 # Create response 

1671 resp = NetrShareEnum_Response( 

1672 InfoStruct=LPSHARE_ENUM_STRUCT( 

1673 Level=level, 

1674 ShareInfo=NDRUnion( 

1675 tag=level, 

1676 value=None, 

1677 ), 

1678 ), 

1679 ndr64=self.ndr64, 

1680 ) 

1681 

1682 if level == 1: 

1683 nbEntries = len(self.shares) 

1684 resp.InfoStruct.ShareInfo.value = NDRPointer( 

1685 referent_id=0x20000, 

1686 value=SHARE_INFO_1_CONTAINER( 

1687 Buffer=[ 

1688 # Add shares 

1689 LPSHARE_INFO_1( 

1690 shi1_netname=x.name, 

1691 shi1_type=x.type, 

1692 shi1_remark=x.remark, 

1693 ) 

1694 for x in self.shares 

1695 ], 

1696 EntriesRead=nbEntries, 

1697 ), 

1698 ) 

1699 resp.TotalEntries = nbEntries 

1700 else: 

1701 # We only support level 1 :( 

1702 resp.status = 0x0000007C # ERROR_INVALID_LEVEL 

1703 

1704 return resp 

1705 

1706 @DCERPC_Server.answer(NetrWkstaGetInfo_Request) 

1707 def netr_wksta_getinfo(self, req): 

1708 """ 

1709 NetrWkstaGetInfo [MS-SRVS] 

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

1711 """ 

1712 level = req.Level 

1713 

1714 # Create response 

1715 resp = NetrWkstaGetInfo_Response( 

1716 WkstaInfo=NDRUnion( 

1717 tag=level, 

1718 value=None, 

1719 ), 

1720 ndr64=self.ndr64, 

1721 ) 

1722 

1723 if level == 100: 

1724 resp.WkstaInfo.value = NDRPointer( 

1725 referent_id=0x20000, 

1726 value=LPWKSTA_INFO_100( 

1727 wki100_platform_id=500, # NT 

1728 wki100_ver_major=5, 

1729 ), 

1730 ) 

1731 else: 

1732 # We only support level 101 :( 

1733 resp.status = 0x0000007C # ERROR_INVALID_LEVEL 

1734 

1735 return resp 

1736 

1737 @DCERPC_Server.answer(NetrServerGetInfo_Request) 

1738 def netr_server_getinfo(self, req): 

1739 """ 

1740 NetrServerGetInfo [MS-WKST] 

1741 "retrieves current configuration information for CIFS and 

1742 SMB Version 1.0 servers." 

1743 """ 

1744 level = req.Level 

1745 

1746 # Create response 

1747 resp = NetrServerGetInfo_Response( 

1748 InfoStruct=NDRUnion( 

1749 tag=level, 

1750 value=None, 

1751 ), 

1752 ndr64=self.ndr64, 

1753 ) 

1754 

1755 if level == 101: 

1756 resp.InfoStruct.value = NDRPointer( 

1757 referent_id=0x20000, 

1758 value=LPSERVER_INFO_101( 

1759 sv101_platform_id=500, # NT 

1760 sv101_name="WORKSTATION", 

1761 sv101_version_major=10, 

1762 sv101_version_minor=0, 

1763 sv101_version_type=1, # Workstation 

1764 ), 

1765 ) 

1766 else: 

1767 # We only support level 101 :( 

1768 resp.status = 0x0000007C # ERROR_INVALID_LEVEL 

1769 

1770 return resp 

1771 

1772 @DCERPC_Server.answer(NetrShareGetInfo_Request) 

1773 def netr_share_getinfo(self, req): 

1774 """ 

1775 NetrShareGetInfo [MS-SRVS] 

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

1777 """ 

1778 level = req.Level 

1779 share_netname = req.payload.payload.valueof("NetName").decode() 

1780 

1781 # Create response 

1782 resp = NetrShareGetInfo_Response( 

1783 InfoStruct=NDRUnion( 

1784 tag=level, 

1785 value=None, 

1786 ), 

1787 ndr64=self.ndr64, 

1788 ) 

1789 

1790 # Find the share the client is asking details for 

1791 try: 

1792 share = next(x for x in self.shares if x.name == share_netname) 

1793 except StopIteration: 

1794 # Share doesn't exist ! 

1795 resp.status = 0x00000906 # NERR_NetNameNotFound 

1796 return resp 

1797 

1798 if level == 1: 

1799 resp.InfoStruct.value = NDRPointer( 

1800 referent_id=0x20000, 

1801 value=LPSHARE_INFO_1( 

1802 shi1_netname=share.name, 

1803 shi1_type=share.type, 

1804 shi1_remark=share.remark, 

1805 ), 

1806 ) 

1807 else: 

1808 # We only support level 1 :( 

1809 resp.status = 0x0000007C # ERROR_INVALID_LEVEL 

1810 

1811 return resp 

1812 

1813 

1814# Util 

1815 

1816 

1817class smbserver: 

1818 r""" 

1819 Spawns a simple smbserver 

1820 

1821 smbserver parameters: 

1822 

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

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

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

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

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

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

1829 Default NTLM with guest 

1830 

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

1832 """ 

1833 

1834 def __init__( 

1835 self, 

1836 shares=None, 

1837 iface: str = None, 

1838 port: int = 445, 

1839 verb: int = 2, 

1840 readonly: bool = True, 

1841 # SMB arguments 

1842 ssp=None, 

1843 **kwargs, 

1844 ): 

1845 # Default Share 

1846 if shares is None: 

1847 shares = [ 

1848 SMBShare( 

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

1850 ) 

1851 ] 

1852 # Verb 

1853 if verb >= 2: 

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

1855 log_runtime.info( 

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

1857 % ( 

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

1859 ( 

1860 conf.color_theme.yellow("YES") 

1861 if readonly 

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

1863 ), 

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

1865 ) 

1866 ) 

1867 for share in shares: 

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

1869 # Start SMB Server 

1870 self.srv = SMB_Server.spawn( 

1871 # TCP server 

1872 port=port, 

1873 iface=iface or conf.loopback_name, 

1874 verb=verb, 

1875 # SMB server 

1876 ssp=ssp, 

1877 shares=shares, 

1878 readonly=readonly, 

1879 # SMB arguments 

1880 **kwargs, 

1881 ) 

1882 

1883 def close(self): 

1884 """ 

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

1886 """ 

1887 if self.srv: 

1888 try: 

1889 self.srv.shutdown(socket.SHUT_RDWR) 

1890 except OSError: 

1891 pass 

1892 self.srv.close() 

1893 

1894 

1895if __name__ == "__main__": 

1896 from scapy.utils import AutoArgparse 

1897 

1898 AutoArgparse(smbserver)