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

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

751 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.base_classes import Net 

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

33 valid_ip6, 

34) 

35from scapy.volatile import RandUUID 

36 

37from scapy.layers.dcerpc import NDRUnion, find_dcerpc_interface 

38from scapy.layers.gssapi import ( 

39 GSS_S_COMPLETE, 

40 GSS_S_CONTINUE_NEEDED, 

41 GSS_C_FLAGS, 

42) 

43from scapy.layers.inet6 import Net6 

44from scapy.layers.kerberos import ( 

45 KerberosSSP, 

46 krb_as_and_tgs, 

47 _parse_upn, 

48) 

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

50 LPSHARE_ENUM_STRUCT, 

51 NetrShareEnum_Request, 

52 NetrShareEnum_Response, 

53 SHARE_INFO_1_CONTAINER, 

54) 

55from scapy.layers.ntlm import ( 

56 NTLMSSP, 

57 MD4le, 

58) 

59from scapy.layers.smb import ( 

60 SMBNegotiate_Request, 

61 SMBNegotiate_Response_Extended_Security, 

62 SMBNegotiate_Response_Security, 

63 SMBSession_Null, 

64 SMBSession_Setup_AndX_Request, 

65 SMBSession_Setup_AndX_Request_Extended_Security, 

66 SMBSession_Setup_AndX_Response, 

67 SMBSession_Setup_AndX_Response_Extended_Security, 

68 SMB_Dialect, 

69 SMB_Header, 

70) 

71from scapy.layers.smb2 import ( 

72 DirectTCP, 

73 FileAllInformation, 

74 FileIdBothDirectoryInformation, 

75 SMB2_Change_Notify_Request, 

76 SMB2_Change_Notify_Response, 

77 SMB2_Close_Request, 

78 SMB2_Close_Response, 

79 SMB2_Create_Context, 

80 SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2, 

81 SMB2_CREATE_REQUEST_LEASE_V2, 

82 SMB2_Create_Request, 

83 SMB2_Create_Response, 

84 SMB2_Encryption_Capabilities, 

85 SMB2_ENCRYPTION_CIPHERS, 

86 SMB2_Error_Response, 

87 SMB2_Header, 

88 SMB2_IOCTL_Request, 

89 SMB2_IOCTL_Response, 

90 SMB2_Negotiate_Context, 

91 SMB2_Negotiate_Protocol_Request, 

92 SMB2_Negotiate_Protocol_Response, 

93 SMB2_Netname_Negotiate_Context_ID, 

94 SMB2_Preauth_Integrity_Capabilities, 

95 SMB2_Query_Directory_Request, 

96 SMB2_Query_Directory_Response, 

97 SMB2_Query_Info_Request, 

98 SMB2_Query_Info_Response, 

99 SMB2_Read_Request, 

100 SMB2_Read_Response, 

101 SMB2_Session_Setup_Request, 

102 SMB2_Session_Setup_Response, 

103 SMB2_SIGNING_ALGORITHMS, 

104 SMB2_Signing_Capabilities, 

105 SMB2_Tree_Connect_Request, 

106 SMB2_Tree_Connect_Response, 

107 SMB2_Tree_Disconnect_Request, 

108 SMB2_Tree_Disconnect_Response, 

109 SMB2_Write_Request, 

110 SMB2_Write_Response, 

111 SMBStreamSocket, 

112 SRVSVC_SHARE_TYPES, 

113 STATUS_ERREF, 

114) 

115from scapy.layers.spnego import SPNEGOSSP 

116 

117 

118class SMB_Client(Automaton): 

119 """ 

120 SMB client automaton 

121 

122 :param sock: the SMBStreamSocket to use 

123 :param ssp: the SSP to use 

124 

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

126 

127 :param REQUIRE_SIGNATURE: set 'Require Signature' 

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

129 :param MAX_DIALECT: maximum SMB dialect. Defaults to 0x0210 (2.1.0) 

130 :param DIALECTS: list of supported SMB2 dialects. 

131 Constructed from MIN_DIALECT, MAX_DIALECT otherwise. 

132 """ 

133 

134 port = 445 

135 cls = DirectTCP 

136 

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

138 # Various SMB client arguments 

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

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

141 self.REQUIRE_SIGNATURE = kwargs.pop("REQUIRE_SIGNATURE", 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.SERVER_NAME = kwargs.pop("SERVER_NAME", "") 

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 # MAX_DIALECT is currently SMB 2.0.2. 3.1.1 support is unfinished 

151 self.MAX_DIALECT = kwargs.pop("MAX_DIALECT", 0x0202) 

152 self.DIALECTS = sorted( 

153 [ 

154 x 

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

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

157 ] 

158 ) 

159 # Internal Session information 

160 self.IsGuest = False 

161 self.ErrorStatus = None 

162 self.NegotiateCapabilities = None 

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

164 self.MaxTransactionSize = 0 

165 self.MaxReadSize = 0 

166 self.MaxWriteSize = 0 

167 if ssp is None: 

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

169 ssp = SPNEGOSSP( 

170 [ 

171 NTLMSSP( 

172 UPN="guest", 

173 HASHNT=b"", 

174 ) 

175 ] 

176 ) 

177 # Initialize 

178 kwargs["sock"] = sock 

179 Automaton.__init__( 

180 self, 

181 *args, 

182 **kwargs, 

183 ) 

184 if self.is_atmt_socket: 

185 self.smb_sock_ready = threading.Event() 

186 # Set session options 

187 self.session.ssp = ssp 

188 self.session.SecurityMode = kwargs.pop( 

189 "SECURITY_MODE", 

190 3 if self.REQUIRE_SIGNATURE else int(bool(ssp)), 

191 ) 

192 self.session.Dialect = self.MAX_DIALECT 

193 

194 @classmethod 

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

196 return cls.smblink( 

197 None, 

198 SMBStreamSocket(sock, DirectTCP), 

199 **kwargs, 

200 ) 

201 

202 @property 

203 def session(self): 

204 # session shorthand 

205 return self.sock.session 

206 

207 def send(self, pkt): 

208 # Calculate what CreditCharge to send. 

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

210 # [MS-SMB2] sect 3.2.4.1.5 

211 typ = type(pkt.payload.payload) 

212 if typ is SMB2_Negotiate_Protocol_Request: 

213 # See [MS-SMB2] 3.2.4.1.2 note 

214 pkt.CreditCharge = 0 

215 elif typ in [ 

216 SMB2_Read_Request, 

217 SMB2_Write_Request, 

218 SMB2_IOCTL_Request, 

219 SMB2_Query_Directory_Request, 

220 SMB2_Change_Notify_Request, 

221 SMB2_Query_Info_Request, 

222 ]: 

223 # [MS-SMB2] 3.1.5.2 

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

225 # "CHANGE_NOTIFY, QUERY_INFO, or SET_INFO" 

226 if typ == SMB2_Read_Request: 

227 Length = pkt.payload.Length 

228 elif typ == SMB2_Write_Request: 

229 Length = len(pkt.payload.Data) 

230 elif typ == SMB2_IOCTL_Request: 

231 # [MS-SMB2] 3.3.5.15 

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

233 elif typ in [ 

234 SMB2_Query_Directory_Request, 

235 SMB2_Change_Notify_Request, 

236 SMB2_Query_Info_Request, 

237 ]: 

238 Length = pkt.payload.OutputBufferLength 

239 else: 

240 raise RuntimeError("impossible case") 

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

242 else: 

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

244 pkt.CreditCharge = 1 

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

246 

247 @ATMT.state(initial=1) 

248 def BEGIN(self): 

249 pass 

250 

251 @ATMT.condition(BEGIN) 

252 def continue_smb2(self): 

253 if self.SMB2: # Directly started in SMB2 

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

255 raise self.SMB2_NEGOTIATE() 

256 

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

258 def send_negotiate(self): 

259 raise self.SENT_NEGOTIATE() 

260 

261 @ATMT.action(send_negotiate) 

262 def on_negotiate(self): 

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

264 self.smb_header = DirectTCP() / SMB_Header( 

265 Flags2=( 

266 "LONG_NAMES+EAS+NT_STATUS+UNICODE+" 

267 "SMB_SECURITY_SIGNATURE+EXTENDED_SECURITY" 

268 ), 

269 TID=0xFFFF, 

270 PIDLow=0xFEFF, 

271 UID=0, 

272 MID=0, 

273 ) 

274 if self.EXTENDED_SECURITY: 

275 self.smb_header.Flags2 += "EXTENDED_SECURITY" 

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

277 Dialects=[ 

278 SMB_Dialect(DialectString=x) 

279 for x in [ 

280 "PC NETWORK PROGRAM 1.0", 

281 "LANMAN1.0", 

282 "Windows for Workgroups 3.1a", 

283 "LM1.2X002", 

284 "LANMAN2.1", 

285 "NT LM 0.12", 

286 ] 

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

288 ], 

289 ) 

290 if not self.EXTENDED_SECURITY: 

291 pkt.Flags2 -= "EXTENDED_SECURITY" 

292 pkt[SMB_Header].Flags2 = ( 

293 pkt[SMB_Header].Flags2 

294 - "SMB_SECURITY_SIGNATURE" 

295 + "SMB_SECURITY_SIGNATURE_REQUIRED+IS_LONG_NAME" 

296 ) 

297 self.send(pkt) 

298 

299 @ATMT.state() 

300 def SENT_NEGOTIATE(self): 

301 pass 

302 

303 @ATMT.state() 

304 def SMB2_NEGOTIATE(self): 

305 pass 

306 

307 @ATMT.condition(SMB2_NEGOTIATE) 

308 def send_negotiate_smb2(self): 

309 raise self.SENT_NEGOTIATE() 

310 

311 @ATMT.action(send_negotiate_smb2) 

312 def on_negotiate_smb2(self): 

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

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

315 Dialects=self.DIALECTS, 

316 SecurityMode=self.session.SecurityMode, 

317 ) 

318 if self.MAX_DIALECT >= 0x0210: 

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

320 # MUST be set to the global ClientGuid value" 

321 pkt.ClientGUID = self.GUID 

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

323 self.NegotiateCapabilities = "+".join( 

324 [ 

325 "DFS", 

326 "LEASING", 

327 "LARGE_MTU", 

328 ] 

329 ) 

330 if self.MAX_DIALECT >= 0x0300: 

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

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

333 [ 

334 "MULTI_CHANNEL", 

335 "PERSISTENT_HANDLES", 

336 "DIRECTORY_LEASING", 

337 ] 

338 ) 

339 if self.MAX_DIALECT >= 0x0300: 

340 # "If the client implements the SMB 3.x dialect family, the client MUST 

341 # set the Capabilities field as follows" 

342 self.NegotiateCapabilities += "+ENCRYPTION" 

343 if self.MAX_DIALECT >= 0x0311: 

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

345 pkt.NegotiateContexts = [ 

346 SMB2_Negotiate_Context() 

347 / SMB2_Preauth_Integrity_Capabilities( 

348 # SHA-512 by default 

349 HashAlgorithms=[self.session.PreauthIntegrityHashId], 

350 Salt=self.session.Salt, 

351 ), 

352 SMB2_Negotiate_Context() 

353 / SMB2_Encryption_Capabilities( 

354 # AES-128-CCM by default 

355 Ciphers=[self.session.CipherId], 

356 ), 

357 # TODO support compression and RDMA 

358 SMB2_Negotiate_Context() 

359 / SMB2_Netname_Negotiate_Context_ID( 

360 NetName=self.SERVER_NAME, 

361 ), 

362 SMB2_Negotiate_Context() 

363 / SMB2_Signing_Capabilities( 

364 # AES-128-CCM by default 

365 SigningAlgorithms=[self.session.SigningAlgorithmId], 

366 ), 

367 ] 

368 pkt.Capabilities = self.NegotiateCapabilities 

369 # Send 

370 self.send(pkt) 

371 # If required, compute sessions 

372 self.session.computeSMBConnectionPreauth( 

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

374 ) 

375 

376 @ATMT.receive_condition(SENT_NEGOTIATE) 

377 def receive_negotiate_response(self, pkt): 

378 if ( 

379 SMBNegotiate_Response_Security in pkt 

380 or SMBNegotiate_Response_Extended_Security in pkt 

381 or SMB2_Negotiate_Protocol_Response in pkt 

382 ): 

383 if SMB2_Negotiate_Protocol_Response in pkt: 

384 # SMB2 

385 self.SMB2 = True # We are using SMB2 to talk to the server 

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

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 self.SMB2: 

397 self.smb_header.MID += 1 

398 if ( 

399 SMB2_Negotiate_Protocol_Response in pkt 

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

401 ): 

402 # Version is SMB X.??? 

403 raise self.SMB2_NEGOTIATE() 

404 else: 

405 if SMB2_Negotiate_Protocol_Response in pkt: 

406 # SMB2 was negotiated ! 

407 self.session.Dialect = pkt.DialectRevision 

408 # If required, compute sessions 

409 self.session.computeSMBConnectionPreauth( 

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

411 ) 

412 # Process max sizes 

413 self.MaxReadSize = pkt.MaxReadSize 

414 self.MaxTransactionSize = pkt.MaxTransactionSize 

415 self.MaxWriteSize = pkt.MaxWriteSize 

416 # Process NegotiateContext 

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

418 for ngctx in pkt.NegotiateContexts: 

419 if ngctx.ContextType == 0x0002: 

420 # SMB2_ENCRYPTION_CAPABILITIES 

421 self.session.CipherId = SMB2_ENCRYPTION_CIPHERS[ 

422 ngctx.Ciphers[0] 

423 ] 

424 elif ngctx.ContextType == 0x0008: 

425 # SMB2_SIGNING_CAPABILITIES 

426 self.session.SigningAlgorithmId = ( 

427 SMB2_SIGNING_ALGORITHMS[ngctx.SigningAlgorithms[0]] 

428 ) 

429 self.update_smbheader(pkt) 

430 raise self.NEGOTIATED(ssp_blob) 

431 elif SMBNegotiate_Response_Security in pkt: 

432 # Non-extended SMB1 

433 # Never tested. FIXME. probably broken 

434 raise self.NEGOTIATED(pkt.Challenge) 

435 

436 @ATMT.state() 

437 def NEGOTIATED(self, ssp_blob=None): 

438 # Negotiated ! We now know the Dialect 

439 if self.session.Dialect > 0x0202: 

440 # [MS-SMB2] sect 3.2.5.1.4 

441 self.smb_header.CreditRequest = 1 

442 # Begin session establishment 

443 ssp_tuple = self.session.ssp.GSS_Init_sec_context( 

444 self.session.sspcontext, 

445 ssp_blob, 

446 req_flags=( 

447 GSS_C_FLAGS.GSS_C_MUTUAL_FLAG 

448 | ( 

449 GSS_C_FLAGS.GSS_C_INTEG_FLAG 

450 if self.session.SecurityMode != 0 

451 else 0 

452 ) 

453 ), 

454 ) 

455 return ssp_tuple 

456 

457 def update_smbheader(self, pkt): 

458 """ 

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

460 """ 

461 # Some values should not be updated when ASYNC 

462 if not pkt.Flags.SMB2_FLAGS_ASYNC_COMMAND: 

463 # Update IDs 

464 self.smb_header.SessionId = pkt.SessionId 

465 self.smb_header.TID = pkt.TID 

466 self.smb_header.PID = pkt.PID 

467 

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

469 

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

471 def should_send_setup_andx_request(self, ssp_tuple): 

472 _, _, negResult = ssp_tuple 

473 if negResult not in [GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED]: 

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

475 raise self.SENT_SETUP_ANDX_REQUEST().action_parameters(ssp_tuple) 

476 

477 @ATMT.state() 

478 def SENT_SETUP_ANDX_REQUEST(self): 

479 pass 

480 

481 @ATMT.action(should_send_setup_andx_request) 

482 def send_setup_andx_request(self, ssp_tuple): 

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

484 self.smb_header.MID += 1 

485 if self.SMB2 and negResult == GSS_S_CONTINUE_NEEDED: 

486 # New session: force 0 

487 self.SessionId = 0 

488 if self.SMB2 or self.EXTENDED_SECURITY: 

489 # SMB1 extended / SMB2 

490 if self.SMB2: 

491 # SMB2 

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

493 Capabilities="DFS", 

494 SecurityMode=self.session.SecurityMode, 

495 ) 

496 else: 

497 # SMB1 extended 

498 pkt = ( 

499 self.smb_header.copy() 

500 / SMBSession_Setup_AndX_Request_Extended_Security( 

501 ServerCapabilities=( 

502 "UNICODE+NT_SMBS+STATUS32+LEVEL_II_OPLOCKS+" 

503 "DYNAMIC_REAUTH+EXTENDED_SECURITY" 

504 ), 

505 NativeOS=b"", 

506 NativeLanMan=b"", 

507 ) 

508 ) 

509 pkt.SecurityBlob = token 

510 else: 

511 # Non-extended security. 

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

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

514 NativeOS=b"", 

515 NativeLanMan=b"", 

516 OEMPassword=b"\0" * 24, 

517 UnicodePassword=token, 

518 ) 

519 self.send(pkt) 

520 if self.SMB2: 

521 # If required, compute sessions 

522 self.session.computeSMBSessionPreauth( 

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

524 ) 

525 

526 @ATMT.receive_condition(SENT_SETUP_ANDX_REQUEST) 

527 def receive_setup_andx_response(self, pkt): 

528 if ( 

529 SMBSession_Null in pkt 

530 or SMBSession_Setup_AndX_Response_Extended_Security in pkt 

531 or SMBSession_Setup_AndX_Response in pkt 

532 ): 

533 # SMB1 

534 if SMBSession_Null in pkt: 

535 # Likely an error 

536 raise self.NEGOTIATED() 

537 # Logging 

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

539 # Not SUCCESS nor MORE_PROCESSING_REQUIRED: log 

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

541 self.debug( 

542 lvl=1, 

543 msg=conf.color_theme.red( 

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

545 ), 

546 ) 

547 # Cases depending on the response packet 

548 if ( 

549 SMBSession_Setup_AndX_Response_Extended_Security in pkt 

550 or SMB2_Session_Setup_Response in pkt 

551 ): 

552 # The server assigns us a SessionId 

553 self.smb_header.SessionId = pkt.SessionId 

554 # SMB1 extended / SMB2 

555 if pkt.Status == 0: # Authenticated 

556 if SMB2_Session_Setup_Response in pkt and pkt.SessionFlags.IS_GUEST: 

557 # We were 'authenticated' in GUEST 

558 self.IsGuest = True 

559 raise self.AUTHENTICATED(pkt.SecurityBlob) 

560 else: 

561 if SMB2_Header in pkt: 

562 # If required, compute sessions 

563 self.session.computeSMBSessionPreauth( 

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

565 ) 

566 # Ongoing auth 

567 raise self.NEGOTIATED(pkt.SecurityBlob) 

568 elif SMBSession_Setup_AndX_Response_Extended_Security in pkt: 

569 # SMB1 non-extended 

570 pass 

571 elif SMB2_Error_Response in pkt: 

572 # Authentication failure 

573 self.session.sspcontext.clifailure() 

574 # Reset Session preauth (SMB 3.1.1) 

575 self.session.SessionPreauthIntegrityHashValue = None 

576 if not self.RETRY: 

577 raise self.AUTH_FAILED() 

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

579 self.RETRY -= 1 

580 raise self.NEGOTIATED() 

581 

582 @ATMT.state(final=1) 

583 def AUTH_FAILED(self): 

584 self.smb_sock_ready.set() 

585 

586 @ATMT.state() 

587 def AUTHENTICATED(self, ssp_blob=None): 

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

589 self.session.sspcontext, ssp_blob 

590 ) 

591 if status != GSS_S_COMPLETE: 

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

593 # Authentication was successful 

594 self.session.computeSMBSessionKey() 

595 if self.IsGuest: 

596 # When authenticated in Guest, the sessionkey the client has is invalid 

597 self.session.SMBSessionKey = None 

598 

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

600 

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

602 def authenticated_post_actions(self): 

603 raise self.SOCKET_BIND() 

604 

605 # Plain SMB Socket 

606 

607 @ATMT.state() 

608 def SOCKET_BIND(self): 

609 self.smb_sock_ready.set() 

610 

611 @ATMT.condition(SOCKET_BIND) 

612 def start_smb_socket(self): 

613 raise self.SOCKET_MODE_SMB() 

614 

615 @ATMT.state() 

616 def SOCKET_MODE_SMB(self): 

617 pass 

618 

619 @ATMT.receive_condition(SOCKET_MODE_SMB) 

620 def incoming_data_received_smb(self, pkt): 

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

622 

623 @ATMT.action(incoming_data_received_smb) 

624 def receive_data_smb(self, pkt): 

625 self.update_smbheader(pkt) 

626 resp = pkt[SMB2_Header].payload 

627 if isinstance(resp, SMB2_Error_Response): 

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

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

630 return 

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

632 # this is a notify cleanup. ignore 

633 return 

634 # Add the status to the response as metadata 

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

636 self.oi.smbpipe.send(resp) 

637 

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

639 def outgoing_data_received_smb(self, fd): 

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

641 

642 @ATMT.action(outgoing_data_received_smb) 

643 def send_data(self, d): 

644 self.smb_header.MID += 1 

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

646 

647 

648class SMB_SOCKET(SuperSocket): 

649 """ 

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

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

652 """ 

653 

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

655 self.ins = smbsock 

656 self.timeout = timeout 

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

658 raise TimeoutError( 

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

660 ) 

661 if self.ins.atmt.ErrorStatus: 

662 raise Scapy_Exception( 

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

664 ) 

665 

666 @classmethod 

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

668 """ 

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

670 SMB_SOCKET/SMB_RPC_SOCKET 

671 """ 

672 return cls( 

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

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

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

676 ) 

677 

678 def set_TID(self, TID): 

679 """ 

680 Set the TID (Tree ID). 

681 This can be called before sending a packet 

682 """ 

683 self.ins.atmt.smb_header.TID = TID 

684 

685 def get_TID(self): 

686 """ 

687 Get the current TID from the underlying socket 

688 """ 

689 return self.ins.atmt.smb_header.TID 

690 

691 def tree_connect(self, name): 

692 """ 

693 Send a TreeConnect request 

694 """ 

695 resp = self.ins.sr1( 

696 SMB2_Tree_Connect_Request( 

697 Buffer=[ 

698 ( 

699 "Path", 

700 "\\\\%s\\%s" 

701 % ( 

702 self.ins.atmt.session.sspcontext.ServerHostname, 

703 name, 

704 ), 

705 ) 

706 ] 

707 ), 

708 verbose=False, 

709 timeout=self.timeout, 

710 ) 

711 if not resp: 

712 raise ValueError("TreeConnect timed out !") 

713 if SMB2_Tree_Connect_Response not in resp: 

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

715 return self.get_TID() 

716 

717 def tree_disconnect(self): 

718 """ 

719 Send a TreeDisconnect request 

720 """ 

721 resp = self.ins.sr1( 

722 SMB2_Tree_Disconnect_Request(), 

723 verbose=False, 

724 timeout=self.timeout, 

725 ) 

726 if not resp: 

727 raise ValueError("TreeDisconnect timed out !") 

728 if SMB2_Tree_Disconnect_Response not in resp: 

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

730 

731 def create_request( 

732 self, 

733 name, 

734 mode="r", 

735 type="pipe", 

736 extra_create_options=[], 

737 extra_desired_access=[], 

738 ): 

739 """ 

740 Open a file/pipe by its name 

741 

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

743 """ 

744 ShareAccess = [] 

745 DesiredAccess = [] 

746 # Common params depending on the access 

747 if "r" in mode: 

748 ShareAccess.append("FILE_SHARE_READ") 

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

750 if "w" in mode: 

751 ShareAccess.append("FILE_SHARE_WRITE") 

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

753 if "d" in mode: 

754 ShareAccess.append("FILE_SHARE_DELETE") 

755 # Params depending on the type 

756 FileAttributes = [] 

757 CreateOptions = [] 

758 CreateContexts = [] 

759 CreateDisposition = "FILE_OPEN" 

760 if type == "folder": 

761 FileAttributes.append("FILE_ATTRIBUTE_DIRECTORY") 

762 CreateOptions.append("FILE_DIRECTORY_FILE") 

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

764 CreateOptions = ["FILE_NON_DIRECTORY_FILE"] 

765 if "r" in mode: 

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

767 if "w" in mode: 

768 CreateDisposition = "FILE_OVERWRITE_IF" 

769 DesiredAccess.append("FILE_WRITE_EA") 

770 if "d" in mode: 

771 DesiredAccess.append("DELETE") 

772 CreateOptions.append("FILE_DELETE_ON_CLOSE") 

773 if type == "file": 

774 FileAttributes.append("FILE_ATTRIBUTE_NORMAL") 

775 elif type: 

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

777 # SMB 3.11 

778 if self.ins.atmt.session.Dialect >= 0x0311 and type in ["file", "folder"]: 

779 CreateContexts.extend( 

780 [ 

781 # [SMB2] sect 3.2.4.3.5 

782 SMB2_Create_Context( 

783 Name=b"DH2Q", 

784 Data=SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2( 

785 CreateGuid=RandUUID()._fix() 

786 ), 

787 ), 

788 # [SMB2] sect 3.2.4.3.9 

789 SMB2_Create_Context( 

790 Name=b"MxAc", 

791 ), 

792 # [SMB2] sect 3.2.4.3.10 

793 SMB2_Create_Context( 

794 Name=b"QFid", 

795 ), 

796 # [SMB2] sect 3.2.4.3.8 

797 SMB2_Create_Context( 

798 Name=b"RqLs", Data=SMB2_CREATE_REQUEST_LEASE_V2() 

799 ), 

800 ] 

801 ) 

802 # Extra options 

803 if extra_create_options: 

804 CreateOptions.extend(extra_create_options) 

805 if extra_desired_access: 

806 DesiredAccess.extend(extra_desired_access) 

807 # Request 

808 resp = self.ins.sr1( 

809 SMB2_Create_Request( 

810 ImpersonationLevel="Impersonation", 

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

812 CreateDisposition=CreateDisposition, 

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

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

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

816 CreateContexts=CreateContexts, 

817 Name=name, 

818 ), 

819 verbose=0, 

820 timeout=self.timeout, 

821 ) 

822 if not resp: 

823 raise ValueError("CreateRequest timed out !") 

824 if SMB2_Create_Response not in resp: 

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

826 return resp[SMB2_Create_Response].FileId 

827 

828 def close_request(self, FileId): 

829 """ 

830 Close the FileId 

831 """ 

832 pkt = SMB2_Close_Request(FileId=FileId) 

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

834 if not resp: 

835 raise ValueError("CloseRequest timed out !") 

836 if SMB2_Close_Response not in resp: 

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

838 

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

840 """ 

841 Read request 

842 """ 

843 resp = self.ins.sr1( 

844 SMB2_Read_Request( 

845 FileId=FileId, 

846 Length=Length, 

847 Offset=Offset, 

848 ), 

849 verbose=0, 

850 timeout=self.timeout, 

851 ) 

852 if not resp: 

853 raise ValueError("ReadRequest timed out !") 

854 if SMB2_Read_Response not in resp: 

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

856 return resp.Data 

857 

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

859 """ 

860 Write request 

861 """ 

862 resp = self.ins.sr1( 

863 SMB2_Write_Request( 

864 FileId=FileId, 

865 Data=Data, 

866 Offset=Offset, 

867 ), 

868 verbose=0, 

869 timeout=self.timeout, 

870 ) 

871 if not resp: 

872 raise ValueError("WriteRequest timed out !") 

873 if SMB2_Write_Response not in resp: 

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

875 return resp.Count 

876 

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

878 """ 

879 Query the Directory with FileId 

880 """ 

881 results = [] 

882 Flags = "SMB2_RESTART_SCANS" 

883 while True: 

884 pkt = SMB2_Query_Directory_Request( 

885 FileInformationClass="FileIdBothDirectoryInformation", 

886 FileId=FileId, 

887 FileName=FileName, 

888 Flags=Flags, 

889 ) 

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

891 Flags = 0 # only the first one is RESTART_SCANS 

892 if not resp: 

893 raise ValueError("QueryDirectory timed out !") 

894 if SMB2_Error_Response in resp: 

895 break 

896 elif SMB2_Query_Directory_Response not in resp: 

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

898 res = FileIdBothDirectoryInformation(resp.Output) 

899 results.extend( 

900 [ 

901 ( 

902 x.FileName, 

903 x.FileAttributes, 

904 x.EndOfFile, 

905 x.LastWriteTime, 

906 ) 

907 for x in res.files 

908 ] 

909 ) 

910 return results 

911 

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

913 """ 

914 Query the Info 

915 """ 

916 pkt = SMB2_Query_Info_Request( 

917 InfoType=InfoType, 

918 FileInfoClass=FileInfoClass, 

919 OutputBufferLength=65535, 

920 FileId=FileId, 

921 AdditionalInformation=AdditionalInformation, 

922 ) 

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

924 if not resp: 

925 raise ValueError("QueryInfo timed out !") 

926 if SMB2_Query_Info_Response not in resp: 

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

928 return resp.Output 

929 

930 def changenotify(self, FileId): 

931 """ 

932 Register change notify 

933 """ 

934 pkt = SMB2_Change_Notify_Request( 

935 Flags="SMB2_WATCH_TREE", 

936 OutputBufferLength=65535, 

937 FileId=FileId, 

938 CompletionFilter=0x0FFF, 

939 ) 

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

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

942 if SMB2_Change_Notify_Response not in resp: 

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

944 return resp.Output 

945 

946 

947class SMB_RPC_SOCKET(ObjectPipe, SMB_SOCKET): 

948 """ 

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

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

951 

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

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

954 """ 

955 

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

957 self.use_ioctl = use_ioctl 

958 ObjectPipe.__init__(self, "SMB_RPC_SOCKET") 

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

960 

961 def open_pipe(self, name): 

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

963 

964 def close_pipe(self): 

965 self.close_request(self.PipeFileId) 

966 self.PipeFileId = None 

967 

968 def send(self, x): 

969 """ 

970 Internal ObjectPipe function. 

971 """ 

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

973 if self.use_ioctl: 

974 # Use IOCTLRequest 

975 pkt = SMB2_IOCTL_Request( 

976 FileId=self.PipeFileId, 

977 Flags="SMB2_0_IOCTL_IS_FSCTL", 

978 CtlCode="FSCTL_PIPE_TRANSCEIVE", 

979 ) 

980 pkt.Input = bytes(x) 

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

982 if SMB2_IOCTL_Response not in resp: 

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

984 data = bytes(resp.Output) 

985 # Handle BUFFER_OVERFLOW (big DCE/RPC response) 

986 while resp.NTStatus == "STATUS_BUFFER_OVERFLOW": 

987 # Retrieve DCE/RPC full size 

988 resp = self.ins.sr1( 

989 SMB2_Read_Request( 

990 FileId=self.PipeFileId, 

991 ), 

992 verbose=0, 

993 ) 

994 data += resp.Data 

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

996 else: 

997 # Use WriteRequest/ReadRequest 

998 pkt = SMB2_Write_Request( 

999 FileId=self.PipeFileId, 

1000 ) 

1001 pkt.Data = bytes(x) 

1002 # We send the Write Request 

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

1004 if SMB2_Write_Response not in resp: 

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

1006 # We send a Read Request afterwards 

1007 resp = self.ins.sr1( 

1008 SMB2_Read_Request( 

1009 FileId=self.PipeFileId, 

1010 ), 

1011 verbose=0, 

1012 ) 

1013 if SMB2_Read_Response not in resp: 

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

1015 data = bytes(resp.Data) 

1016 # Handle BUFFER_OVERFLOW (big DCE/RPC response) 

1017 while resp.NTStatus == "STATUS_BUFFER_OVERFLOW": 

1018 # Retrieve DCE/RPC full size 

1019 resp = self.ins.sr1( 

1020 SMB2_Read_Request( 

1021 FileId=self.PipeFileId, 

1022 ), 

1023 verbose=0, 

1024 ) 

1025 data += resp.Data 

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

1027 

1028 def close(self): 

1029 SMB_SOCKET.close(self) 

1030 ObjectPipe.close(self) 

1031 

1032 

1033@conf.commands.register 

1034class smbclient(CLIUtil): 

1035 r""" 

1036 A simple smbclient CLI 

1037 

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

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

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

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

1042 :param kerberos: if available, whether to use Kerberos or not 

1043 :param kerberos_required: require kerberos 

1044 :param port: the TCP port. default 445 

1045 :param password: (string) if provided, used for auth 

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

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

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

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

1050 """ 

1051 

1052 def __init__( 

1053 self, 

1054 target: str, 

1055 UPN: str = None, 

1056 password: str = None, 

1057 guest: bool = False, 

1058 kerberos: bool = True, 

1059 kerberos_required: bool = False, 

1060 HashNt: str = None, 

1061 port: int = 445, 

1062 timeout: int = 2, 

1063 debug: int = 0, 

1064 ssp=None, 

1065 ST=None, 

1066 KEY=None, 

1067 cli=True, 

1068 # SMB arguments 

1069 **kwargs, 

1070 ): 

1071 if cli: 

1072 self._depcheck() 

1073 hostname = None 

1074 # Check if target is a hostname / Check IP 

1075 if ":" in target: 

1076 family = socket.AF_INET6 

1077 if not valid_ip6(target): 

1078 hostname = target 

1079 target = str(Net6(target)) 

1080 else: 

1081 family = socket.AF_INET 

1082 if not valid_ip(target): 

1083 hostname = target 

1084 target = str(Net(target)) 

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

1086 # Do we need to build a SSP? 

1087 if ssp is None: 

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

1089 if not guest: 

1090 # Check UPN 

1091 try: 

1092 _, realm = _parse_upn(UPN) 

1093 if realm == ".": 

1094 # Local 

1095 kerberos = False 

1096 except ValueError: 

1097 # not a UPN: NTLM 

1098 kerberos = False 

1099 # Do we need to ask the password? 

1100 if HashNt is None and password is None and ST is None: 

1101 # yes. 

1102 from prompt_toolkit import prompt 

1103 

1104 password = prompt("Password: ", is_password=True) 

1105 ssps = [] 

1106 # Kerberos 

1107 if kerberos and hostname: 

1108 if ST is None: 

1109 resp = krb_as_and_tgs( 

1110 upn=UPN, 

1111 spn="cifs/%s" % hostname, 

1112 password=password, 

1113 debug=debug, 

1114 ) 

1115 if resp is not None: 

1116 ST, KEY = resp.tgsrep.ticket, resp.sessionkey 

1117 if ST: 

1118 ssps.append(KerberosSSP(UPN=UPN, ST=ST, KEY=KEY, debug=debug)) 

1119 elif kerberos_required: 

1120 raise ValueError( 

1121 "Kerberos required but target isn't a hostname !" 

1122 ) 

1123 elif kerberos_required: 

1124 raise ValueError( 

1125 "Kerberos required but domain not specified in the UPN, " 

1126 "or target isn't a hostname !" 

1127 ) 

1128 # NTLM 

1129 if not kerberos_required: 

1130 if HashNt is None and password is not None: 

1131 HashNt = MD4le(password) 

1132 ssps.append(NTLMSSP(UPN=UPN, HASHNT=HashNt)) 

1133 # Build the SSP 

1134 ssp = SPNEGOSSP(ssps) 

1135 else: 

1136 # Guest mode 

1137 ssp = None 

1138 # Open socket 

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

1140 sock.settimeout(timeout) 

1141 sock.connect((target, port)) 

1142 self.extra_create_options = [] 

1143 # Wrap with the automaton 

1144 self.timeout = timeout 

1145 kwargs.setdefault("SERVER_NAME", target) 

1146 self.sock = SMB_Client.from_tcpsock( 

1147 sock, 

1148 ssp=ssp, 

1149 debug=debug, 

1150 **kwargs, 

1151 ) 

1152 try: 

1153 # Wrap with SMB_SOCKET 

1154 self.smbsock = SMB_SOCKET(self.sock) 

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

1156 _t = time.time() 

1157 while True: 

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

1159 # yay 

1160 break 

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

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

1163 raise Scapy_Exception( 

1164 "%s with status %s" 

1165 % ( 

1166 self.sock.atmt.state.state, 

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

1168 ) 

1169 ) 

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

1171 self.sock.close() 

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

1173 time.sleep(0.1) 

1174 except Exception: 

1175 # Something bad happened, end the socket/automaton 

1176 self.sock.close() 

1177 raise 

1178 

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

1180 from scapy.layers.msrpce.rpcclient import DCERPC_Client 

1181 

1182 self.rpcclient = DCERPC_Client.from_smblink(self.sock, ndr64=False, verb=False) 

1183 # We have a valid smb connection ! 

1184 print( 

1185 "SMB authentication successful using %s%s !" 

1186 % ( 

1187 repr(self.sock.atmt.session.sspcontext), 

1188 " as GUEST" if self.sock.atmt.IsGuest else "", 

1189 ) 

1190 ) 

1191 # Now define some variables for our CLI 

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

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

1194 self.current_tree = None 

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

1196 # Start CLI 

1197 if cli: 

1198 self.loop(debug=debug) 

1199 

1200 def ps1(self): 

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

1202 

1203 def close(self): 

1204 print("Connection closed") 

1205 self.smbsock.close() 

1206 

1207 def _require_share(self, silent=False): 

1208 if self.current_tree is None: 

1209 if not silent: 

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

1211 return True 

1212 

1213 def collapse_path(self, path): 

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

1215 # is ridiculous 

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

1217 

1218 def normalize_path(self, path): 

1219 """ 

1220 Normalize path for CIFS usage 

1221 """ 

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

1223 

1224 @CLIUtil.addcommand() 

1225 def shares(self): 

1226 """ 

1227 List the shares available 

1228 """ 

1229 # One of the 'hardest' considering it's an RPC 

1230 self.rpcclient.open_smbpipe("srvsvc") 

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

1232 req = NetrShareEnum_Request( 

1233 InfoStruct=LPSHARE_ENUM_STRUCT( 

1234 Level=1, 

1235 ShareInfo=NDRUnion( 

1236 tag=1, 

1237 value=SHARE_INFO_1_CONTAINER(Buffer=None), 

1238 ), 

1239 ), 

1240 PreferedMaximumLength=0xFFFFFFFF, 

1241 ) 

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

1243 self.rpcclient.close_smbpipe() 

1244 if not isinstance(resp, NetrShareEnum_Response): 

1245 raise ValueError("NetrShareEnum_Request failed !") 

1246 results = [] 

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

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

1249 results.append( 

1250 ( 

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

1252 SRVSVC_SHARE_TYPES.get(shi1_type, shi1_type), 

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

1254 ) 

1255 ) 

1256 return results 

1257 

1258 @CLIUtil.addoutput(shares) 

1259 def shares_output(self, results): 

1260 """ 

1261 Print the output of 'shares' 

1262 """ 

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

1264 

1265 @CLIUtil.addcommand() 

1266 def use(self, share): 

1267 """ 

1268 Open a share 

1269 """ 

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

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

1272 self.ls_cache.clear() 

1273 

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

1275 """ 

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

1277 """ 

1278 # Find parent directory if it exists 

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

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

1281 eltname = elt.name 

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

1283 eltpar = elt 

1284 eltname = "" 

1285 elif elt.parent and elt.parent.name: 

1286 eltpar = elt.parent 

1287 return eltpar, eltname 

1288 

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

1290 """ 

1291 Return a listing of the remote files for completion purposes 

1292 """ 

1293 if cond is None: 

1294 cond = lambda _: True 

1295 eltpar, eltname = self._parsepath(arg) 

1296 # ls in that directory 

1297 try: 

1298 files = self.ls(parent=eltpar) 

1299 except ValueError: 

1300 return [] 

1301 return [ 

1302 str(eltpar / x[0]) 

1303 for x in files 

1304 if ( 

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

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

1307 and cond(x[1]) 

1308 ) 

1309 ] 

1310 

1311 def _dir_complete(self, arg): 

1312 """ 

1313 Return a directories of remote files for completion purposes 

1314 """ 

1315 results = self._fs_complete( 

1316 arg, 

1317 cond=lambda x: x.FILE_ATTRIBUTE_DIRECTORY, 

1318 ) 

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

1320 # skip through folders 

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

1322 return results 

1323 

1324 @CLIUtil.addcommand(spaces=True) 

1325 def ls(self, parent=None): 

1326 """ 

1327 List the files in the remote directory 

1328 -t: sort by timestamp 

1329 -S: sort by size 

1330 -r: reverse while sorting 

1331 """ 

1332 if self._require_share(): 

1333 return 

1334 # Get pwd of the ls 

1335 pwd = self.pwd 

1336 if parent is not None: 

1337 pwd /= parent 

1338 pwd = self.normalize_path(pwd) 

1339 # Poll the cache 

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

1341 return self.ls_cache[pwd] 

1342 self.smbsock.set_TID(self.current_tree) 

1343 # Open folder 

1344 fileId = self.smbsock.create_request( 

1345 pwd, 

1346 type="folder", 

1347 extra_create_options=self.extra_create_options, 

1348 ) 

1349 # Query the folder 

1350 files = self.smbsock.query_directory(fileId) 

1351 # Close the folder 

1352 self.smbsock.close_request(fileId) 

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

1354 return files 

1355 

1356 @CLIUtil.addoutput(ls) 

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

1358 """ 

1359 Print the output of 'ls' 

1360 """ 

1361 fld = UTCTimeField( 

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

1363 ) 

1364 if t: 

1365 # Sort by time 

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

1367 if S: 

1368 # Sort by size 

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

1370 if r: 

1371 # Reverse sort 

1372 results = results[::-1] 

1373 results = [ 

1374 ( 

1375 x[0], 

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

1377 human_size(x[2]), 

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

1379 ) 

1380 for x in results 

1381 ] 

1382 print( 

1383 pretty_list( 

1384 results, 

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

1386 sortBy=None, 

1387 ) 

1388 ) 

1389 

1390 @CLIUtil.addcomplete(ls) 

1391 def ls_complete(self, folder): 

1392 """ 

1393 Auto-complete ls 

1394 """ 

1395 if self._require_share(silent=True): 

1396 return [] 

1397 return self._dir_complete(folder) 

1398 

1399 @CLIUtil.addcommand(spaces=True) 

1400 def cd(self, folder): 

1401 """ 

1402 Change the remote current directory 

1403 """ 

1404 if self._require_share(): 

1405 return 

1406 if not folder: 

1407 # show mode 

1408 return str(self.pwd) 

1409 self.pwd /= folder 

1410 self.pwd = self.collapse_path(self.pwd) 

1411 self.ls_cache.clear() 

1412 

1413 @CLIUtil.addcomplete(cd) 

1414 def cd_complete(self, folder): 

1415 """ 

1416 Auto-complete cd 

1417 """ 

1418 if self._require_share(silent=True): 

1419 return [] 

1420 return self._dir_complete(folder) 

1421 

1422 def _lfs_complete(self, arg, cond): 

1423 """ 

1424 Return a listing of local files for completion purposes 

1425 """ 

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

1427 eltpar = self.localpwd / eltpar 

1428 return [ 

1429 str(x.relative_to(self.localpwd)) 

1430 for x in eltpar.glob("*") 

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

1432 ] 

1433 

1434 @CLIUtil.addoutput(cd) 

1435 def cd_output(self, result): 

1436 """ 

1437 Print the output of 'cd' 

1438 """ 

1439 if result: 

1440 print(result) 

1441 

1442 @CLIUtil.addcommand() 

1443 def lls(self): 

1444 """ 

1445 List the files in the local directory 

1446 """ 

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

1448 

1449 @CLIUtil.addoutput(lls) 

1450 def lls_output(self, results): 

1451 """ 

1452 Print the output of 'lls' 

1453 """ 

1454 results = [ 

1455 ( 

1456 x.name, 

1457 human_size(stat.st_size), 

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

1459 ) 

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

1461 ] 

1462 print( 

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

1464 ) 

1465 

1466 @CLIUtil.addcommand(spaces=True) 

1467 def lcd(self, folder): 

1468 """ 

1469 Change the local current directory 

1470 """ 

1471 if not folder: 

1472 # show mode 

1473 return str(self.localpwd) 

1474 self.localpwd /= folder 

1475 self.localpwd = self.localpwd.resolve() 

1476 

1477 @CLIUtil.addcomplete(lcd) 

1478 def lcd_complete(self, folder): 

1479 """ 

1480 Auto-complete lcd 

1481 """ 

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

1483 

1484 @CLIUtil.addoutput(lcd) 

1485 def lcd_output(self, result): 

1486 """ 

1487 Print the output of 'lcd' 

1488 """ 

1489 if result: 

1490 print(result) 

1491 

1492 def _get_file(self, file, fd): 

1493 """ 

1494 Gets the file bytes from a remote host 

1495 """ 

1496 # Get pwd of the ls 

1497 fpath = self.pwd / file 

1498 self.smbsock.set_TID(self.current_tree) 

1499 # Open file 

1500 fileId = self.smbsock.create_request( 

1501 self.normalize_path(fpath), 

1502 type="file", 

1503 extra_create_options=[ 

1504 "FILE_SEQUENTIAL_ONLY", 

1505 ] 

1506 + self.extra_create_options, 

1507 ) 

1508 # Get the file size 

1509 info = FileAllInformation( 

1510 self.smbsock.query_info( 

1511 FileId=fileId, 

1512 InfoType="SMB2_0_INFO_FILE", 

1513 FileInfoClass="FileAllInformation", 

1514 ) 

1515 ) 

1516 length = info.StandardInformation.EndOfFile 

1517 offset = 0 

1518 # Read the file 

1519 while length: 

1520 lengthRead = min(self.sock.atmt.MaxReadSize, length) 

1521 fd.write( 

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

1523 ) 

1524 offset += lengthRead 

1525 length -= lengthRead 

1526 # Close the file 

1527 self.smbsock.close_request(fileId) 

1528 return offset 

1529 

1530 def _send_file(self, fname, fd): 

1531 """ 

1532 Send the file bytes to a remote host 

1533 """ 

1534 # Get destination file 

1535 fpath = self.pwd / fname 

1536 self.smbsock.set_TID(self.current_tree) 

1537 # Open file 

1538 fileId = self.smbsock.create_request( 

1539 self.normalize_path(fpath), 

1540 type="file", 

1541 mode="w", 

1542 extra_create_options=self.extra_create_options, 

1543 ) 

1544 # Send the file 

1545 offset = 0 

1546 while True: 

1547 data = fd.read(self.sock.atmt.MaxWriteSize) 

1548 if not data: 

1549 # end of file 

1550 break 

1551 offset += self.smbsock.write_request( 

1552 Data=data, 

1553 FileId=fileId, 

1554 Offset=offset, 

1555 ) 

1556 # Close the file 

1557 self.smbsock.close_request(fileId) 

1558 return offset 

1559 

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

1561 """ 

1562 Internal recursive function to get a directory 

1563 

1564 :param directory: the remote directory to get 

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

1566 """ 

1567 size = 0 

1568 if not _root.exists(): 

1569 _root.mkdir() 

1570 # ls the directory 

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

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

1573 # Discard . and .. 

1574 continue 

1575 remote = directory / x[0] 

1576 local = _root / x[0] 

1577 try: 

1578 if x[1].FILE_ATTRIBUTE_DIRECTORY: 

1579 # Sub-directory 

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

1581 else: 

1582 # Sub-file 

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

1584 if _verb: 

1585 print(remote) 

1586 except ValueError as ex: 

1587 if _verb: 

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

1589 return size 

1590 

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

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

1593 """ 

1594 Retrieve a file 

1595 -r: recursively download a directory 

1596 """ 

1597 if self._require_share(): 

1598 return 

1599 if r: 

1600 dirpar, dirname = self._parsepath(file) 

1601 return file, self._getr( 

1602 dirpar / dirname, # Remotely 

1603 _root=self.localpwd / dirname, # Locally 

1604 _verb=_verb, 

1605 ) 

1606 else: 

1607 fname = pathlib.PureWindowsPath(file).name 

1608 # Write the buffer 

1609 if _dest is None: 

1610 _dest = self.localpwd / fname 

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

1612 size = self._get_file(file, fd) 

1613 return fname, size 

1614 

1615 @CLIUtil.addoutput(get) 

1616 def get_output(self, info): 

1617 """ 

1618 Print the output of 'get' 

1619 """ 

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

1621 

1622 @CLIUtil.addcomplete(get) 

1623 def get_complete(self, file): 

1624 """ 

1625 Auto-complete get 

1626 """ 

1627 if self._require_share(silent=True): 

1628 return [] 

1629 return self._fs_complete(file) 

1630 

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

1632 def cat(self, file): 

1633 """ 

1634 Print a file 

1635 """ 

1636 if self._require_share(): 

1637 return 

1638 # Write the buffer to buffer 

1639 buf = io.BytesIO() 

1640 self._get_file(file, buf) 

1641 return buf.getvalue() 

1642 

1643 @CLIUtil.addoutput(cat) 

1644 def cat_output(self, result): 

1645 """ 

1646 Print the output of 'cat' 

1647 """ 

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

1649 

1650 @CLIUtil.addcomplete(cat) 

1651 def cat_complete(self, file): 

1652 """ 

1653 Auto-complete cat 

1654 """ 

1655 if self._require_share(silent=True): 

1656 return [] 

1657 return self._fs_complete(file) 

1658 

1659 @CLIUtil.addcommand(spaces=True) 

1660 def put(self, file): 

1661 """ 

1662 Upload a file 

1663 """ 

1664 if self._require_share(): 

1665 return 

1666 local_file = self.localpwd / file 

1667 if local_file.is_dir(): 

1668 # Directory 

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

1670 else: 

1671 fname = pathlib.Path(file).name 

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

1673 size = self._send_file(fname, fd) 

1674 self.ls_cache.clear() 

1675 return fname, size 

1676 

1677 @CLIUtil.addcomplete(put) 

1678 def put_complete(self, folder): 

1679 """ 

1680 Auto-complete put 

1681 """ 

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

1683 

1684 @CLIUtil.addcommand(spaces=True) 

1685 def rm(self, file): 

1686 """ 

1687 Delete a file 

1688 """ 

1689 if self._require_share(): 

1690 return 

1691 # Get pwd of the ls 

1692 fpath = self.pwd / file 

1693 self.smbsock.set_TID(self.current_tree) 

1694 # Open file 

1695 fileId = self.smbsock.create_request( 

1696 self.normalize_path(fpath), 

1697 type="file", 

1698 mode="d", 

1699 extra_create_options=self.extra_create_options, 

1700 ) 

1701 # Close the file 

1702 self.smbsock.close_request(fileId) 

1703 self.ls_cache.clear() 

1704 return fpath.name 

1705 

1706 @CLIUtil.addcomplete(rm) 

1707 def rm_complete(self, file): 

1708 """ 

1709 Auto-complete rm 

1710 """ 

1711 if self._require_share(silent=True): 

1712 return [] 

1713 return self._fs_complete(file) 

1714 

1715 @CLIUtil.addcommand() 

1716 def backup(self): 

1717 """ 

1718 Turn on or off backup intent 

1719 """ 

1720 if "FILE_OPEN_FOR_BACKUP_INTENT" in self.extra_create_options: 

1721 print("Backup Intent: Off") 

1722 self.extra_create_options.remove("FILE_OPEN_FOR_BACKUP_INTENT") 

1723 else: 

1724 print("Backup Intent: On") 

1725 self.extra_create_options.append("FILE_OPEN_FOR_BACKUP_INTENT") 

1726 

1727 

1728if __name__ == "__main__": 

1729 from scapy.utils import AutoArgparse 

1730 

1731 AutoArgparse(smbclient)