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

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

805 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 1 / 2 Client Automaton 

8 

9 

10.. note:: 

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

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

13""" 

14 

15import io 

16import os 

17import pathlib 

18import socket 

19import time 

20import threading 

21 

22from scapy.automaton import ATMT, Automaton, ObjectPipe 

23from scapy.config import conf 

24from scapy.error import Scapy_Exception 

25from scapy.fields import UTCTimeField 

26from scapy.supersocket import SuperSocket 

27from scapy.utils import ( 

28 CLIUtil, 

29 pretty_list, 

30 human_size, 

31) 

32from scapy.volatile import RandUUID 

33 

34from scapy.layers.dcerpc import NDRUnion, find_dcerpc_interface 

35from scapy.layers.gssapi import ( 

36 GSS_S_COMPLETE, 

37 GSS_S_CONTINUE_NEEDED, 

38 GSS_C_FLAGS, 

39) 

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

41 LPSHARE_ENUM_STRUCT, 

42 NetrShareEnum_Request, 

43 SHARE_INFO_1_CONTAINER, 

44) 

45from scapy.layers.ntlm import ( 

46 NTLMSSP, 

47) 

48from scapy.layers.smb import ( 

49 SMBNegotiate_Request, 

50 SMBNegotiate_Response_Extended_Security, 

51 SMBNegotiate_Response_Security, 

52 SMBSession_Null, 

53 SMBSession_Setup_AndX_Request, 

54 SMBSession_Setup_AndX_Request_Extended_Security, 

55 SMBSession_Setup_AndX_Response, 

56 SMBSession_Setup_AndX_Response_Extended_Security, 

57 SMB_Dialect, 

58 SMB_Header, 

59) 

60from scapy.layers.windows.security import SECURITY_DESCRIPTOR 

61from scapy.layers.smb2 import ( 

62 DirectTCP, 

63 FileAllInformation, 

64 FileIdBothDirectoryInformation, 

65 SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2, 

66 SMB2_CREATE_REQUEST_LEASE, 

67 SMB2_CREATE_REQUEST_LEASE_V2, 

68 SMB2_Change_Notify_Request, 

69 SMB2_Change_Notify_Response, 

70 SMB2_Close_Request, 

71 SMB2_Close_Response, 

72 SMB2_Create_Context, 

73 SMB2_Create_Request, 

74 SMB2_Create_Response, 

75 SMB2_ENCRYPTION_CIPHERS, 

76 SMB2_Encryption_Capabilities, 

77 SMB2_Error_Response, 

78 SMB2_Header, 

79 SMB2_IOCTL_Request, 

80 SMB2_IOCTL_Response, 

81 SMB2_Negotiate_Context, 

82 SMB2_Negotiate_Protocol_Request, 

83 SMB2_Negotiate_Protocol_Response, 

84 SMB2_Netname_Negotiate_Context_ID, 

85 SMB2_Preauth_Integrity_Capabilities, 

86 SMB2_Query_Directory_Request, 

87 SMB2_Query_Directory_Response, 

88 SMB2_Query_Info_Request, 

89 SMB2_Query_Info_Response, 

90 SMB2_Read_Request, 

91 SMB2_Read_Response, 

92 SMB2_SIGNING_ALGORITHMS, 

93 SMB2_Session_Setup_Request, 

94 SMB2_Session_Setup_Response, 

95 SMB2_Signing_Capabilities, 

96 SMB2_Tree_Connect_Request, 

97 SMB2_Tree_Connect_Response, 

98 SMB2_Tree_Disconnect_Request, 

99 SMB2_Tree_Disconnect_Response, 

100 SMB2_Write_Request, 

101 SMB2_Write_Response, 

102 SMBStreamSocket, 

103 SMB_DIALECTS, 

104 SRVSVC_SHARE_TYPES, 

105 STATUS_ERREF, 

106) 

107from scapy.layers.spnego import SPNEGOSSP 

108 

109 

110class SMB_Client(Automaton): 

111 """ 

112 SMB client automaton 

113 

114 :param sock: the SMBStreamSocket to use 

115 :param ssp: the SSP to use 

116 

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

118 

119 :param REQUIRE_SIGNATURE: set 'Require Signature' 

120 :param REQUIRE_ENCRYPTION: set 'Requite Encryption' 

121 :param MIN_DIALECT: minimum SMB dialect. Defaults to 0x0202 (2.0.2) 

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

123 :param DIALECTS: list of supported SMB2 dialects. 

124 Constructed from MIN_DIALECT, MAX_DIALECT otherwise. 

125 """ 

126 

127 port = 445 

128 cls = DirectTCP 

129 

130 def __init__(self, sock, ssp=None, *args, **kwargs): 

131 # Various SMB client arguments 

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

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

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

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

136 self.RETRY = kwargs.pop("RETRY", 0) # optionally: retry n times session setup 

137 self.SMB2 = kwargs.pop("SMB2", False) # optionally: start directly in SMB2 

138 self.HOST = kwargs.pop("HOST", "") 

139 # Store supported dialects 

140 if "DIALECTS" in kwargs: 

141 self.DIALECTS = kwargs.pop("DIALECTS") 

142 else: 

143 MIN_DIALECT = kwargs.pop("MIN_DIALECT", 0x0202) 

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

145 self.DIALECTS = sorted( 

146 [ 

147 x 

148 for x in [0x0202, 0x0210, 0x0300, 0x0302, 0x0311] 

149 if x >= MIN_DIALECT and x <= self.MAX_DIALECT 

150 ] 

151 ) 

152 # Internal Session information 

153 self.ErrorStatus = None 

154 self.NegotiateCapabilities = None 

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

156 self.SequenceWindow = (0, 0) # keep track of allowed MIDs 

157 self.CurrentCreditCount = 0 

158 self.MaxCreditCount = 128 

159 if ssp is None: 

160 # We got no SSP. Assuming the server allows anonymous 

161 ssp = SPNEGOSSP( 

162 [ 

163 NTLMSSP( 

164 UPN="guest", 

165 HASHNT=b"", 

166 ) 

167 ] 

168 ) 

169 # Initialize 

170 kwargs["sock"] = sock 

171 Automaton.__init__( 

172 self, 

173 *args, 

174 **kwargs, 

175 ) 

176 if self.is_atmt_socket: 

177 self.smb_sock_ready = threading.Event() 

178 # Set session options 

179 self.session.ssp = ssp 

180 self.session.SigningRequired = ( 

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

182 ) 

183 self.session.Dialect = self.MAX_DIALECT 

184 

185 @classmethod 

186 def from_tcpsock(cls, sock, **kwargs): 

187 return cls.smblink( 

188 None, 

189 SMBStreamSocket(sock, DirectTCP), 

190 **kwargs, 

191 ) 

192 

193 @property 

194 def session(self): 

195 # session shorthand 

196 return self.sock.session 

197 

198 def send(self, pkt): 

199 # Calculate what CreditCharge to send. 

200 if self.session.Dialect > 0x0202 and isinstance(pkt.payload, SMB2_Header): 

201 # [MS-SMB2] sect 3.2.4.1.5 

202 typ = type(pkt.payload.payload) 

203 if typ is SMB2_Negotiate_Protocol_Request: 

204 # See [MS-SMB2] 3.2.4.1.2 note 

205 pkt.CreditCharge = 0 

206 elif typ in [ 

207 SMB2_Read_Request, 

208 SMB2_Write_Request, 

209 SMB2_IOCTL_Request, 

210 SMB2_Query_Directory_Request, 

211 SMB2_Change_Notify_Request, 

212 SMB2_Query_Info_Request, 

213 ]: 

214 # [MS-SMB2] 3.1.5.2 

215 # "For READ, WRITE, IOCTL, and QUERY_DIRECTORY requests" 

216 # "CHANGE_NOTIFY, QUERY_INFO, or SET_INFO" 

217 if typ == SMB2_Read_Request: 

218 Length = pkt.payload.Length 

219 elif typ == SMB2_Write_Request: 

220 Length = len(pkt.payload.Data) 

221 elif typ == SMB2_IOCTL_Request: 

222 # [MS-SMB2] 3.3.5.15 

223 Length = max(len(pkt.payload.Input), pkt.payload.MaxOutputResponse) 

224 elif typ in [ 

225 SMB2_Query_Directory_Request, 

226 SMB2_Change_Notify_Request, 

227 SMB2_Query_Info_Request, 

228 ]: 

229 Length = pkt.payload.OutputBufferLength 

230 else: 

231 raise RuntimeError("impossible case") 

232 pkt.CreditCharge = 1 + (Length - 1) // 65536 

233 else: 

234 # "For all other requests, the client MUST set CreditCharge to 1" 

235 pkt.CreditCharge = 1 

236 # Keep track of our credits 

237 self.CurrentCreditCount -= pkt.CreditCharge 

238 # [MS-SMB2] note <110> 

239 # "The Windows-based client will request credits up to a configurable 

240 # maximum of 128 by default." 

241 pkt.CreditRequest = self.MaxCreditCount - self.CurrentCreditCount 

242 # Get first available message ID: [MS-SMB2] 3.2.4.1.3 and 3.2.4.1.5 

243 pkt.MID = self.SequenceWindow[0] 

244 return super(SMB_Client, self).send(pkt) 

245 

246 @ATMT.state(initial=1) 

247 def BEGIN(self): 

248 pass 

249 

250 @ATMT.condition(BEGIN) 

251 def continue_smb2(self): 

252 if self.SMB2: # Directly started in SMB2 

253 self.smb_header = DirectTCP() / SMB2_Header(PID=0xFEFF) 

254 raise self.SMB2_NEGOTIATE() 

255 

256 @ATMT.condition(BEGIN, prio=1) 

257 def send_negotiate(self): 

258 raise self.SENT_NEGOTIATE() 

259 

260 @ATMT.action(send_negotiate) 

261 def on_negotiate(self): 

262 # [MS-SMB2] sect 3.2.4.2.2.1 - Multi-Protocol Negotiate 

263 self.smb_header = DirectTCP() / SMB_Header( 

264 Flags2=( 

265 "LONG_NAMES+EAS+NT_STATUS+UNICODE+" 

266 "SMB_SECURITY_SIGNATURE+EXTENDED_SECURITY" 

267 ), 

268 TID=0xFFFF, 

269 PIDLow=0xFEFF, 

270 UID=0, 

271 MID=0, 

272 ) 

273 if self.EXTENDED_SECURITY: 

274 self.smb_header.Flags2 += "EXTENDED_SECURITY" 

275 pkt = self.smb_header.copy() / SMBNegotiate_Request( 

276 Dialects=[ 

277 SMB_Dialect(DialectString=x) 

278 for x in [ 

279 "PC NETWORK PROGRAM 1.0", 

280 "LANMAN1.0", 

281 "Windows for Workgroups 3.1a", 

282 "LM1.2X002", 

283 "LANMAN2.1", 

284 "NT LM 0.12", 

285 ] 

286 + (["SMB 2.002", "SMB 2.???"] if not self.USE_SMB1 else []) 

287 ], 

288 ) 

289 if not self.EXTENDED_SECURITY: 

290 pkt.Flags2 -= "EXTENDED_SECURITY" 

291 pkt[SMB_Header].Flags2 = ( 

292 pkt[SMB_Header].Flags2 

293 - "SMB_SECURITY_SIGNATURE" 

294 + "SMB_SECURITY_SIGNATURE_REQUIRED+IS_LONG_NAME" 

295 ) 

296 self.send(pkt) 

297 

298 @ATMT.state() 

299 def SENT_NEGOTIATE(self): 

300 pass 

301 

302 @ATMT.state() 

303 def SMB2_NEGOTIATE(self): 

304 pass 

305 

306 @ATMT.condition(SMB2_NEGOTIATE) 

307 def send_negotiate_smb2(self): 

308 raise self.SENT_NEGOTIATE() 

309 

310 @ATMT.action(send_negotiate_smb2) 

311 def on_negotiate_smb2(self): 

312 # [MS-SMB2] sect 3.2.4.2.2.2 - SMB2-Only Negotiate 

313 pkt = self.smb_header.copy() / SMB2_Negotiate_Protocol_Request( 

314 Dialects=self.DIALECTS, 

315 SecurityMode=( 

316 "SIGNING_ENABLED+SIGNING_REQUIRED" 

317 if self.session.SigningRequired 

318 else "SIGNING_ENABLED" 

319 ), 

320 ) 

321 if self.MAX_DIALECT >= 0x0210: 

322 # "If the client implements the SMB 2.1 or SMB 3.x dialect, ClientGuid 

323 # MUST be set to the global ClientGuid value" 

324 pkt.ClientGUID = self.GUID 

325 # Capabilities: same as [MS-SMB2] 3.3.5.4 

326 self.NegotiateCapabilities = "+".join( 

327 [ 

328 "DFS", 

329 "LEASING", 

330 "LARGE_MTU", 

331 ] 

332 ) 

333 if self.MAX_DIALECT >= 0x0300: 

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

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

336 [ 

337 "MULTI_CHANNEL", 

338 "PERSISTENT_HANDLES", 

339 "DIRECTORY_LEASING", 

340 "ENCRYPTION", 

341 ] 

342 ) 

343 if self.MAX_DIALECT >= 0x0311: 

344 # "If the client implements the SMB 3.1.1 dialect, it MUST do" 

345 pkt.NegotiateContexts = [ 

346 SMB2_Negotiate_Context() 

347 / SMB2_Preauth_Integrity_Capabilities( 

348 # As for today, no other hash algorithm is described by the spec 

349 HashAlgorithms=["SHA-512"], 

350 Salt=self.session.Salt, 

351 ), 

352 SMB2_Negotiate_Context() 

353 / SMB2_Encryption_Capabilities( 

354 Ciphers=self.session.SupportedCipherIds, 

355 ), 

356 # TODO support compression and RDMA 

357 SMB2_Negotiate_Context() 

358 / SMB2_Netname_Negotiate_Context_ID( 

359 NetName=self.HOST, 

360 ), 

361 SMB2_Negotiate_Context() 

362 / SMB2_Signing_Capabilities( 

363 SigningAlgorithms=self.session.SupportedSigningAlgorithmIds, 

364 ), 

365 ] 

366 pkt.Capabilities = self.NegotiateCapabilities 

367 # Send 

368 self.send(pkt) 

369 # If required, compute sessions 

370 self.session.computeSMBConnectionPreauth( 

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

372 ) 

373 

374 @ATMT.receive_condition(SENT_NEGOTIATE) 

375 def receive_negotiate_response(self, pkt): 

376 if ( 

377 SMBNegotiate_Response_Extended_Security in pkt 

378 or SMB2_Negotiate_Protocol_Response in pkt 

379 ): 

380 # Extended SMB1 / SMB2 

381 try: 

382 ssp_blob = pkt.SecurityBlob # eventually SPNEGO server initiation 

383 except AttributeError: 

384 ssp_blob = None 

385 if ( 

386 SMB2_Negotiate_Protocol_Response in pkt 

387 and pkt.DialectRevision & 0xFF == 0xFF 

388 ): 

389 # Version is SMB X.??? 

390 # [MS-SMB2] 3.2.5.2 

391 # If the DialectRevision field in the SMB2 NEGOTIATE Response is 

392 # 0x02FF ... the client MUST allocate sequence number 1 from 

393 # Connection.SequenceWindow, and MUST set MessageId field of the 

394 # SMB2 header to 1. 

395 self.SequenceWindow = (1, 1) 

396 self.smb_header = DirectTCP() / SMB2_Header(PID=0xFEFF, MID=1) 

397 self.SMB2 = True # We're now using SMB2 to talk to the server 

398 raise self.SMB2_NEGOTIATE() 

399 else: 

400 if SMB2_Negotiate_Protocol_Response in pkt: 

401 # SMB2 was negotiated ! 

402 self.session.Dialect = pkt.DialectRevision 

403 # If required, compute sessions 

404 self.session.computeSMBConnectionPreauth( 

405 bytes(pkt[SMB2_Header]), # nego response 

406 ) 

407 # Process max sizes 

408 self.session.MaxReadSize = pkt.MaxReadSize 

409 self.session.MaxTransactionSize = pkt.MaxTransactionSize 

410 self.session.MaxWriteSize = pkt.MaxWriteSize 

411 # Process SecurityMode 

412 if pkt.SecurityMode.SIGNING_REQUIRED: 

413 self.session.SigningRequired = True 

414 # Process capabilities 

415 if self.session.Dialect >= 0x0300: 

416 self.session.SupportsEncryption = pkt.Capabilities.ENCRYPTION 

417 # Process NegotiateContext 

418 if self.session.Dialect >= 0x0311 and pkt.NegotiateContextsCount: 

419 for ngctx in pkt.NegotiateContexts: 

420 if ngctx.ContextType == 0x0002: 

421 # SMB2_ENCRYPTION_CAPABILITIES 

422 if ngctx.Ciphers[0] != 0: 

423 self.session.CipherId = SMB2_ENCRYPTION_CIPHERS[ 

424 ngctx.Ciphers[0] 

425 ] 

426 self.session.SupportsEncryption = True 

427 elif ngctx.ContextType == 0x0008: 

428 # SMB2_SIGNING_CAPABILITIES 

429 self.session.SigningAlgorithmId = ( 

430 SMB2_SIGNING_ALGORITHMS[ngctx.SigningAlgorithms[0]] 

431 ) 

432 if self.REQUIRE_ENCRYPTION and not self.session.SupportsEncryption: 

433 self.ErrorStatus = "NEGOTIATE FAILURE: encryption." 

434 raise self.NEGO_FAILED() 

435 self.update_smbheader(pkt) 

436 raise self.NEGOTIATED(ssp_blob) 

437 elif SMBNegotiate_Response_Security in pkt: 

438 # Non-extended SMB1 

439 # Never tested. FIXME. probably broken 

440 raise self.NEGOTIATED(pkt.Challenge) 

441 

442 @ATMT.state(final=1) 

443 def NEGO_FAILED(self): 

444 self.smb_sock_ready.set() 

445 

446 @ATMT.state() 

447 def NEGOTIATED(self, ssp_blob=None): 

448 # Negotiated ! We now know the Dialect 

449 if self.session.Dialect > 0x0202: 

450 # [MS-SMB2] sect 3.2.5.1.4 

451 self.smb_header.CreditRequest = 1 

452 # Begin session establishment 

453 ssp_tuple = self.session.ssp.GSS_Init_sec_context( 

454 self.session.sspcontext, 

455 input_token=ssp_blob, 

456 target_name="cifs/" + self.HOST if self.HOST else None, 

457 req_flags=( 

458 GSS_C_FLAGS.GSS_C_MUTUAL_FLAG 

459 | (GSS_C_FLAGS.GSS_C_INTEG_FLAG if self.session.SigningRequired else 0) 

460 ), 

461 ) 

462 return ssp_tuple 

463 

464 def update_smbheader(self, pkt): 

465 """ 

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

467 """ 

468 # Some values should not be updated when ASYNC 

469 if not pkt.Flags.SMB2_FLAGS_ASYNC_COMMAND: 

470 # Update IDs 

471 self.smb_header.SessionId = pkt.SessionId 

472 self.smb_header.TID = pkt.TID 

473 self.smb_header.PID = pkt.PID 

474 # Update credits 

475 self.CurrentCreditCount += pkt.CreditRequest 

476 # [MS-SMB2] 3.2.5.1.4 

477 self.SequenceWindow = ( 

478 self.SequenceWindow[0] + max(pkt.CreditCharge, 1), 

479 self.SequenceWindow[1] + pkt.CreditRequest, 

480 ) 

481 

482 # DEV: add a condition on NEGOTIATED with prio=0 

483 

484 @ATMT.condition(NEGOTIATED, prio=1) 

485 def should_send_session_setup_request(self, ssp_tuple): 

486 _, _, status = ssp_tuple 

487 if status not in [GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED]: 

488 raise ValueError("Internal error: the SSP completed with an error.") 

489 raise self.SENT_SESSION_REQUEST().action_parameters(ssp_tuple) 

490 

491 @ATMT.state() 

492 def SENT_SESSION_REQUEST(self): 

493 pass 

494 

495 @ATMT.action(should_send_session_setup_request) 

496 def send_setup_session_request(self, ssp_tuple): 

497 self.session.sspcontext, token, status = ssp_tuple 

498 if self.SMB2 and status == GSS_S_CONTINUE_NEEDED: 

499 # New session: force 0 

500 self.SessionId = 0 

501 if self.SMB2 or self.EXTENDED_SECURITY: 

502 # SMB1 extended / SMB2 

503 if self.SMB2: 

504 # SMB2 

505 pkt = self.smb_header.copy() / SMB2_Session_Setup_Request( 

506 Capabilities="DFS", 

507 SecurityMode=( 

508 "SIGNING_ENABLED+SIGNING_REQUIRED" 

509 if self.session.SigningRequired 

510 else "SIGNING_ENABLED" 

511 ), 

512 ) 

513 else: 

514 # SMB1 extended 

515 pkt = ( 

516 self.smb_header.copy() 

517 / SMBSession_Setup_AndX_Request_Extended_Security( 

518 ServerCapabilities=( 

519 "UNICODE+NT_SMBS+STATUS32+LEVEL_II_OPLOCKS+" 

520 "DYNAMIC_REAUTH+EXTENDED_SECURITY" 

521 ), 

522 NativeOS=b"", 

523 NativeLanMan=b"", 

524 ) 

525 ) 

526 pkt.SecurityBlob = token 

527 else: 

528 # Non-extended security. 

529 pkt = self.smb_header.copy() / SMBSession_Setup_AndX_Request( 

530 ServerCapabilities="UNICODE+NT_SMBS+STATUS32+LEVEL_II_OPLOCKS", 

531 NativeOS=b"", 

532 NativeLanMan=b"", 

533 OEMPassword=b"\0" * 24, 

534 UnicodePassword=token, 

535 ) 

536 self.send(pkt) 

537 if self.SMB2: 

538 # If required, compute sessions 

539 self.session.computeSMBSessionPreauth( 

540 bytes(pkt[SMB2_Header]), # session request 

541 ) 

542 

543 @ATMT.receive_condition(SENT_SESSION_REQUEST) 

544 def receive_session_setup_response(self, pkt): 

545 if ( 

546 SMBSession_Null in pkt 

547 or SMBSession_Setup_AndX_Response_Extended_Security in pkt 

548 or SMBSession_Setup_AndX_Response in pkt 

549 ): 

550 # SMB1 

551 if SMBSession_Null in pkt: 

552 # Likely an error 

553 raise self.NEGOTIATED() 

554 # Logging 

555 if pkt.Status != 0 and pkt.Status != 0xC0000016: 

556 # Not SUCCESS nor MORE_PROCESSING_REQUIRED: log 

557 self.ErrorStatus = pkt.sprintf("%SMB2_Header.Status%") 

558 self.debug( 

559 lvl=1, 

560 msg=conf.color_theme.red( 

561 pkt.sprintf("SMB Session Setup Response: %SMB2_Header.Status%") 

562 ), 

563 ) 

564 if self.SMB2: 

565 self.update_smbheader(pkt) 

566 # Cases depending on the response packet 

567 if ( 

568 SMBSession_Setup_AndX_Response_Extended_Security in pkt 

569 or SMB2_Session_Setup_Response in pkt 

570 ): 

571 # The server assigns us a SessionId 

572 self.smb_header.SessionId = pkt.SessionId 

573 # SMB1 extended / SMB2 

574 if pkt.Status == 0: # Authenticated 

575 if SMB2_Session_Setup_Response in pkt: 

576 # [MS-SMB2] sect 3.2.5.3.1 

577 if pkt.SessionFlags.IS_GUEST: 

578 # "If the security subsystem indicates that the session 

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

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

581 self.session.IsGuest = True 

582 self.session.SigningRequired = False 

583 elif self.session.Dialect >= 0x0300: 

584 if pkt.SessionFlags.ENCRYPT_DATA or self.REQUIRE_ENCRYPTION: 

585 self.session.EncryptData = True 

586 self.session.SigningRequired = False 

587 raise self.AUTHENTICATED(pkt.SecurityBlob) 

588 else: 

589 if SMB2_Header in pkt: 

590 # If required, compute sessions 

591 self.session.computeSMBSessionPreauth( 

592 bytes(pkt[SMB2_Header]), # session response 

593 ) 

594 # Ongoing auth 

595 raise self.NEGOTIATED(pkt.SecurityBlob) 

596 elif SMBSession_Setup_AndX_Response_Extended_Security in pkt: 

597 # SMB1 non-extended 

598 pass 

599 elif SMB2_Error_Response in pkt: 

600 # Authentication failure 

601 self.session.sspcontext.clifailure() 

602 # Reset Session preauth (SMB 3.1.1) 

603 self.session.SessionPreauthIntegrityHashValue = None 

604 if not self.RETRY: 

605 raise self.AUTH_FAILED() 

606 self.debug(lvl=2, msg="RETRY: %s" % self.RETRY) 

607 self.RETRY -= 1 

608 raise self.NEGOTIATED() 

609 

610 @ATMT.state(final=1) 

611 def AUTH_FAILED(self): 

612 self.smb_sock_ready.set() 

613 

614 @ATMT.state() 

615 def AUTHENTICATED(self, ssp_blob=None): 

616 self.session.sspcontext, _, status = self.session.ssp.GSS_Init_sec_context( 

617 self.session.sspcontext, 

618 input_token=ssp_blob, 

619 target_name="cifs/" + self.HOST if self.HOST else None, 

620 ) 

621 if status != GSS_S_COMPLETE: 

622 raise ValueError("Internal error: the SSP completed with an error.") 

623 # Authentication was successful 

624 self.session.computeSMBSessionKeys(IsClient=True) 

625 

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

627 

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

629 def authenticated_post_actions(self): 

630 raise self.SOCKET_BIND() 

631 

632 # Plain SMB Socket 

633 

634 @ATMT.state() 

635 def SOCKET_BIND(self): 

636 self.smb_sock_ready.set() 

637 

638 @ATMT.condition(SOCKET_BIND) 

639 def start_smb_socket(self): 

640 raise self.SOCKET_MODE_SMB() 

641 

642 @ATMT.state() 

643 def SOCKET_MODE_SMB(self): 

644 pass 

645 

646 @ATMT.receive_condition(SOCKET_MODE_SMB) 

647 def incoming_data_received_smb(self, pkt): 

648 raise self.SOCKET_MODE_SMB().action_parameters(pkt) 

649 

650 @ATMT.action(incoming_data_received_smb) 

651 def receive_data_smb(self, pkt): 

652 resp = pkt[SMB2_Header].payload 

653 if isinstance(resp, SMB2_Error_Response): 

654 if pkt.Status == 0x00000103: # STATUS_PENDING 

655 # answer is coming later.. just wait... 

656 return 

657 if pkt.Status == 0x0000010B: # STATUS_NOTIFY_CLEANUP 

658 # this is a notify cleanup. ignore 

659 return 

660 self.update_smbheader(pkt) 

661 # Add the status to the response as metadata 

662 resp.NTStatus = pkt.sprintf("%SMB2_Header.Status%") 

663 self.oi.smbpipe.send(resp) 

664 

665 @ATMT.ioevent(SOCKET_MODE_SMB, name="smbpipe", as_supersocket="smblink") 

666 def outgoing_data_received_smb(self, fd): 

667 raise self.SOCKET_MODE_SMB().action_parameters(fd.recv()) 

668 

669 @ATMT.action(outgoing_data_received_smb) 

670 def send_data(self, d): 

671 self.send(self.smb_header.copy() / d) 

672 

673 

674class SMB_SOCKET(SuperSocket): 

675 """ 

676 Mid-level wrapper over SMB_Client.smblink that provides some basic SMB 

677 client functions, such as tree connect, directory query, etc. 

678 """ 

679 

680 def __init__(self, smbsock, use_ioctl=True, timeout=3): 

681 self.ins = smbsock 

682 self.timeout = timeout 

683 if not self.ins.atmt.smb_sock_ready.wait(timeout=timeout): 

684 # If we have a SSP, tell it we failed. 

685 if self.session.sspcontext: 

686 self.session.sspcontext.clifailure() 

687 raise TimeoutError( 

688 "The SMB handshake timed out ! (enable debug=1 for logs)" 

689 ) 

690 if self.ins.atmt.ErrorStatus: 

691 raise Scapy_Exception( 

692 "SMB Session Setup failed: %s" % self.ins.atmt.ErrorStatus 

693 ) 

694 

695 @classmethod 

696 def from_tcpsock(cls, sock, **kwargs): 

697 """ 

698 Wraps the tcp socket in a SMB_Client.smblink first, then into the 

699 SMB_SOCKET/SMB_RPC_SOCKET 

700 """ 

701 return cls( 

702 use_ioctl=kwargs.pop("use_ioctl", True), 

703 timeout=kwargs.pop("timeout", 3), 

704 smbsock=SMB_Client.from_tcpsock(sock, **kwargs), 

705 ) 

706 

707 @property 

708 def session(self): 

709 return self.ins.atmt.session 

710 

711 def set_TID(self, TID): 

712 """ 

713 Set the TID (Tree ID). 

714 This can be called before sending a packet 

715 """ 

716 self.ins.atmt.smb_header.TID = TID 

717 

718 def get_TID(self): 

719 """ 

720 Get the current TID from the underlying socket 

721 """ 

722 return self.ins.atmt.smb_header.TID 

723 

724 def tree_connect(self, name): 

725 """ 

726 Send a TreeConnect request 

727 """ 

728 resp = self.ins.sr1( 

729 SMB2_Tree_Connect_Request( 

730 Buffer=[ 

731 ( 

732 "Path", 

733 "\\\\%s\\%s" 

734 % ( 

735 self.session.sspcontext.ServerHostname, 

736 name, 

737 ), 

738 ) 

739 ] 

740 ), 

741 verbose=False, 

742 timeout=self.timeout, 

743 ) 

744 if not resp: 

745 raise ValueError("TreeConnect timed out !") 

746 if SMB2_Tree_Connect_Response not in resp: 

747 raise ValueError("Failed TreeConnect ! %s" % resp.NTStatus) 

748 # [MS-SMB2] sect 3.2.5.5 

749 if self.session.Dialect >= 0x0300: 

750 if resp.ShareFlags.ENCRYPT_DATA and self.session.SupportsEncryption: 

751 self.session.TreeEncryptData = True 

752 else: 

753 self.session.TreeEncryptData = False 

754 return self.get_TID() 

755 

756 def tree_disconnect(self): 

757 """ 

758 Send a TreeDisconnect request 

759 """ 

760 resp = self.ins.sr1( 

761 SMB2_Tree_Disconnect_Request(), 

762 verbose=False, 

763 timeout=self.timeout, 

764 ) 

765 if not resp: 

766 raise ValueError("TreeDisconnect timed out !") 

767 if SMB2_Tree_Disconnect_Response not in resp: 

768 raise ValueError("Failed TreeDisconnect ! %s" % resp.NTStatus) 

769 

770 def create_request( 

771 self, 

772 name, 

773 mode="r", 

774 type="pipe", 

775 extra_create_options=[], 

776 extra_desired_access=[], 

777 ): 

778 """ 

779 Open a file/pipe by its name 

780 

781 :param name: the name of the file or named pipe. e.g. 'srvsvc' 

782 """ 

783 ShareAccess = [] 

784 DesiredAccess = [] 

785 # Common params depending on the access 

786 if "r" in mode: 

787 ShareAccess.append("FILE_SHARE_READ") 

788 DesiredAccess.extend(["FILE_READ_DATA", "FILE_READ_ATTRIBUTES"]) 

789 if "w" in mode: 

790 ShareAccess.append("FILE_SHARE_WRITE") 

791 DesiredAccess.extend(["FILE_WRITE_DATA", "FILE_WRITE_ATTRIBUTES"]) 

792 if "d" in mode: 

793 ShareAccess.append("FILE_SHARE_DELETE") 

794 # Params depending on the type 

795 FileAttributes = [] 

796 CreateOptions = [] 

797 CreateContexts = [] 

798 CreateDisposition = "FILE_OPEN" 

799 if type == "folder": 

800 FileAttributes.append("FILE_ATTRIBUTE_DIRECTORY") 

801 CreateOptions.append("FILE_DIRECTORY_FILE") 

802 elif type in ["file", "pipe"]: 

803 CreateOptions = ["FILE_NON_DIRECTORY_FILE"] 

804 if "r" in mode: 

805 DesiredAccess.extend(["FILE_READ_EA", "READ_CONTROL", "SYNCHRONIZE"]) 

806 if "w" in mode: 

807 CreateDisposition = "FILE_OVERWRITE_IF" 

808 DesiredAccess.append("FILE_WRITE_EA") 

809 if "d" in mode: 

810 DesiredAccess.append("DELETE") 

811 CreateOptions.append("FILE_DELETE_ON_CLOSE") 

812 if type == "file": 

813 FileAttributes.append("FILE_ATTRIBUTE_NORMAL") 

814 elif type: 

815 raise ValueError("Unknown type: %s" % type) 

816 # [MS-SMB2] 3.2.4.3.8 

817 RequestedOplockLevel = 0 

818 if self.session.Dialect >= 0x0300: 

819 RequestedOplockLevel = "SMB2_OPLOCK_LEVEL_LEASE" 

820 elif self.session.Dialect >= 0x0210 and type == "file": 

821 RequestedOplockLevel = "SMB2_OPLOCK_LEVEL_LEASE" 

822 # SMB 3.X 

823 if self.session.Dialect >= 0x0300 and type in ["file", "folder"]: 

824 CreateContexts.extend( 

825 [ 

826 # [SMB2] sect 3.2.4.3.5 

827 SMB2_Create_Context( 

828 Name=b"DH2Q", 

829 Data=SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2( 

830 CreateGuid=RandUUID()._fix() 

831 ), 

832 ), 

833 # [SMB2] sect 3.2.4.3.9 

834 SMB2_Create_Context( 

835 Name=b"MxAc", 

836 ), 

837 # [SMB2] sect 3.2.4.3.10 

838 SMB2_Create_Context( 

839 Name=b"QFid", 

840 ), 

841 # [SMB2] sect 3.2.4.3.8 

842 SMB2_Create_Context( 

843 Name=b"RqLs", 

844 Data=SMB2_CREATE_REQUEST_LEASE_V2(LeaseKey=RandUUID()._fix()), 

845 ), 

846 ] 

847 ) 

848 elif self.session.Dialect == 0x0210 and type == "file": 

849 CreateContexts.extend( 

850 [ 

851 # [SMB2] sect 3.2.4.3.8 

852 SMB2_Create_Context( 

853 Name=b"RqLs", 

854 Data=SMB2_CREATE_REQUEST_LEASE(LeaseKey=RandUUID()._fix()), 

855 ), 

856 ] 

857 ) 

858 # Extra options 

859 if extra_create_options: 

860 CreateOptions.extend(extra_create_options) 

861 if extra_desired_access: 

862 DesiredAccess.extend(extra_desired_access) 

863 # Request 

864 resp = self.ins.sr1( 

865 SMB2_Create_Request( 

866 ImpersonationLevel="Impersonation", 

867 DesiredAccess="+".join(DesiredAccess), 

868 CreateDisposition=CreateDisposition, 

869 CreateOptions="+".join(CreateOptions), 

870 ShareAccess="+".join(ShareAccess), 

871 FileAttributes="+".join(FileAttributes), 

872 CreateContexts=CreateContexts, 

873 RequestedOplockLevel=RequestedOplockLevel, 

874 Name=name, 

875 ), 

876 verbose=0, 

877 timeout=self.timeout, 

878 ) 

879 if not resp: 

880 raise ValueError("CreateRequest timed out !") 

881 if SMB2_Create_Response not in resp: 

882 raise ValueError("Failed CreateRequest ! %s" % resp.NTStatus) 

883 return resp[SMB2_Create_Response].FileId 

884 

885 def close_request(self, FileId): 

886 """ 

887 Close the FileId 

888 """ 

889 pkt = SMB2_Close_Request(FileId=FileId) 

890 resp = self.ins.sr1(pkt, verbose=0, timeout=self.timeout) 

891 if not resp: 

892 raise ValueError("CloseRequest timed out !") 

893 if SMB2_Close_Response not in resp: 

894 raise ValueError("Failed CloseRequest ! %s" % resp.NTStatus) 

895 

896 def read_request(self, FileId, Length, Offset=0): 

897 """ 

898 Read request 

899 """ 

900 resp = self.ins.sr1( 

901 SMB2_Read_Request( 

902 FileId=FileId, 

903 Length=Length, 

904 Offset=Offset, 

905 ), 

906 verbose=0, 

907 timeout=self.timeout * 10, 

908 ) 

909 if not resp: 

910 raise ValueError("ReadRequest timed out !") 

911 if SMB2_Read_Response not in resp: 

912 raise ValueError("Failed ReadRequest ! %s" % resp.NTStatus) 

913 return resp.Data 

914 

915 def write_request(self, Data, FileId, Offset=0): 

916 """ 

917 Write request 

918 """ 

919 resp = self.ins.sr1( 

920 SMB2_Write_Request( 

921 FileId=FileId, 

922 Data=Data, 

923 Offset=Offset, 

924 ), 

925 verbose=0, 

926 timeout=self.timeout * 10, 

927 ) 

928 if not resp: 

929 raise ValueError("WriteRequest timed out !") 

930 if SMB2_Write_Response not in resp: 

931 raise ValueError("Failed WriteRequest ! %s" % resp.NTStatus) 

932 return resp.Count 

933 

934 def query_directory(self, FileId, FileName="*"): 

935 """ 

936 Query the Directory with FileId 

937 """ 

938 results = [] 

939 Flags = "SMB2_RESTART_SCANS" 

940 while True: 

941 pkt = SMB2_Query_Directory_Request( 

942 FileInformationClass="FileIdBothDirectoryInformation", 

943 FileId=FileId, 

944 FileName=FileName, 

945 Flags=Flags, 

946 ) 

947 resp = self.ins.sr1(pkt, verbose=0, timeout=self.timeout) 

948 Flags = 0 # only the first one is RESTART_SCANS 

949 if not resp: 

950 raise ValueError("QueryDirectory timed out !") 

951 if SMB2_Error_Response in resp: 

952 break 

953 elif SMB2_Query_Directory_Response not in resp: 

954 raise ValueError("Failed QueryDirectory ! %s" % resp.NTStatus) 

955 res = FileIdBothDirectoryInformation(resp.Output) 

956 results.extend( 

957 [ 

958 ( 

959 x.FileName, 

960 x.FileAttributes, 

961 x.EndOfFile, 

962 x.LastWriteTime, 

963 ) 

964 for x in res.files 

965 ] 

966 ) 

967 return results 

968 

969 def query_info(self, FileId, InfoType, FileInfoClass, AdditionalInformation=0): 

970 """ 

971 Query the Info 

972 """ 

973 pkt = SMB2_Query_Info_Request( 

974 InfoType=InfoType, 

975 FileInfoClass=FileInfoClass, 

976 OutputBufferLength=65535, 

977 FileId=FileId, 

978 AdditionalInformation=AdditionalInformation, 

979 ) 

980 resp = self.ins.sr1(pkt, verbose=0, timeout=self.timeout) 

981 if not resp: 

982 raise ValueError("QueryInfo timed out !") 

983 if SMB2_Query_Info_Response not in resp: 

984 raise ValueError("Failed QueryInfo ! %s" % resp.NTStatus) 

985 return resp.Output 

986 

987 def changenotify(self, FileId): 

988 """ 

989 Register change notify 

990 """ 

991 pkt = SMB2_Change_Notify_Request( 

992 Flags="SMB2_WATCH_TREE", 

993 OutputBufferLength=65535, 

994 FileId=FileId, 

995 CompletionFilter=0x0FFF, 

996 ) 

997 # we can wait forever, not a problem in this one 

998 resp = self.ins.sr1(pkt, verbose=0, chainCC=True) 

999 if SMB2_Change_Notify_Response not in resp: 

1000 raise ValueError("Failed ChangeNotify ! %s" % resp.NTStatus) 

1001 return resp.Output 

1002 

1003 

1004class SMB_RPC_SOCKET(ObjectPipe, SMB_SOCKET): 

1005 """ 

1006 Extends SMB_SOCKET (which is a wrapper over SMB_Client.smblink) to send 

1007 DCE/RPC messages (bind, reqs, etc.) 

1008 

1009 This is usable as a normal SuperSocket (sr1, etc.) and performs the 

1010 wrapping of the DCE/RPC messages into SMB2_Write/Read packets. 

1011 """ 

1012 

1013 def __init__(self, smbsock, use_ioctl=True, timeout=3): 

1014 self.use_ioctl = use_ioctl 

1015 ObjectPipe.__init__(self, "SMB_RPC_SOCKET") 

1016 SMB_SOCKET.__init__(self, smbsock, timeout=timeout) 

1017 

1018 def open_pipe(self, name): 

1019 self.PipeFileId = self.create_request(name, mode="rw", type="pipe") 

1020 

1021 def close_pipe(self): 

1022 self.close_request(self.PipeFileId) 

1023 self.PipeFileId = None 

1024 

1025 def send(self, x): 

1026 """ 

1027 Internal ObjectPipe function. 

1028 """ 

1029 # Reminder: this class is an ObjectPipe, it's just a queue. 

1030 

1031 # Detect if DCE/RPC is fragmented. Then we must use Read/Write 

1032 is_frag = x.pfc_flags & 3 != 3 

1033 

1034 if self.use_ioctl and not is_frag and self.session.Dialect >= 0x0210: 

1035 # Use IOCTLRequest 

1036 pkt = SMB2_IOCTL_Request( 

1037 FileId=self.PipeFileId, 

1038 Flags="SMB2_0_IOCTL_IS_FSCTL", 

1039 CtlCode="FSCTL_PIPE_TRANSCEIVE", 

1040 ) 

1041 pkt.Input = bytes(x) 

1042 resp = self.ins.sr1(pkt, verbose=0) 

1043 if SMB2_IOCTL_Response not in resp: 

1044 raise ValueError("Failed reading IOCTL_Response ! %s" % resp.NTStatus) 

1045 data = bytes(resp.Output) 

1046 super(SMB_RPC_SOCKET, self).send(data) 

1047 # Handle BUFFER_OVERFLOW (big DCE/RPC response) 

1048 while resp.NTStatus == "STATUS_BUFFER_OVERFLOW" or data[3] & 2 != 2: 

1049 # Retrieve DCE/RPC full size 

1050 resp = self.ins.sr1( 

1051 SMB2_Read_Request( 

1052 FileId=self.PipeFileId, 

1053 ), 

1054 verbose=0, 

1055 ) 

1056 data = resp.Data 

1057 super(SMB_RPC_SOCKET, self).send(data) 

1058 else: 

1059 # Use WriteRequest/ReadRequest 

1060 pkt = SMB2_Write_Request( 

1061 FileId=self.PipeFileId, 

1062 ) 

1063 pkt.Data = bytes(x) 

1064 # We send the Write Request 

1065 resp = self.ins.sr1(pkt, verbose=0) 

1066 if SMB2_Write_Response not in resp: 

1067 raise ValueError("Failed sending WriteResponse ! %s" % resp.NTStatus) 

1068 # If fragmented, only read if it's the last. 

1069 if is_frag and not x.pfc_flags.PFC_LAST_FRAG: 

1070 return 

1071 # We send a Read Request afterwards 

1072 resp = self.ins.sr1( 

1073 SMB2_Read_Request( 

1074 FileId=self.PipeFileId, 

1075 ), 

1076 verbose=0, 

1077 ) 

1078 if SMB2_Read_Response not in resp: 

1079 raise ValueError("Failed reading ReadResponse ! %s" % resp.NTStatus) 

1080 super(SMB_RPC_SOCKET, self).send(resp.Data) 

1081 # Handle fragmented response 

1082 while resp.Data[3] & 2 != 2: # PFC_LAST_FRAG not set 

1083 # Retrieve DCE/RPC full size 

1084 resp = self.ins.sr1( 

1085 SMB2_Read_Request( 

1086 FileId=self.PipeFileId, 

1087 ), 

1088 verbose=0, 

1089 ) 

1090 super(SMB_RPC_SOCKET, self).send(resp.Data) 

1091 

1092 def close(self): 

1093 SMB_SOCKET.close(self) 

1094 ObjectPipe.close(self) 

1095 

1096 

1097@conf.commands.register 

1098class smbclient(CLIUtil): 

1099 r""" 

1100 A simple SMB client CLI powered by Scapy 

1101 

1102 :param target: can be a hostname, the IPv4 or the IPv6 to connect to 

1103 :param UPN: the upn to use (DOMAIN/USER, DOMAIN\USER, USER@DOMAIN or USER) 

1104 :param guest: use guest mode (over NTLM) 

1105 :param ssp: if provided, use this SSP for auth. 

1106 :param kerberos_required: require kerberos 

1107 :param port: the TCP port. default 445 

1108 :param password: if provided, used for auth 

1109 :param HashNt: if provided, used for auth (NTLM) 

1110 :param HashAes256Sha96: if provided, used for auth (Kerberos) 

1111 :param HashAes128Sha96: if provided, used for auth (Kerberos) 

1112 :param ST: if provided, the service ticket to use (Kerberos) 

1113 :param KEY: if provided, the session key associated to the ticket (Kerberos) 

1114 :param cli: CLI mode (default True). False to use for scripting 

1115 

1116 Some additional SMB parameters are available under help(SMB_Client). Some of 

1117 them include the following: 

1118 

1119 :param REQUIRE_ENCRYPTION: requires encryption. 

1120 """ 

1121 

1122 def __init__( 

1123 self, 

1124 target: str, 

1125 UPN: str = None, 

1126 password: str = None, 

1127 guest: bool = False, 

1128 kerberos_required: bool = False, 

1129 HashNt: bytes = None, 

1130 HashAes256Sha96: bytes = None, 

1131 HashAes128Sha96: bytes = None, 

1132 use_krb5ccname: bool = False, 

1133 port: int = 445, 

1134 timeout: int = 5, 

1135 debug: int = 0, 

1136 ssp=None, 

1137 ST=None, 

1138 KEY=None, 

1139 cli=True, 

1140 # SMB arguments 

1141 REQUIRE_ENCRYPTION=False, 

1142 **kwargs, 

1143 ): 

1144 if cli: 

1145 self._depcheck() 

1146 assert UPN or ssp or guest, "Either UPN, ssp or guest must be provided !" 

1147 # Do we need to build a SSP? 

1148 if ssp is None: 

1149 # Create the SSP (only if not guest mode) 

1150 if not guest: 

1151 ssp = SPNEGOSSP.from_cli_arguments( 

1152 UPN=UPN, 

1153 target=target, 

1154 password=password, 

1155 HashNt=HashNt, 

1156 HashAes256Sha96=HashAes256Sha96, 

1157 HashAes128Sha96=HashAes128Sha96, 

1158 ST=ST, 

1159 KEY=KEY, 

1160 kerberos_required=kerberos_required, 

1161 use_krb5ccname=use_krb5ccname, 

1162 ) 

1163 else: 

1164 # Guest mode 

1165 ssp = None 

1166 # Check if target is IPv4 or IPv6 

1167 if ":" in target: 

1168 family = socket.AF_INET6 

1169 else: 

1170 family = socket.AF_INET 

1171 # Open socket 

1172 sock = socket.socket(family, socket.SOCK_STREAM) 

1173 # Configure socket for SMB: 

1174 # - TCP KEEPALIVE, TCP_KEEPIDLE and TCP_KEEPINTVL. Against a Windows server this 

1175 # isn't necessary, but samba kills the socket VERY fast otherwise. 

1176 # - set TCP_NODELAY to disable Nagle's algorithm (we're streaming data) 

1177 sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) 

1178 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 

1179 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 10) 

1180 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10) 

1181 # Timeout & connect 

1182 sock.settimeout(timeout) 

1183 if debug: 

1184 print("Connecting to %s:%s" % (target, port)) 

1185 sock.connect((target, port)) 

1186 self.extra_create_options = [] 

1187 # Wrap with the automaton 

1188 self.timeout = timeout 

1189 kwargs.setdefault("HOST", target) 

1190 self.sock = SMB_Client.from_tcpsock( 

1191 sock, 

1192 ssp=ssp, 

1193 debug=debug, 

1194 REQUIRE_ENCRYPTION=REQUIRE_ENCRYPTION, 

1195 timeout=timeout, 

1196 **kwargs, 

1197 ) 

1198 try: 

1199 # Wrap with SMB_SOCKET 

1200 self.smbsock = SMB_SOCKET(self.sock, timeout=self.timeout) 

1201 # Wait for either the atmt to fail, or the smb_sock_ready to timeout 

1202 _t = time.time() 

1203 while True: 

1204 if self.sock.atmt.smb_sock_ready.is_set(): 

1205 # yay 

1206 break 

1207 if not self.sock.atmt.isrunning(): 

1208 status = self.sock.atmt.get("Status") 

1209 raise Scapy_Exception( 

1210 "%s with status %s" 

1211 % ( 

1212 self.sock.atmt.state.state, 

1213 STATUS_ERREF.get(status, hex(status)), 

1214 ) 

1215 ) 

1216 if time.time() - _t > timeout: 

1217 self.sock.close() 

1218 raise TimeoutError("The SMB handshake timed out.") 

1219 time.sleep(0.1) 

1220 except Exception: 

1221 # Something bad happened, end the socket/automaton 

1222 self.sock.close() 

1223 raise 

1224 

1225 # For some usages, we will also need the RPC wrapper 

1226 from scapy.layers.msrpce.rpcclient import DCERPC_Client 

1227 

1228 self.rpcclient = DCERPC_Client.from_smblink( 

1229 self.sock, 

1230 ndr64=False, 

1231 verb=bool(debug), 

1232 ) 

1233 # We have a valid smb connection ! 

1234 print( 

1235 "%s authentication successful using %s%s !" 

1236 % ( 

1237 SMB_DIALECTS.get( 

1238 self.smbsock.session.Dialect, 

1239 "SMB %s" % self.smbsock.session.Dialect, 

1240 ), 

1241 repr(self.smbsock.session.sspcontext), 

1242 " as GUEST" if self.smbsock.session.IsGuest else "", 

1243 ) 

1244 ) 

1245 # Now define some variables for our CLI 

1246 self.pwd = pathlib.PureWindowsPath("/") 

1247 self.localpwd = pathlib.Path(".").resolve() 

1248 self.current_tree = None 

1249 self.ls_cache = {} # cache the listing of the current directory 

1250 self.sh_cache = [] # cache the shares 

1251 # Start CLI 

1252 if cli: 

1253 self.loop(debug=debug) 

1254 

1255 def ps1(self): 

1256 return r"smb: \%s> " % self.normalize_path(self.pwd) 

1257 

1258 def close(self): 

1259 print("Connection closed") 

1260 self.smbsock.close() 

1261 

1262 def _require_share(self, silent=False): 

1263 if self.current_tree is None: 

1264 if not silent: 

1265 print("No share selected ! Try 'shares' then 'use'.") 

1266 return True 

1267 

1268 def collapse_path(self, path): 

1269 # the amount of pathlib.wtf you need to do to resolve .. on all platforms 

1270 # is ridiculous 

1271 return pathlib.PureWindowsPath(os.path.normpath(path.as_posix())) 

1272 

1273 def normalize_path(self, path): 

1274 """ 

1275 Normalize path for CIFS usage 

1276 """ 

1277 return str(self.collapse_path(path)).lstrip("\\") 

1278 

1279 @CLIUtil.addcommand() 

1280 def shares(self): 

1281 """ 

1282 List the shares available 

1283 """ 

1284 # Poll cache 

1285 if self.sh_cache: 

1286 return self.sh_cache 

1287 # It's an RPC 

1288 self.rpcclient.open_smbpipe("srvsvc") 

1289 self.rpcclient.bind(find_dcerpc_interface("srvsvc")) 

1290 req = NetrShareEnum_Request( 

1291 InfoStruct=LPSHARE_ENUM_STRUCT( 

1292 Level=1, 

1293 ShareInfo=NDRUnion( 

1294 tag=1, 

1295 value=SHARE_INFO_1_CONTAINER(Buffer=None), 

1296 ), 

1297 ), 

1298 PreferedMaximumLength=0xFFFFFFFF, 

1299 ndr64=self.rpcclient.ndr64, 

1300 ) 

1301 resp = self.rpcclient.sr1_req(req, timeout=self.timeout) 

1302 self.rpcclient.close_smbpipe() 

1303 if resp.status != 0: 

1304 resp.show() 

1305 raise ValueError("NetrShareEnum_Request failed !") 

1306 results = [] 

1307 for share in resp.valueof("InfoStruct.ShareInfo.Buffer"): 

1308 shi1_type = share.valueof("shi1_type") & 0x0FFFFFFF 

1309 results.append( 

1310 ( 

1311 share.valueof("shi1_netname").decode(), 

1312 SRVSVC_SHARE_TYPES.get(shi1_type, shi1_type), 

1313 share.valueof("shi1_remark").decode(), 

1314 ) 

1315 ) 

1316 self.sh_cache = results # cache 

1317 return results 

1318 

1319 @CLIUtil.addoutput(shares) 

1320 def shares_output(self, results): 

1321 """ 

1322 Print the output of 'shares' 

1323 """ 

1324 print(pretty_list(results, [("ShareName", "ShareType", "Comment")])) 

1325 

1326 @CLIUtil.addcommand(mono=True) 

1327 def use(self, share): 

1328 """ 

1329 Open a share 

1330 """ 

1331 self.current_tree = self.smbsock.tree_connect(share) 

1332 self.pwd = pathlib.PureWindowsPath("/") 

1333 self.ls_cache.clear() 

1334 

1335 @CLIUtil.addcomplete(use) 

1336 def use_complete(self, share): 

1337 """ 

1338 Auto-complete 'use' 

1339 """ 

1340 return [ 

1341 x[0] for x in self.shares() if x[0].startswith(share) and x[0] != "IPC$" 

1342 ] 

1343 

1344 def _parsepath(self, arg, remote=True): 

1345 """ 

1346 Parse a path. Returns the parent folder and file name 

1347 """ 

1348 # Find parent directory if it exists 

1349 elt = (pathlib.PureWindowsPath if remote else pathlib.Path)(arg) 

1350 eltpar = (pathlib.PureWindowsPath if remote else pathlib.Path)(".") 

1351 eltname = elt.name 

1352 if arg.endswith("/") or arg.endswith("\\"): 

1353 eltpar = elt 

1354 eltname = "" 

1355 elif elt.parent and elt.parent.name or elt.is_absolute(): 

1356 eltpar = elt.parent 

1357 return eltpar, eltname 

1358 

1359 def _fs_complete(self, arg, cond=None): 

1360 """ 

1361 Return a listing of the remote files for completion purposes 

1362 """ 

1363 if cond is None: 

1364 cond = lambda _: True 

1365 eltpar, eltname = self._parsepath(arg) 

1366 # ls in that directory 

1367 try: 

1368 files = self.ls(parent=eltpar) 

1369 except ValueError: 

1370 return [] 

1371 return [ 

1372 str(eltpar / x[0]) 

1373 for x in files 

1374 if ( 

1375 x[0].lower().startswith(eltname.lower()) 

1376 and x[0] not in [".", ".."] 

1377 and cond(x[1]) 

1378 ) 

1379 ] 

1380 

1381 def _dir_complete(self, arg): 

1382 """ 

1383 Return a directories of remote files for completion purposes 

1384 """ 

1385 results = self._fs_complete( 

1386 arg, 

1387 cond=lambda x: x.FILE_ATTRIBUTE_DIRECTORY, 

1388 ) 

1389 if len(results) == 1 and results[0].startswith(arg): 

1390 # skip through folders 

1391 return [results[0] + "\\"] 

1392 return results 

1393 

1394 @CLIUtil.addcommand(mono=True) 

1395 def ls(self, parent=None): 

1396 """ 

1397 List the files in the remote directory 

1398 -t: sort by timestamp 

1399 -S: sort by size 

1400 -r: reverse while sorting 

1401 """ 

1402 if self._require_share(): 

1403 return 

1404 # Get pwd of the ls 

1405 pwd = self.pwd 

1406 if parent is not None: 

1407 pwd /= parent 

1408 pwd = self.normalize_path(pwd) 

1409 # Poll the cache 

1410 if self.ls_cache and pwd in self.ls_cache: 

1411 return self.ls_cache[pwd] 

1412 self.smbsock.set_TID(self.current_tree) 

1413 # Open folder 

1414 fileId = self.smbsock.create_request( 

1415 pwd, 

1416 type="folder", 

1417 extra_create_options=self.extra_create_options, 

1418 ) 

1419 # Query the folder 

1420 files = self.smbsock.query_directory(fileId) 

1421 # Close the folder 

1422 self.smbsock.close_request(fileId) 

1423 self.ls_cache[pwd] = files # Store cache 

1424 return files 

1425 

1426 @CLIUtil.addoutput(ls) 

1427 def ls_output(self, results, *, t=False, S=False, r=False): 

1428 """ 

1429 Print the output of 'ls' 

1430 """ 

1431 fld = UTCTimeField( 

1432 "", None, fmt="<Q", epoch=[1601, 1, 1, 0, 0, 0], custom_scaling=1e7 

1433 ) 

1434 if t: 

1435 # Sort by time 

1436 results.sort(key=lambda x: -x[3]) 

1437 if S: 

1438 # Sort by size 

1439 results.sort(key=lambda x: -x[2]) 

1440 if r: 

1441 # Reverse sort 

1442 results = results[::-1] 

1443 results = [ 

1444 ( 

1445 x[0], 

1446 "+".join(y.lstrip("FILE_ATTRIBUTE_") for y in str(x[1]).split("+")), 

1447 human_size(x[2]), 

1448 fld.i2repr(None, x[3]), 

1449 ) 

1450 for x in results 

1451 ] 

1452 print( 

1453 pretty_list( 

1454 results, 

1455 [("FileName", "FileAttributes", "EndOfFile", "LastWriteTime")], 

1456 sortBy=None, 

1457 ) 

1458 ) 

1459 

1460 @CLIUtil.addcomplete(ls) 

1461 def ls_complete(self, folder): 

1462 """ 

1463 Auto-complete ls 

1464 """ 

1465 if self._require_share(silent=True): 

1466 return [] 

1467 return self._dir_complete(folder) 

1468 

1469 @CLIUtil.addcommand(mono=True) 

1470 def cd(self, folder): 

1471 """ 

1472 Change the remote current directory 

1473 """ 

1474 if self._require_share(): 

1475 return 

1476 if not folder: 

1477 # show mode 

1478 return str(self.pwd) 

1479 self.pwd /= folder 

1480 self.pwd = self.collapse_path(self.pwd) 

1481 self.ls_cache.clear() 

1482 

1483 @CLIUtil.addcomplete(cd) 

1484 def cd_complete(self, folder): 

1485 """ 

1486 Auto-complete cd 

1487 """ 

1488 if self._require_share(silent=True): 

1489 return [] 

1490 return self._dir_complete(folder) 

1491 

1492 def _lfs_complete(self, arg, cond): 

1493 """ 

1494 Return a listing of local files for completion purposes 

1495 """ 

1496 eltpar, eltname = self._parsepath(arg, remote=False) 

1497 eltpar = self.localpwd / eltpar 

1498 return [ 

1499 # trickery so that ../<TAB> works 

1500 str(eltpar / x.name) 

1501 for x in eltpar.resolve().glob("*") 

1502 if (x.name.lower().startswith(eltname.lower()) and cond(x)) 

1503 ] 

1504 

1505 @CLIUtil.addoutput(cd) 

1506 def cd_output(self, result): 

1507 """ 

1508 Print the output of 'cd' 

1509 """ 

1510 if result: 

1511 print(result) 

1512 

1513 @CLIUtil.addcommand() 

1514 def lls(self): 

1515 """ 

1516 List the files in the local directory 

1517 """ 

1518 return list(self.localpwd.glob("*")) 

1519 

1520 @CLIUtil.addoutput(lls) 

1521 def lls_output(self, results): 

1522 """ 

1523 Print the output of 'lls' 

1524 """ 

1525 results = [ 

1526 ( 

1527 x.name, 

1528 human_size(stat.st_size), 

1529 time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(stat.st_mtime)), 

1530 ) 

1531 for x, stat in ((x, x.stat()) for x in results) 

1532 ] 

1533 print( 

1534 pretty_list(results, [("FileName", "File Size", "Last Modification Time")]) 

1535 ) 

1536 

1537 @CLIUtil.addcommand(mono=True) 

1538 def lcd(self, folder): 

1539 """ 

1540 Change the local current directory 

1541 """ 

1542 if not folder: 

1543 # show mode 

1544 return str(self.localpwd) 

1545 self.localpwd /= folder 

1546 self.localpwd = self.localpwd.resolve() 

1547 

1548 @CLIUtil.addcomplete(lcd) 

1549 def lcd_complete(self, folder): 

1550 """ 

1551 Auto-complete lcd 

1552 """ 

1553 return self._lfs_complete(folder, lambda x: x.is_dir()) 

1554 

1555 @CLIUtil.addoutput(lcd) 

1556 def lcd_output(self, result): 

1557 """ 

1558 Print the output of 'lcd' 

1559 """ 

1560 if result: 

1561 print(result) 

1562 

1563 def _get_file(self, file, fd): 

1564 """ 

1565 Gets the file bytes from a remote host 

1566 """ 

1567 # Get pwd of the ls 

1568 fpath = self.pwd / file 

1569 self.smbsock.set_TID(self.current_tree) 

1570 

1571 # Open file 

1572 fileId = self.smbsock.create_request( 

1573 self.normalize_path(fpath), 

1574 type="file", 

1575 extra_create_options=[ 

1576 "FILE_SEQUENTIAL_ONLY", 

1577 ] 

1578 + self.extra_create_options, 

1579 ) 

1580 

1581 # Get the file size 

1582 info = FileAllInformation( 

1583 self.smbsock.query_info( 

1584 FileId=fileId, 

1585 InfoType="SMB2_0_INFO_FILE", 

1586 FileInfoClass="FileAllInformation", 

1587 ) 

1588 ) 

1589 length = info.StandardInformation.EndOfFile 

1590 offset = 0 

1591 

1592 # Read the file 

1593 while length: 

1594 lengthRead = min(self.smbsock.session.MaxReadSize, length) 

1595 fd.write( 

1596 self.smbsock.read_request(fileId, Length=lengthRead, Offset=offset) 

1597 ) 

1598 offset += lengthRead 

1599 length -= lengthRead 

1600 

1601 # Close the file 

1602 self.smbsock.close_request(fileId) 

1603 return offset 

1604 

1605 def _send_file(self, fname, fd): 

1606 """ 

1607 Send the file bytes to a remote host 

1608 """ 

1609 # Get destination file 

1610 fpath = self.pwd / fname 

1611 self.smbsock.set_TID(self.current_tree) 

1612 # Open file 

1613 fileId = self.smbsock.create_request( 

1614 self.normalize_path(fpath), 

1615 type="file", 

1616 mode="w", 

1617 extra_create_options=self.extra_create_options, 

1618 ) 

1619 # Send the file 

1620 offset = 0 

1621 while True: 

1622 data = fd.read(self.smbsock.session.MaxWriteSize) 

1623 if not data: 

1624 # end of file 

1625 break 

1626 offset += self.smbsock.write_request( 

1627 Data=data, 

1628 FileId=fileId, 

1629 Offset=offset, 

1630 ) 

1631 # Close the file 

1632 self.smbsock.close_request(fileId) 

1633 return offset 

1634 

1635 def _getr(self, directory, _root, _verb=True): 

1636 """ 

1637 Internal recursive function to get a directory 

1638 

1639 :param directory: the remote directory to get 

1640 :param _root: locally, the directory to store any found files 

1641 """ 

1642 size = 0 

1643 if not _root.exists(): 

1644 _root.mkdir() 

1645 # ls the directory 

1646 for x in self.ls(parent=directory): 

1647 if x[0] in [".", ".."]: 

1648 # Discard . and .. 

1649 continue 

1650 remote = directory / x[0] 

1651 local = _root / x[0] 

1652 try: 

1653 if x[1].FILE_ATTRIBUTE_DIRECTORY: 

1654 # Sub-directory 

1655 size += self._getr(remote, local) 

1656 else: 

1657 # Sub-file 

1658 size += self.get(remote, local)[1] 

1659 if _verb: 

1660 print(remote) 

1661 except ValueError as ex: 

1662 if _verb: 

1663 print(conf.color_theme.red(remote), "->", str(ex)) 

1664 return size 

1665 

1666 @CLIUtil.addcommand(mono=True, globsupport=True) 

1667 def get(self, file, _dest=None, _verb=True, *, r=False): 

1668 """ 

1669 Retrieve a file 

1670 -r: recursively download a directory 

1671 """ 

1672 if self._require_share(): 

1673 return 

1674 if r: 

1675 dirpar, dirname = self._parsepath(file) 

1676 return file, self._getr( 

1677 dirpar / dirname, # Remotely 

1678 _root=self.localpwd / dirname, # Locally 

1679 _verb=_verb, 

1680 ) 

1681 else: 

1682 fname = pathlib.PureWindowsPath(file).name 

1683 # Write the buffer 

1684 if _dest is None: 

1685 _dest = self.localpwd / fname 

1686 with _dest.open("wb") as fd: 

1687 size = self._get_file(file, fd) 

1688 return fname, size 

1689 

1690 @CLIUtil.addoutput(get) 

1691 def get_output(self, info): 

1692 """ 

1693 Print the output of 'get' 

1694 """ 

1695 print("Retrieved '%s' of size %s" % (info[0], human_size(info[1]))) 

1696 

1697 @CLIUtil.addcomplete(get) 

1698 def get_complete(self, file): 

1699 """ 

1700 Auto-complete get 

1701 """ 

1702 if self._require_share(silent=True): 

1703 return [] 

1704 return self._fs_complete(file) 

1705 

1706 @CLIUtil.addcommand(mono=True, globsupport=True) 

1707 def cat(self, file): 

1708 """ 

1709 Print a file 

1710 """ 

1711 if self._require_share(): 

1712 return 

1713 # Write the buffer to buffer 

1714 buf = io.BytesIO() 

1715 self._get_file(file, buf) 

1716 return buf.getvalue() 

1717 

1718 @CLIUtil.addoutput(cat) 

1719 def cat_output(self, result): 

1720 """ 

1721 Print the output of 'cat' 

1722 """ 

1723 print(result.decode(errors="backslashreplace")) 

1724 

1725 @CLIUtil.addcomplete(cat) 

1726 def cat_complete(self, file): 

1727 """ 

1728 Auto-complete cat 

1729 """ 

1730 if self._require_share(silent=True): 

1731 return [] 

1732 return self._fs_complete(file) 

1733 

1734 @CLIUtil.addcommand(mono=True, globsupport=True) 

1735 def put(self, file): 

1736 """ 

1737 Upload a file 

1738 """ 

1739 if self._require_share(): 

1740 return 

1741 local_file = self.localpwd / file 

1742 if local_file.is_dir(): 

1743 # Directory 

1744 raise ValueError("put on dir not impl") 

1745 else: 

1746 fname = pathlib.Path(file).name 

1747 with local_file.open("rb") as fd: 

1748 size = self._send_file(fname, fd) 

1749 self.ls_cache.clear() 

1750 return fname, size 

1751 

1752 @CLIUtil.addcomplete(put) 

1753 def put_complete(self, folder): 

1754 """ 

1755 Auto-complete put 

1756 """ 

1757 return self._lfs_complete(folder, lambda x: not x.is_dir()) 

1758 

1759 @CLIUtil.addcommand(mono=True) 

1760 def rm(self, file): 

1761 """ 

1762 Delete a file 

1763 """ 

1764 if self._require_share(): 

1765 return 

1766 # Get pwd of the ls 

1767 fpath = self.pwd / file 

1768 self.smbsock.set_TID(self.current_tree) 

1769 # Open file 

1770 fileId = self.smbsock.create_request( 

1771 self.normalize_path(fpath), 

1772 type="file", 

1773 mode="d", 

1774 extra_create_options=self.extra_create_options, 

1775 ) 

1776 # Close the file 

1777 self.smbsock.close_request(fileId) 

1778 self.ls_cache.clear() 

1779 return fpath.name 

1780 

1781 @CLIUtil.addcomplete(rm) 

1782 def rm_complete(self, file): 

1783 """ 

1784 Auto-complete rm 

1785 """ 

1786 if self._require_share(silent=True): 

1787 return [] 

1788 return self._fs_complete(file) 

1789 

1790 @CLIUtil.addcommand() 

1791 def backup(self): 

1792 """ 

1793 Turn on or off backup intent 

1794 """ 

1795 if "FILE_OPEN_FOR_BACKUP_INTENT" in self.extra_create_options: 

1796 print("Backup Intent: Off") 

1797 self.extra_create_options.remove("FILE_OPEN_FOR_BACKUP_INTENT") 

1798 else: 

1799 print("Backup Intent: On") 

1800 self.extra_create_options.append("FILE_OPEN_FOR_BACKUP_INTENT") 

1801 

1802 @CLIUtil.addcommand(mono=True) 

1803 def watch(self, folder): 

1804 """ 

1805 Watch file changes in folder (recursively) 

1806 """ 

1807 if self._require_share(): 

1808 return 

1809 # Get pwd of the ls 

1810 fpath = self.pwd / folder 

1811 self.smbsock.set_TID(self.current_tree) 

1812 # Open file 

1813 fileId = self.smbsock.create_request( 

1814 self.normalize_path(fpath), 

1815 type="folder", 

1816 extra_create_options=self.extra_create_options, 

1817 ) 

1818 print("Watching '%s'" % fpath) 

1819 # Watch for changes 

1820 try: 

1821 while True: 

1822 changes = self.smbsock.changenotify(fileId) 

1823 for chg in changes: 

1824 print(chg.sprintf("%.time%: %Action% %FileName%")) 

1825 except KeyboardInterrupt: 

1826 pass 

1827 print("Cancelled.") 

1828 

1829 @CLIUtil.addcommand(mono=True) 

1830 def getsd(self, file): 

1831 """ 

1832 Get the Security Descriptor 

1833 """ 

1834 if self._require_share(): 

1835 return 

1836 fpath = self.pwd / file 

1837 self.smbsock.set_TID(self.current_tree) 

1838 # Open file 

1839 fileId = self.smbsock.create_request( 

1840 self.normalize_path(fpath), 

1841 type="", 

1842 mode="", 

1843 extra_desired_access=["READ_CONTROL", "ACCESS_SYSTEM_SECURITY"], 

1844 ) 

1845 # Get the file size 

1846 info = self.smbsock.query_info( 

1847 FileId=fileId, 

1848 InfoType="SMB2_0_INFO_SECURITY", 

1849 FileInfoClass=0, 

1850 AdditionalInformation=( 

1851 0x00000001 

1852 | 0x00000002 

1853 | 0x00000004 

1854 | 0x00000008 

1855 | 0x00000010 

1856 | 0x00000020 

1857 | 0x00000040 

1858 | 0x00010000 

1859 ), 

1860 ) 

1861 self.smbsock.close_request(fileId) 

1862 return info 

1863 

1864 @CLIUtil.addcomplete(getsd) 

1865 def getsd_complete(self, file): 

1866 """ 

1867 Auto-complete getsd 

1868 """ 

1869 if self._require_share(silent=True): 

1870 return [] 

1871 return self._fs_complete(file) 

1872 

1873 @CLIUtil.addoutput(getsd) 

1874 def getsd_output(self, results): 

1875 """ 

1876 Print the output of 'getsd' 

1877 """ 

1878 sd = SECURITY_DESCRIPTOR(results) 

1879 sd.show_print() 

1880 

1881 

1882if __name__ == "__main__": 

1883 from scapy.utils import AutoArgparse 

1884 

1885 AutoArgparse(smbclient)