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

908 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 re 

19import socket 

20import threading 

21import time 

22 

23from scapy.automaton import ATMT, Automaton, ObjectPipe 

24from scapy.config import conf 

25from scapy.error import Scapy_Exception 

26from scapy.fields import UTCTimeField 

27from scapy.supersocket import SuperSocket 

28from scapy.utils import ( 

29 CLIUtil, 

30 pretty_list, 

31 human_size, 

32) 

33from scapy.volatile import RandUUID 

34 

35from scapy.layers.dcerpc import NDRUnion, find_dcerpc_interface 

36from scapy.layers.gssapi import ( 

37 GSS_C_FLAGS, 

38 GSS_S_COMPLETE, 

39 GSS_S_CONTINUE_NEEDED, 

40 GSS_S_DEFECTIVE_TOKEN, 

41) 

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

43 LPSHARE_ENUM_STRUCT, 

44 NetrSessionEnum_Request, 

45 NetrShareEnum_Request, 

46 PSESSION_ENUM_STRUCT, 

47 SESSION_INFO_502_CONTAINER, 

48 SHARE_INFO_1_CONTAINER, 

49) 

50from scapy.layers.ntlm import ( 

51 NTLMSSP, 

52) 

53from scapy.layers.smb import ( 

54 SMBNegotiate_Request, 

55 SMBNegotiate_Response_Extended_Security, 

56 SMBNegotiate_Response_Security, 

57 SMBSession_Null, 

58 SMBSession_Setup_AndX_Request, 

59 SMBSession_Setup_AndX_Request_Extended_Security, 

60 SMBSession_Setup_AndX_Response, 

61 SMBSession_Setup_AndX_Response_Extended_Security, 

62 SMB_Dialect, 

63 SMB_Header, 

64) 

65from scapy.layers.windows.security import SECURITY_DESCRIPTOR 

66from scapy.layers.smb2 import ( 

67 DirectTCP, 

68 FileAllInformation, 

69 FileIdBothDirectoryInformation, 

70 SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2, 

71 SMB2_CREATE_REQUEST_LEASE, 

72 SMB2_CREATE_REQUEST_LEASE_V2, 

73 SMB2_Change_Notify_Request, 

74 SMB2_Change_Notify_Response, 

75 SMB2_Close_Request, 

76 SMB2_Close_Response, 

77 SMB2_Create_Context, 

78 SMB2_Create_Request, 

79 SMB2_Create_Response, 

80 SMB2_ENCRYPTION_CIPHERS, 

81 SMB2_Encryption_Capabilities, 

82 SMB2_Error_Response, 

83 SMB2_FILEID, 

84 SMB2_Header, 

85 SMB2_IOCTL_Request, 

86 SMB2_IOCTL_Response, 

87 SMB2_Negotiate_Context, 

88 SMB2_Negotiate_Protocol_Request, 

89 SMB2_Negotiate_Protocol_Response, 

90 SMB2_Netname_Negotiate_Context_ID, 

91 SMB2_Preauth_Integrity_Capabilities, 

92 SMB2_Query_Directory_Request, 

93 SMB2_Query_Directory_Response, 

94 SMB2_Query_Info_Request, 

95 SMB2_Query_Info_Response, 

96 SMB2_Read_Request, 

97 SMB2_Read_Response, 

98 SMB2_SIGNING_ALGORITHMS, 

99 SMB2_Session_Setup_Request, 

100 SMB2_Session_Setup_Response, 

101 SMB2_Signing_Capabilities, 

102 SMB2_Tree_Connect_Request, 

103 SMB2_Tree_Connect_Response, 

104 SMB2_Tree_Disconnect_Request, 

105 SMB2_Tree_Disconnect_Response, 

106 SMB2_Write_Request, 

107 SMB2_Write_Response, 

108 SMBStreamSocket, 

109 SMB_DIALECTS, 

110 SRVSVC_SHARE_TYPES, 

111 STATUS_ERREF, 

112) 

113from scapy.layers.spnego import SPNEGOSSP 

114 

115 

116class SMB_Client(Automaton): 

117 """ 

118 SMB client automaton 

119 

120 :param sock: the SMBStreamSocket to use 

121 :param ssp: the SSP to use 

122 

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

124 

125 :param REQUIRE_SIGNATURE: set 'Require Signature' 

126 :param REQUIRE_ENCRYPTION: set 'Requite Encryption' 

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

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

129 :param DIALECTS: list of supported SMB2 dialects. 

130 Constructed from MIN_DIALECT, MAX_DIALECT otherwise. 

131 """ 

132 

133 port = 445 

134 cls = DirectTCP 

135 

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

137 # Various SMB client arguments 

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

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

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

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

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

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

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

145 # Store supported dialects 

146 if "DIALECTS" in kwargs: 

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

148 else: 

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

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

151 self.DIALECTS = sorted( 

152 [ 

153 x 

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

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

156 ] 

157 ) 

158 # Internal Session information 

159 self.ErrorStatus = None 

160 self.NegotiateCapabilities = None 

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

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

163 self.CurrentCreditCount = 0 

164 self.MaxCreditCount = 128 

165 if ssp is None: 

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

167 ssp = SPNEGOSSP( 

168 [ 

169 NTLMSSP( 

170 UPN="guest", 

171 HASHNT=b"", 

172 ) 

173 ] 

174 ) 

175 # Initialize 

176 kwargs["sock"] = sock 

177 Automaton.__init__( 

178 self, 

179 *args, 

180 **kwargs, 

181 ) 

182 if self.is_atmt_socket: 

183 self.smb_sock_ready = threading.Event() 

184 # Set session options 

185 self.session.ssp = ssp 

186 self.session.SigningRequired = ( 

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

188 ) 

189 self.session.Dialect = self.MAX_DIALECT 

190 

191 @classmethod 

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

193 return cls.smblink( 

194 None, 

195 SMBStreamSocket(sock, DirectTCP), 

196 **kwargs, 

197 ) 

198 

199 @property 

200 def session(self): 

201 # session shorthand 

202 return self.sock.session 

203 

204 def send(self, pkt): 

205 # Calculate what CreditCharge to send. 

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

207 # [MS-SMB2] sect 3.2.4.1.5 

208 typ = type(pkt.payload.payload) 

209 if typ is SMB2_Negotiate_Protocol_Request: 

210 # See [MS-SMB2] 3.2.4.1.2 note 

211 pkt.CreditCharge = 0 

212 elif typ in [ 

213 SMB2_Read_Request, 

214 SMB2_Write_Request, 

215 SMB2_IOCTL_Request, 

216 SMB2_Query_Directory_Request, 

217 SMB2_Change_Notify_Request, 

218 SMB2_Query_Info_Request, 

219 ]: 

220 # [MS-SMB2] 3.1.5.2 

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

222 # "CHANGE_NOTIFY, QUERY_INFO, or SET_INFO" 

223 if typ == SMB2_Read_Request: 

224 Length = pkt.payload.Length 

225 elif typ == SMB2_Write_Request: 

226 Length = len(pkt.payload.Data) 

227 elif typ == SMB2_IOCTL_Request: 

228 # [MS-SMB2] 3.3.5.15 

229 try: 

230 Length = max( 

231 len(pkt.payload.Input), pkt.payload.MaxOutputResponse 

232 ) 

233 except AttributeError: 

234 Length = 0 

235 elif typ in [ 

236 SMB2_Query_Directory_Request, 

237 SMB2_Change_Notify_Request, 

238 SMB2_Query_Info_Request, 

239 ]: 

240 Length = pkt.payload.OutputBufferLength 

241 else: 

242 raise RuntimeError("impossible case") 

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

244 else: 

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

246 pkt.CreditCharge = 1 

247 # Keep track of our credits 

248 self.CurrentCreditCount -= pkt.CreditCharge 

249 # [MS-SMB2] note <110> 

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

251 # maximum of 128 by default." 

252 pkt.CreditRequest = max(self.MaxCreditCount - self.CurrentCreditCount, 0) 

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

254 pkt.MID = self.SequenceWindow[0] 

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

256 

257 @ATMT.state(initial=1) 

258 def BEGIN(self): 

259 pass 

260 

261 @ATMT.condition(BEGIN) 

262 def continue_smb2(self): 

263 if self.SMB2: # Directly started in SMB2 

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

265 raise self.SMB2_NEGOTIATE() 

266 

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

268 def send_negotiate(self): 

269 raise self.SENT_NEGOTIATE() 

270 

271 @ATMT.action(send_negotiate) 

272 def on_negotiate(self): 

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

274 self.smb_header = DirectTCP() / SMB_Header( 

275 Flags2=( 

276 "LONG_NAMES+EAS+NT_STATUS+UNICODE+" 

277 "SMB_SECURITY_SIGNATURE+EXTENDED_SECURITY" 

278 ), 

279 TID=0xFFFF, 

280 PIDLow=0xFEFF, 

281 UID=0, 

282 MID=0, 

283 ) 

284 if self.EXTENDED_SECURITY: 

285 self.smb_header.Flags2 += "EXTENDED_SECURITY" 

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

287 Dialects=[ 

288 SMB_Dialect(DialectString=x) 

289 for x in [ 

290 "PC NETWORK PROGRAM 1.0", 

291 "LANMAN1.0", 

292 "Windows for Workgroups 3.1a", 

293 "LM1.2X002", 

294 "LANMAN2.1", 

295 "NT LM 0.12", 

296 ] 

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

298 ], 

299 ) 

300 if not self.EXTENDED_SECURITY: 

301 pkt.Flags2 -= "EXTENDED_SECURITY" 

302 pkt[SMB_Header].Flags2 = ( 

303 pkt[SMB_Header].Flags2 

304 - "SMB_SECURITY_SIGNATURE" 

305 + "SMB_SECURITY_SIGNATURE_REQUIRED+IS_LONG_NAME" 

306 ) 

307 self.send(pkt) 

308 

309 @ATMT.state() 

310 def SENT_NEGOTIATE(self): 

311 pass 

312 

313 @ATMT.state() 

314 def SMB2_NEGOTIATE(self): 

315 pass 

316 

317 @ATMT.condition(SMB2_NEGOTIATE) 

318 def send_negotiate_smb2(self): 

319 raise self.SENT_NEGOTIATE() 

320 

321 @ATMT.action(send_negotiate_smb2) 

322 def on_negotiate_smb2(self): 

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

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

325 Dialects=self.DIALECTS, 

326 SecurityMode=( 

327 "SIGNING_ENABLED+SIGNING_REQUIRED" 

328 if self.session.SigningRequired 

329 else "SIGNING_ENABLED" 

330 ), 

331 ) 

332 if self.MAX_DIALECT >= 0x0210: 

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

334 # MUST be set to the global ClientGuid value" 

335 pkt.ClientGUID = self.GUID 

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

337 self.NegotiateCapabilities = "+".join( 

338 [ 

339 "DFS", 

340 "LEASING", 

341 "LARGE_MTU", 

342 ] 

343 ) 

344 if self.MAX_DIALECT >= 0x0300: 

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

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

347 [ 

348 "MULTI_CHANNEL", 

349 "PERSISTENT_HANDLES", 

350 "DIRECTORY_LEASING", 

351 "ENCRYPTION", 

352 ] 

353 ) 

354 if self.MAX_DIALECT >= 0x0311: 

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

356 pkt.NegotiateContexts = [ 

357 SMB2_Negotiate_Context() 

358 / SMB2_Preauth_Integrity_Capabilities( 

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

360 HashAlgorithms=["SHA-512"], 

361 Salt=self.session.Salt, 

362 ), 

363 SMB2_Negotiate_Context() 

364 / SMB2_Encryption_Capabilities( 

365 Ciphers=self.session.SupportedCipherIds, 

366 ), 

367 # TODO support compression and RDMA 

368 SMB2_Negotiate_Context() 

369 / SMB2_Netname_Negotiate_Context_ID( 

370 NetName=self.HOST, 

371 ), 

372 SMB2_Negotiate_Context() 

373 / SMB2_Signing_Capabilities( 

374 SigningAlgorithms=self.session.SupportedSigningAlgorithmIds, 

375 ), 

376 ] 

377 pkt.Capabilities = self.NegotiateCapabilities 

378 # Send 

379 self.send(pkt) 

380 # If required, compute sessions 

381 self.session.computeSMBConnectionPreauth( 

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

383 ) 

384 

385 @ATMT.receive_condition(SENT_NEGOTIATE) 

386 def receive_negotiate_response(self, pkt): 

387 if ( 

388 SMBNegotiate_Response_Extended_Security in pkt 

389 or SMB2_Negotiate_Protocol_Response in pkt 

390 ): 

391 # Extended SMB1 / SMB2 

392 try: 

393 ssp_blob = pkt.SecurityBlob # eventually SPNEGO server initiation 

394 except AttributeError: 

395 ssp_blob = None 

396 if ( 

397 SMB2_Negotiate_Protocol_Response in pkt 

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

399 ): 

400 # Version is SMB X.??? 

401 # [MS-SMB2] 3.2.5.2 

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

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

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

405 # SMB2 header to 1. 

406 self.SequenceWindow = (1, 1) 

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

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

409 raise self.SMB2_NEGOTIATE() 

410 else: 

411 if SMB2_Negotiate_Protocol_Response in pkt: 

412 # SMB2 was negotiated ! 

413 self.session.Dialect = pkt.DialectRevision 

414 # If required, compute sessions 

415 self.session.computeSMBConnectionPreauth( 

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

417 ) 

418 # Process max sizes 

419 self.session.MaxReadSize = pkt.MaxReadSize 

420 self.session.MaxTransactionSize = pkt.MaxTransactionSize 

421 self.session.MaxWriteSize = pkt.MaxWriteSize 

422 # Process SecurityMode 

423 if pkt.SecurityMode.SIGNING_REQUIRED: 

424 self.session.SigningRequired = True 

425 # Process capabilities 

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

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

428 # Process NegotiateContext 

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

430 for ngctx in pkt.NegotiateContexts: 

431 if ngctx.ContextType == 0x0002: 

432 # SMB2_ENCRYPTION_CAPABILITIES 

433 if ngctx.Ciphers and ngctx.Ciphers[0] != 0: 

434 self.session.CipherId = SMB2_ENCRYPTION_CIPHERS[ 

435 ngctx.Ciphers[0] 

436 ] 

437 self.session.SupportsEncryption = True 

438 elif ngctx.ContextType == 0x0008: 

439 # SMB2_SIGNING_CAPABILITIES 

440 self.session.SigningAlgorithmId = ( 

441 SMB2_SIGNING_ALGORITHMS[ngctx.SigningAlgorithms[0]] 

442 ) 

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

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

445 raise self.NEGO_FAILED() 

446 if self.SMB2: 

447 self.update_smbheader(pkt) 

448 raise self.NEGOTIATED(ssp_blob) 

449 elif SMBNegotiate_Response_Security in pkt: 

450 # Non-extended SMB1 

451 # Never tested. FIXME. probably broken 

452 raise self.NEGOTIATED(pkt.Challenge) 

453 

454 @ATMT.state(final=1) 

455 def NEGO_FAILED(self): 

456 self.smb_sock_ready.set() 

457 

458 @ATMT.state() 

459 def NEGOTIATED(self, ssp_blob=None): 

460 # Negotiated ! We now know the Dialect 

461 if self.session.Dialect > 0x0202: 

462 # [MS-SMB2] sect 3.2.5.1.4 

463 self.smb_header.CreditRequest = 1 

464 # Begin session establishment 

465 ssp_tuple = self.session.ssp.GSS_Init_sec_context( 

466 self.session.sspcontext, 

467 input_token=ssp_blob, 

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

469 req_flags=( 

470 GSS_C_FLAGS.GSS_C_MUTUAL_FLAG 

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

472 ), 

473 ) 

474 return ssp_tuple 

475 

476 def update_smbheader(self, pkt): 

477 """ 

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

479 """ 

480 # Some values should not be updated when ASYNC 

481 if not pkt.Flags.SMB2_FLAGS_ASYNC_COMMAND: 

482 # Update IDs 

483 self.smb_header.SessionId = pkt.SessionId 

484 self.smb_header.TID = pkt.TID 

485 self.smb_header.PID = pkt.PID 

486 # Update credits 

487 self.CurrentCreditCount += pkt.CreditRequest 

488 # [MS-SMB2] 3.2.5.1.4 

489 self.SequenceWindow = ( 

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

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

492 ) 

493 

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

495 

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

497 def should_retry_without_blob(self, ssp_tuple): 

498 _, _, status = ssp_tuple 

499 if status == GSS_S_DEFECTIVE_TOKEN: 

500 # Token was defective. This could be that we passed a SPNEGO initial token 

501 # to a NTLM SSP (not using SPNEGO). Retry using no input blob 

502 raise self.NEGOTIATED() 

503 

504 @ATMT.condition(NEGOTIATED, prio=2) 

505 def should_send_session_setup_request(self, ssp_tuple): 

506 _, _, status = ssp_tuple 

507 if status not in [GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED]: 

508 raise ValueError( 

509 "Internal error: the SSP completed with error: %s" % status 

510 ) 

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

512 

513 @ATMT.state() 

514 def SENT_SESSION_REQUEST(self): 

515 pass 

516 

517 @ATMT.action(should_send_session_setup_request) 

518 def send_setup_session_request(self, ssp_tuple): 

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

520 if self.SMB2 and status == GSS_S_CONTINUE_NEEDED: 

521 # New session: force 0 

522 self.SessionId = 0 

523 if self.SMB2 or self.EXTENDED_SECURITY: 

524 # SMB1 extended / SMB2 

525 if self.SMB2: 

526 # SMB2 

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

528 Capabilities="DFS", 

529 SecurityMode=( 

530 "SIGNING_ENABLED+SIGNING_REQUIRED" 

531 if self.session.SigningRequired 

532 else "SIGNING_ENABLED" 

533 ), 

534 ) 

535 else: 

536 # SMB1 extended 

537 pkt = ( 

538 self.smb_header.copy() 

539 / SMBSession_Setup_AndX_Request_Extended_Security( 

540 ServerCapabilities=( 

541 "UNICODE+NT_SMBS+STATUS32+LEVEL_II_OPLOCKS+" 

542 "DYNAMIC_REAUTH+EXTENDED_SECURITY" 

543 ), 

544 NativeOS=b"", 

545 NativeLanMan=b"", 

546 ) 

547 ) 

548 pkt.SecurityBlob = token 

549 else: 

550 # Non-extended security. 

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

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

553 NativeOS=b"", 

554 NativeLanMan=b"", 

555 OEMPassword=b"\0" * 24, 

556 UnicodePassword=token, 

557 ) 

558 self.send(pkt) 

559 if self.SMB2: 

560 # If required, compute sessions 

561 self.session.computeSMBSessionPreauth( 

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

563 ) 

564 

565 @ATMT.receive_condition(SENT_SESSION_REQUEST) 

566 def receive_session_setup_response(self, pkt): 

567 if ( 

568 SMBSession_Null in pkt 

569 or SMBSession_Setup_AndX_Response_Extended_Security in pkt 

570 or SMBSession_Setup_AndX_Response in pkt 

571 ): 

572 # SMB1 

573 if SMBSession_Null in pkt: 

574 # Likely an error 

575 raise self.NEGOTIATED() 

576 # Logging 

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

578 # Not SUCCESS nor MORE_PROCESSING_REQUIRED: log 

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

580 self.debug( 

581 lvl=1, 

582 msg=conf.color_theme.red( 

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

584 ), 

585 ) 

586 if self.SMB2: 

587 self.update_smbheader(pkt) 

588 # Cases depending on the response packet 

589 if ( 

590 SMBSession_Setup_AndX_Response_Extended_Security in pkt 

591 or SMB2_Session_Setup_Response in pkt 

592 ): 

593 # The server assigns us a SessionId 

594 self.smb_header.SessionId = pkt.SessionId 

595 # SMB1 extended / SMB2 

596 if pkt.Status == 0: # Authenticated 

597 if SMB2_Session_Setup_Response in pkt: 

598 # [MS-SMB2] sect 3.2.5.3.1 

599 if pkt.SessionFlags.IS_GUEST: 

600 if self.REQUIRE_SIGNATURE or self.REQUIRE_ENCRYPTION: 

601 self.ErrorStatus = "Guest session lacks required protection" 

602 raise self.AUTH_FAILED() 

603 # "If the security subsystem indicates that the session 

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

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

606 self.session.IsGuest = True 

607 self.session.SigningRequired = False 

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

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

610 self.session.EncryptData = True 

611 self.session.SigningRequired = False 

612 raise self.AUTHENTICATED(pkt.SecurityBlob) 

613 else: 

614 if SMB2_Header in pkt: 

615 # If required, compute sessions 

616 self.session.computeSMBSessionPreauth( 

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

618 ) 

619 # Ongoing auth 

620 raise self.NEGOTIATED(pkt.SecurityBlob) 

621 elif SMBSession_Setup_AndX_Response_Extended_Security in pkt: 

622 # SMB1 non-extended 

623 pass 

624 elif SMB2_Error_Response in pkt: 

625 # Authentication failure 

626 self.session.sspcontext.clifailure() 

627 # Reset Session preauth (SMB 3.1.1) 

628 self.session.SessionPreauthIntegrityHashValue = None 

629 if not self.RETRY: 

630 raise self.AUTH_FAILED() 

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

632 self.RETRY -= 1 

633 raise self.NEGOTIATED() 

634 

635 @ATMT.state(final=1) 

636 def AUTH_FAILED(self): 

637 self.smb_sock_ready.set() 

638 

639 @ATMT.state() 

640 def AUTHENTICATED(self, ssp_blob=None): 

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

642 self.session.sspcontext, 

643 input_token=ssp_blob, 

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

645 ) 

646 if status != GSS_S_COMPLETE: 

647 raise ValueError( 

648 "Internal error: the SSP completed with error: %s" % status 

649 ) 

650 # Authentication was successful 

651 self.session.computeSMBSessionKeys(IsClient=True) 

652 

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

654 

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

656 def authenticated_post_actions(self): 

657 raise self.SOCKET_BIND() 

658 

659 # Plain SMB Socket 

660 

661 @ATMT.state() 

662 def SOCKET_BIND(self): 

663 self.smb_sock_ready.set() 

664 

665 @ATMT.condition(SOCKET_BIND) 

666 def start_smb_socket(self): 

667 raise self.SOCKET_MODE_SMB() 

668 

669 @ATMT.state() 

670 def SOCKET_MODE_SMB(self): 

671 pass 

672 

673 @ATMT.receive_condition(SOCKET_MODE_SMB) 

674 def incoming_data_received_smb(self, pkt): 

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

676 

677 @ATMT.action(incoming_data_received_smb) 

678 def receive_data_smb(self, pkt): 

679 resp = pkt[SMB2_Header].payload 

680 if isinstance(resp, SMB2_Error_Response): 

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

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

683 return 

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

685 # this is a notify cleanup. ignore 

686 return 

687 self.update_smbheader(pkt) 

688 # Add the status to the response as metadata 

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

690 self.oi.smbpipe.send(resp) 

691 

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

693 def outgoing_data_received_smb(self, fd): 

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

695 

696 @ATMT.action(outgoing_data_received_smb) 

697 def send_data(self, d): 

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

699 

700 

701class SMB_SOCKET(SuperSocket): 

702 """ 

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

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

705 """ 

706 

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

708 self.ins = smbsock 

709 self.timeout = timeout 

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

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

712 if self.session.sspcontext: 

713 self.session.sspcontext.clifailure() 

714 raise TimeoutError( 

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

716 ) 

717 if self.ins.atmt.ErrorStatus: 

718 raise Scapy_Exception( 

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

720 ) 

721 

722 @classmethod 

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

724 """ 

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

726 SMB_SOCKET/SMB_RPC_SOCKET 

727 """ 

728 return cls( 

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

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

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

732 ) 

733 

734 @property 

735 def session(self): 

736 return self.ins.atmt.session 

737 

738 def set_TID(self, TID): 

739 """ 

740 Set the TID (Tree ID). 

741 This can be called before sending a packet 

742 """ 

743 self.ins.atmt.smb_header.TID = TID 

744 

745 def get_TID(self): 

746 """ 

747 Get the current TID from the underlying socket 

748 """ 

749 return self.ins.atmt.smb_header.TID 

750 

751 def tree_connect(self, name): 

752 """ 

753 Send a TreeConnect request 

754 """ 

755 resp = self.ins.sr1( 

756 SMB2_Tree_Connect_Request( 

757 Buffer=[ 

758 ( 

759 "Path", 

760 "\\\\%s\\%s" 

761 % ( 

762 self.session.sspcontext.ServerHostname, 

763 name, 

764 ), 

765 ) 

766 ] 

767 ), 

768 chainCC=True, 

769 verbose=False, 

770 timeout=self.timeout, 

771 ) 

772 if not resp: 

773 raise ValueError("TreeConnect timed out !") 

774 if SMB2_Tree_Connect_Response not in resp: 

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

776 # [MS-SMB2] sect 3.2.5.5 

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

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

779 self.session.TreeEncryptData = True 

780 else: 

781 self.session.TreeEncryptData = False 

782 return self.get_TID() 

783 

784 def tree_disconnect(self): 

785 """ 

786 Send a TreeDisconnect request 

787 """ 

788 resp = self.ins.sr1( 

789 SMB2_Tree_Disconnect_Request(), 

790 verbose=False, 

791 timeout=self.timeout, 

792 chainCC=True, 

793 ) 

794 if not resp: 

795 raise ValueError("TreeDisconnect timed out !") 

796 if SMB2_Tree_Disconnect_Response not in resp: 

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

798 

799 def create_request( 

800 self, 

801 name, 

802 mode="r", 

803 type="pipe", 

804 extra_create_options=[], 

805 extra_desired_access=[], 

806 ): 

807 """ 

808 Open a file/pipe by its name 

809 

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

811 """ 

812 ShareAccess = [] 

813 DesiredAccess = [] 

814 # Common params depending on the access 

815 if "r" in mode: 

816 ShareAccess.append("FILE_SHARE_READ") 

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

818 if "w" in mode: 

819 ShareAccess.append("FILE_SHARE_WRITE") 

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

821 if "d" in mode: 

822 ShareAccess.append("FILE_SHARE_DELETE") 

823 # Params depending on the type 

824 FileAttributes = [] 

825 CreateOptions = [] 

826 CreateContexts = [] 

827 CreateDisposition = "FILE_OPEN" 

828 if type == "folder": 

829 FileAttributes.append("FILE_ATTRIBUTE_DIRECTORY") 

830 CreateOptions.append("FILE_DIRECTORY_FILE") 

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

832 CreateOptions = ["FILE_NON_DIRECTORY_FILE"] 

833 if "r" in mode: 

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

835 if "w" in mode: 

836 CreateDisposition = "FILE_OVERWRITE_IF" 

837 DesiredAccess.append("FILE_WRITE_EA") 

838 if "d" in mode: 

839 DesiredAccess.append("DELETE") 

840 CreateOptions.append("FILE_DELETE_ON_CLOSE") 

841 if type == "file": 

842 FileAttributes.append("FILE_ATTRIBUTE_NORMAL") 

843 elif type: 

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

845 # [MS-SMB2] 3.2.4.3.8 

846 RequestedOplockLevel = 0 

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

848 RequestedOplockLevel = "SMB2_OPLOCK_LEVEL_LEASE" 

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

850 RequestedOplockLevel = "SMB2_OPLOCK_LEVEL_LEASE" 

851 # SMB 3.X 

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

853 CreateContexts.extend( 

854 [ 

855 # [SMB2] sect 3.2.4.3.5 

856 SMB2_Create_Context( 

857 Name=b"DH2Q", 

858 Data=SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2( 

859 CreateGuid=RandUUID()._fix() 

860 ), 

861 ), 

862 # [SMB2] sect 3.2.4.3.9 

863 SMB2_Create_Context( 

864 Name=b"MxAc", 

865 ), 

866 # [SMB2] sect 3.2.4.3.10 

867 SMB2_Create_Context( 

868 Name=b"QFid", 

869 ), 

870 # [SMB2] sect 3.2.4.3.8 

871 SMB2_Create_Context( 

872 Name=b"RqLs", 

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

874 ), 

875 ] 

876 ) 

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

878 CreateContexts.extend( 

879 [ 

880 # [SMB2] sect 3.2.4.3.8 

881 SMB2_Create_Context( 

882 Name=b"RqLs", 

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

884 ), 

885 ] 

886 ) 

887 # Extra options 

888 if extra_create_options: 

889 CreateOptions.extend(extra_create_options) 

890 if extra_desired_access: 

891 DesiredAccess.extend(extra_desired_access) 

892 # Request 

893 resp = self.ins.sr1( 

894 SMB2_Create_Request( 

895 ImpersonationLevel="Impersonation", 

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

897 CreateDisposition=CreateDisposition, 

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

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

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

901 CreateContexts=CreateContexts, 

902 RequestedOplockLevel=RequestedOplockLevel, 

903 Name=name, 

904 ), 

905 chainCC=True, 

906 verbose=0, 

907 timeout=self.timeout, 

908 ) 

909 if not resp: 

910 raise ValueError("CreateRequest timed out !") 

911 if SMB2_Create_Response not in resp: 

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

913 return resp[SMB2_Create_Response].FileId 

914 

915 def close_request(self, FileId): 

916 """ 

917 Close the FileId 

918 """ 

919 pkt = SMB2_Close_Request(FileId=FileId) 

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

921 if not resp: 

922 raise ValueError("CloseRequest timed out !") 

923 if SMB2_Close_Response not in resp: 

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

925 

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

927 """ 

928 Read request 

929 """ 

930 resp = self.ins.sr1( 

931 SMB2_Read_Request( 

932 FileId=FileId, 

933 Length=Length, 

934 Offset=Offset, 

935 ), 

936 chainCC=True, 

937 verbose=0, 

938 timeout=self.timeout * 10, 

939 ) 

940 if not resp: 

941 raise ValueError("ReadRequest timed out !") 

942 if SMB2_Read_Response not in resp: 

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

944 return resp.Data 

945 

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

947 """ 

948 Write request 

949 """ 

950 resp = self.ins.sr1( 

951 SMB2_Write_Request( 

952 FileId=FileId, 

953 Data=Data, 

954 Offset=Offset, 

955 ), 

956 chainCC=True, 

957 verbose=0, 

958 timeout=self.timeout * 10, 

959 ) 

960 if not resp: 

961 raise ValueError("WriteRequest timed out !") 

962 if SMB2_Write_Response not in resp: 

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

964 return resp.Count 

965 

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

967 """ 

968 Query the Directory with FileId 

969 """ 

970 results = [] 

971 Flags = "SMB2_RESTART_SCANS" 

972 while True: 

973 pkt = SMB2_Query_Directory_Request( 

974 FileInformationClass="FileIdBothDirectoryInformation", 

975 FileId=FileId, 

976 FileName=FileName, 

977 Flags=Flags, 

978 ) 

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

980 Flags = 0 # only the first one is RESTART_SCANS 

981 if not resp: 

982 raise ValueError("QueryDirectory timed out !") 

983 if SMB2_Error_Response in resp: 

984 break 

985 elif SMB2_Query_Directory_Response not in resp: 

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

987 res = FileIdBothDirectoryInformation(resp.Output) 

988 results.extend( 

989 [ 

990 ( 

991 x.FileName, 

992 x.FileAttributes, 

993 x.EndOfFile, 

994 x.LastWriteTime, 

995 ) 

996 for x in res.files 

997 ] 

998 ) 

999 return results 

1000 

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

1002 """ 

1003 Query the Info 

1004 """ 

1005 pkt = SMB2_Query_Info_Request( 

1006 InfoType=InfoType, 

1007 FileInfoClass=FileInfoClass, 

1008 OutputBufferLength=65535, 

1009 FileId=FileId, 

1010 AdditionalInformation=AdditionalInformation, 

1011 ) 

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

1013 if not resp: 

1014 raise ValueError("QueryInfo timed out !") 

1015 if SMB2_Query_Info_Response not in resp: 

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

1017 return resp.Output 

1018 

1019 def ioctl(self, CtlCode, Flags="SMB2_0_IOCTL_IS_FSCTL", Input=None): 

1020 """ 

1021 Perform an IOCTL 

1022 """ 

1023 pkt = SMB2_IOCTL_Request( 

1024 FileId=SMB2_FILEID( 

1025 Persistent=0xFFFFFFFFFFFFFFFF, Volatile=0xFFFFFFFFFFFFFFFF 

1026 ), 

1027 Flags=Flags, 

1028 CtlCode=CtlCode, 

1029 ) 

1030 if Input is not None: 

1031 pkt.Input = bytes(Input) 

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

1033 if SMB2_IOCTL_Response not in resp: 

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

1035 return resp.Output 

1036 

1037 def changenotify(self, FileId): 

1038 """ 

1039 Register change notify 

1040 """ 

1041 pkt = SMB2_Change_Notify_Request( 

1042 Flags="SMB2_WATCH_TREE", 

1043 OutputBufferLength=65535, 

1044 FileId=FileId, 

1045 CompletionFilter=0x0FFF, 

1046 ) 

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

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

1049 if SMB2_Change_Notify_Response not in resp: 

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

1051 return resp.Output 

1052 

1053 

1054class SMB_RPC_SOCKET(ObjectPipe, SMB_SOCKET): 

1055 """ 

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

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

1058 

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

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

1061 """ 

1062 

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

1064 self.use_ioctl = use_ioctl 

1065 ObjectPipe.__init__(self, "SMB_RPC_SOCKET") 

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

1067 

1068 def open_pipe(self, name): 

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

1070 

1071 def close_pipe(self): 

1072 self.close_request(self.PipeFileId) 

1073 self.PipeFileId = None 

1074 

1075 def send(self, x, is_sr1=True): 

1076 # Reminder: this class is an ObjectPipe ! It doesn't act as a real socket 

1077 # but just a queue. When someone calls the "send" function, they pipe 

1078 # some data that we must send, and tell us if they expect an answer through 

1079 # the is_sr1 flag. 

1080 

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

1082 is_frag = x.pfc_flags & 3 != 3 

1083 

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

1085 # Use IOCTLRequest 

1086 pkt = SMB2_IOCTL_Request( 

1087 FileId=self.PipeFileId, 

1088 Flags="SMB2_0_IOCTL_IS_FSCTL", 

1089 CtlCode="FSCTL_PIPE_TRANSCEIVE", 

1090 ) 

1091 pkt.Input = bytes(x) 

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

1093 if SMB2_IOCTL_Response not in resp: 

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

1095 data = bytes(resp.Output) 

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

1097 

1098 # Handle BUFFER_OVERFLOW (big DCE/RPC response) 

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

1100 # Retrieve DCE/RPC full size 

1101 resp = self.ins.sr1( 

1102 SMB2_Read_Request( 

1103 FileId=self.PipeFileId, 

1104 ), 

1105 verbose=0, 

1106 ) 

1107 data = resp.Data 

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

1109 else: 

1110 # Use WriteRequest/ReadRequest 

1111 pkt = SMB2_Write_Request( 

1112 FileId=self.PipeFileId, 

1113 ) 

1114 pkt.Data = bytes(x) 

1115 # We send the Write Request 

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

1117 if SMB2_Write_Response not in resp: 

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

1119 

1120 # We may not be expecting an answer 

1121 if not is_sr1: 

1122 return 

1123 

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

1125 if is_frag and not x.pfc_flags.PFC_LAST_FRAG: 

1126 return 

1127 

1128 # We send a Read Request afterwards 

1129 resp = self.ins.sr1( 

1130 SMB2_Read_Request( 

1131 FileId=self.PipeFileId, 

1132 ), 

1133 verbose=0, 

1134 ) 

1135 if SMB2_Read_Response not in resp: 

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

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

1138 # Handle fragmented response 

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

1140 # Retrieve DCE/RPC full size 

1141 resp = self.ins.sr1( 

1142 SMB2_Read_Request( 

1143 FileId=self.PipeFileId, 

1144 ), 

1145 verbose=0, 

1146 ) 

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

1148 

1149 def close(self): 

1150 SMB_SOCKET.close(self) 

1151 ObjectPipe.close(self) 

1152 

1153 

1154@conf.commands.register 

1155class smbclient(CLIUtil): 

1156 r""" 

1157 A simple SMB client CLI powered by Scapy 

1158 

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

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

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

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

1163 :param kerberos_required: require kerberos 

1164 :param port: the TCP port. default 445 

1165 :param password: if provided, used for auth 

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

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

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

1169 :param use_krb5ccname: (bool) if true, the KRB5CCNAME environment variable will 

1170 be used if available. 

1171 :param use_winssp: (bool) (only works on Windows). Use implicit authentication 

1172 through WinSSP. 

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

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

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

1176 

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

1178 them include the following: 

1179 

1180 :param REQUIRE_ENCRYPTION: requires encryption. 

1181 """ 

1182 

1183 def __init__( 

1184 self, 

1185 target: str, 

1186 UPN: str = None, 

1187 password: str = None, 

1188 guest: bool = False, 

1189 kerberos_required: bool = False, 

1190 HashNt: bytes = None, 

1191 HashAes256Sha96: bytes = None, 

1192 HashAes128Sha96: bytes = None, 

1193 use_krb5ccname: bool = False, 

1194 use_winssp: bool = False, 

1195 port: int = 445, 

1196 timeout: int = 5, 

1197 debug: int = 0, 

1198 ssp=None, 

1199 ST=None, 

1200 KEY=None, 

1201 cli=True, 

1202 # SMB arguments 

1203 REQUIRE_ENCRYPTION=False, 

1204 **kwargs, 

1205 ): 

1206 if cli: 

1207 self._depcheck() 

1208 assert ( 

1209 UPN or ssp or guest or use_winssp 

1210 ), "Either UPN, ssp or guest must be provided !" 

1211 # Do we need to build a SSP? 

1212 if ssp is None: 

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

1214 if not guest: 

1215 ssp = SPNEGOSSP.from_cli_arguments( 

1216 UPN=UPN, 

1217 target=target, 

1218 password=password, 

1219 HashNt=HashNt, 

1220 HashAes256Sha96=HashAes256Sha96, 

1221 HashAes128Sha96=HashAes128Sha96, 

1222 ST=ST, 

1223 KEY=KEY, 

1224 kerberos_required=kerberos_required, 

1225 use_krb5ccname=use_krb5ccname, 

1226 use_winssp=use_winssp, 

1227 ) 

1228 else: 

1229 # Guest mode 

1230 ssp = None 

1231 # Check if target is IPv4 or IPv6 

1232 if ":" in target: 

1233 family = socket.AF_INET6 

1234 else: 

1235 family = socket.AF_INET 

1236 # Open socket 

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

1238 # Configure socket for SMB: 

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

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

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

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

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

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

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

1246 # Timeout & connect 

1247 sock.settimeout(timeout) 

1248 if debug: 

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

1250 sock.connect((target, port)) 

1251 self.extra_create_options = [] 

1252 # Wrap with the automaton 

1253 self.timeout = timeout 

1254 kwargs.setdefault("HOST", target) 

1255 self.sock = SMB_Client.from_tcpsock( 

1256 sock, 

1257 ssp=ssp, 

1258 debug=debug, 

1259 REQUIRE_ENCRYPTION=REQUIRE_ENCRYPTION, 

1260 timeout=timeout, 

1261 **kwargs, 

1262 ) 

1263 try: 

1264 # Wrap with SMB_SOCKET 

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

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

1267 _t = time.time() 

1268 while True: 

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

1270 # yay 

1271 break 

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

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

1274 raise Scapy_Exception( 

1275 "%s with status %s" 

1276 % ( 

1277 self.sock.atmt.state.state, 

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

1279 ) 

1280 ) 

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

1282 self.sock.close() 

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

1284 time.sleep(0.1) 

1285 except Exception: 

1286 # Something bad happened, end the socket/automaton 

1287 self.sock.close() 

1288 raise 

1289 

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

1291 from scapy.layers.msrpce.rpcclient import DCERPC_Client 

1292 

1293 self.rpcclient = DCERPC_Client.from_smblink( 

1294 self.sock, 

1295 ndr64=False, 

1296 verb=bool(debug), 

1297 ) 

1298 # We have a valid smb connection ! 

1299 print( 

1300 "%s authentication successful using %s%s !" 

1301 % ( 

1302 SMB_DIALECTS.get( 

1303 self.smbsock.session.Dialect, 

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

1305 ), 

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

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

1308 ) 

1309 ) 

1310 # Now define some variables for our CLI 

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

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

1313 self.current_tree = None 

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

1315 self.sh_cache = [] # cache the shares 

1316 # Start CLI 

1317 if cli: 

1318 self.loop(debug=debug) 

1319 

1320 def ps1(self): 

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

1322 

1323 def close(self): 

1324 print("Connection closed") 

1325 self.smbsock.close() 

1326 

1327 def _require_share(self, silent=False): 

1328 if self.current_tree is None: 

1329 if not silent: 

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

1331 return True 

1332 

1333 def collapse_path(self, path): 

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

1335 # is ridiculous 

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

1337 

1338 def normalize_path(self, path): 

1339 """ 

1340 Normalize path for CIFS usage 

1341 """ 

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

1343 

1344 @CLIUtil.addcommand() 

1345 def shares(self): 

1346 """ 

1347 List the shares available 

1348 """ 

1349 # Poll cache 

1350 if self.sh_cache: 

1351 return self.sh_cache 

1352 # It's an RPC 

1353 self.rpcclient.open_smbpipe("srvsvc") 

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

1355 req = NetrShareEnum_Request( 

1356 InfoStruct=LPSHARE_ENUM_STRUCT( 

1357 Level=1, 

1358 ShareInfo=NDRUnion( 

1359 tag=1, 

1360 value=SHARE_INFO_1_CONTAINER(Buffer=None), 

1361 ), 

1362 ), 

1363 PreferedMaximumLength=0xFFFFFFFF, 

1364 ndr64=self.rpcclient.ndr64, 

1365 ) 

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

1367 self.rpcclient.close_smbpipe() 

1368 if resp.status != 0: 

1369 resp.show() 

1370 raise ValueError("NetrShareEnum_Request failed !") 

1371 results = [] 

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

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

1374 results.append( 

1375 ( 

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

1377 SRVSVC_SHARE_TYPES.get(shi1_type, shi1_type), 

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

1379 ) 

1380 ) 

1381 self.sh_cache = results # cache 

1382 return results 

1383 

1384 @CLIUtil.addoutput(shares) 

1385 def shares_output(self, results): 

1386 """ 

1387 Print the output of 'shares' 

1388 """ 

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

1390 

1391 @CLIUtil.addcommand(mono=True) 

1392 def use(self, share): 

1393 """ 

1394 Open a share 

1395 """ 

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

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

1398 self.ls_cache.clear() 

1399 

1400 @CLIUtil.addcomplete(use) 

1401 def use_complete(self, share): 

1402 """ 

1403 Auto-complete 'use' 

1404 """ 

1405 return [ 

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

1407 ] 

1408 

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

1410 """ 

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

1412 """ 

1413 # Find parent directory if it exists 

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

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

1416 eltname = elt.name 

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

1418 eltpar = elt 

1419 eltname = "" 

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

1421 eltpar = elt.parent 

1422 return eltpar, eltname 

1423 

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

1425 """ 

1426 Return a listing of the remote files for completion purposes 

1427 """ 

1428 if cond is None: 

1429 cond = lambda _: True 

1430 eltpar, eltname = self._parsepath(arg) 

1431 # ls in that directory 

1432 try: 

1433 files = self.ls(parent=eltpar) 

1434 except ValueError: 

1435 return [] 

1436 return [ 

1437 str(eltpar / x[0]) 

1438 for x in files 

1439 if ( 

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

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

1442 and cond(x[1]) 

1443 ) 

1444 ] 

1445 

1446 def _dir_complete(self, arg): 

1447 """ 

1448 Return a directories of remote files for completion purposes 

1449 """ 

1450 results = self._fs_complete( 

1451 arg, 

1452 cond=lambda x: x.FILE_ATTRIBUTE_DIRECTORY, 

1453 ) 

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

1455 # skip through folders 

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

1457 return results 

1458 

1459 @CLIUtil.addcommand(mono=True) 

1460 def ls(self, parent=None): 

1461 """ 

1462 List the files in the remote directory 

1463 -t: sort by timestamp 

1464 -S: sort by size 

1465 -r: reverse while sorting 

1466 """ 

1467 if self._require_share(): 

1468 return 

1469 # Get pwd of the ls 

1470 pwd = self.pwd 

1471 if parent is not None: 

1472 pwd /= parent 

1473 pwd = self.normalize_path(pwd) 

1474 # Poll the cache 

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

1476 return self.ls_cache[pwd] 

1477 self.smbsock.set_TID(self.current_tree) 

1478 # Open folder 

1479 fileId = self.smbsock.create_request( 

1480 pwd, 

1481 type="folder", 

1482 extra_create_options=self.extra_create_options, 

1483 ) 

1484 # Query the folder 

1485 files = self.smbsock.query_directory(fileId) 

1486 # Close the folder 

1487 self.smbsock.close_request(fileId) 

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

1489 return files 

1490 

1491 @CLIUtil.addoutput(ls) 

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

1493 """ 

1494 Print the output of 'ls' 

1495 """ 

1496 fld = UTCTimeField( 

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

1498 ) 

1499 if t: 

1500 # Sort by time 

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

1502 if S: 

1503 # Sort by size 

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

1505 if r: 

1506 # Reverse sort 

1507 results = results[::-1] 

1508 results = [ 

1509 ( 

1510 x[0], 

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

1512 human_size(x[2]), 

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

1514 ) 

1515 for x in results 

1516 ] 

1517 print( 

1518 pretty_list( 

1519 results, 

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

1521 sortBy=None, 

1522 ) 

1523 ) 

1524 

1525 @CLIUtil.addcomplete(ls) 

1526 def ls_complete(self, folder): 

1527 """ 

1528 Auto-complete ls 

1529 """ 

1530 if self._require_share(silent=True): 

1531 return [] 

1532 return self._dir_complete(folder) 

1533 

1534 @CLIUtil.addcommand(mono=True) 

1535 def cd(self, folder): 

1536 """ 

1537 Change the remote current directory 

1538 """ 

1539 if self._require_share(): 

1540 return 

1541 if not folder: 

1542 # show mode 

1543 return str(self.pwd) 

1544 self.pwd /= folder 

1545 self.pwd = self.collapse_path(self.pwd) 

1546 self.ls_cache.clear() 

1547 

1548 @CLIUtil.addcomplete(cd) 

1549 def cd_complete(self, folder): 

1550 """ 

1551 Auto-complete cd 

1552 """ 

1553 if self._require_share(silent=True): 

1554 return [] 

1555 return self._dir_complete(folder) 

1556 

1557 def _lfs_complete(self, arg, cond): 

1558 """ 

1559 Return a listing of local files for completion purposes 

1560 """ 

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

1562 eltpar = self.localpwd / eltpar 

1563 return [ 

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

1565 str(eltpar / x.name) 

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

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

1568 ] 

1569 

1570 @CLIUtil.addoutput(cd) 

1571 def cd_output(self, result): 

1572 """ 

1573 Print the output of 'cd' 

1574 """ 

1575 if result: 

1576 print(result) 

1577 

1578 @CLIUtil.addcommand() 

1579 def lls(self): 

1580 """ 

1581 List the files in the local directory 

1582 """ 

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

1584 

1585 @CLIUtil.addoutput(lls) 

1586 def lls_output(self, results): 

1587 """ 

1588 Print the output of 'lls' 

1589 """ 

1590 results = [ 

1591 ( 

1592 x.name, 

1593 human_size(stat.st_size), 

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

1595 ) 

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

1597 ] 

1598 print( 

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

1600 ) 

1601 

1602 @CLIUtil.addcommand(mono=True) 

1603 def lcd(self, folder): 

1604 """ 

1605 Change the local current directory 

1606 """ 

1607 if not folder: 

1608 # show mode 

1609 return str(self.localpwd) 

1610 self.localpwd /= folder 

1611 self.localpwd = self.localpwd.resolve() 

1612 

1613 @CLIUtil.addcomplete(lcd) 

1614 def lcd_complete(self, folder): 

1615 """ 

1616 Auto-complete lcd 

1617 """ 

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

1619 

1620 @CLIUtil.addoutput(lcd) 

1621 def lcd_output(self, result): 

1622 """ 

1623 Print the output of 'lcd' 

1624 """ 

1625 if result: 

1626 print(result) 

1627 

1628 def _get_file(self, file, fd): 

1629 """ 

1630 Gets the file bytes from a remote host 

1631 """ 

1632 # Get pwd of the ls 

1633 fpath = self.pwd / file 

1634 self.smbsock.set_TID(self.current_tree) 

1635 

1636 # Open file 

1637 fileId = self.smbsock.create_request( 

1638 self.normalize_path(fpath), 

1639 type="file", 

1640 extra_create_options=[ 

1641 "FILE_SEQUENTIAL_ONLY", 

1642 ] 

1643 + self.extra_create_options, 

1644 ) 

1645 

1646 # Get the file size 

1647 info = FileAllInformation( 

1648 self.smbsock.query_info( 

1649 FileId=fileId, 

1650 InfoType="SMB2_0_INFO_FILE", 

1651 FileInfoClass="FileAllInformation", 

1652 ) 

1653 ) 

1654 length = info.StandardInformation.EndOfFile 

1655 offset = 0 

1656 

1657 # Read the file 

1658 while length: 

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

1660 fd.write( 

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

1662 ) 

1663 offset += lengthRead 

1664 length -= lengthRead 

1665 

1666 # Close the file 

1667 self.smbsock.close_request(fileId) 

1668 return offset 

1669 

1670 def _send_file(self, fname, fd): 

1671 """ 

1672 Send the file bytes to a remote host 

1673 """ 

1674 # Get destination file 

1675 fpath = self.pwd / fname 

1676 self.smbsock.set_TID(self.current_tree) 

1677 # Open file 

1678 fileId = self.smbsock.create_request( 

1679 self.normalize_path(fpath), 

1680 type="file", 

1681 mode="w", 

1682 extra_create_options=self.extra_create_options, 

1683 ) 

1684 # Send the file 

1685 offset = 0 

1686 while True: 

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

1688 if not data: 

1689 # end of file 

1690 break 

1691 offset += self.smbsock.write_request( 

1692 Data=data, 

1693 FileId=fileId, 

1694 Offset=offset, 

1695 ) 

1696 # Close the file 

1697 self.smbsock.close_request(fileId) 

1698 return offset 

1699 

1700 def _listr(self, directory, regx=None, max_depth=None, depth=0, _verb=True): 

1701 """ 

1702 Internal recursive function that yields the remote files 

1703 """ 

1704 if max_depth is not None and depth > max_depth: 

1705 return 

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

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

1708 # Discard . and .. 

1709 continue 

1710 remote = directory / x[0] 

1711 try: 

1712 if x[1].FILE_ATTRIBUTE_DIRECTORY: 

1713 # Sub-directory 

1714 yield from self._listr( 

1715 remote, 

1716 regx=regx, 

1717 depth=depth + 1, 

1718 max_depth=max_depth, 

1719 _verb=_verb, 

1720 ) 

1721 else: 

1722 # Sub-file 

1723 if regx is None or regx.match(str(remote)): 

1724 yield remote 

1725 except ValueError as ex: 

1726 if _verb: 

1727 print(conf.color_theme.red(directory), "->", str(ex)) 

1728 

1729 def _getr(self, directory, root, dest, _verb=True): 

1730 """ 

1731 Internal recursive function to get a directory 

1732 

1733 :param directory: the remote directory to get 

1734 :param root: the root directory to store files into 

1735 :param dest: locally, the directory to store any found files (rec) 

1736 """ 

1737 size = 0 

1738 if dest is None: 

1739 dest = root 

1740 if not dest.exists(): 

1741 dest.mkdir() 

1742 # ls the directory 

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

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

1745 # Discard . and .. 

1746 continue 

1747 remote = directory / x[0] 

1748 local = dest / x[0] 

1749 # Word of caution: this check ONLY works because root and local have been 

1750 # resolve(). Be careful 

1751 if root not in local.parents and local != root: 

1752 raise FileNotFoundError 

1753 try: 

1754 if x[1].FILE_ATTRIBUTE_DIRECTORY: 

1755 # Sub-directory 

1756 size += self._getr(remote, root, local) 

1757 else: 

1758 # Sub-file 

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

1760 if _verb: 

1761 print(remote) 

1762 except ValueError as ex: 

1763 if _verb: 

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

1765 return size 

1766 

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

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

1769 """ 

1770 Retrieve a file 

1771 -r: recursively download a directory 

1772 """ 

1773 if self._require_share(): 

1774 return 

1775 if r: 

1776 dirpar, dirname = self._parsepath(file) 

1777 return file, self._getr( 

1778 dirpar / dirname, # Remotely 

1779 self.localpwd / dirname, # Locally 

1780 None, 

1781 _verb=_verb, 

1782 ) 

1783 else: 

1784 fname = pathlib.PureWindowsPath(file).name 

1785 # Write the buffer 

1786 if _dest is None: 

1787 _dest = self.localpwd / fname 

1788 if not _dest.parent.exists(): 

1789 _dest.parent.mkdir() 

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

1791 size = self._get_file(file, fd) 

1792 return fname, size 

1793 

1794 @CLIUtil.addoutput(get) 

1795 def get_output(self, info): 

1796 """ 

1797 Print the output of 'get' 

1798 """ 

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

1800 

1801 @CLIUtil.addcomplete(get) 

1802 def get_complete(self, file): 

1803 """ 

1804 Auto-complete get 

1805 """ 

1806 if self._require_share(silent=True): 

1807 return [] 

1808 return self._fs_complete(file) 

1809 

1810 @CLIUtil.addcommand() 

1811 def tree(self, regx: str = None, max_depth=None): 

1812 """ 

1813 Show the tree of files from the current directory 

1814 """ 

1815 if self._require_share(): 

1816 return 

1817 if regx is not None: 

1818 regx = re.compile(regx) 

1819 if max_depth is not None: 

1820 max_depth = int(max_depth) 

1821 return self._listr(self.pwd, regx=regx, max_depth=max_depth) 

1822 

1823 @CLIUtil.addoutput(tree) 

1824 def tree_output(self, result): 

1825 """ 

1826 Print the output of 'tree' 

1827 """ 

1828 if self._require_share(silent=True): 

1829 return 

1830 for file in result: 

1831 print(str(file), flush=True) 

1832 

1833 @CLIUtil.addcommand() 

1834 def server(self, command): 

1835 """ 

1836 Get information about the remote server 

1837 """ 

1838 if command == "network": 

1839 # We get the list of interfaces 

1840 if self._require_share(): 

1841 return 

1842 resp = self.smbsock.ioctl(CtlCode="FSCTL_QUERY_NETWORK_INTERFACE_INFO") 

1843 return (command, resp) 

1844 elif command == "sessions": 

1845 # We get the list of active connections via RPC 

1846 self.rpcclient.open_smbpipe("srvsvc") 

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

1848 ResumeHandle = None 

1849 results = [] 

1850 while True: 

1851 req = NetrSessionEnum_Request( 

1852 ClientName=None, 

1853 UserName=None, 

1854 InfoStruct=PSESSION_ENUM_STRUCT( 

1855 Level=502, 

1856 SessionInfo=NDRUnion( 

1857 tag=502, 

1858 value=SESSION_INFO_502_CONTAINER(Buffer=None), 

1859 ), 

1860 ), 

1861 PreferedMaximumLength=0xFFFFFFFF, 

1862 ResumeHandle=ResumeHandle, 

1863 ) 

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

1865 resp.show() 

1866 ResumeHandle = resp.ResumeHandle 

1867 if resp.status in [0, 0x000000EA]: # ERROR_SUCCESS / ERROR_MORE_DATA 

1868 results.extend(resp.valueof("InfoStruct.SessionInfo.Buffer")) 

1869 if resp.status == 0: 

1870 break 

1871 else: 

1872 break 

1873 self.rpcclient.close_smbpipe() 

1874 return (command, results) 

1875 else: 

1876 raise ValueError("Unknown server command: %s" % command) 

1877 

1878 @CLIUtil.addoutput(server) 

1879 def server_output(self, result): 

1880 """ 

1881 Print the output of 'server' 

1882 """ 

1883 command, result = result 

1884 if command == "network": 

1885 # Show the various interfaces, their IP and capabilities 

1886 result.interfaces = [x for x in result.interfaces if x.IfIndex != 0] 

1887 result.show() 

1888 elif command == "sessions": 

1889 # Show the list of active sessions 

1890 for sess in result: 

1891 print("- cname:", sess.valueof("sesi502_cname")) 

1892 print(" username:", sess.valueof("sesi502_username")) 

1893 print(" num_opens:", sess.valueof("sesi502_num_opens")) 

1894 print(" time:", sess.valueof("sesi502_time")) 

1895 print(" idle_time:", sess.valueof("sesi502_idle_time")) 

1896 print(" user_flags:", sess.valueof("sesi502_user_flags")) 

1897 print(" cltype_name:", sess.valueof("sesi502_cltype_name")) 

1898 print(" transport:", sess.valueof("sesi502_transport")) 

1899 

1900 @CLIUtil.addcomplete(server) 

1901 def server_complete(self, line): 

1902 """ 

1903 Auto-complete server 

1904 """ 

1905 return ["network", "sessions"] 

1906 

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

1908 def cat(self, file): 

1909 """ 

1910 Print a file 

1911 """ 

1912 if self._require_share(): 

1913 return 

1914 # Write the buffer to buffer 

1915 buf = io.BytesIO() 

1916 self._get_file(file, buf) 

1917 return buf.getvalue() 

1918 

1919 @CLIUtil.addoutput(cat) 

1920 def cat_output(self, result): 

1921 """ 

1922 Print the output of 'cat' 

1923 """ 

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

1925 

1926 @CLIUtil.addcomplete(cat) 

1927 def cat_complete(self, file): 

1928 """ 

1929 Auto-complete cat 

1930 """ 

1931 if self._require_share(silent=True): 

1932 return [] 

1933 return self._fs_complete(file) 

1934 

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

1936 def put(self, file): 

1937 """ 

1938 Upload a file 

1939 """ 

1940 if self._require_share(): 

1941 return 

1942 local_file = self.localpwd / file 

1943 if local_file.is_dir(): 

1944 # Directory 

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

1946 else: 

1947 fname = pathlib.Path(file).name 

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

1949 size = self._send_file(fname, fd) 

1950 self.ls_cache.clear() 

1951 return fname, size 

1952 

1953 @CLIUtil.addcomplete(put) 

1954 def put_complete(self, folder): 

1955 """ 

1956 Auto-complete put 

1957 """ 

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

1959 

1960 @CLIUtil.addcommand(mono=True) 

1961 def rm(self, file): 

1962 """ 

1963 Delete a file 

1964 """ 

1965 if self._require_share(): 

1966 return 

1967 # Get pwd of the ls 

1968 fpath = self.pwd / file 

1969 self.smbsock.set_TID(self.current_tree) 

1970 # Open file 

1971 fileId = self.smbsock.create_request( 

1972 self.normalize_path(fpath), 

1973 type="file", 

1974 mode="d", 

1975 extra_create_options=self.extra_create_options, 

1976 ) 

1977 # Close the file 

1978 self.smbsock.close_request(fileId) 

1979 self.ls_cache.clear() 

1980 return fpath.name 

1981 

1982 @CLIUtil.addcomplete(rm) 

1983 def rm_complete(self, file): 

1984 """ 

1985 Auto-complete rm 

1986 """ 

1987 if self._require_share(silent=True): 

1988 return [] 

1989 return self._fs_complete(file) 

1990 

1991 @CLIUtil.addcommand() 

1992 def backup(self): 

1993 """ 

1994 Turn on or off backup intent 

1995 """ 

1996 if "FILE_OPEN_FOR_BACKUP_INTENT" in self.extra_create_options: 

1997 print("Backup Intent: Off") 

1998 self.extra_create_options.remove("FILE_OPEN_FOR_BACKUP_INTENT") 

1999 else: 

2000 print("Backup Intent: On") 

2001 self.extra_create_options.append("FILE_OPEN_FOR_BACKUP_INTENT") 

2002 

2003 @CLIUtil.addcommand(mono=True) 

2004 def watch(self, folder): 

2005 """ 

2006 Watch file changes in folder (recursively) 

2007 """ 

2008 if self._require_share(): 

2009 return 

2010 # Get pwd of the ls 

2011 fpath = self.pwd / folder 

2012 self.smbsock.set_TID(self.current_tree) 

2013 # Open file 

2014 fileId = self.smbsock.create_request( 

2015 self.normalize_path(fpath), 

2016 type="folder", 

2017 extra_create_options=self.extra_create_options, 

2018 ) 

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

2020 # Watch for changes 

2021 try: 

2022 while True: 

2023 changes = self.smbsock.changenotify(fileId) 

2024 for chg in changes: 

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

2026 except KeyboardInterrupt: 

2027 pass 

2028 print("Cancelled.") 

2029 

2030 @CLIUtil.addcommand(mono=True) 

2031 def getsd(self, file): 

2032 """ 

2033 Get the Security Descriptor 

2034 """ 

2035 if self._require_share(): 

2036 return 

2037 fpath = self.pwd / file 

2038 self.smbsock.set_TID(self.current_tree) 

2039 # Open file 

2040 fileId = self.smbsock.create_request( 

2041 self.normalize_path(fpath), 

2042 type="", 

2043 mode="", 

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

2045 ) 

2046 # Get the file size 

2047 info = self.smbsock.query_info( 

2048 FileId=fileId, 

2049 InfoType="SMB2_0_INFO_SECURITY", 

2050 FileInfoClass=0, 

2051 AdditionalInformation=( 

2052 0x00000001 

2053 | 0x00000002 

2054 | 0x00000004 

2055 | 0x00000008 

2056 | 0x00000010 

2057 | 0x00000020 

2058 | 0x00000040 

2059 | 0x00010000 

2060 ), 

2061 ) 

2062 self.smbsock.close_request(fileId) 

2063 return info 

2064 

2065 @CLIUtil.addcomplete(getsd) 

2066 def getsd_complete(self, file): 

2067 """ 

2068 Auto-complete getsd 

2069 """ 

2070 if self._require_share(silent=True): 

2071 return [] 

2072 return self._fs_complete(file) 

2073 

2074 @CLIUtil.addoutput(getsd) 

2075 def getsd_output(self, results): 

2076 """ 

2077 Print the output of 'getsd' 

2078 """ 

2079 sd = SECURITY_DESCRIPTOR(results) 

2080 sd.show_print() 

2081 

2082 

2083if __name__ == "__main__": 

2084 from scapy.utils import AutoArgparse 

2085 

2086 AutoArgparse(smbclient)