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

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

786 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 SMBNegotiate_Response_Extended_Security, 

50 SMBNegotiate_Response_Security, 

51 SMBSession_Null, 

52 SMBSession_Setup_AndX_Request, 

53 SMBSession_Setup_AndX_Request_Extended_Security, 

54 SMBSession_Setup_AndX_Response, 

55 SMBSession_Setup_AndX_Response_Extended_Security, 

56 SMBTree_Connect_AndX, 

57 SMB_Header, 

58) 

59from scapy.layers.windows.security import SECURITY_DESCRIPTOR 

60from scapy.layers.smb2 import ( 

61 DFS_REFERRAL_ENTRY1, 

62 DFS_REFERRAL_V3, 

63 DirectTCP, 

64 FILE_BOTH_DIR_INFORMATION, 

65 FILE_FULL_DIR_INFORMATION, 

66 FILE_ID_BOTH_DIR_INFORMATION, 

67 FILE_NAME_INFORMATION, 

68 FileAllInformation, 

69 FileAlternateNameInformation, 

70 FileBasicInformation, 

71 FileEaInformation, 

72 FileFsAttributeInformation, 

73 FileFsSizeInformation, 

74 FileFsVolumeInformation, 

75 FileIdBothDirectoryInformation, 

76 FileInternalInformation, 

77 FileNetworkOpenInformation, 

78 FileStandardInformation, 

79 FileStreamInformation, 

80 NETWORK_INTERFACE_INFO, 

81 SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2, 

82 SMB2_CREATE_QUERY_MAXIMAL_ACCESS_RESPONSE, 

83 SMB2_CREATE_QUERY_ON_DISK_ID, 

84 SMB2_Cancel_Request, 

85 SMB2_Change_Notify_Request, 

86 SMB2_Change_Notify_Response, 

87 SMB2_Close_Request, 

88 SMB2_Close_Response, 

89 SMB2_Create_Context, 

90 SMB2_Create_Request, 

91 SMB2_Create_Response, 

92 SMB2_ENCRYPTION_CIPHERS, 

93 SMB2_Echo_Request, 

94 SMB2_Echo_Response, 

95 SMB2_Encryption_Capabilities, 

96 SMB2_Error_Response, 

97 SMB2_FILEID, 

98 SMB2_Header, 

99 SMB2_IOCTL_Network_Interface_Info, 

100 SMB2_IOCTL_RESP_GET_DFS_Referral, 

101 SMB2_IOCTL_Request, 

102 SMB2_IOCTL_Response, 

103 SMB2_IOCTL_Validate_Negotiate_Info_Response, 

104 SMB2_Negotiate_Context, 

105 SMB2_Negotiate_Protocol_Request, 

106 SMB2_Negotiate_Protocol_Response, 

107 SMB2_Preauth_Integrity_Capabilities, 

108 SMB2_Query_Directory_Request, 

109 SMB2_Query_Directory_Response, 

110 SMB2_Query_Info_Request, 

111 SMB2_Query_Info_Response, 

112 SMB2_Read_Request, 

113 SMB2_Read_Response, 

114 SMB2_SIGNING_ALGORITHMS, 

115 SMB2_Session_Logoff_Request, 

116 SMB2_Session_Logoff_Response, 

117 SMB2_Session_Setup_Request, 

118 SMB2_Session_Setup_Response, 

119 SMB2_Set_Info_Request, 

120 SMB2_Set_Info_Response, 

121 SMB2_Signing_Capabilities, 

122 SMB2_Tree_Connect_Request, 

123 SMB2_Tree_Connect_Response, 

124 SMB2_Tree_Disconnect_Request, 

125 SMB2_Tree_Disconnect_Response, 

126 SMB2_Write_Request, 

127 SMB2_Write_Response, 

128 SMBStreamSocket, 

129 SOCKADDR_STORAGE, 

130 SRVSVC_SHARE_TYPES, 

131) 

132from scapy.layers.spnego import SPNEGOSSP 

133 

134# Import DCE/RPC 

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

136 LPSERVER_INFO_101, 

137 LPSHARE_ENUM_STRUCT, 

138 LPSHARE_INFO_1, 

139 NetrServerGetInfo_Request, 

140 NetrServerGetInfo_Response, 

141 NetrShareEnum_Request, 

142 NetrShareEnum_Response, 

143 NetrShareGetInfo_Request, 

144 NetrShareGetInfo_Response, 

145 SHARE_INFO_1_CONTAINER, 

146) 

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

148 LPWKSTA_INFO_100, 

149 NetrWkstaGetInfo_Request, 

150 NetrWkstaGetInfo_Response, 

151) 

152 

153 

154class SMBShare: 

155 """ 

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

157 

158 :param name: the share name 

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

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

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

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

163 share. This only applies to SMB 3.1.1. 

164 """ 

165 

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

167 # Set the default type 

168 if type is None: 

169 type = 0 # DISKTREE 

170 if name.endswith("$"): 

171 type &= 0x80000000 # SPECIAL 

172 # Lower case the name for resolution 

173 self._name = name.lower() 

174 # Resolve path 

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

176 # props 

177 self.name = name 

178 self.type = type 

179 self.remark = remark 

180 self.encryptdata = encryptdata 

181 

182 def __repr__(self): 

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

184 if self.type & 0x80000000: 

185 type = "SPECIAL+" + type 

186 if self.type & 0x40000000: 

187 type = "TEMPORARY+" + type 

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

189 self.name, 

190 type, 

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

192 str(self.path), 

193 ) 

194 

195 

196# The SMB Automaton 

197 

198 

199class SMB_Server(Automaton): 

200 """ 

201 SMB server automaton 

202 

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

204 Note that IPC$ is appended. 

205 :param ssp: the SSP to use 

206 

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

208 

209 :param ANONYMOUS_LOGIN: mark the clients as anonymous 

210 :param GUEST_LOGIN: mark the clients as guest 

211 :param REQUIRE_SIGNATURE: set 'Require Signature' 

212 :param REQUIRE_ENCRYPTION: globally require encryption. 

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

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

215 :param TREE_SHARE_FLAGS: flags to announce on Tree_Connect_Response 

216 :param TREE_CAPABILITIES: capabilities to announce on Tree_Connect_Response 

217 :param TREE_MAXIMAL_ACCESS: maximal access to announce on Tree_Connect_Response 

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

219 """ 

220 

221 pkt_cls = DirectTCP 

222 socketcls = SMBStreamSocket 

223 

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

225 self.verb = verb 

226 if "sock" not in kwargs: 

227 raise ValueError( 

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

229 ) 

230 # Various SMB server arguments 

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

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

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

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

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

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

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

238 self.TREE_SHARE_FLAGS = kwargs.pop( 

239 "TREE_SHARE_FLAGS", "FORCE_LEVELII_OPLOCK+RESTRICT_EXCLUSIVE_OPENS" 

240 ) 

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

242 self.TREE_MAXIMAL_ACCESS = kwargs.pop( 

243 "TREE_MAXIMAL_ACCESS", 

244 "+".join( 

245 [ 

246 "FILE_READ_DATA", 

247 "FILE_WRITE_DATA", 

248 "FILE_APPEND_DATA", 

249 "FILE_READ_EA", 

250 "FILE_WRITE_EA", 

251 "FILE_EXECUTE", 

252 "FILE_DELETE_CHILD", 

253 "FILE_READ_ATTRIBUTES", 

254 "FILE_WRITE_ATTRIBUTES", 

255 "DELETE", 

256 "READ_CONTROL", 

257 "WRITE_DAC", 

258 "WRITE_OWNER", 

259 "SYNCHRONIZE", 

260 ] 

261 ), 

262 ) 

263 self.FILE_MAXIMAL_ACCESS = kwargs.pop( 

264 # Read-only 

265 "FILE_MAXIMAL_ACCESS", 

266 "+".join( 

267 [ 

268 "FILE_READ_DATA", 

269 "FILE_READ_EA", 

270 "FILE_EXECUTE", 

271 "FILE_READ_ATTRIBUTES", 

272 "READ_CONTROL", 

273 "SYNCHRONIZE", 

274 ] 

275 ), 

276 ) 

277 self.LOCAL_IPS = kwargs.pop( 

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

279 ) 

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

281 if self.USE_SMB1: 

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

283 self.readonly = readonly 

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

285 self.shares = shares.copy() 

286 # Append the IPC$ share 

287 self.shares.append( 

288 SMBShare( 

289 name="IPC$", 

290 type=0x80000003, # SPECIAL+IPC 

291 remark="Remote IPC", 

292 ) 

293 ) 

294 # Initialize the DCE/RPC server for SMB 

295 self.rpc_server = SMB_DCERPC_Server( 

296 DCERPC_Transport.NCACN_NP, 

297 shares=self.shares, 

298 verb=self.verb, 

299 ) 

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

301 if "DCERPC_SERVER_CLS" in kwargs: 

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

303 # Internal Session information 

304 self.SMB2 = False 

305 self.NegotiateCapabilities = None 

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

307 self.NextForceSign = False 

308 self.NextForceEncrypt = False 

309 # Compounds are handled on receiving by the StreamSocket, 

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

311 self.NextCompound = False 

312 self.CompoundedHandle = None 

313 # SSP provider 

314 if ssp is None: 

315 # No SSP => fallback on NTLM with guest 

316 ssp = SPNEGOSSP( 

317 [ 

318 NTLMSSP( 

319 USE_MIC=False, 

320 DO_NOT_CHECK_LOGIN=True, 

321 ), 

322 ] 

323 ) 

324 if self.GUEST_LOGIN is None: 

325 self.GUEST_LOGIN = True 

326 # Initialize 

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

328 # Set session options 

329 self.session.ssp = ssp 

330 self.session.SigningRequired = ( 

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

332 ) 

333 

334 @property 

335 def session(self): 

336 # session shorthand 

337 return self.sock.session 

338 

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

340 """ 

341 Verbose print (if enabled) 

342 """ 

343 if self.verb: 

344 if conf.interactive: 

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

346 else: 

347 print("> %s" % s) 

348 

349 def send(self, pkt): 

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

351 self.NextForceSign = self.NextForceEncrypt = False 

352 return super(SMB_Server, self).send( 

353 pkt, 

354 Compounded=self.NextCompound, 

355 ForceSign=ForceSign, 

356 ForceEncrypt=ForceEncrypt, 

357 ) 

358 

359 @ATMT.state(initial=1) 

360 def BEGIN(self): 

361 self.authenticated = False 

362 

363 @ATMT.receive_condition(BEGIN) 

364 def received_negotiate(self, pkt): 

365 if SMBNegotiate_Request in pkt: 

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

367 

368 @ATMT.receive_condition(BEGIN) 

369 def received_negotiate_smb2_begin(self, pkt): 

370 if SMB2_Negotiate_Protocol_Request in pkt: 

371 self.SMB2 = True 

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

373 

374 @ATMT.action(received_negotiate_smb2_begin) 

375 def on_negotiate_smb2_begin(self, pkt): 

376 self.on_negotiate(pkt) 

377 

378 @ATMT.action(received_negotiate) 

379 def on_negotiate(self, pkt): 

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

381 # Build negotiate response 

382 DialectIndex = None 

383 DialectRevision = None 

384 if SMB2_Negotiate_Protocol_Request in pkt: 

385 # SMB2 

386 DialectRevisions = pkt[SMB2_Negotiate_Protocol_Request].Dialects 

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

388 DialectRevisions.sort(reverse=True) 

389 if DialectRevisions: 

390 DialectRevision = DialectRevisions[0] 

391 else: 

392 # SMB1 

393 DialectIndexes = [ 

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

395 ] 

396 if self.USE_SMB1: 

397 # Enforce SMB1 

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

399 else: 

400 # Find a value matching SMB2, fallback to SMB1 

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

402 try: 

403 DialectIndex = DialectIndexes.index(key) 

404 DialectRevision = rev 

405 self.SMB2 = True 

406 break 

407 except ValueError: 

408 pass 

409 else: 

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

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

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

413 self.session.Dialect = DialectRevision 

414 cls = None 

415 if self.SMB2: 

416 # SMB2 

417 cls = SMB2_Negotiate_Protocol_Response 

418 self.smb_header = DirectTCP() / SMB2_Header( 

419 Flags="SMB2_FLAGS_SERVER_TO_REDIR", 

420 CreditRequest=1, 

421 CreditCharge=1, 

422 ) 

423 if SMB2_Negotiate_Protocol_Request in pkt: 

424 self.update_smbheader(pkt) 

425 else: 

426 # SMB1 

427 self.smb_header = DirectTCP() / SMB_Header( 

428 Flags="REPLY+CASE_INSENSITIVE+CANONICALIZED_PATHS", 

429 Flags2=( 

430 "LONG_NAMES+EAS+NT_STATUS+SMB_SECURITY_SIGNATURE+" 

431 "UNICODE+EXTENDED_SECURITY" 

432 ), 

433 TID=pkt.TID, 

434 MID=pkt.MID, 

435 UID=pkt.UID, 

436 PIDLow=pkt.PIDLow, 

437 ) 

438 if self.EXTENDED_SECURITY: 

439 cls = SMBNegotiate_Response_Extended_Security 

440 else: 

441 cls = SMBNegotiate_Response_Security 

442 if DialectRevision is None and DialectIndex is None: 

443 # No common dialect found. 

444 if self.SMB2: 

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

446 resp.Command = "SMB2_NEGOTIATE" 

447 else: 

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

449 resp.Command = "SMB_COM_NEGOTIATE" 

450 resp.Status = "STATUS_NOT_SUPPORTED" 

451 self.send(resp) 

452 return 

453 if self.SMB2: # SMB2 

454 # SecurityMode 

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

456 self.session.SigningRequired = True 

457 # Capabilities: [MS-SMB2] 3.3.5.4 

458 self.NegotiateCapabilities = "+".join( 

459 [ 

460 "DFS", 

461 "LEASING", 

462 "LARGE_MTU", 

463 ] 

464 ) 

465 if DialectRevision >= 0x0300: 

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

467 # the server supports..." 

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

469 [ 

470 "MULTI_CHANNEL", 

471 "PERSISTENT_HANDLES", 

472 "DIRECTORY_LEASING", 

473 "ENCRYPTION", 

474 ] 

475 ) 

476 # Build response 

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

478 DialectRevision=DialectRevision, 

479 SecurityMode=( 

480 "SIGNING_ENABLED+SIGNING_REQUIRED" 

481 if self.session.SigningRequired 

482 else "SIGNING_ENABLED" 

483 ), 

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

485 ServerStartTime=0, 

486 MaxTransactionSize=65536, 

487 MaxReadSize=65536, 

488 MaxWriteSize=65536, 

489 Capabilities=self.NegotiateCapabilities, 

490 ) 

491 # SMB >= 3.0.0 

492 if DialectRevision >= 0x0300: 

493 # [MS-SMB2] sect 3.3.5.3.1 note 253 

494 resp.MaxTransactionSize = 0x800000 

495 resp.MaxReadSize = 0x800000 

496 resp.MaxWriteSize = 0x800000 

497 # SMB 3.1.1 

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

499 # Negotiate context-capabilities 

500 for ngctx in pkt.NegotiateContexts: 

501 if ngctx.ContextType == 0x0002: 

502 # SMB2_ENCRYPTION_CAPABILITIES 

503 for ciph in ngctx.Ciphers: 

504 tciph = SMB2_ENCRYPTION_CIPHERS.get(ciph, None) 

505 if tciph in self.session.SupportedCipherIds: 

506 # Common ! 

507 self.session.CipherId = tciph 

508 self.session.SupportsEncryption = True 

509 break 

510 elif ngctx.ContextType == 0x0008: 

511 # SMB2_SIGNING_CAPABILITIES 

512 for signalg in ngctx.SigningAlgorithms: 

513 tsignalg = SMB2_SIGNING_ALGORITHMS.get(signalg, None) 

514 if tsignalg in self.session.SupportedSigningAlgorithmIds: 

515 # Common ! 

516 self.session.SigningAlgorithmId = tsignalg 

517 break 

518 # Send back the negotiated algorithms 

519 resp.NegotiateContexts = [ 

520 # Preauth capabilities 

521 SMB2_Negotiate_Context() 

522 / SMB2_Preauth_Integrity_Capabilities( 

523 # SHA-512 by default 

524 HashAlgorithms=[self.session.PreauthIntegrityHashId], 

525 Salt=self.session.Salt, 

526 ), 

527 # Encryption capabilities 

528 SMB2_Negotiate_Context() 

529 / SMB2_Encryption_Capabilities( 

530 # AES-128-CCM by default 

531 Ciphers=[self.session.CipherId], 

532 ), 

533 # Signing capabilities 

534 SMB2_Negotiate_Context() 

535 / SMB2_Signing_Capabilities( 

536 # AES-128-CCM by default 

537 SigningAlgorithms=[self.session.SigningAlgorithmId], 

538 ), 

539 ] 

540 else: 

541 # SMB1 

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

543 DialectIndex=DialectIndex, 

544 ServerCapabilities=( 

545 "UNICODE+LARGE_FILES+NT_SMBS+RPC_REMOTE_APIS+STATUS32+" 

546 "LEVEL_II_OPLOCKS+LOCK_AND_READ+NT_FIND+" 

547 "LWIO+INFOLEVEL_PASSTHRU+LARGE_READX+LARGE_WRITEX" 

548 ), 

549 SecurityMode=( 

550 "SIGNING_ENABLED+SIGNING_REQUIRED" 

551 if self.session.SigningRequired 

552 else "SIGNING_ENABLED" 

553 ), 

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

555 ServerTimeZone=0x3C, 

556 ) 

557 if self.EXTENDED_SECURITY: 

558 resp.ServerCapabilities += "EXTENDED_SECURITY" 

559 if self.EXTENDED_SECURITY or self.SMB2: 

560 # Extended SMB1 / SMB2 

561 resp.GUID = self.GUID 

562 # Add security blob 

563 resp.SecurityBlob = spnego_token 

564 else: 

565 # Non-extended SMB1 

566 # FIXME never tested. 

567 resp.SecurityBlob = spnego_token 

568 resp.Flags2 -= "EXTENDED_SECURITY" 

569 if not self.SMB2: 

570 resp[SMB_Header].Flags2 = ( 

571 resp[SMB_Header].Flags2 

572 - "SMB_SECURITY_SIGNATURE" 

573 + "SMB_SECURITY_SIGNATURE_REQUIRED+IS_LONG_NAME" 

574 ) 

575 if SMB2_Header in pkt: 

576 # If required, compute sessions 

577 self.session.computeSMBConnectionPreauth( 

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

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

580 ) 

581 self.send(resp) 

582 

583 @ATMT.state(final=1) 

584 def NEGO_FAILED(self): 

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

586 self.end() 

587 

588 @ATMT.state() 

589 def NEGOTIATED(self): 

590 pass 

591 

592 def update_smbheader(self, pkt): 

593 """ 

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

595 """ 

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

597 self.smb_header.CreditRequest = pkt.CreditRequest 

598 # [MS-SMB2] sect 3.3.4.1 

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

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

601 self.smb_header.CreditCharge = pkt.CreditCharge 

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

603 self.NextCompound = bool(pkt.NextCommand) 

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

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

606 if ( 

607 not self.session.SigningRequired 

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

609 ): 

610 self.NextForceSign = True 

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

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

613 if pkt[SMB2_Header]._decrypted: 

614 self.NextForceEncrypt = True 

615 # [MS-SMB2] sect 3.3.5.2.7.2 

616 # Add SMB2_FLAGS_RELATED_OPERATIONS to the response if present 

617 if pkt.Flags.SMB2_FLAGS_RELATED_OPERATIONS: 

618 self.smb_header.Flags += "SMB2_FLAGS_RELATED_OPERATIONS" 

619 else: 

620 self.smb_header.Flags -= "SMB2_FLAGS_RELATED_OPERATIONS" 

621 # [MS-SMB2] sect 2.2.1.2 - Priority 

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

623 self.smb_header.Flags &= 0xFF8F 

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

625 # Update IDs 

626 self.smb_header.SessionId = pkt.SessionId 

627 self.smb_header.TID = pkt.TID 

628 self.smb_header.MID = pkt.MID 

629 self.smb_header.PID = pkt.PID 

630 

631 @ATMT.receive_condition(NEGOTIATED) 

632 def received_negotiate_smb2(self, pkt): 

633 if SMB2_Negotiate_Protocol_Request in pkt: 

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

635 

636 @ATMT.action(received_negotiate_smb2) 

637 def on_negotiate_smb2(self, pkt): 

638 self.on_negotiate(pkt) 

639 

640 @ATMT.receive_condition(NEGOTIATED) 

641 def receive_setup_andx_request(self, pkt): 

642 if ( 

643 SMBSession_Setup_AndX_Request_Extended_Security in pkt 

644 or SMBSession_Setup_AndX_Request in pkt 

645 ): 

646 # SMB1 

647 if SMBSession_Setup_AndX_Request_Extended_Security in pkt: 

648 # Extended 

649 ssp_blob = pkt.SecurityBlob 

650 else: 

651 # Non-extended 

652 ssp_blob = pkt[SMBSession_Setup_AndX_Request].UnicodePassword 

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

654 elif SMB2_Session_Setup_Request in pkt: 

655 # SMB2 

656 ssp_blob = pkt.SecurityBlob 

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

658 

659 @ATMT.state() 

660 def RECEIVED_SETUP_ANDX_REQUEST(self): 

661 pass 

662 

663 @ATMT.action(receive_setup_andx_request) 

664 def on_setup_andx_request(self, pkt, ssp_blob): 

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

666 self.session.sspcontext, 

667 ssp_blob, 

668 ) 

669 self.update_smbheader(pkt) 

670 if SMB2_Session_Setup_Request in pkt: 

671 # SMB2 

672 self.smb_header.SessionId = 0x0001000000000015 

673 if status not in [GSS_S_CONTINUE_NEEDED, GSS_S_COMPLETE]: 

674 # Error 

675 if SMB2_Session_Setup_Request in pkt: 

676 # SMB2 

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

678 # Set security blob (if any) 

679 resp.SecurityBlob = tok 

680 else: 

681 # SMB1 

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

683 # Map some GSS return codes to NTStatus 

684 if status == GSS_S_CREDENTIALS_EXPIRED: 

685 resp.Status = "STATUS_PASSWORD_EXPIRED" 

686 else: 

687 resp.Status = "STATUS_LOGON_FAILURE" 

688 # Reset Session preauth (SMB 3.1.1) 

689 self.session.SessionPreauthIntegrityHashValue = None 

690 else: 

691 # Negotiation 

692 if ( 

693 SMBSession_Setup_AndX_Request_Extended_Security in pkt 

694 or SMB2_Session_Setup_Request in pkt 

695 ): 

696 # SMB1 extended / SMB2 

697 if SMB2_Session_Setup_Request in pkt: 

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

699 if self.GUEST_LOGIN: 

700 # "If the security subsystem indicates that the session 

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

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

703 resp.SessionFlags = "IS_GUEST" 

704 self.session.IsGuest = True 

705 self.session.SigningRequired = False 

706 if self.ANONYMOUS_LOGIN: 

707 resp.SessionFlags = "IS_NULL" 

708 # [MS-SMB2] sect 3.3.5.5.3 

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

710 resp.SessionFlags += "ENCRYPT_DATA" 

711 else: 

712 # SMB1 extended 

713 resp = ( 

714 self.smb_header.copy() 

715 / SMBSession_Setup_AndX_Response_Extended_Security( 

716 NativeOS="Windows 4.0", 

717 NativeLanMan="Windows 4.0", 

718 ) 

719 ) 

720 if self.GUEST_LOGIN: 

721 resp.Action = "SMB_SETUP_GUEST" 

722 # Set security blob 

723 resp.SecurityBlob = tok 

724 elif SMBSession_Setup_AndX_Request in pkt: 

725 # Non-extended 

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

727 NativeOS="Windows 4.0", 

728 NativeLanMan="Windows 4.0", 

729 ) 

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

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

732 if status == GSS_S_CONTINUE_NEEDED: 

733 # the setup session response is used in hash 

734 self.session.computeSMBSessionPreauth( 

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

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

737 ) 

738 else: 

739 # the setup session response is not used in hash 

740 self.session.computeSMBSessionPreauth( 

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

742 ) 

743 if status == GSS_S_COMPLETE: 

744 # Authentication was successful 

745 self.session.computeSMBSessionKeys(IsClient=False) 

746 self.authenticated = True 

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

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

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

750 self.NextForceSign = True 

751 self.send(resp) 

752 # Check whether we must enable encryption from now on 

753 if ( 

754 self.authenticated 

755 and not self.session.IsGuest 

756 and self.session.Dialect >= 0x0300 

757 and self.REQUIRE_ENCRYPTION 

758 ): 

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

760 self.session.EncryptData = True 

761 self.session.SigningRequired = False 

762 

763 @ATMT.condition(RECEIVED_SETUP_ANDX_REQUEST) 

764 def wait_for_next_request(self): 

765 if self.authenticated: 

766 self.vprint( 

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

768 ) 

769 raise self.AUTHENTICATED() 

770 else: 

771 raise self.NEGOTIATED() 

772 

773 @ATMT.state() 

774 def AUTHENTICATED(self): 

775 """Dev: overload this""" 

776 pass 

777 

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

779 

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

781 def should_serve(self): 

782 # Serve files 

783 self.current_trees = {} 

784 self.current_handles = {} 

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

786 self.tree_id = 0 

787 self.base_time_t = self.current_smb_time() 

788 raise self.SERVING() 

789 

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

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

792 pkt.Status = Status 

793 pkt.Command = "SMB2_IOCTL" 

794 self.send(pkt) 

795 

796 @ATMT.state(final=1) 

797 def END(self): 

798 self.end() 

799 

800 # SERVE FILES 

801 

802 def current_tree(self): 

803 """ 

804 Return the current tree name 

805 """ 

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

807 

808 def root_path(self): 

809 """ 

810 Return the root path of the current tree 

811 """ 

812 curtree = self.current_tree() 

813 try: 

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

815 except StopIteration: 

816 return None 

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

818 

819 @ATMT.state() 

820 def SERVING(self): 

821 """ 

822 Main state when serving files 

823 """ 

824 pass 

825 

826 @ATMT.receive_condition(SERVING) 

827 def receive_logoff_request(self, pkt): 

828 if SMB2_Session_Logoff_Request in pkt: 

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

830 

831 @ATMT.action(receive_logoff_request) 

832 def send_logoff_response(self, pkt): 

833 self.update_smbheader(pkt) 

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

835 

836 @ATMT.receive_condition(SERVING) 

837 def receive_setup_andx_request_in_serving(self, pkt): 

838 self.receive_setup_andx_request(pkt) 

839 

840 @ATMT.receive_condition(SERVING) 

841 def is_smb1_tree(self, pkt): 

842 if SMBTree_Connect_AndX in pkt: 

843 # Unsupported 

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

845 raise self.END() 

846 

847 @ATMT.receive_condition(SERVING) 

848 def receive_tree_connect(self, pkt): 

849 if SMB2_Tree_Connect_Request in pkt: 

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

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

852 

853 @ATMT.action(receive_tree_connect) 

854 def send_tree_connect_response(self, pkt, tree_name): 

855 self.update_smbheader(pkt) 

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

857 try: 

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

859 except StopIteration: 

860 # Unknown tree 

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

862 resp.Command = "SMB2_TREE_CONNECT" 

863 resp.Status = "STATUS_BAD_NETWORK_NAME" 

864 self.send(resp) 

865 return 

866 # Add tree to current trees 

867 if tree_name not in self.current_trees: 

868 self.tree_id += 1 

869 self.smb_header.TID = self.tree_id 

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

871 

872 # Construct ShareFlags 

873 ShareFlags = ( 

874 "AUTO_CACHING+NO_CACHING" 

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

876 else self.TREE_SHARE_FLAGS 

877 ) 

878 # [MS-SMB2] sect 3.3.5.7 

879 if ( 

880 self.session.Dialect >= 0x0311 

881 and not self.session.EncryptData 

882 and share.encryptdata 

883 ): 

884 if not self.session.SupportsEncryption: 

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

886 ShareFlags += "+ENCRYPT_DATA" 

887 

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

889 self.send( 

890 self.smb_header.copy() 

891 / SMB2_Tree_Connect_Response( 

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

893 ShareFlags=ShareFlags, 

894 Capabilities=( 

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

896 ), 

897 MaximalAccess=self.TREE_MAXIMAL_ACCESS, 

898 ) 

899 ) 

900 

901 @ATMT.receive_condition(SERVING) 

902 def receive_ioctl(self, pkt): 

903 if SMB2_IOCTL_Request in pkt: 

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

905 

906 @ATMT.action(receive_ioctl) 

907 def send_ioctl_response(self, pkt): 

908 self.update_smbheader(pkt) 

909 if pkt.CtlCode == 0x11C017: 

910 # FSCTL_PIPE_TRANSCEIVE 

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

912 self.send( 

913 self.smb_header.copy() 

914 / SMB2_IOCTL_Response( 

915 CtlCode=0x11C017, 

916 FileId=pkt[SMB2_IOCTL_Request].FileId, 

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

918 ) 

919 ) 

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

921 # FSCTL_VALIDATE_NEGOTIATE_INFO 

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

923 # what flags were negotiated during the SMBNegotiate exchange. 

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

925 

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

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

928 # > STATUS_NOT_SUPPORTED or STATUS_INVALID_DEVICE_REQUEST 

929 # > since they do not allow or implement 

930 # > FSCTL_VALIDATE_NEGOTIATE_INFO. 

931 # > The client should accept the 

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

933 

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

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

936 self._ioctl_error(Status="STATUS_FILE_CLOSED") 

937 return 

938 

939 # SMB3 

940 self.send( 

941 self.smb_header.copy() 

942 / SMB2_IOCTL_Response( 

943 CtlCode=0x00140204, 

944 FileId=pkt[SMB2_IOCTL_Request].FileId, 

945 Buffer=[ 

946 ( 

947 "Output", 

948 SMB2_IOCTL_Validate_Negotiate_Info_Response( 

949 GUID=self.GUID, 

950 DialectRevision=self.session.Dialect, 

951 SecurityMode=( 

952 "SIGNING_ENABLED+SIGNING_REQUIRED" 

953 if self.session.SigningRequired 

954 else "SIGNING_ENABLED" 

955 ), 

956 Capabilities=self.NegotiateCapabilities, 

957 ), 

958 ) 

959 ], 

960 ) 

961 ) 

962 elif pkt.CtlCode == 0x001401FC: 

963 # FSCTL_QUERY_NETWORK_INTERFACE_INFO 

964 self.send( 

965 self.smb_header.copy() 

966 / SMB2_IOCTL_Response( 

967 CtlCode=0x001401FC, 

968 FileId=pkt[SMB2_IOCTL_Request].FileId, 

969 Output=SMB2_IOCTL_Network_Interface_Info( 

970 interfaces=[ 

971 NETWORK_INTERFACE_INFO( 

972 SockAddr_Storage=SOCKADDR_STORAGE( 

973 Family=0x0002, 

974 IPv4Adddress=x, 

975 ) 

976 ) 

977 for x in self.LOCAL_IPS 

978 ] 

979 ), 

980 ) 

981 ) 

982 elif pkt.CtlCode == 0x00060194: 

983 # FSCTL_DFS_GET_REFERRALS 

984 if ( 

985 self.DOMAIN_REFERRALS 

986 and not pkt[SMB2_IOCTL_Request].Input.RequestFileName 

987 ): 

988 # Requesting domain referrals 

989 self.send( 

990 self.smb_header.copy() 

991 / SMB2_IOCTL_Response( 

992 CtlCode=0x00060194, 

993 FileId=pkt[SMB2_IOCTL_Request].FileId, 

994 Output=SMB2_IOCTL_RESP_GET_DFS_Referral( 

995 ReferralEntries=[ 

996 DFS_REFERRAL_V3( 

997 ReferralEntryFlags="NameListReferral", 

998 TimeToLive=600, 

999 ) 

1000 for _ in self.DOMAIN_REFERRALS 

1001 ], 

1002 ReferralBuffer=[ 

1003 DFS_REFERRAL_ENTRY1(SpecialName=name) 

1004 for name in self.DOMAIN_REFERRALS 

1005 ], 

1006 ), 

1007 ) 

1008 ) 

1009 return 

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

1011 resp.Command = "SMB2_IOCTL" 

1012 resp.Status = "STATUS_FS_DRIVER_REQUIRED" 

1013 self.send(resp) 

1014 else: 

1015 # Among other things, FSCTL_VALIDATE_NEGOTIATE_INFO 

1016 self._ioctl_error(Status="STATUS_NOT_SUPPORTED") 

1017 

1018 @ATMT.receive_condition(SERVING) 

1019 def receive_create_file(self, pkt): 

1020 if SMB2_Create_Request in pkt: 

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

1022 

1023 PIPES_TABLE = { 

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

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

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

1027 } 

1028 

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

1030 # that points to the chained opened file handle 

1031 LAST_HANDLE = SMB2_FILEID( 

1032 Persistent=0xFFFFFFFFFFFFFFFF, Volatile=0xFFFFFFFFFFFFFFFF 

1033 ) 

1034 

1035 def current_smb_time(self): 

1036 return ( 

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

1038 - 864000000000 # one day ago 

1039 ) 

1040 

1041 def make_file_id(self, fname): 

1042 """ 

1043 Generate deterministic FileId based on the fname 

1044 """ 

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

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

1047 

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

1049 """ 

1050 Lookup the file and build it's SMB2_FILEID 

1051 """ 

1052 root = self.root_path() 

1053 if isinstance(fname, pathlib.Path): 

1054 path = fname 

1055 fname = path.name 

1056 else: 

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

1058 path = path.resolve() 

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

1060 # resolve(). Be careful 

1061 # Note: symbolic links are currently unsupported. 

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

1063 raise FileNotFoundError 

1064 if WINDOWS and path.is_reserved(): 

1065 raise FileNotFoundError 

1066 if not path.exists(): 

1067 if create and createOptions: 

1068 if createOptions.FILE_DIRECTORY_FILE: 

1069 # Folder creation 

1070 path.mkdir() 

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

1072 else: 

1073 # File creation 

1074 path.touch() 

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

1076 else: 

1077 raise FileNotFoundError 

1078 if durable_handle is None: 

1079 handle = SMB2_FILEID( 

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

1081 ) 

1082 else: 

1083 # We were given a durable handle. Use it 

1084 handle = durable_handle 

1085 attrs = { 

1086 "CreationTime": self.base_time_t, 

1087 "LastAccessTime": self.base_time_t, 

1088 "LastWriteTime": self.base_time_t, 

1089 "ChangeTime": self.base_time_t, 

1090 "EndOfFile": 0, 

1091 "AllocationSize": 0, 

1092 } 

1093 path_stat = path.stat() 

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

1095 if fname is None: 

1096 # special case 

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

1098 [ 

1099 "FILE_ATTRIBUTE_HIDDEN", 

1100 "FILE_ATTRIBUTE_SYSTEM", 

1101 "FILE_ATTRIBUTE_DIRECTORY", 

1102 ] 

1103 ) 

1104 elif path.is_dir(): 

1105 attrs["FileAttributes"] = "FILE_ATTRIBUTE_DIRECTORY" 

1106 else: 

1107 attrs["FileAttributes"] = "FILE_ATTRIBUTE_ARCHIVE" 

1108 self.current_handles[handle] = ( 

1109 path, # file path 

1110 attrs, # file attributes 

1111 ) 

1112 self.enumerate_index[handle] = 0 

1113 return handle 

1114 

1115 def set_compounded_handle(self, handle): 

1116 """ 

1117 Mark a handle as the current one being compounded. 

1118 """ 

1119 self.CompoundedHandle = handle 

1120 

1121 def get_file_id(self, pkt): 

1122 """ 

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

1124 """ 

1125 fid = pkt.FileId 

1126 if fid == self.LAST_HANDLE: 

1127 return self.CompoundedHandle 

1128 return fid 

1129 

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

1131 """ 

1132 Lookup a folder handle 

1133 """ 

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

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

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

1137 if not path.is_dir(): 

1138 raise NotADirectoryError 

1139 return sorted( 

1140 [ 

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

1142 for x in path.glob(filter) 

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

1144 # for path traversal on them. 

1145 if not x.is_symlink() 

1146 ] 

1147 + [ 

1148 cls( 

1149 FileAttributes=("FILE_ATTRIBUTE_DIRECTORY"), 

1150 FileName=".", 

1151 ) 

1152 ] 

1153 + ( 

1154 [ 

1155 cls( 

1156 FileAttributes=("FILE_ATTRIBUTE_DIRECTORY"), 

1157 FileName="..", 

1158 ) 

1159 ] 

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

1161 else [] 

1162 ), 

1163 key=lambda x: x.FileName, 

1164 )[offset:] 

1165 

1166 @ATMT.action(receive_create_file) 

1167 def send_create_file_response(self, pkt): 

1168 """ 

1169 Handle CreateFile request 

1170 

1171 See [MS-SMB2] 3.3.5.9 () 

1172 """ 

1173 self.update_smbheader(pkt) 

1174 if pkt[SMB2_Create_Request].NameLen: 

1175 fname = pkt[SMB2_Create_Request].Name 

1176 else: 

1177 fname = None 

1178 if fname: 

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

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

1181 # Special IPC$ case: opening a pipe 

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

1183 if FILE_ID: 

1184 attrs = { 

1185 "CreationTime": 0, 

1186 "LastAccessTime": 0, 

1187 "LastWriteTime": 0, 

1188 "ChangeTime": 0, 

1189 "EndOfFile": 0, 

1190 "AllocationSize": 4096, 

1191 } 

1192 self.current_handles[FILE_ID] = ( 

1193 fname, 

1194 attrs, 

1195 ) 

1196 self.send( 

1197 self.smb_header.copy() 

1198 / SMB2_Create_Response( 

1199 OplockLevel=pkt.RequestedOplockLevel, 

1200 FileId=FILE_ID, 

1201 **attrs, 

1202 ) 

1203 ) 

1204 else: 

1205 # NOT_FOUND 

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

1207 resp.Command = "SMB2_CREATE" 

1208 resp.Status = "STATUS_OBJECT_NAME_NOT_FOUND" 

1209 self.send(resp) 

1210 return 

1211 else: 

1212 # Check if there is a Durable Handle Reconnect Request 

1213 durable_handle = None 

1214 if pkt[SMB2_Create_Request].CreateContextsLen: 

1215 try: 

1216 durable_handle = next( 

1217 x.Data.FileId 

1218 for x in pkt[SMB2_Create_Request].CreateContexts 

1219 if x.Name == b"DH2C" 

1220 ) 

1221 except StopIteration: 

1222 pass 

1223 # Lookup file handle 

1224 try: 

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

1226 except FileNotFoundError: 

1227 # NOT_FOUND 

1228 if pkt[SMB2_Create_Request].CreateDisposition in [ 

1229 0x00000002, # FILE_CREATE 

1230 0x00000005, # FILE_OVERWRITE_IF 

1231 ]: 

1232 if self.readonly: 

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

1234 resp.Command = "SMB2_CREATE" 

1235 resp.Status = "STATUS_ACCESS_DENIED" 

1236 self.send(resp) 

1237 return 

1238 else: 

1239 # Create file 

1240 handle = self.lookup_file( 

1241 fname, 

1242 durable_handle=durable_handle, 

1243 create=True, 

1244 createOptions=pkt[SMB2_Create_Request].CreateOptions, 

1245 ) 

1246 else: 

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

1248 resp.Command = "SMB2_CREATE" 

1249 resp.Status = "STATUS_OBJECT_NAME_NOT_FOUND" 

1250 self.send(resp) 

1251 return 

1252 # Store compounded handle 

1253 self.set_compounded_handle(handle) 

1254 # Build response 

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

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

1257 OplockLevel=pkt.RequestedOplockLevel, 

1258 FileId=handle, 

1259 **attrs, 

1260 ) 

1261 # Handle the various chain elements 

1262 if pkt[SMB2_Create_Request].CreateContextsLen: 

1263 CreateContexts = [] 

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

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

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

1267 for elt in pkt[SMB2_Create_Request].CreateContexts: 

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

1269 # [MS-SMB2] sect 3.3.5.9.9 

1270 CreateContexts.append( 

1271 SMB2_Create_Context( 

1272 Name=b"QFid", 

1273 Data=SMB2_CREATE_QUERY_ON_DISK_ID( 

1274 DiskFileId=self.make_file_id(fname), 

1275 VolumeId=0xBA39CD11, 

1276 ), 

1277 ) 

1278 ) 

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

1280 # [MS-SMB2] sect 3.3.5.9.5 

1281 CreateContexts.append( 

1282 SMB2_Create_Context( 

1283 Name=b"MxAc", 

1284 Data=SMB2_CREATE_QUERY_MAXIMAL_ACCESS_RESPONSE( 

1285 QueryStatus=0, 

1286 MaximalAccess=self.FILE_MAXIMAL_ACCESS, 

1287 ), 

1288 ) 

1289 ) 

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

1291 # [MS-SMB2] sect 3.3.5.9.10 

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

1293 continue 

1294 CreateContexts.append( 

1295 SMB2_Create_Context( 

1296 Name=b"DH2Q", 

1297 Data=SMB2_CREATE_DURABLE_HANDLE_RESPONSE_V2( 

1298 Timeout=180000 

1299 ), 

1300 ) 

1301 ) 

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

1303 # [MS-SMB2] sect 3.3.5.9.11 

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

1305 CreateContexts.append( 

1306 SMB2_Create_Context( 

1307 Name=b"RqLs", 

1308 Data=elt.Data, 

1309 ) 

1310 ) 

1311 resp.CreateContexts = CreateContexts 

1312 self.send(resp) 

1313 

1314 @ATMT.receive_condition(SERVING) 

1315 def receive_change_notify_info(self, pkt): 

1316 if SMB2_Change_Notify_Request in pkt: 

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

1318 

1319 @ATMT.action(receive_change_notify_info) 

1320 def send_change_notify_info_response(self, pkt): 

1321 # [MS-SMB2] sect 3.3.5.19 

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

1323 # server MUST fail this request with STATUS_NOT_SUPPORTED." 

1324 self.update_smbheader(pkt) 

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

1326 resp.Command = "SMB2_CHANGE_NOTIFY" 

1327 # ScapyFS doesn't support notifications 

1328 resp.Status = "STATUS_NOT_SUPPORTED" 

1329 self.send(resp) 

1330 

1331 @ATMT.receive_condition(SERVING) 

1332 def receive_query_directory_info(self, pkt): 

1333 if SMB2_Query_Directory_Request in pkt: 

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

1335 

1336 @ATMT.action(receive_query_directory_info) 

1337 def send_query_directory_response(self, pkt): 

1338 self.update_smbheader(pkt) 

1339 if not pkt.FileNameLen: 

1340 # this is broken. 

1341 return 

1342 query = pkt.FileName 

1343 fid = self.get_file_id(pkt) 

1344 # Check for handled FileInformationClass 

1345 # 0x02: FileFullDirectoryInformation 

1346 # 0x03: FileBothDirectoryInformation 

1347 # 0x25: FileIdBothDirectoryInformation 

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

1349 # Unknown FileInformationClass 

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

1351 resp.Command = "SMB2_QUERY_DIRECTORY" 

1352 resp.Status = "STATUS_INVALID_INFO_CLASS" 

1353 self.send(resp) 

1354 return 

1355 # Handle SMB2_RESTART_SCANS 

1356 if pkt[SMB2_Query_Directory_Request].Flags.SMB2_RESTART_SCANS: 

1357 self.enumerate_index[fid] = 0 

1358 # Lookup the files 

1359 try: 

1360 files = self.lookup_folder( 

1361 fid, 

1362 query, 

1363 self.enumerate_index[fid], 

1364 { 

1365 0x02: FILE_FULL_DIR_INFORMATION, 

1366 0x03: FILE_BOTH_DIR_INFORMATION, 

1367 0x25: FILE_ID_BOTH_DIR_INFORMATION, 

1368 }[pkt.FileInformationClass], 

1369 ) 

1370 except NotADirectoryError: 

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

1372 resp.Command = "SMB2_QUERY_DIRECTORY" 

1373 resp.Status = "STATUS_INVALID_PARAMETER" 

1374 self.send(resp) 

1375 return 

1376 if not files: 

1377 # No more files ! 

1378 self.enumerate_index[fid] = 0 

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

1380 resp.Command = "SMB2_QUERY_DIRECTORY" 

1381 resp.Status = "STATUS_NO_MORE_FILES" 

1382 self.send(resp) 

1383 return 

1384 # Handle SMB2_RETURN_SINGLE_ENTRY 

1385 if pkt[SMB2_Query_Directory_Request].Flags.SMB2_RETURN_SINGLE_ENTRY: 

1386 files = files[:1] 

1387 # Increment index 

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

1389 # Build response based on the FileInformationClass 

1390 fileinfo = FileIdBothDirectoryInformation( 

1391 files=files, 

1392 ) 

1393 self.send( 

1394 self.smb_header.copy() 

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

1396 ) 

1397 

1398 @ATMT.receive_condition(SERVING) 

1399 def receive_query_info(self, pkt): 

1400 if SMB2_Query_Info_Request in pkt: 

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

1402 

1403 @ATMT.action(receive_query_info) 

1404 def send_query_info_response(self, pkt): 

1405 self.update_smbheader(pkt) 

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

1407 fid = self.get_file_id(pkt) 

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

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

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

1411 fileinfo = FileStandardInformation( 

1412 EndOfFile=attrs["EndOfFile"], 

1413 AllocationSize=attrs["AllocationSize"], 

1414 ) 

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

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

1417 fileinfo = FileInternalInformation( 

1418 IndexNumber=hash(pth) & 0xFFFFFFFFFFFFFFFF, 

1419 ) 

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

1421 fileinfo = FileEaInformation() 

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

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

1424 fileinfo = FileAllInformation( 

1425 BasicInformation=FileBasicInformation( 

1426 CreationTime=attrs["CreationTime"], 

1427 LastAccessTime=attrs["LastAccessTime"], 

1428 LastWriteTime=attrs["LastWriteTime"], 

1429 ChangeTime=attrs["ChangeTime"], 

1430 FileAttributes=attrs["FileAttributes"], 

1431 ), 

1432 StandardInformation=FileStandardInformation( 

1433 EndOfFile=attrs["EndOfFile"], 

1434 AllocationSize=attrs["AllocationSize"], 

1435 ), 

1436 ) 

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

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

1439 fileinfo = FileAlternateNameInformation( 

1440 FileName=pth.name, 

1441 ) 

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

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

1444 fileinfo = FileStreamInformation( 

1445 StreamSize=attrs["EndOfFile"], 

1446 StreamAllocationSize=attrs["AllocationSize"], 

1447 ) 

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

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

1450 fileinfo = FileNetworkOpenInformation( 

1451 **attrs, 

1452 ) 

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

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

1455 fileinfo = FILE_NAME_INFORMATION( 

1456 FileName=pth.name, 

1457 ) 

1458 else: 

1459 log_runtime.warning( 

1460 "Unimplemented: %s" 

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

1462 ) 

1463 return 

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

1465 # [MS-FSCC] sect 2.5 

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

1467 fileinfo = FileFsVolumeInformation() 

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

1469 fileinfo = FileFsSizeInformation() 

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

1471 fileinfo = FileFsAttributeInformation( 

1472 FileSystemAttributes=0x88000F, 

1473 ) 

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

1475 fileinfo = FileEaInformation() 

1476 else: 

1477 log_runtime.warning( 

1478 "Unimplemented: %s" 

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

1480 ) 

1481 return 

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

1483 # [MS-FSCC] 2.4.6 

1484 fileinfo = SECURITY_DESCRIPTOR() 

1485 # TODO: fill it 

1486 if pkt.AdditionalInformation.OWNER_SECURITY_INFORMATION: 

1487 pass 

1488 if pkt.AdditionalInformation.GROUP_SECURITY_INFORMATION: 

1489 pass 

1490 if pkt.AdditionalInformation.DACL_SECURITY_INFORMATION: 

1491 pass 

1492 if pkt.AdditionalInformation.SACL_SECURITY_INFORMATION: 

1493 pass 

1494 # Observed: 

1495 if ( 

1496 pkt.AdditionalInformation.OWNER_SECURITY_INFORMATION 

1497 or pkt.AdditionalInformation.SACL_SECURITY_INFORMATION 

1498 or pkt.AdditionalInformation.GROUP_SECURITY_INFORMATION 

1499 or pkt.AdditionalInformation.DACL_SECURITY_INFORMATION 

1500 ): 

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

1502 pkt.Status = "STATUS_ACCESS_DENIED" 

1503 pkt.Command = "SMB2_QUERY_INFO" 

1504 self.send(pkt) 

1505 return 

1506 if pkt.AdditionalInformation.ATTRIBUTE_SECURITY_INFORMATION: 

1507 fileinfo.Control = 0x8800 

1508 self.send( 

1509 self.smb_header.copy() 

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

1511 ) 

1512 

1513 @ATMT.receive_condition(SERVING) 

1514 def receive_set_info_request(self, pkt): 

1515 if SMB2_Set_Info_Request in pkt: 

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

1517 

1518 @ATMT.action(receive_set_info_request) 

1519 def send_set_info_response(self, pkt): 

1520 self.update_smbheader(pkt) 

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

1522 

1523 @ATMT.receive_condition(SERVING) 

1524 def receive_write_request(self, pkt): 

1525 if SMB2_Write_Request in pkt: 

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

1527 

1528 @ATMT.action(receive_write_request) 

1529 def send_write_response(self, pkt): 

1530 self.update_smbheader(pkt) 

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

1532 fid = self.get_file_id(pkt) 

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

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

1535 # A pipe 

1536 self.rpc_server.recv(pkt.Data) 

1537 else: 

1538 if self.readonly: 

1539 # Read only ! 

1540 resp = SMB2_Error_Response() 

1541 resp.Command = "SMB2_WRITE" 

1542 resp.Status = "ERROR_FILE_READ_ONLY" 

1543 else: 

1544 # Write file 

1545 pth, _ = self.current_handles[fid] 

1546 length = pkt[SMB2_Write_Request].DataLen 

1547 off = pkt[SMB2_Write_Request].Offset 

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

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

1550 fd.seek(off) 

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

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

1553 

1554 @ATMT.receive_condition(SERVING) 

1555 def receive_read_request(self, pkt): 

1556 if SMB2_Read_Request in pkt: 

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

1558 

1559 @ATMT.action(receive_read_request) 

1560 def send_read_response(self, pkt): 

1561 self.update_smbheader(pkt) 

1562 resp = SMB2_Read_Response() 

1563 fid = self.get_file_id(pkt) 

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

1565 # Read output from DCE/RPC server 

1566 r = self.rpc_server.get_response() 

1567 resp.Data = bytes(r) 

1568 else: 

1569 # Read file and send content 

1570 pth, _ = self.current_handles[fid] 

1571 length = pkt[SMB2_Read_Request].Length 

1572 off = pkt[SMB2_Read_Request].Offset 

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

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

1575 fd.seek(off) 

1576 resp.Data = fd.read(length) 

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

1578 

1579 @ATMT.receive_condition(SERVING) 

1580 def receive_close_request(self, pkt): 

1581 if SMB2_Close_Request in pkt: 

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

1583 

1584 @ATMT.action(receive_close_request) 

1585 def send_close_response(self, pkt): 

1586 self.update_smbheader(pkt) 

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

1588 fid = self.get_file_id(pkt) 

1589 pth, attrs = self.current_handles[fid] 

1590 if pth: 

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

1592 del self.current_handles[fid] 

1593 del self.enumerate_index[fid] 

1594 self.send( 

1595 self.smb_header.copy() 

1596 / SMB2_Close_Response( 

1597 Flags=pkt[SMB2_Close_Request].Flags, 

1598 **attrs, 

1599 ) 

1600 ) 

1601 else: 

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

1603 

1604 @ATMT.receive_condition(SERVING) 

1605 def receive_tree_disconnect_request(self, pkt): 

1606 if SMB2_Tree_Disconnect_Request in pkt: 

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

1608 

1609 @ATMT.action(receive_tree_disconnect_request) 

1610 def send_tree_disconnect_response(self, pkt): 

1611 self.update_smbheader(pkt) 

1612 try: 

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

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

1615 except KeyError: 

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

1617 resp.Command = "SMB2_TREE_DISCONNECT" 

1618 resp.Status = "STATUS_NETWORK_NAME_DELETED" 

1619 self.send(resp) 

1620 

1621 @ATMT.receive_condition(SERVING) 

1622 def receive_cancel_request(self, pkt): 

1623 if SMB2_Cancel_Request in pkt: 

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

1625 

1626 @ATMT.action(receive_cancel_request) 

1627 def send_notify_cancel_response(self, pkt): 

1628 self.update_smbheader(pkt) 

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

1630 resp.Status = "STATUS_CANCELLED" 

1631 self.send(resp) 

1632 

1633 @ATMT.receive_condition(SERVING) 

1634 def receive_echo_request(self, pkt): 

1635 if SMB2_Echo_Request in pkt: 

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

1637 

1638 @ATMT.action(receive_echo_request) 

1639 def send_echo_reply(self, pkt): 

1640 self.update_smbheader(pkt) 

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

1642 

1643 

1644# DCE/RPC server for SMB 

1645 

1646 

1647class SMB_DCERPC_Server(DCERPC_Server): 

1648 """ 

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

1650 """ 

1651 

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

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

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

1655 

1656 @DCERPC_Server.answer(NetrShareEnum_Request) 

1657 def netr_share_enum(self, req): 

1658 """ 

1659 NetrShareEnum [MS-SRVS] 

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

1661 """ 

1662 level = req.InfoStruct.Level 

1663 

1664 # Create response 

1665 resp = NetrShareEnum_Response( 

1666 InfoStruct=LPSHARE_ENUM_STRUCT( 

1667 Level=level, 

1668 ShareInfo=NDRUnion( 

1669 tag=level, 

1670 value=None, 

1671 ), 

1672 ), 

1673 ndr64=self.ndr64, 

1674 ) 

1675 

1676 if level == 1: 

1677 nbEntries = len(self.shares) 

1678 resp.InfoStruct.ShareInfo.value = NDRPointer( 

1679 referent_id=0x20000, 

1680 value=SHARE_INFO_1_CONTAINER( 

1681 Buffer=[ 

1682 # Add shares 

1683 LPSHARE_INFO_1( 

1684 shi1_netname=x.name, 

1685 shi1_type=x.type, 

1686 shi1_remark=x.remark, 

1687 ) 

1688 for x in self.shares 

1689 ], 

1690 EntriesRead=nbEntries, 

1691 ), 

1692 ) 

1693 resp.TotalEntries = nbEntries 

1694 else: 

1695 # We only support level 1 :( 

1696 resp.status = 0x0000007C # ERROR_INVALID_LEVEL 

1697 

1698 return resp 

1699 

1700 @DCERPC_Server.answer(NetrWkstaGetInfo_Request) 

1701 def netr_wksta_getinfo(self, req): 

1702 """ 

1703 NetrWkstaGetInfo [MS-SRVS] 

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

1705 """ 

1706 level = req.Level 

1707 

1708 # Create response 

1709 resp = NetrWkstaGetInfo_Response( 

1710 WkstaInfo=NDRUnion( 

1711 tag=level, 

1712 value=None, 

1713 ), 

1714 ndr64=self.ndr64, 

1715 ) 

1716 

1717 if level == 100: 

1718 resp.WkstaInfo.value = NDRPointer( 

1719 referent_id=0x20000, 

1720 value=LPWKSTA_INFO_100( 

1721 wki100_platform_id=500, # NT 

1722 wki100_ver_major=5, 

1723 ), 

1724 ) 

1725 else: 

1726 # We only support level 101 :( 

1727 resp.status = 0x0000007C # ERROR_INVALID_LEVEL 

1728 

1729 return resp 

1730 

1731 @DCERPC_Server.answer(NetrServerGetInfo_Request) 

1732 def netr_server_getinfo(self, req): 

1733 """ 

1734 NetrServerGetInfo [MS-WKST] 

1735 "retrieves current configuration information for CIFS and 

1736 SMB Version 1.0 servers." 

1737 """ 

1738 level = req.Level 

1739 

1740 # Create response 

1741 resp = NetrServerGetInfo_Response( 

1742 InfoStruct=NDRUnion( 

1743 tag=level, 

1744 value=None, 

1745 ), 

1746 ndr64=self.ndr64, 

1747 ) 

1748 

1749 if level == 101: 

1750 resp.InfoStruct.value = NDRPointer( 

1751 referent_id=0x20000, 

1752 value=LPSERVER_INFO_101( 

1753 sv101_platform_id=500, # NT 

1754 sv101_name="WORKSTATION", 

1755 sv101_version_major=10, 

1756 sv101_version_minor=0, 

1757 sv101_version_type=1, # Workstation 

1758 ), 

1759 ) 

1760 else: 

1761 # We only support level 101 :( 

1762 resp.status = 0x0000007C # ERROR_INVALID_LEVEL 

1763 

1764 return resp 

1765 

1766 @DCERPC_Server.answer(NetrShareGetInfo_Request) 

1767 def netr_share_getinfo(self, req): 

1768 """ 

1769 NetrShareGetInfo [MS-SRVS] 

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

1771 """ 

1772 level = req.Level 

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

1774 

1775 # Create response 

1776 resp = NetrShareGetInfo_Response( 

1777 InfoStruct=NDRUnion( 

1778 tag=level, 

1779 value=None, 

1780 ), 

1781 ndr64=self.ndr64, 

1782 ) 

1783 

1784 # Find the share the client is asking details for 

1785 try: 

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

1787 except StopIteration: 

1788 # Share doesn't exist ! 

1789 resp.status = 0x00000906 # NERR_NetNameNotFound 

1790 return resp 

1791 

1792 if level == 1: 

1793 resp.InfoStruct.value = NDRPointer( 

1794 referent_id=0x20000, 

1795 value=LPSHARE_INFO_1( 

1796 shi1_netname=share.name, 

1797 shi1_type=share.type, 

1798 shi1_remark=share.remark, 

1799 ), 

1800 ) 

1801 else: 

1802 # We only support level 1 :( 

1803 resp.status = 0x0000007C # ERROR_INVALID_LEVEL 

1804 

1805 return resp 

1806 

1807 

1808# Util 

1809 

1810 

1811class smbserver: 

1812 r""" 

1813 Spawns a simple smbserver 

1814 

1815 smbserver parameters: 

1816 

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

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

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

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

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

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

1823 Default NTLM with guest 

1824 

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

1826 """ 

1827 

1828 def __init__( 

1829 self, 

1830 shares=None, 

1831 iface: str = None, 

1832 port: int = 445, 

1833 verb: int = 2, 

1834 readonly: bool = True, 

1835 # SMB arguments 

1836 ssp=None, 

1837 **kwargs, 

1838 ): 

1839 # Default Share 

1840 if shares is None: 

1841 shares = [ 

1842 SMBShare( 

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

1844 ) 

1845 ] 

1846 # Verb 

1847 if verb >= 2: 

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

1849 log_runtime.info( 

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

1851 % ( 

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

1853 ( 

1854 conf.color_theme.yellow("YES") 

1855 if readonly 

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

1857 ), 

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

1859 ) 

1860 ) 

1861 for share in shares: 

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

1863 # Start SMB Server 

1864 self.srv = SMB_Server.spawn( 

1865 # TCP server 

1866 port=port, 

1867 iface=iface or conf.loopback_name, 

1868 verb=verb, 

1869 # SMB server 

1870 ssp=ssp, 

1871 shares=shares, 

1872 readonly=readonly, 

1873 # SMB arguments 

1874 **kwargs, 

1875 ) 

1876 

1877 def close(self): 

1878 """ 

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

1880 """ 

1881 if self.srv: 

1882 try: 

1883 self.srv.shutdown(socket.SHUT_RDWR) 

1884 except OSError: 

1885 pass 

1886 self.srv.close() 

1887 

1888 

1889if __name__ == "__main__": 

1890 from scapy.utils import AutoArgparse 

1891 

1892 AutoArgparse(smbserver)