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

810 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 NetrShareEnum_Response, 

44 SHARE_INFO_1_CONTAINER, 

45) 

46from scapy.layers.ntlm import ( 

47 NTLMSSP, 

48) 

49from scapy.layers.smb import ( 

50 SMBNegotiate_Request, 

51 SMBNegotiate_Response_Extended_Security, 

52 SMBNegotiate_Response_Security, 

53 SMBSession_Null, 

54 SMBSession_Setup_AndX_Request, 

55 SMBSession_Setup_AndX_Request_Extended_Security, 

56 SMBSession_Setup_AndX_Response, 

57 SMBSession_Setup_AndX_Response_Extended_Security, 

58 SMB_Dialect, 

59 SMB_Header, 

60) 

61from scapy.layers.smb2 import ( 

62 DirectTCP, 

63 FileAllInformation, 

64 FileIdBothDirectoryInformation, 

65 SECURITY_DESCRIPTOR, 

66 SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2, 

67 SMB2_CREATE_REQUEST_LEASE, 

68 SMB2_CREATE_REQUEST_LEASE_V2, 

69 SMB2_Change_Notify_Request, 

70 SMB2_Change_Notify_Response, 

71 SMB2_Close_Request, 

72 SMB2_Close_Response, 

73 SMB2_Create_Context, 

74 SMB2_Create_Request, 

75 SMB2_Create_Response, 

76 SMB2_ENCRYPTION_CIPHERS, 

77 SMB2_Encryption_Capabilities, 

78 SMB2_Error_Response, 

79 SMB2_Header, 

80 SMB2_IOCTL_Request, 

81 SMB2_IOCTL_Response, 

82 SMB2_Negotiate_Context, 

83 SMB2_Negotiate_Protocol_Request, 

84 SMB2_Negotiate_Protocol_Response, 

85 SMB2_Netname_Negotiate_Context_ID, 

86 SMB2_Preauth_Integrity_Capabilities, 

87 SMB2_Query_Directory_Request, 

88 SMB2_Query_Directory_Response, 

89 SMB2_Query_Info_Request, 

90 SMB2_Query_Info_Response, 

91 SMB2_Read_Request, 

92 SMB2_Read_Response, 

93 SMB2_SIGNING_ALGORITHMS, 

94 SMB2_Session_Setup_Request, 

95 SMB2_Session_Setup_Response, 

96 SMB2_Signing_Capabilities, 

97 SMB2_Tree_Connect_Request, 

98 SMB2_Tree_Connect_Response, 

99 SMB2_Tree_Disconnect_Request, 

100 SMB2_Tree_Disconnect_Response, 

101 SMB2_Write_Request, 

102 SMB2_Write_Response, 

103 SMBStreamSocket, 

104 SMB_DIALECTS, 

105 SRVSVC_SHARE_TYPES, 

106 STATUS_ERREF, 

107) 

108from scapy.layers.spnego import SPNEGOSSP 

109 

110 

111class SMB_Client(Automaton): 

112 """ 

113 SMB client automaton 

114 

115 :param sock: the SMBStreamSocket to use 

116 :param ssp: the SSP to use 

117 

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

119 

120 :param REQUIRE_SIGNATURE: set 'Require Signature' 

121 :param REQUIRE_ENCRYPTION: set 'Requite Encryption' 

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

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

124 :param DIALECTS: list of supported SMB2 dialects. 

125 Constructed from MIN_DIALECT, MAX_DIALECT otherwise. 

126 """ 

127 

128 port = 445 

129 cls = DirectTCP 

130 

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

132 # Various SMB client arguments 

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

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

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

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

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

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

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

140 # Store supported dialects 

141 if "DIALECTS" in kwargs: 

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

143 else: 

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

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

146 self.DIALECTS = sorted( 

147 [ 

148 x 

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

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

151 ] 

152 ) 

153 # Internal Session information 

154 self.ErrorStatus = None 

155 self.NegotiateCapabilities = None 

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

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

158 if ssp is None: 

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

160 ssp = SPNEGOSSP( 

161 [ 

162 NTLMSSP( 

163 UPN="guest", 

164 HASHNT=b"", 

165 ) 

166 ] 

167 ) 

168 # Initialize 

169 kwargs["sock"] = sock 

170 Automaton.__init__( 

171 self, 

172 *args, 

173 **kwargs, 

174 ) 

175 if self.is_atmt_socket: 

176 self.smb_sock_ready = threading.Event() 

177 # Set session options 

178 self.session.ssp = ssp 

179 self.session.SigningRequired = ( 

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

181 ) 

182 self.session.Dialect = self.MAX_DIALECT 

183 

184 @classmethod 

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

186 return cls.smblink( 

187 None, 

188 SMBStreamSocket(sock, DirectTCP), 

189 **kwargs, 

190 ) 

191 

192 @property 

193 def session(self): 

194 # session shorthand 

195 return self.sock.session 

196 

197 def send(self, pkt): 

198 # Calculate what CreditCharge to send. 

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

200 # [MS-SMB2] sect 3.2.4.1.5 

201 typ = type(pkt.payload.payload) 

202 if typ is SMB2_Negotiate_Protocol_Request: 

203 # See [MS-SMB2] 3.2.4.1.2 note 

204 pkt.CreditCharge = 0 

205 elif typ in [ 

206 SMB2_Read_Request, 

207 SMB2_Write_Request, 

208 SMB2_IOCTL_Request, 

209 SMB2_Query_Directory_Request, 

210 SMB2_Change_Notify_Request, 

211 SMB2_Query_Info_Request, 

212 ]: 

213 # [MS-SMB2] 3.1.5.2 

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

215 # "CHANGE_NOTIFY, QUERY_INFO, or SET_INFO" 

216 if typ == SMB2_Read_Request: 

217 Length = pkt.payload.Length 

218 elif typ == SMB2_Write_Request: 

219 Length = len(pkt.payload.Data) 

220 elif typ == SMB2_IOCTL_Request: 

221 # [MS-SMB2] 3.3.5.15 

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

223 elif typ in [ 

224 SMB2_Query_Directory_Request, 

225 SMB2_Change_Notify_Request, 

226 SMB2_Query_Info_Request, 

227 ]: 

228 Length = pkt.payload.OutputBufferLength 

229 else: 

230 raise RuntimeError("impossible case") 

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

232 else: 

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

234 pkt.CreditCharge = 1 

235 # [MS-SMB2] 3.2.4.1.2 

236 pkt.CreditRequest = pkt.CreditCharge + 1 # this code is a bit lazy 

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

238 pkt.MID = self.SequenceWindow[0] 

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

240 

241 @ATMT.state(initial=1) 

242 def BEGIN(self): 

243 pass 

244 

245 @ATMT.condition(BEGIN) 

246 def continue_smb2(self): 

247 if self.SMB2: # Directly started in SMB2 

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

249 raise self.SMB2_NEGOTIATE() 

250 

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

252 def send_negotiate(self): 

253 raise self.SENT_NEGOTIATE() 

254 

255 @ATMT.action(send_negotiate) 

256 def on_negotiate(self): 

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

258 self.smb_header = DirectTCP() / SMB_Header( 

259 Flags2=( 

260 "LONG_NAMES+EAS+NT_STATUS+UNICODE+" 

261 "SMB_SECURITY_SIGNATURE+EXTENDED_SECURITY" 

262 ), 

263 TID=0xFFFF, 

264 PIDLow=0xFEFF, 

265 UID=0, 

266 MID=0, 

267 ) 

268 if self.EXTENDED_SECURITY: 

269 self.smb_header.Flags2 += "EXTENDED_SECURITY" 

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

271 Dialects=[ 

272 SMB_Dialect(DialectString=x) 

273 for x in [ 

274 "PC NETWORK PROGRAM 1.0", 

275 "LANMAN1.0", 

276 "Windows for Workgroups 3.1a", 

277 "LM1.2X002", 

278 "LANMAN2.1", 

279 "NT LM 0.12", 

280 ] 

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

282 ], 

283 ) 

284 if not self.EXTENDED_SECURITY: 

285 pkt.Flags2 -= "EXTENDED_SECURITY" 

286 pkt[SMB_Header].Flags2 = ( 

287 pkt[SMB_Header].Flags2 

288 - "SMB_SECURITY_SIGNATURE" 

289 + "SMB_SECURITY_SIGNATURE_REQUIRED+IS_LONG_NAME" 

290 ) 

291 self.send(pkt) 

292 

293 @ATMT.state() 

294 def SENT_NEGOTIATE(self): 

295 pass 

296 

297 @ATMT.state() 

298 def SMB2_NEGOTIATE(self): 

299 pass 

300 

301 @ATMT.condition(SMB2_NEGOTIATE) 

302 def send_negotiate_smb2(self): 

303 raise self.SENT_NEGOTIATE() 

304 

305 @ATMT.action(send_negotiate_smb2) 

306 def on_negotiate_smb2(self): 

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

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

309 Dialects=self.DIALECTS, 

310 SecurityMode=( 

311 "SIGNING_ENABLED+SIGNING_REQUIRED" 

312 if self.session.SigningRequired 

313 else "SIGNING_ENABLED" 

314 ), 

315 ) 

316 if self.MAX_DIALECT >= 0x0210: 

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

318 # MUST be set to the global ClientGuid value" 

319 pkt.ClientGUID = self.GUID 

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

321 self.NegotiateCapabilities = "+".join( 

322 [ 

323 "DFS", 

324 "LEASING", 

325 "LARGE_MTU", 

326 ] 

327 ) 

328 if self.MAX_DIALECT >= 0x0300: 

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

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

331 [ 

332 "MULTI_CHANNEL", 

333 "PERSISTENT_HANDLES", 

334 "DIRECTORY_LEASING", 

335 "ENCRYPTION", 

336 ] 

337 ) 

338 if self.MAX_DIALECT >= 0x0311: 

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

340 pkt.NegotiateContexts = [ 

341 SMB2_Negotiate_Context() 

342 / SMB2_Preauth_Integrity_Capabilities( 

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

344 HashAlgorithms=["SHA-512"], 

345 Salt=self.session.Salt, 

346 ), 

347 SMB2_Negotiate_Context() 

348 / SMB2_Encryption_Capabilities( 

349 Ciphers=self.session.SupportedCipherIds, 

350 ), 

351 # TODO support compression and RDMA 

352 SMB2_Negotiate_Context() 

353 / SMB2_Netname_Negotiate_Context_ID( 

354 NetName=self.HOST, 

355 ), 

356 SMB2_Negotiate_Context() 

357 / SMB2_Signing_Capabilities( 

358 SigningAlgorithms=self.session.SupportedSigningAlgorithmIds, 

359 ), 

360 ] 

361 pkt.Capabilities = self.NegotiateCapabilities 

362 # Send 

363 self.send(pkt) 

364 # If required, compute sessions 

365 self.session.computeSMBConnectionPreauth( 

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

367 ) 

368 

369 @ATMT.receive_condition(SENT_NEGOTIATE) 

370 def receive_negotiate_response(self, pkt): 

371 if ( 

372 SMBNegotiate_Response_Extended_Security in pkt 

373 or SMB2_Negotiate_Protocol_Response in pkt 

374 ): 

375 # Extended SMB1 / SMB2 

376 try: 

377 ssp_blob = pkt.SecurityBlob # eventually SPNEGO server initiation 

378 except AttributeError: 

379 ssp_blob = None 

380 if ( 

381 SMB2_Negotiate_Protocol_Response in pkt 

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

383 ): 

384 # Version is SMB X.??? 

385 # [MS-SMB2] 3.2.5.2 

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

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

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

389 # SMB2 header to 1. 

390 self.SequenceWindow = (1, 1) 

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

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

393 raise self.SMB2_NEGOTIATE() 

394 else: 

395 if SMB2_Negotiate_Protocol_Response in pkt: 

396 # SMB2 was negotiated ! 

397 self.session.Dialect = pkt.DialectRevision 

398 # If required, compute sessions 

399 self.session.computeSMBConnectionPreauth( 

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

401 ) 

402 # Process max sizes 

403 self.session.MaxReadSize = pkt.MaxReadSize 

404 self.session.MaxTransactionSize = pkt.MaxTransactionSize 

405 self.session.MaxWriteSize = pkt.MaxWriteSize 

406 # Process SecurityMode 

407 if pkt.SecurityMode.SIGNING_REQUIRED: 

408 self.session.SigningRequired = True 

409 # Process capabilities 

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

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

412 # Process NegotiateContext 

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

414 for ngctx in pkt.NegotiateContexts: 

415 if ngctx.ContextType == 0x0002: 

416 # SMB2_ENCRYPTION_CAPABILITIES 

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

418 self.session.CipherId = SMB2_ENCRYPTION_CIPHERS[ 

419 ngctx.Ciphers[0] 

420 ] 

421 self.session.SupportsEncryption = True 

422 elif ngctx.ContextType == 0x0008: 

423 # SMB2_SIGNING_CAPABILITIES 

424 self.session.SigningAlgorithmId = ( 

425 SMB2_SIGNING_ALGORITHMS[ngctx.SigningAlgorithms[0]] 

426 ) 

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

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

429 raise self.NEGO_FAILED() 

430 self.update_smbheader(pkt) 

431 raise self.NEGOTIATED(ssp_blob) 

432 elif SMBNegotiate_Response_Security in pkt: 

433 # Non-extended SMB1 

434 # Never tested. FIXME. probably broken 

435 raise self.NEGOTIATED(pkt.Challenge) 

436 

437 @ATMT.state(final=1) 

438 def NEGO_FAILED(self): 

439 self.smb_sock_ready.set() 

440 

441 @ATMT.state() 

442 def NEGOTIATED(self, ssp_blob=None): 

443 # Negotiated ! We now know the Dialect 

444 if self.session.Dialect > 0x0202: 

445 # [MS-SMB2] sect 3.2.5.1.4 

446 self.smb_header.CreditRequest = 1 

447 # Begin session establishment 

448 ssp_tuple = self.session.ssp.GSS_Init_sec_context( 

449 self.session.sspcontext, 

450 token=ssp_blob, 

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

452 req_flags=( 

453 GSS_C_FLAGS.GSS_C_MUTUAL_FLAG 

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

455 ), 

456 ) 

457 return ssp_tuple 

458 

459 def update_smbheader(self, pkt): 

460 """ 

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

462 """ 

463 # Some values should not be updated when ASYNC 

464 if not pkt.Flags.SMB2_FLAGS_ASYNC_COMMAND: 

465 # Update IDs 

466 self.smb_header.SessionId = pkt.SessionId 

467 self.smb_header.TID = pkt.TID 

468 self.smb_header.PID = pkt.PID 

469 # [MS-SMB2] 3.2.5.1.4 

470 self.SequenceWindow = ( 

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

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

473 ) 

474 

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

476 

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

478 def should_send_session_setup_request(self, ssp_tuple): 

479 _, _, negResult = ssp_tuple 

480 if negResult not in [GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED]: 

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

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

483 

484 @ATMT.state() 

485 def SENT_SESSION_REQUEST(self): 

486 pass 

487 

488 @ATMT.action(should_send_session_setup_request) 

489 def send_setup_session_request(self, ssp_tuple): 

490 self.session.sspcontext, token, negResult = ssp_tuple 

491 if self.SMB2 and negResult == GSS_S_CONTINUE_NEEDED: 

492 # New session: force 0 

493 self.SessionId = 0 

494 if self.SMB2 or self.EXTENDED_SECURITY: 

495 # SMB1 extended / SMB2 

496 if self.SMB2: 

497 # SMB2 

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

499 Capabilities="DFS", 

500 SecurityMode=( 

501 "SIGNING_ENABLED+SIGNING_REQUIRED" 

502 if self.session.SigningRequired 

503 else "SIGNING_ENABLED" 

504 ), 

505 ) 

506 else: 

507 # SMB1 extended 

508 pkt = ( 

509 self.smb_header.copy() 

510 / SMBSession_Setup_AndX_Request_Extended_Security( 

511 ServerCapabilities=( 

512 "UNICODE+NT_SMBS+STATUS32+LEVEL_II_OPLOCKS+" 

513 "DYNAMIC_REAUTH+EXTENDED_SECURITY" 

514 ), 

515 NativeOS=b"", 

516 NativeLanMan=b"", 

517 ) 

518 ) 

519 pkt.SecurityBlob = token 

520 else: 

521 # Non-extended security. 

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

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

524 NativeOS=b"", 

525 NativeLanMan=b"", 

526 OEMPassword=b"\0" * 24, 

527 UnicodePassword=token, 

528 ) 

529 self.send(pkt) 

530 if self.SMB2: 

531 # If required, compute sessions 

532 self.session.computeSMBSessionPreauth( 

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

534 ) 

535 

536 @ATMT.receive_condition(SENT_SESSION_REQUEST) 

537 def receive_session_setup_response(self, pkt): 

538 if ( 

539 SMBSession_Null in pkt 

540 or SMBSession_Setup_AndX_Response_Extended_Security in pkt 

541 or SMBSession_Setup_AndX_Response in pkt 

542 ): 

543 # SMB1 

544 if SMBSession_Null in pkt: 

545 # Likely an error 

546 raise self.NEGOTIATED() 

547 # Logging 

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

549 # Not SUCCESS nor MORE_PROCESSING_REQUIRED: log 

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

551 self.debug( 

552 lvl=1, 

553 msg=conf.color_theme.red( 

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

555 ), 

556 ) 

557 if self.SMB2: 

558 self.update_smbheader(pkt) 

559 # Cases depending on the response packet 

560 if ( 

561 SMBSession_Setup_AndX_Response_Extended_Security in pkt 

562 or SMB2_Session_Setup_Response in pkt 

563 ): 

564 # The server assigns us a SessionId 

565 self.smb_header.SessionId = pkt.SessionId 

566 # SMB1 extended / SMB2 

567 if pkt.Status == 0: # Authenticated 

568 if SMB2_Session_Setup_Response in pkt: 

569 # [MS-SMB2] sect 3.2.5.3.1 

570 if pkt.SessionFlags.IS_GUEST: 

571 # "If the security subsystem indicates that the session 

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

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

574 self.session.IsGuest = True 

575 self.session.SigningRequired = False 

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

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

578 self.session.EncryptData = True 

579 self.session.SigningRequired = False 

580 raise self.AUTHENTICATED(pkt.SecurityBlob) 

581 else: 

582 if SMB2_Header in pkt: 

583 # If required, compute sessions 

584 self.session.computeSMBSessionPreauth( 

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

586 ) 

587 # Ongoing auth 

588 raise self.NEGOTIATED(pkt.SecurityBlob) 

589 elif SMBSession_Setup_AndX_Response_Extended_Security in pkt: 

590 # SMB1 non-extended 

591 pass 

592 elif SMB2_Error_Response in pkt: 

593 # Authentication failure 

594 self.session.sspcontext.clifailure() 

595 # Reset Session preauth (SMB 3.1.1) 

596 self.session.SessionPreauthIntegrityHashValue = None 

597 if not self.RETRY: 

598 raise self.AUTH_FAILED() 

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

600 self.RETRY -= 1 

601 raise self.NEGOTIATED() 

602 

603 @ATMT.state(final=1) 

604 def AUTH_FAILED(self): 

605 self.smb_sock_ready.set() 

606 

607 @ATMT.state() 

608 def AUTHENTICATED(self, ssp_blob=None): 

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

610 self.session.sspcontext, 

611 token=ssp_blob, 

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

613 ) 

614 if status != GSS_S_COMPLETE: 

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

616 # Authentication was successful 

617 self.session.computeSMBSessionKeys(IsClient=True) 

618 

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

620 

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

622 def authenticated_post_actions(self): 

623 raise self.SOCKET_BIND() 

624 

625 # Plain SMB Socket 

626 

627 @ATMT.state() 

628 def SOCKET_BIND(self): 

629 self.smb_sock_ready.set() 

630 

631 @ATMT.condition(SOCKET_BIND) 

632 def start_smb_socket(self): 

633 raise self.SOCKET_MODE_SMB() 

634 

635 @ATMT.state() 

636 def SOCKET_MODE_SMB(self): 

637 pass 

638 

639 @ATMT.receive_condition(SOCKET_MODE_SMB) 

640 def incoming_data_received_smb(self, pkt): 

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

642 

643 @ATMT.action(incoming_data_received_smb) 

644 def receive_data_smb(self, pkt): 

645 resp = pkt[SMB2_Header].payload 

646 if isinstance(resp, SMB2_Error_Response): 

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

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

649 return 

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

651 # this is a notify cleanup. ignore 

652 return 

653 self.update_smbheader(pkt) 

654 # Add the status to the response as metadata 

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

656 self.oi.smbpipe.send(resp) 

657 

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

659 def outgoing_data_received_smb(self, fd): 

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

661 

662 @ATMT.action(outgoing_data_received_smb) 

663 def send_data(self, d): 

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

665 

666 

667class SMB_SOCKET(SuperSocket): 

668 """ 

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

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

671 """ 

672 

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

674 self.ins = smbsock 

675 self.timeout = timeout 

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

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

678 if self.ins.atmt.session.sspcontext: 

679 self.ins.atmt.session.sspcontext.clifailure() 

680 raise TimeoutError( 

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

682 ) 

683 if self.ins.atmt.ErrorStatus: 

684 raise Scapy_Exception( 

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

686 ) 

687 

688 @classmethod 

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

690 """ 

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

692 SMB_SOCKET/SMB_RPC_SOCKET 

693 """ 

694 return cls( 

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

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

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

698 ) 

699 

700 @property 

701 def session(self): 

702 return self.ins.atmt.session 

703 

704 def set_TID(self, TID): 

705 """ 

706 Set the TID (Tree ID). 

707 This can be called before sending a packet 

708 """ 

709 self.ins.atmt.smb_header.TID = TID 

710 

711 def get_TID(self): 

712 """ 

713 Get the current TID from the underlying socket 

714 """ 

715 return self.ins.atmt.smb_header.TID 

716 

717 def tree_connect(self, name): 

718 """ 

719 Send a TreeConnect request 

720 """ 

721 resp = self.ins.sr1( 

722 SMB2_Tree_Connect_Request( 

723 Buffer=[ 

724 ( 

725 "Path", 

726 "\\\\%s\\%s" 

727 % ( 

728 self.session.sspcontext.ServerHostname, 

729 name, 

730 ), 

731 ) 

732 ] 

733 ), 

734 verbose=False, 

735 timeout=self.timeout, 

736 ) 

737 if not resp: 

738 raise ValueError("TreeConnect timed out !") 

739 if SMB2_Tree_Connect_Response not in resp: 

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

741 # [MS-SMB2] sect 3.2.5.5 

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

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

744 self.session.TreeEncryptData = True 

745 else: 

746 self.session.TreeEncryptData = False 

747 return self.get_TID() 

748 

749 def tree_disconnect(self): 

750 """ 

751 Send a TreeDisconnect request 

752 """ 

753 resp = self.ins.sr1( 

754 SMB2_Tree_Disconnect_Request(), 

755 verbose=False, 

756 timeout=self.timeout, 

757 ) 

758 if not resp: 

759 raise ValueError("TreeDisconnect timed out !") 

760 if SMB2_Tree_Disconnect_Response not in resp: 

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

762 

763 def create_request( 

764 self, 

765 name, 

766 mode="r", 

767 type="pipe", 

768 extra_create_options=[], 

769 extra_desired_access=[], 

770 ): 

771 """ 

772 Open a file/pipe by its name 

773 

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

775 """ 

776 ShareAccess = [] 

777 DesiredAccess = [] 

778 # Common params depending on the access 

779 if "r" in mode: 

780 ShareAccess.append("FILE_SHARE_READ") 

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

782 if "w" in mode: 

783 ShareAccess.append("FILE_SHARE_WRITE") 

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

785 if "d" in mode: 

786 ShareAccess.append("FILE_SHARE_DELETE") 

787 # Params depending on the type 

788 FileAttributes = [] 

789 CreateOptions = [] 

790 CreateContexts = [] 

791 CreateDisposition = "FILE_OPEN" 

792 if type == "folder": 

793 FileAttributes.append("FILE_ATTRIBUTE_DIRECTORY") 

794 CreateOptions.append("FILE_DIRECTORY_FILE") 

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

796 CreateOptions = ["FILE_NON_DIRECTORY_FILE"] 

797 if "r" in mode: 

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

799 if "w" in mode: 

800 CreateDisposition = "FILE_OVERWRITE_IF" 

801 DesiredAccess.append("FILE_WRITE_EA") 

802 if "d" in mode: 

803 DesiredAccess.append("DELETE") 

804 CreateOptions.append("FILE_DELETE_ON_CLOSE") 

805 if type == "file": 

806 FileAttributes.append("FILE_ATTRIBUTE_NORMAL") 

807 elif type: 

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

809 # [MS-SMB2] 3.2.4.3.8 

810 RequestedOplockLevel = 0 

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

812 RequestedOplockLevel = "SMB2_OPLOCK_LEVEL_LEASE" 

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

814 RequestedOplockLevel = "SMB2_OPLOCK_LEVEL_LEASE" 

815 # SMB 3.X 

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

817 CreateContexts.extend( 

818 [ 

819 # [SMB2] sect 3.2.4.3.5 

820 SMB2_Create_Context( 

821 Name=b"DH2Q", 

822 Data=SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2( 

823 CreateGuid=RandUUID()._fix() 

824 ), 

825 ), 

826 # [SMB2] sect 3.2.4.3.9 

827 SMB2_Create_Context( 

828 Name=b"MxAc", 

829 ), 

830 # [SMB2] sect 3.2.4.3.10 

831 SMB2_Create_Context( 

832 Name=b"QFid", 

833 ), 

834 # [SMB2] sect 3.2.4.3.8 

835 SMB2_Create_Context( 

836 Name=b"RqLs", 

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

838 ), 

839 ] 

840 ) 

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

842 CreateContexts.extend( 

843 [ 

844 # [SMB2] sect 3.2.4.3.8 

845 SMB2_Create_Context( 

846 Name=b"RqLs", 

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

848 ), 

849 ] 

850 ) 

851 # Extra options 

852 if extra_create_options: 

853 CreateOptions.extend(extra_create_options) 

854 if extra_desired_access: 

855 DesiredAccess.extend(extra_desired_access) 

856 # Request 

857 resp = self.ins.sr1( 

858 SMB2_Create_Request( 

859 ImpersonationLevel="Impersonation", 

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

861 CreateDisposition=CreateDisposition, 

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

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

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

865 CreateContexts=CreateContexts, 

866 RequestedOplockLevel=RequestedOplockLevel, 

867 Name=name, 

868 ), 

869 verbose=0, 

870 timeout=self.timeout, 

871 ) 

872 if not resp: 

873 raise ValueError("CreateRequest timed out !") 

874 if SMB2_Create_Response not in resp: 

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

876 return resp[SMB2_Create_Response].FileId 

877 

878 def close_request(self, FileId): 

879 """ 

880 Close the FileId 

881 """ 

882 pkt = SMB2_Close_Request(FileId=FileId) 

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

884 if not resp: 

885 raise ValueError("CloseRequest timed out !") 

886 if SMB2_Close_Response not in resp: 

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

888 

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

890 """ 

891 Read request 

892 """ 

893 resp = self.ins.sr1( 

894 SMB2_Read_Request( 

895 FileId=FileId, 

896 Length=Length, 

897 Offset=Offset, 

898 ), 

899 verbose=0, 

900 timeout=self.timeout, 

901 ) 

902 if not resp: 

903 raise ValueError("ReadRequest timed out !") 

904 if SMB2_Read_Response not in resp: 

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

906 return resp.Data 

907 

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

909 """ 

910 Write request 

911 """ 

912 resp = self.ins.sr1( 

913 SMB2_Write_Request( 

914 FileId=FileId, 

915 Data=Data, 

916 Offset=Offset, 

917 ), 

918 verbose=0, 

919 timeout=self.timeout, 

920 ) 

921 if not resp: 

922 raise ValueError("WriteRequest timed out !") 

923 if SMB2_Write_Response not in resp: 

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

925 return resp.Count 

926 

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

928 """ 

929 Query the Directory with FileId 

930 """ 

931 results = [] 

932 Flags = "SMB2_RESTART_SCANS" 

933 while True: 

934 pkt = SMB2_Query_Directory_Request( 

935 FileInformationClass="FileIdBothDirectoryInformation", 

936 FileId=FileId, 

937 FileName=FileName, 

938 Flags=Flags, 

939 ) 

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

941 Flags = 0 # only the first one is RESTART_SCANS 

942 if not resp: 

943 raise ValueError("QueryDirectory timed out !") 

944 if SMB2_Error_Response in resp: 

945 break 

946 elif SMB2_Query_Directory_Response not in resp: 

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

948 res = FileIdBothDirectoryInformation(resp.Output) 

949 results.extend( 

950 [ 

951 ( 

952 x.FileName, 

953 x.FileAttributes, 

954 x.EndOfFile, 

955 x.LastWriteTime, 

956 ) 

957 for x in res.files 

958 ] 

959 ) 

960 return results 

961 

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

963 """ 

964 Query the Info 

965 """ 

966 pkt = SMB2_Query_Info_Request( 

967 InfoType=InfoType, 

968 FileInfoClass=FileInfoClass, 

969 OutputBufferLength=65535, 

970 FileId=FileId, 

971 AdditionalInformation=AdditionalInformation, 

972 ) 

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

974 if not resp: 

975 raise ValueError("QueryInfo timed out !") 

976 if SMB2_Query_Info_Response not in resp: 

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

978 return resp.Output 

979 

980 def changenotify(self, FileId): 

981 """ 

982 Register change notify 

983 """ 

984 pkt = SMB2_Change_Notify_Request( 

985 Flags="SMB2_WATCH_TREE", 

986 OutputBufferLength=65535, 

987 FileId=FileId, 

988 CompletionFilter=0x0FFF, 

989 ) 

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

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

992 if SMB2_Change_Notify_Response not in resp: 

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

994 return resp.Output 

995 

996 

997class SMB_RPC_SOCKET(ObjectPipe, SMB_SOCKET): 

998 """ 

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

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

1001 

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

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

1004 """ 

1005 

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

1007 self.use_ioctl = use_ioctl 

1008 ObjectPipe.__init__(self, "SMB_RPC_SOCKET") 

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

1010 

1011 def open_pipe(self, name): 

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

1013 

1014 def close_pipe(self): 

1015 self.close_request(self.PipeFileId) 

1016 self.PipeFileId = None 

1017 

1018 def send(self, x): 

1019 """ 

1020 Internal ObjectPipe function. 

1021 """ 

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

1023 

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

1025 is_frag = x.pfc_flags & 3 != 3 

1026 

1027 if self.use_ioctl and not is_frag: 

1028 # Use IOCTLRequest 

1029 pkt = SMB2_IOCTL_Request( 

1030 FileId=self.PipeFileId, 

1031 Flags="SMB2_0_IOCTL_IS_FSCTL", 

1032 CtlCode="FSCTL_PIPE_TRANSCEIVE", 

1033 ) 

1034 pkt.Input = bytes(x) 

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

1036 if SMB2_IOCTL_Response not in resp: 

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

1038 data = bytes(resp.Output) 

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

1040 # Handle BUFFER_OVERFLOW (big DCE/RPC response) 

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

1042 # Retrieve DCE/RPC full size 

1043 resp = self.ins.sr1( 

1044 SMB2_Read_Request( 

1045 FileId=self.PipeFileId, 

1046 ), 

1047 verbose=0, 

1048 ) 

1049 data = resp.Data 

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

1051 else: 

1052 # Use WriteRequest/ReadRequest 

1053 pkt = SMB2_Write_Request( 

1054 FileId=self.PipeFileId, 

1055 ) 

1056 pkt.Data = bytes(x) 

1057 # We send the Write Request 

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

1059 if SMB2_Write_Response not in resp: 

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

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

1062 if is_frag and not x.pfc_flags.PFC_LAST_FRAG: 

1063 return 

1064 # We send a Read Request afterwards 

1065 resp = self.ins.sr1( 

1066 SMB2_Read_Request( 

1067 FileId=self.PipeFileId, 

1068 ), 

1069 verbose=0, 

1070 ) 

1071 if SMB2_Read_Response not in resp: 

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

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

1074 # Handle fragmented response 

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

1076 # Retrieve DCE/RPC full size 

1077 resp = self.ins.sr1( 

1078 SMB2_Read_Request( 

1079 FileId=self.PipeFileId, 

1080 ), 

1081 verbose=0, 

1082 ) 

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

1084 

1085 def close(self): 

1086 SMB_SOCKET.close(self) 

1087 ObjectPipe.close(self) 

1088 

1089 

1090@conf.commands.register 

1091class smbclient(CLIUtil): 

1092 r""" 

1093 A simple SMB client CLI powered by Scapy 

1094 

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

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

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

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

1099 :param kerberos_required: require kerberos 

1100 :param port: the TCP port. default 445 

1101 :param password: if provided, used for auth 

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

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

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

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

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

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

1108 

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

1110 them include the following: 

1111 

1112 :param REQUIRE_ENCRYPTION: requires encryption. 

1113 """ 

1114 

1115 def __init__( 

1116 self, 

1117 target: str, 

1118 UPN: str = None, 

1119 password: str = None, 

1120 guest: bool = False, 

1121 kerberos_required: bool = False, 

1122 HashNt: bytes = None, 

1123 HashAes256Sha96: bytes = None, 

1124 HashAes128Sha96: bytes = None, 

1125 port: int = 445, 

1126 timeout: int = 2, 

1127 debug: int = 0, 

1128 ssp=None, 

1129 ST=None, 

1130 KEY=None, 

1131 cli=True, 

1132 # SMB arguments 

1133 REQUIRE_ENCRYPTION=False, 

1134 **kwargs, 

1135 ): 

1136 if cli: 

1137 self._depcheck() 

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

1139 # Do we need to build a SSP? 

1140 if ssp is None: 

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

1142 if not guest: 

1143 ssp = SPNEGOSSP.from_cli_arguments( 

1144 UPN=UPN, 

1145 target=target, 

1146 password=password, 

1147 HashNt=HashNt, 

1148 HashAes256Sha96=HashAes256Sha96, 

1149 HashAes128Sha96=HashAes128Sha96, 

1150 ST=ST, 

1151 KEY=KEY, 

1152 kerberos_required=kerberos_required, 

1153 ) 

1154 else: 

1155 # Guest mode 

1156 ssp = None 

1157 # Check if target is IPv4 or IPv6 

1158 if ":" in target: 

1159 family = socket.AF_INET6 

1160 else: 

1161 family = socket.AF_INET 

1162 # Open socket 

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

1164 # Configure socket for SMB: 

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

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

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

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

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

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

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

1172 # Timeout & connect 

1173 sock.settimeout(timeout) 

1174 if debug: 

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

1176 sock.connect((target, port)) 

1177 self.extra_create_options = [] 

1178 # Wrap with the automaton 

1179 self.timeout = timeout 

1180 kwargs.setdefault("HOST", target) 

1181 self.sock = SMB_Client.from_tcpsock( 

1182 sock, 

1183 ssp=ssp, 

1184 debug=debug, 

1185 REQUIRE_ENCRYPTION=REQUIRE_ENCRYPTION, 

1186 timeout=timeout, 

1187 **kwargs, 

1188 ) 

1189 try: 

1190 # Wrap with SMB_SOCKET 

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

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

1193 _t = time.time() 

1194 while True: 

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

1196 # yay 

1197 break 

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

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

1200 raise Scapy_Exception( 

1201 "%s with status %s" 

1202 % ( 

1203 self.sock.atmt.state.state, 

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

1205 ) 

1206 ) 

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

1208 self.sock.close() 

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

1210 time.sleep(0.1) 

1211 except Exception: 

1212 # Something bad happened, end the socket/automaton 

1213 self.sock.close() 

1214 raise 

1215 

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

1217 from scapy.layers.msrpce.rpcclient import DCERPC_Client 

1218 

1219 self.rpcclient = DCERPC_Client.from_smblink( 

1220 self.sock, 

1221 ndr64=False, 

1222 verb=bool(debug), 

1223 ) 

1224 # We have a valid smb connection ! 

1225 print( 

1226 "%s authentication successful using %s%s !" 

1227 % ( 

1228 SMB_DIALECTS.get( 

1229 self.smbsock.session.Dialect, 

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

1231 ), 

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

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

1234 ) 

1235 ) 

1236 # Now define some variables for our CLI 

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

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

1239 self.current_tree = None 

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

1241 self.sh_cache = [] # cache the shares 

1242 # Start CLI 

1243 if cli: 

1244 self.loop(debug=debug) 

1245 

1246 def ps1(self): 

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

1248 

1249 def close(self): 

1250 print("Connection closed") 

1251 self.smbsock.close() 

1252 

1253 def _require_share(self, silent=False): 

1254 if self.current_tree is None: 

1255 if not silent: 

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

1257 return True 

1258 

1259 def collapse_path(self, path): 

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

1261 # is ridiculous 

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

1263 

1264 def normalize_path(self, path): 

1265 """ 

1266 Normalize path for CIFS usage 

1267 """ 

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

1269 

1270 @CLIUtil.addcommand() 

1271 def shares(self): 

1272 """ 

1273 List the shares available 

1274 """ 

1275 # Poll cache 

1276 if self.sh_cache: 

1277 return self.sh_cache 

1278 # It's an RPC 

1279 self.rpcclient.open_smbpipe("srvsvc") 

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

1281 req = NetrShareEnum_Request( 

1282 InfoStruct=LPSHARE_ENUM_STRUCT( 

1283 Level=1, 

1284 ShareInfo=NDRUnion( 

1285 tag=1, 

1286 value=SHARE_INFO_1_CONTAINER(Buffer=None), 

1287 ), 

1288 ), 

1289 PreferedMaximumLength=0xFFFFFFFF, 

1290 ndr64=self.rpcclient.ndr64, 

1291 ) 

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

1293 self.rpcclient.close_smbpipe() 

1294 if not isinstance(resp, NetrShareEnum_Response): 

1295 resp.show() 

1296 raise ValueError("NetrShareEnum_Request failed !") 

1297 results = [] 

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

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

1300 results.append( 

1301 ( 

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

1303 SRVSVC_SHARE_TYPES.get(shi1_type, shi1_type), 

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

1305 ) 

1306 ) 

1307 self.sh_cache = results # cache 

1308 return results 

1309 

1310 @CLIUtil.addoutput(shares) 

1311 def shares_output(self, results): 

1312 """ 

1313 Print the output of 'shares' 

1314 """ 

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

1316 

1317 @CLIUtil.addcommand() 

1318 def use(self, share): 

1319 """ 

1320 Open a share 

1321 """ 

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

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

1324 self.ls_cache.clear() 

1325 

1326 @CLIUtil.addcomplete(use) 

1327 def use_complete(self, share): 

1328 """ 

1329 Auto-complete 'use' 

1330 """ 

1331 return [ 

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

1333 ] 

1334 

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

1336 """ 

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

1338 """ 

1339 # Find parent directory if it exists 

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

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

1342 eltname = elt.name 

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

1344 eltpar = elt 

1345 eltname = "" 

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

1347 eltpar = elt.parent 

1348 return eltpar, eltname 

1349 

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

1351 """ 

1352 Return a listing of the remote files for completion purposes 

1353 """ 

1354 if cond is None: 

1355 cond = lambda _: True 

1356 eltpar, eltname = self._parsepath(arg) 

1357 # ls in that directory 

1358 try: 

1359 files = self.ls(parent=eltpar) 

1360 except ValueError: 

1361 return [] 

1362 return [ 

1363 str(eltpar / x[0]) 

1364 for x in files 

1365 if ( 

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

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

1368 and cond(x[1]) 

1369 ) 

1370 ] 

1371 

1372 def _dir_complete(self, arg): 

1373 """ 

1374 Return a directories of remote files for completion purposes 

1375 """ 

1376 results = self._fs_complete( 

1377 arg, 

1378 cond=lambda x: x.FILE_ATTRIBUTE_DIRECTORY, 

1379 ) 

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

1381 # skip through folders 

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

1383 return results 

1384 

1385 @CLIUtil.addcommand(spaces=True) 

1386 def ls(self, parent=None): 

1387 """ 

1388 List the files in the remote directory 

1389 -t: sort by timestamp 

1390 -S: sort by size 

1391 -r: reverse while sorting 

1392 """ 

1393 if self._require_share(): 

1394 return 

1395 # Get pwd of the ls 

1396 pwd = self.pwd 

1397 if parent is not None: 

1398 pwd /= parent 

1399 pwd = self.normalize_path(pwd) 

1400 # Poll the cache 

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

1402 return self.ls_cache[pwd] 

1403 self.smbsock.set_TID(self.current_tree) 

1404 # Open folder 

1405 fileId = self.smbsock.create_request( 

1406 pwd, 

1407 type="folder", 

1408 extra_create_options=self.extra_create_options, 

1409 ) 

1410 # Query the folder 

1411 files = self.smbsock.query_directory(fileId) 

1412 # Close the folder 

1413 self.smbsock.close_request(fileId) 

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

1415 return files 

1416 

1417 @CLIUtil.addoutput(ls) 

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

1419 """ 

1420 Print the output of 'ls' 

1421 """ 

1422 fld = UTCTimeField( 

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

1424 ) 

1425 if t: 

1426 # Sort by time 

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

1428 if S: 

1429 # Sort by size 

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

1431 if r: 

1432 # Reverse sort 

1433 results = results[::-1] 

1434 results = [ 

1435 ( 

1436 x[0], 

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

1438 human_size(x[2]), 

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

1440 ) 

1441 for x in results 

1442 ] 

1443 print( 

1444 pretty_list( 

1445 results, 

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

1447 sortBy=None, 

1448 ) 

1449 ) 

1450 

1451 @CLIUtil.addcomplete(ls) 

1452 def ls_complete(self, folder): 

1453 """ 

1454 Auto-complete ls 

1455 """ 

1456 if self._require_share(silent=True): 

1457 return [] 

1458 return self._dir_complete(folder) 

1459 

1460 @CLIUtil.addcommand(spaces=True) 

1461 def cd(self, folder): 

1462 """ 

1463 Change the remote current directory 

1464 """ 

1465 if self._require_share(): 

1466 return 

1467 if not folder: 

1468 # show mode 

1469 return str(self.pwd) 

1470 self.pwd /= folder 

1471 self.pwd = self.collapse_path(self.pwd) 

1472 self.ls_cache.clear() 

1473 

1474 @CLIUtil.addcomplete(cd) 

1475 def cd_complete(self, folder): 

1476 """ 

1477 Auto-complete cd 

1478 """ 

1479 if self._require_share(silent=True): 

1480 return [] 

1481 return self._dir_complete(folder) 

1482 

1483 def _lfs_complete(self, arg, cond): 

1484 """ 

1485 Return a listing of local files for completion purposes 

1486 """ 

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

1488 eltpar = self.localpwd / eltpar 

1489 return [ 

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

1491 str(eltpar / x.name) 

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

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

1494 ] 

1495 

1496 @CLIUtil.addoutput(cd) 

1497 def cd_output(self, result): 

1498 """ 

1499 Print the output of 'cd' 

1500 """ 

1501 if result: 

1502 print(result) 

1503 

1504 @CLIUtil.addcommand() 

1505 def lls(self): 

1506 """ 

1507 List the files in the local directory 

1508 """ 

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

1510 

1511 @CLIUtil.addoutput(lls) 

1512 def lls_output(self, results): 

1513 """ 

1514 Print the output of 'lls' 

1515 """ 

1516 results = [ 

1517 ( 

1518 x.name, 

1519 human_size(stat.st_size), 

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

1521 ) 

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

1523 ] 

1524 print( 

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

1526 ) 

1527 

1528 @CLIUtil.addcommand(spaces=True) 

1529 def lcd(self, folder): 

1530 """ 

1531 Change the local current directory 

1532 """ 

1533 if not folder: 

1534 # show mode 

1535 return str(self.localpwd) 

1536 self.localpwd /= folder 

1537 self.localpwd = self.localpwd.resolve() 

1538 

1539 @CLIUtil.addcomplete(lcd) 

1540 def lcd_complete(self, folder): 

1541 """ 

1542 Auto-complete lcd 

1543 """ 

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

1545 

1546 @CLIUtil.addoutput(lcd) 

1547 def lcd_output(self, result): 

1548 """ 

1549 Print the output of 'lcd' 

1550 """ 

1551 if result: 

1552 print(result) 

1553 

1554 def _get_file(self, file, fd): 

1555 """ 

1556 Gets the file bytes from a remote host 

1557 """ 

1558 # Get pwd of the ls 

1559 fpath = self.pwd / file 

1560 self.smbsock.set_TID(self.current_tree) 

1561 # Open file 

1562 fileId = self.smbsock.create_request( 

1563 self.normalize_path(fpath), 

1564 type="file", 

1565 extra_create_options=[ 

1566 "FILE_SEQUENTIAL_ONLY", 

1567 ] 

1568 + self.extra_create_options, 

1569 ) 

1570 # Get the file size 

1571 info = FileAllInformation( 

1572 self.smbsock.query_info( 

1573 FileId=fileId, 

1574 InfoType="SMB2_0_INFO_FILE", 

1575 FileInfoClass="FileAllInformation", 

1576 ) 

1577 ) 

1578 length = info.StandardInformation.EndOfFile 

1579 offset = 0 

1580 # Read the file 

1581 while length: 

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

1583 fd.write( 

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

1585 ) 

1586 offset += lengthRead 

1587 length -= lengthRead 

1588 # Close the file 

1589 self.smbsock.close_request(fileId) 

1590 return offset 

1591 

1592 def _send_file(self, fname, fd): 

1593 """ 

1594 Send the file bytes to a remote host 

1595 """ 

1596 # Get destination file 

1597 fpath = self.pwd / fname 

1598 self.smbsock.set_TID(self.current_tree) 

1599 # Open file 

1600 fileId = self.smbsock.create_request( 

1601 self.normalize_path(fpath), 

1602 type="file", 

1603 mode="w", 

1604 extra_create_options=self.extra_create_options, 

1605 ) 

1606 # Send the file 

1607 offset = 0 

1608 while True: 

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

1610 if not data: 

1611 # end of file 

1612 break 

1613 offset += self.smbsock.write_request( 

1614 Data=data, 

1615 FileId=fileId, 

1616 Offset=offset, 

1617 ) 

1618 # Close the file 

1619 self.smbsock.close_request(fileId) 

1620 return offset 

1621 

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

1623 """ 

1624 Internal recursive function to get a directory 

1625 

1626 :param directory: the remote directory to get 

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

1628 """ 

1629 size = 0 

1630 if not _root.exists(): 

1631 _root.mkdir() 

1632 # ls the directory 

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

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

1635 # Discard . and .. 

1636 continue 

1637 remote = directory / x[0] 

1638 local = _root / x[0] 

1639 try: 

1640 if x[1].FILE_ATTRIBUTE_DIRECTORY: 

1641 # Sub-directory 

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

1643 else: 

1644 # Sub-file 

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

1646 if _verb: 

1647 print(remote) 

1648 except ValueError as ex: 

1649 if _verb: 

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

1651 return size 

1652 

1653 @CLIUtil.addcommand(spaces=True, globsupport=True) 

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

1655 """ 

1656 Retrieve a file 

1657 -r: recursively download a directory 

1658 """ 

1659 if self._require_share(): 

1660 return 

1661 if r: 

1662 dirpar, dirname = self._parsepath(file) 

1663 return file, self._getr( 

1664 dirpar / dirname, # Remotely 

1665 _root=self.localpwd / dirname, # Locally 

1666 _verb=_verb, 

1667 ) 

1668 else: 

1669 fname = pathlib.PureWindowsPath(file).name 

1670 # Write the buffer 

1671 if _dest is None: 

1672 _dest = self.localpwd / fname 

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

1674 size = self._get_file(file, fd) 

1675 return fname, size 

1676 

1677 @CLIUtil.addoutput(get) 

1678 def get_output(self, info): 

1679 """ 

1680 Print the output of 'get' 

1681 """ 

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

1683 

1684 @CLIUtil.addcomplete(get) 

1685 def get_complete(self, file): 

1686 """ 

1687 Auto-complete get 

1688 """ 

1689 if self._require_share(silent=True): 

1690 return [] 

1691 return self._fs_complete(file) 

1692 

1693 @CLIUtil.addcommand(spaces=True, globsupport=True) 

1694 def cat(self, file): 

1695 """ 

1696 Print a file 

1697 """ 

1698 if self._require_share(): 

1699 return 

1700 # Write the buffer to buffer 

1701 buf = io.BytesIO() 

1702 self._get_file(file, buf) 

1703 return buf.getvalue() 

1704 

1705 @CLIUtil.addoutput(cat) 

1706 def cat_output(self, result): 

1707 """ 

1708 Print the output of 'cat' 

1709 """ 

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

1711 

1712 @CLIUtil.addcomplete(cat) 

1713 def cat_complete(self, file): 

1714 """ 

1715 Auto-complete cat 

1716 """ 

1717 if self._require_share(silent=True): 

1718 return [] 

1719 return self._fs_complete(file) 

1720 

1721 @CLIUtil.addcommand(spaces=True) 

1722 def put(self, file): 

1723 """ 

1724 Upload a file 

1725 """ 

1726 if self._require_share(): 

1727 return 

1728 local_file = self.localpwd / file 

1729 if local_file.is_dir(): 

1730 # Directory 

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

1732 else: 

1733 fname = pathlib.Path(file).name 

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

1735 size = self._send_file(fname, fd) 

1736 self.ls_cache.clear() 

1737 return fname, size 

1738 

1739 @CLIUtil.addcomplete(put) 

1740 def put_complete(self, folder): 

1741 """ 

1742 Auto-complete put 

1743 """ 

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

1745 

1746 @CLIUtil.addcommand(spaces=True) 

1747 def rm(self, file): 

1748 """ 

1749 Delete a file 

1750 """ 

1751 if self._require_share(): 

1752 return 

1753 # Get pwd of the ls 

1754 fpath = self.pwd / file 

1755 self.smbsock.set_TID(self.current_tree) 

1756 # Open file 

1757 fileId = self.smbsock.create_request( 

1758 self.normalize_path(fpath), 

1759 type="file", 

1760 mode="d", 

1761 extra_create_options=self.extra_create_options, 

1762 ) 

1763 # Close the file 

1764 self.smbsock.close_request(fileId) 

1765 self.ls_cache.clear() 

1766 return fpath.name 

1767 

1768 @CLIUtil.addcomplete(rm) 

1769 def rm_complete(self, file): 

1770 """ 

1771 Auto-complete rm 

1772 """ 

1773 if self._require_share(silent=True): 

1774 return [] 

1775 return self._fs_complete(file) 

1776 

1777 @CLIUtil.addcommand() 

1778 def backup(self): 

1779 """ 

1780 Turn on or off backup intent 

1781 """ 

1782 if "FILE_OPEN_FOR_BACKUP_INTENT" in self.extra_create_options: 

1783 print("Backup Intent: Off") 

1784 self.extra_create_options.remove("FILE_OPEN_FOR_BACKUP_INTENT") 

1785 else: 

1786 print("Backup Intent: On") 

1787 self.extra_create_options.append("FILE_OPEN_FOR_BACKUP_INTENT") 

1788 

1789 @CLIUtil.addcommand(spaces=True) 

1790 def watch(self, folder): 

1791 """ 

1792 Watch file changes in folder (recursively) 

1793 """ 

1794 if self._require_share(): 

1795 return 

1796 # Get pwd of the ls 

1797 fpath = self.pwd / folder 

1798 self.smbsock.set_TID(self.current_tree) 

1799 # Open file 

1800 fileId = self.smbsock.create_request( 

1801 self.normalize_path(fpath), 

1802 type="folder", 

1803 extra_create_options=self.extra_create_options, 

1804 ) 

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

1806 # Watch for changes 

1807 try: 

1808 while True: 

1809 changes = self.smbsock.changenotify(fileId) 

1810 for chg in changes: 

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

1812 except KeyboardInterrupt: 

1813 pass 

1814 # Close the file 

1815 self.smbsock.close_request(fileId) 

1816 print("Cancelled.") 

1817 

1818 @CLIUtil.addcommand(spaces=True) 

1819 def getsd(self, file): 

1820 """ 

1821 Get the Security Descriptor 

1822 """ 

1823 if self._require_share(): 

1824 return 

1825 fpath = self.pwd / file 

1826 self.smbsock.set_TID(self.current_tree) 

1827 # Open file 

1828 fileId = self.smbsock.create_request( 

1829 self.normalize_path(fpath), 

1830 type="", 

1831 mode="", 

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

1833 ) 

1834 # Get the file size 

1835 info = self.smbsock.query_info( 

1836 FileId=fileId, 

1837 InfoType="SMB2_0_INFO_SECURITY", 

1838 FileInfoClass=0, 

1839 AdditionalInformation=( 

1840 0x00000001 

1841 | 0x00000002 

1842 | 0x00000004 

1843 | 0x00000008 

1844 | 0x00000010 

1845 | 0x00000020 

1846 | 0x00000040 

1847 | 0x00010000 

1848 ), 

1849 ) 

1850 self.smbsock.close_request(fileId) 

1851 return info 

1852 

1853 @CLIUtil.addcomplete(getsd) 

1854 def getsd_complete(self, file): 

1855 """ 

1856 Auto-complete getsd 

1857 """ 

1858 if self._require_share(silent=True): 

1859 return [] 

1860 return self._fs_complete(file) 

1861 

1862 @CLIUtil.addoutput(getsd) 

1863 def getsd_output(self, results): 

1864 """ 

1865 Print the output of 'getsd' 

1866 """ 

1867 sd = SECURITY_DESCRIPTOR(results) 

1868 print("Owner:", sd.OwnerSid.summary()) 

1869 print("Group:", sd.GroupSid.summary()) 

1870 if getattr(sd, "DACL", None): 

1871 print("DACL:") 

1872 for ace in sd.DACL.Aces: 

1873 print(" - ", ace.toSDDL()) 

1874 if getattr(sd, "SACL", None): 

1875 print("SACL:") 

1876 for ace in sd.SACL.Aces: 

1877 print(" - ", ace.toSDDL()) 

1878 

1879 

1880if __name__ == "__main__": 

1881 from scapy.utils import AutoArgparse 

1882 

1883 AutoArgparse(smbclient)