1# SPDX-License-Identifier: GPL-2.0-only
2# This file is part of Scapy
3# See https://scapy.net/ for more information
4# Copyright (C) Gabriel Potter
5
6"""
7SMB 1 / 2 Client Automaton
8
9
10.. note::
11 You will find more complete documentation for this layer over at
12 `SMB <https://scapy.readthedocs.io/en/latest/layers/smb.html#client>`_
13"""
14
15import io
16import os
17import pathlib
18import re
19import socket
20import threading
21import time
22
23from scapy.automaton import ATMT, Automaton, ObjectPipe
24from scapy.config import conf
25from scapy.error import Scapy_Exception
26from scapy.fields import UTCTimeField
27from scapy.supersocket import SuperSocket
28from scapy.utils import (
29 CLIUtil,
30 pretty_list,
31 human_size,
32)
33from scapy.volatile import RandUUID
34
35from scapy.layers.dcerpc import NDRUnion, find_dcerpc_interface
36from scapy.layers.gssapi import (
37 GSS_C_FLAGS,
38 GSS_S_COMPLETE,
39 GSS_S_CONTINUE_NEEDED,
40 GSS_S_DEFECTIVE_TOKEN,
41)
42from scapy.layers.msrpce.raw.ms_srvs import (
43 LPSHARE_ENUM_STRUCT,
44 NetrSessionEnum_Request,
45 NetrShareEnum_Request,
46 PSESSION_ENUM_STRUCT,
47 SESSION_INFO_502_CONTAINER,
48 SHARE_INFO_1_CONTAINER,
49)
50from scapy.layers.ntlm import (
51 NTLMSSP,
52)
53from scapy.layers.smb import (
54 SMBNegotiate_Request,
55 SMBNegotiate_Response_Extended_Security,
56 SMBNegotiate_Response_Security,
57 SMBSession_Null,
58 SMBSession_Setup_AndX_Request,
59 SMBSession_Setup_AndX_Request_Extended_Security,
60 SMBSession_Setup_AndX_Response,
61 SMBSession_Setup_AndX_Response_Extended_Security,
62 SMB_Dialect,
63 SMB_Header,
64)
65from scapy.layers.windows.security import SECURITY_DESCRIPTOR
66from scapy.layers.smb2 import (
67 DirectTCP,
68 FileAllInformation,
69 FileIdBothDirectoryInformation,
70 SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2,
71 SMB2_CREATE_REQUEST_LEASE,
72 SMB2_CREATE_REQUEST_LEASE_V2,
73 SMB2_Change_Notify_Request,
74 SMB2_Change_Notify_Response,
75 SMB2_Close_Request,
76 SMB2_Close_Response,
77 SMB2_Create_Context,
78 SMB2_Create_Request,
79 SMB2_Create_Response,
80 SMB2_ENCRYPTION_CIPHERS,
81 SMB2_Encryption_Capabilities,
82 SMB2_Error_Response,
83 SMB2_FILEID,
84 SMB2_Header,
85 SMB2_IOCTL_Request,
86 SMB2_IOCTL_Response,
87 SMB2_Negotiate_Context,
88 SMB2_Negotiate_Protocol_Request,
89 SMB2_Negotiate_Protocol_Response,
90 SMB2_Netname_Negotiate_Context_ID,
91 SMB2_Preauth_Integrity_Capabilities,
92 SMB2_Query_Directory_Request,
93 SMB2_Query_Directory_Response,
94 SMB2_Query_Info_Request,
95 SMB2_Query_Info_Response,
96 SMB2_Read_Request,
97 SMB2_Read_Response,
98 SMB2_SIGNING_ALGORITHMS,
99 SMB2_Session_Setup_Request,
100 SMB2_Session_Setup_Response,
101 SMB2_Signing_Capabilities,
102 SMB2_Tree_Connect_Request,
103 SMB2_Tree_Connect_Response,
104 SMB2_Tree_Disconnect_Request,
105 SMB2_Tree_Disconnect_Response,
106 SMB2_Write_Request,
107 SMB2_Write_Response,
108 SMBStreamSocket,
109 SMB_DIALECTS,
110 SRVSVC_SHARE_TYPES,
111 STATUS_ERREF,
112)
113from scapy.layers.spnego import SPNEGOSSP
114
115
116class SMB_Client(Automaton):
117 """
118 SMB client automaton
119
120 :param sock: the SMBStreamSocket to use
121 :param ssp: the SSP to use
122
123 All other options (in caps) are optional, and SMB specific:
124
125 :param REQUIRE_SIGNATURE: set 'Require Signature'
126 :param REQUIRE_ENCRYPTION: set 'Requite Encryption'
127 :param MIN_DIALECT: minimum SMB dialect. Defaults to 0x0202 (2.0.2)
128 :param MAX_DIALECT: maximum SMB dialect. Defaults to 0x0311 (3.1.1)
129 :param DIALECTS: list of supported SMB2 dialects.
130 Constructed from MIN_DIALECT, MAX_DIALECT otherwise.
131 """
132
133 port = 445
134 cls = DirectTCP
135
136 def __init__(self, sock, ssp=None, *args, **kwargs):
137 # Various SMB client arguments
138 self.EXTENDED_SECURITY = kwargs.pop("EXTENDED_SECURITY", True)
139 self.USE_SMB1 = kwargs.pop("USE_SMB1", False)
140 self.REQUIRE_SIGNATURE = kwargs.pop("REQUIRE_SIGNATURE", None)
141 self.REQUIRE_ENCRYPTION = kwargs.pop("REQUIRE_ENCRYPTION", False)
142 self.RETRY = kwargs.pop("RETRY", 0) # optionally: retry n times session setup
143 self.SMB2 = kwargs.pop("SMB2", False) # optionally: start directly in SMB2
144 self.HOST = kwargs.pop("HOST", "")
145 # Store supported dialects
146 if "DIALECTS" in kwargs:
147 self.DIALECTS = kwargs.pop("DIALECTS")
148 else:
149 MIN_DIALECT = kwargs.pop("MIN_DIALECT", 0x0202)
150 self.MAX_DIALECT = kwargs.pop("MAX_DIALECT", 0x0311)
151 self.DIALECTS = sorted(
152 [
153 x
154 for x in [0x0202, 0x0210, 0x0300, 0x0302, 0x0311]
155 if x >= MIN_DIALECT and x <= self.MAX_DIALECT
156 ]
157 )
158 # Internal Session information
159 self.ErrorStatus = None
160 self.NegotiateCapabilities = None
161 self.GUID = RandUUID()._fix()
162 self.SequenceWindow = (0, 0) # keep track of allowed MIDs
163 self.CurrentCreditCount = 0
164 self.MaxCreditCount = 128
165 if ssp is None:
166 # We got no SSP. Assuming the server allows anonymous
167 ssp = SPNEGOSSP(
168 [
169 NTLMSSP(
170 UPN="guest",
171 HASHNT=b"",
172 )
173 ]
174 )
175 # Initialize
176 kwargs["sock"] = sock
177 Automaton.__init__(
178 self,
179 *args,
180 **kwargs,
181 )
182 if self.is_atmt_socket:
183 self.smb_sock_ready = threading.Event()
184 # Set session options
185 self.session.ssp = ssp
186 self.session.SigningRequired = (
187 self.REQUIRE_SIGNATURE if self.REQUIRE_SIGNATURE is not None else bool(ssp)
188 )
189 self.session.Dialect = self.MAX_DIALECT
190
191 @classmethod
192 def from_tcpsock(cls, sock, **kwargs):
193 return cls.smblink(
194 None,
195 SMBStreamSocket(sock, DirectTCP),
196 **kwargs,
197 )
198
199 @property
200 def session(self):
201 # session shorthand
202 return self.sock.session
203
204 def send(self, pkt):
205 # Calculate what CreditCharge to send.
206 if self.session.Dialect > 0x0202 and isinstance(pkt.payload, SMB2_Header):
207 # [MS-SMB2] sect 3.2.4.1.5
208 typ = type(pkt.payload.payload)
209 if typ is SMB2_Negotiate_Protocol_Request:
210 # See [MS-SMB2] 3.2.4.1.2 note
211 pkt.CreditCharge = 0
212 elif typ in [
213 SMB2_Read_Request,
214 SMB2_Write_Request,
215 SMB2_IOCTL_Request,
216 SMB2_Query_Directory_Request,
217 SMB2_Change_Notify_Request,
218 SMB2_Query_Info_Request,
219 ]:
220 # [MS-SMB2] 3.1.5.2
221 # "For READ, WRITE, IOCTL, and QUERY_DIRECTORY requests"
222 # "CHANGE_NOTIFY, QUERY_INFO, or SET_INFO"
223 if typ == SMB2_Read_Request:
224 Length = pkt.payload.Length
225 elif typ == SMB2_Write_Request:
226 Length = len(pkt.payload.Data)
227 elif typ == SMB2_IOCTL_Request:
228 # [MS-SMB2] 3.3.5.15
229 try:
230 Length = max(
231 len(pkt.payload.Input), pkt.payload.MaxOutputResponse
232 )
233 except AttributeError:
234 Length = 0
235 elif typ in [
236 SMB2_Query_Directory_Request,
237 SMB2_Change_Notify_Request,
238 SMB2_Query_Info_Request,
239 ]:
240 Length = pkt.payload.OutputBufferLength
241 else:
242 raise RuntimeError("impossible case")
243 pkt.CreditCharge = 1 + (Length - 1) // 65536
244 else:
245 # "For all other requests, the client MUST set CreditCharge to 1"
246 pkt.CreditCharge = 1
247 # Keep track of our credits
248 self.CurrentCreditCount -= pkt.CreditCharge
249 # [MS-SMB2] note <110>
250 # "The Windows-based client will request credits up to a configurable
251 # maximum of 128 by default."
252 pkt.CreditRequest = max(self.MaxCreditCount - self.CurrentCreditCount, 0)
253 # Get first available message ID: [MS-SMB2] 3.2.4.1.3 and 3.2.4.1.5
254 pkt.MID = self.SequenceWindow[0]
255 return super(SMB_Client, self).send(pkt)
256
257 @ATMT.state(initial=1)
258 def BEGIN(self):
259 pass
260
261 @ATMT.condition(BEGIN)
262 def continue_smb2(self):
263 if self.SMB2: # Directly started in SMB2
264 self.smb_header = DirectTCP() / SMB2_Header(PID=0xFEFF)
265 raise self.SMB2_NEGOTIATE()
266
267 @ATMT.condition(BEGIN, prio=1)
268 def send_negotiate(self):
269 raise self.SENT_NEGOTIATE()
270
271 @ATMT.action(send_negotiate)
272 def on_negotiate(self):
273 # [MS-SMB2] sect 3.2.4.2.2.1 - Multi-Protocol Negotiate
274 self.smb_header = DirectTCP() / SMB_Header(
275 Flags2=(
276 "LONG_NAMES+EAS+NT_STATUS+UNICODE+"
277 "SMB_SECURITY_SIGNATURE+EXTENDED_SECURITY"
278 ),
279 TID=0xFFFF,
280 PIDLow=0xFEFF,
281 UID=0,
282 MID=0,
283 )
284 if self.EXTENDED_SECURITY:
285 self.smb_header.Flags2 += "EXTENDED_SECURITY"
286 pkt = self.smb_header.copy() / SMBNegotiate_Request(
287 Dialects=[
288 SMB_Dialect(DialectString=x)
289 for x in [
290 "PC NETWORK PROGRAM 1.0",
291 "LANMAN1.0",
292 "Windows for Workgroups 3.1a",
293 "LM1.2X002",
294 "LANMAN2.1",
295 "NT LM 0.12",
296 ]
297 + (["SMB 2.002", "SMB 2.???"] if not self.USE_SMB1 else [])
298 ],
299 )
300 if not self.EXTENDED_SECURITY:
301 pkt.Flags2 -= "EXTENDED_SECURITY"
302 pkt[SMB_Header].Flags2 = (
303 pkt[SMB_Header].Flags2
304 - "SMB_SECURITY_SIGNATURE"
305 + "SMB_SECURITY_SIGNATURE_REQUIRED+IS_LONG_NAME"
306 )
307 self.send(pkt)
308
309 @ATMT.state()
310 def SENT_NEGOTIATE(self):
311 pass
312
313 @ATMT.state()
314 def SMB2_NEGOTIATE(self):
315 pass
316
317 @ATMT.condition(SMB2_NEGOTIATE)
318 def send_negotiate_smb2(self):
319 raise self.SENT_NEGOTIATE()
320
321 @ATMT.action(send_negotiate_smb2)
322 def on_negotiate_smb2(self):
323 # [MS-SMB2] sect 3.2.4.2.2.2 - SMB2-Only Negotiate
324 pkt = self.smb_header.copy() / SMB2_Negotiate_Protocol_Request(
325 Dialects=self.DIALECTS,
326 SecurityMode=(
327 "SIGNING_ENABLED+SIGNING_REQUIRED"
328 if self.session.SigningRequired
329 else "SIGNING_ENABLED"
330 ),
331 )
332 if self.MAX_DIALECT >= 0x0210:
333 # "If the client implements the SMB 2.1 or SMB 3.x dialect, ClientGuid
334 # MUST be set to the global ClientGuid value"
335 pkt.ClientGUID = self.GUID
336 # Capabilities: same as [MS-SMB2] 3.3.5.4
337 self.NegotiateCapabilities = "+".join(
338 [
339 "DFS",
340 "LEASING",
341 "LARGE_MTU",
342 ]
343 )
344 if self.MAX_DIALECT >= 0x0300:
345 # "if Connection.Dialect belongs to the SMB 3.x dialect family ..."
346 self.NegotiateCapabilities += "+" + "+".join(
347 [
348 "MULTI_CHANNEL",
349 "PERSISTENT_HANDLES",
350 "DIRECTORY_LEASING",
351 "ENCRYPTION",
352 ]
353 )
354 if self.MAX_DIALECT >= 0x0311:
355 # "If the client implements the SMB 3.1.1 dialect, it MUST do"
356 pkt.NegotiateContexts = [
357 SMB2_Negotiate_Context()
358 / SMB2_Preauth_Integrity_Capabilities(
359 # As for today, no other hash algorithm is described by the spec
360 HashAlgorithms=["SHA-512"],
361 Salt=self.session.Salt,
362 ),
363 SMB2_Negotiate_Context()
364 / SMB2_Encryption_Capabilities(
365 Ciphers=self.session.SupportedCipherIds,
366 ),
367 # TODO support compression and RDMA
368 SMB2_Negotiate_Context()
369 / SMB2_Netname_Negotiate_Context_ID(
370 NetName=self.HOST,
371 ),
372 SMB2_Negotiate_Context()
373 / SMB2_Signing_Capabilities(
374 SigningAlgorithms=self.session.SupportedSigningAlgorithmIds,
375 ),
376 ]
377 pkt.Capabilities = self.NegotiateCapabilities
378 # Send
379 self.send(pkt)
380 # If required, compute sessions
381 self.session.computeSMBConnectionPreauth(
382 bytes(pkt[SMB2_Header]), # nego request
383 )
384
385 @ATMT.receive_condition(SENT_NEGOTIATE)
386 def receive_negotiate_response(self, pkt):
387 if (
388 SMBNegotiate_Response_Extended_Security in pkt
389 or SMB2_Negotiate_Protocol_Response in pkt
390 ):
391 # Extended SMB1 / SMB2
392 try:
393 ssp_blob = pkt.SecurityBlob # eventually SPNEGO server initiation
394 except AttributeError:
395 ssp_blob = None
396 if (
397 SMB2_Negotiate_Protocol_Response in pkt
398 and pkt.DialectRevision & 0xFF == 0xFF
399 ):
400 # Version is SMB X.???
401 # [MS-SMB2] 3.2.5.2
402 # If the DialectRevision field in the SMB2 NEGOTIATE Response is
403 # 0x02FF ... the client MUST allocate sequence number 1 from
404 # Connection.SequenceWindow, and MUST set MessageId field of the
405 # SMB2 header to 1.
406 self.SequenceWindow = (1, 1)
407 self.smb_header = DirectTCP() / SMB2_Header(PID=0xFEFF, MID=1)
408 self.SMB2 = True # We're now using SMB2 to talk to the server
409 raise self.SMB2_NEGOTIATE()
410 else:
411 if SMB2_Negotiate_Protocol_Response in pkt:
412 # SMB2 was negotiated !
413 self.session.Dialect = pkt.DialectRevision
414 # If required, compute sessions
415 self.session.computeSMBConnectionPreauth(
416 bytes(pkt[SMB2_Header]), # nego response
417 )
418 # Process max sizes
419 self.session.MaxReadSize = pkt.MaxReadSize
420 self.session.MaxTransactionSize = pkt.MaxTransactionSize
421 self.session.MaxWriteSize = pkt.MaxWriteSize
422 # Process SecurityMode
423 if pkt.SecurityMode.SIGNING_REQUIRED:
424 self.session.SigningRequired = True
425 # Process capabilities
426 if self.session.Dialect >= 0x0300:
427 self.session.SupportsEncryption = pkt.Capabilities.ENCRYPTION
428 # Process NegotiateContext
429 if self.session.Dialect >= 0x0311 and pkt.NegotiateContextsCount:
430 for ngctx in pkt.NegotiateContexts:
431 if ngctx.ContextType == 0x0002:
432 # SMB2_ENCRYPTION_CAPABILITIES
433 if ngctx.Ciphers and ngctx.Ciphers[0] != 0:
434 self.session.CipherId = SMB2_ENCRYPTION_CIPHERS[
435 ngctx.Ciphers[0]
436 ]
437 self.session.SupportsEncryption = True
438 elif ngctx.ContextType == 0x0008:
439 # SMB2_SIGNING_CAPABILITIES
440 self.session.SigningAlgorithmId = (
441 SMB2_SIGNING_ALGORITHMS[ngctx.SigningAlgorithms[0]]
442 )
443 if self.REQUIRE_ENCRYPTION and not self.session.SupportsEncryption:
444 self.ErrorStatus = "NEGOTIATE FAILURE: encryption."
445 raise self.NEGO_FAILED()
446 if self.SMB2:
447 self.update_smbheader(pkt)
448 raise self.NEGOTIATED(ssp_blob)
449 elif SMBNegotiate_Response_Security in pkt:
450 # Non-extended SMB1
451 # Never tested. FIXME. probably broken
452 raise self.NEGOTIATED(pkt.Challenge)
453
454 @ATMT.state(final=1)
455 def NEGO_FAILED(self):
456 self.smb_sock_ready.set()
457
458 @ATMT.state()
459 def NEGOTIATED(self, ssp_blob=None):
460 # Negotiated ! We now know the Dialect
461 if self.session.Dialect > 0x0202:
462 # [MS-SMB2] sect 3.2.5.1.4
463 self.smb_header.CreditRequest = 1
464 # Begin session establishment
465 ssp_tuple = self.session.ssp.GSS_Init_sec_context(
466 self.session.sspcontext,
467 input_token=ssp_blob,
468 target_name="cifs/" + self.HOST if self.HOST else None,
469 req_flags=(
470 GSS_C_FLAGS.GSS_C_MUTUAL_FLAG
471 | (GSS_C_FLAGS.GSS_C_INTEG_FLAG if self.session.SigningRequired else 0)
472 ),
473 )
474 return ssp_tuple
475
476 def update_smbheader(self, pkt):
477 """
478 Called when receiving a SMB2 packet to update the current smb_header
479 """
480 # Some values should not be updated when ASYNC
481 if not pkt.Flags.SMB2_FLAGS_ASYNC_COMMAND:
482 # Update IDs
483 self.smb_header.SessionId = pkt.SessionId
484 self.smb_header.TID = pkt.TID
485 self.smb_header.PID = pkt.PID
486 # Update credits
487 self.CurrentCreditCount += pkt.CreditRequest
488 # [MS-SMB2] 3.2.5.1.4
489 self.SequenceWindow = (
490 self.SequenceWindow[0] + max(pkt.CreditCharge, 1),
491 self.SequenceWindow[1] + pkt.CreditRequest,
492 )
493
494 # DEV: add a condition on NEGOTIATED with prio=0
495
496 @ATMT.condition(NEGOTIATED, prio=1)
497 def should_retry_without_blob(self, ssp_tuple):
498 _, _, status = ssp_tuple
499 if status == GSS_S_DEFECTIVE_TOKEN:
500 # Token was defective. This could be that we passed a SPNEGO initial token
501 # to a NTLM SSP (not using SPNEGO). Retry using no input blob
502 raise self.NEGOTIATED()
503
504 @ATMT.condition(NEGOTIATED, prio=2)
505 def should_send_session_setup_request(self, ssp_tuple):
506 _, _, status = ssp_tuple
507 if status not in [GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED]:
508 raise ValueError(
509 "Internal error: the SSP completed with error: %s" % status
510 )
511 raise self.SENT_SESSION_REQUEST().action_parameters(ssp_tuple)
512
513 @ATMT.state()
514 def SENT_SESSION_REQUEST(self):
515 pass
516
517 @ATMT.action(should_send_session_setup_request)
518 def send_setup_session_request(self, ssp_tuple):
519 self.session.sspcontext, token, status = ssp_tuple
520 if self.SMB2 and status == GSS_S_CONTINUE_NEEDED:
521 # New session: force 0
522 self.SessionId = 0
523 if self.SMB2 or self.EXTENDED_SECURITY:
524 # SMB1 extended / SMB2
525 if self.SMB2:
526 # SMB2
527 pkt = self.smb_header.copy() / SMB2_Session_Setup_Request(
528 Capabilities="DFS",
529 SecurityMode=(
530 "SIGNING_ENABLED+SIGNING_REQUIRED"
531 if self.session.SigningRequired
532 else "SIGNING_ENABLED"
533 ),
534 )
535 else:
536 # SMB1 extended
537 pkt = (
538 self.smb_header.copy()
539 / SMBSession_Setup_AndX_Request_Extended_Security(
540 ServerCapabilities=(
541 "UNICODE+NT_SMBS+STATUS32+LEVEL_II_OPLOCKS+"
542 "DYNAMIC_REAUTH+EXTENDED_SECURITY"
543 ),
544 NativeOS=b"",
545 NativeLanMan=b"",
546 )
547 )
548 pkt.SecurityBlob = token
549 else:
550 # Non-extended security.
551 pkt = self.smb_header.copy() / SMBSession_Setup_AndX_Request(
552 ServerCapabilities="UNICODE+NT_SMBS+STATUS32+LEVEL_II_OPLOCKS",
553 NativeOS=b"",
554 NativeLanMan=b"",
555 OEMPassword=b"\0" * 24,
556 UnicodePassword=token,
557 )
558 self.send(pkt)
559 if self.SMB2:
560 # If required, compute sessions
561 self.session.computeSMBSessionPreauth(
562 bytes(pkt[SMB2_Header]), # session request
563 )
564
565 @ATMT.receive_condition(SENT_SESSION_REQUEST)
566 def receive_session_setup_response(self, pkt):
567 if (
568 SMBSession_Null in pkt
569 or SMBSession_Setup_AndX_Response_Extended_Security in pkt
570 or SMBSession_Setup_AndX_Response in pkt
571 ):
572 # SMB1
573 if SMBSession_Null in pkt:
574 # Likely an error
575 raise self.NEGOTIATED()
576 # Logging
577 if pkt.Status != 0 and pkt.Status != 0xC0000016:
578 # Not SUCCESS nor MORE_PROCESSING_REQUIRED: log
579 self.ErrorStatus = pkt.sprintf("%SMB2_Header.Status%")
580 self.debug(
581 lvl=1,
582 msg=conf.color_theme.red(
583 pkt.sprintf("SMB Session Setup Response: %SMB2_Header.Status%")
584 ),
585 )
586 if self.SMB2:
587 self.update_smbheader(pkt)
588 # Cases depending on the response packet
589 if (
590 SMBSession_Setup_AndX_Response_Extended_Security in pkt
591 or SMB2_Session_Setup_Response in pkt
592 ):
593 # The server assigns us a SessionId
594 self.smb_header.SessionId = pkt.SessionId
595 # SMB1 extended / SMB2
596 if pkt.Status == 0: # Authenticated
597 if SMB2_Session_Setup_Response in pkt:
598 # [MS-SMB2] sect 3.2.5.3.1
599 if pkt.SessionFlags.IS_GUEST:
600 if self.REQUIRE_SIGNATURE or self.REQUIRE_ENCRYPTION:
601 self.ErrorStatus = "Guest session lacks required protection"
602 raise self.AUTH_FAILED()
603 # "If the security subsystem indicates that the session
604 # was established by a guest user, Session.SigningRequired
605 # MUST be set to FALSE and Session.IsGuest MUST be set to TRUE."
606 self.session.IsGuest = True
607 self.session.SigningRequired = False
608 elif self.session.Dialect >= 0x0300:
609 if pkt.SessionFlags.ENCRYPT_DATA or self.REQUIRE_ENCRYPTION:
610 self.session.EncryptData = True
611 self.session.SigningRequired = False
612 raise self.AUTHENTICATED(pkt.SecurityBlob)
613 else:
614 if SMB2_Header in pkt:
615 # If required, compute sessions
616 self.session.computeSMBSessionPreauth(
617 bytes(pkt[SMB2_Header]), # session response
618 )
619 # Ongoing auth
620 raise self.NEGOTIATED(pkt.SecurityBlob)
621 elif SMBSession_Setup_AndX_Response_Extended_Security in pkt:
622 # SMB1 non-extended
623 pass
624 elif SMB2_Error_Response in pkt:
625 # Authentication failure
626 self.session.sspcontext.clifailure()
627 # Reset Session preauth (SMB 3.1.1)
628 self.session.SessionPreauthIntegrityHashValue = None
629 if not self.RETRY:
630 raise self.AUTH_FAILED()
631 self.debug(lvl=2, msg="RETRY: %s" % self.RETRY)
632 self.RETRY -= 1
633 raise self.NEGOTIATED()
634
635 @ATMT.state(final=1)
636 def AUTH_FAILED(self):
637 self.smb_sock_ready.set()
638
639 @ATMT.state()
640 def AUTHENTICATED(self, ssp_blob=None):
641 self.session.sspcontext, _, status = self.session.ssp.GSS_Init_sec_context(
642 self.session.sspcontext,
643 input_token=ssp_blob,
644 target_name="cifs/" + self.HOST if self.HOST else None,
645 )
646 if status != GSS_S_COMPLETE:
647 raise ValueError(
648 "Internal error: the SSP completed with error: %s" % status
649 )
650 # Authentication was successful
651 self.session.computeSMBSessionKeys(IsClient=True)
652
653 # DEV: add a condition on AUTHENTICATED with prio=0
654
655 @ATMT.condition(AUTHENTICATED, prio=1)
656 def authenticated_post_actions(self):
657 raise self.SOCKET_BIND()
658
659 # Plain SMB Socket
660
661 @ATMT.state()
662 def SOCKET_BIND(self):
663 self.smb_sock_ready.set()
664
665 @ATMT.condition(SOCKET_BIND)
666 def start_smb_socket(self):
667 raise self.SOCKET_MODE_SMB()
668
669 @ATMT.state()
670 def SOCKET_MODE_SMB(self):
671 pass
672
673 @ATMT.receive_condition(SOCKET_MODE_SMB)
674 def incoming_data_received_smb(self, pkt):
675 raise self.SOCKET_MODE_SMB().action_parameters(pkt)
676
677 @ATMT.action(incoming_data_received_smb)
678 def receive_data_smb(self, pkt):
679 resp = pkt[SMB2_Header].payload
680 if isinstance(resp, SMB2_Error_Response):
681 if pkt.Status == 0x00000103: # STATUS_PENDING
682 # answer is coming later.. just wait...
683 return
684 if pkt.Status == 0x0000010B: # STATUS_NOTIFY_CLEANUP
685 # this is a notify cleanup. ignore
686 return
687 self.update_smbheader(pkt)
688 # Add the status to the response as metadata
689 resp.NTStatus = pkt.sprintf("%SMB2_Header.Status%")
690 self.oi.smbpipe.send(resp)
691
692 @ATMT.ioevent(SOCKET_MODE_SMB, name="smbpipe", as_supersocket="smblink")
693 def outgoing_data_received_smb(self, fd):
694 raise self.SOCKET_MODE_SMB().action_parameters(fd.recv())
695
696 @ATMT.action(outgoing_data_received_smb)
697 def send_data(self, d):
698 self.send(self.smb_header.copy() / d)
699
700
701class SMB_SOCKET(SuperSocket):
702 """
703 Mid-level wrapper over SMB_Client.smblink that provides some basic SMB
704 client functions, such as tree connect, directory query, etc.
705 """
706
707 def __init__(self, smbsock, use_ioctl=True, timeout=3):
708 self.ins = smbsock
709 self.timeout = timeout
710 if not self.ins.atmt.smb_sock_ready.wait(timeout=timeout):
711 # If we have a SSP, tell it we failed.
712 if self.session.sspcontext:
713 self.session.sspcontext.clifailure()
714 raise TimeoutError(
715 "The SMB handshake timed out ! (enable debug=1 for logs)"
716 )
717 if self.ins.atmt.ErrorStatus:
718 raise Scapy_Exception(
719 "SMB Session Setup failed: %s" % self.ins.atmt.ErrorStatus
720 )
721
722 @classmethod
723 def from_tcpsock(cls, sock, **kwargs):
724 """
725 Wraps the tcp socket in a SMB_Client.smblink first, then into the
726 SMB_SOCKET/SMB_RPC_SOCKET
727 """
728 return cls(
729 use_ioctl=kwargs.pop("use_ioctl", True),
730 timeout=kwargs.pop("timeout", 3),
731 smbsock=SMB_Client.from_tcpsock(sock, **kwargs),
732 )
733
734 @property
735 def session(self):
736 return self.ins.atmt.session
737
738 def set_TID(self, TID):
739 """
740 Set the TID (Tree ID).
741 This can be called before sending a packet
742 """
743 self.ins.atmt.smb_header.TID = TID
744
745 def get_TID(self):
746 """
747 Get the current TID from the underlying socket
748 """
749 return self.ins.atmt.smb_header.TID
750
751 def tree_connect(self, name):
752 """
753 Send a TreeConnect request
754 """
755 resp = self.ins.sr1(
756 SMB2_Tree_Connect_Request(
757 Buffer=[
758 (
759 "Path",
760 "\\\\%s\\%s"
761 % (
762 self.session.sspcontext.ServerHostname,
763 name,
764 ),
765 )
766 ]
767 ),
768 chainCC=True,
769 verbose=False,
770 timeout=self.timeout,
771 )
772 if not resp:
773 raise ValueError("TreeConnect timed out !")
774 if SMB2_Tree_Connect_Response not in resp:
775 raise ValueError("Failed TreeConnect ! %s" % resp.NTStatus)
776 # [MS-SMB2] sect 3.2.5.5
777 if self.session.Dialect >= 0x0300:
778 if resp.ShareFlags.ENCRYPT_DATA and self.session.SupportsEncryption:
779 self.session.TreeEncryptData = True
780 else:
781 self.session.TreeEncryptData = False
782 return self.get_TID()
783
784 def tree_disconnect(self):
785 """
786 Send a TreeDisconnect request
787 """
788 resp = self.ins.sr1(
789 SMB2_Tree_Disconnect_Request(),
790 verbose=False,
791 timeout=self.timeout,
792 chainCC=True,
793 )
794 if not resp:
795 raise ValueError("TreeDisconnect timed out !")
796 if SMB2_Tree_Disconnect_Response not in resp:
797 raise ValueError("Failed TreeDisconnect ! %s" % resp.NTStatus)
798
799 def create_request(
800 self,
801 name,
802 mode="r",
803 type="pipe",
804 extra_create_options=[],
805 extra_desired_access=[],
806 ):
807 """
808 Open a file/pipe by its name
809
810 :param name: the name of the file or named pipe. e.g. 'srvsvc'
811 """
812 ShareAccess = []
813 DesiredAccess = []
814 # Common params depending on the access
815 if "r" in mode:
816 ShareAccess.append("FILE_SHARE_READ")
817 DesiredAccess.extend(["FILE_READ_DATA", "FILE_READ_ATTRIBUTES"])
818 if "w" in mode:
819 ShareAccess.append("FILE_SHARE_WRITE")
820 DesiredAccess.extend(["FILE_WRITE_DATA", "FILE_WRITE_ATTRIBUTES"])
821 if "d" in mode:
822 ShareAccess.append("FILE_SHARE_DELETE")
823 # Params depending on the type
824 FileAttributes = []
825 CreateOptions = []
826 CreateContexts = []
827 CreateDisposition = "FILE_OPEN"
828 if type == "folder":
829 FileAttributes.append("FILE_ATTRIBUTE_DIRECTORY")
830 CreateOptions.append("FILE_DIRECTORY_FILE")
831 elif type in ["file", "pipe"]:
832 CreateOptions = ["FILE_NON_DIRECTORY_FILE"]
833 if "r" in mode:
834 DesiredAccess.extend(["FILE_READ_EA", "READ_CONTROL", "SYNCHRONIZE"])
835 if "w" in mode:
836 CreateDisposition = "FILE_OVERWRITE_IF"
837 DesiredAccess.append("FILE_WRITE_EA")
838 if "d" in mode:
839 DesiredAccess.append("DELETE")
840 CreateOptions.append("FILE_DELETE_ON_CLOSE")
841 if type == "file":
842 FileAttributes.append("FILE_ATTRIBUTE_NORMAL")
843 elif type:
844 raise ValueError("Unknown type: %s" % type)
845 # [MS-SMB2] 3.2.4.3.8
846 RequestedOplockLevel = 0
847 if self.session.Dialect >= 0x0300:
848 RequestedOplockLevel = "SMB2_OPLOCK_LEVEL_LEASE"
849 elif self.session.Dialect >= 0x0210 and type == "file":
850 RequestedOplockLevel = "SMB2_OPLOCK_LEVEL_LEASE"
851 # SMB 3.X
852 if self.session.Dialect >= 0x0300 and type in ["file", "folder"]:
853 CreateContexts.extend(
854 [
855 # [SMB2] sect 3.2.4.3.5
856 SMB2_Create_Context(
857 Name=b"DH2Q",
858 Data=SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2(
859 CreateGuid=RandUUID()._fix()
860 ),
861 ),
862 # [SMB2] sect 3.2.4.3.9
863 SMB2_Create_Context(
864 Name=b"MxAc",
865 ),
866 # [SMB2] sect 3.2.4.3.10
867 SMB2_Create_Context(
868 Name=b"QFid",
869 ),
870 # [SMB2] sect 3.2.4.3.8
871 SMB2_Create_Context(
872 Name=b"RqLs",
873 Data=SMB2_CREATE_REQUEST_LEASE_V2(LeaseKey=RandUUID()._fix()),
874 ),
875 ]
876 )
877 elif self.session.Dialect == 0x0210 and type == "file":
878 CreateContexts.extend(
879 [
880 # [SMB2] sect 3.2.4.3.8
881 SMB2_Create_Context(
882 Name=b"RqLs",
883 Data=SMB2_CREATE_REQUEST_LEASE(LeaseKey=RandUUID()._fix()),
884 ),
885 ]
886 )
887 # Extra options
888 if extra_create_options:
889 CreateOptions.extend(extra_create_options)
890 if extra_desired_access:
891 DesiredAccess.extend(extra_desired_access)
892 # Request
893 resp = self.ins.sr1(
894 SMB2_Create_Request(
895 ImpersonationLevel="Impersonation",
896 DesiredAccess="+".join(DesiredAccess),
897 CreateDisposition=CreateDisposition,
898 CreateOptions="+".join(CreateOptions),
899 ShareAccess="+".join(ShareAccess),
900 FileAttributes="+".join(FileAttributes),
901 CreateContexts=CreateContexts,
902 RequestedOplockLevel=RequestedOplockLevel,
903 Name=name,
904 ),
905 chainCC=True,
906 verbose=0,
907 timeout=self.timeout,
908 )
909 if not resp:
910 raise ValueError("CreateRequest timed out !")
911 if SMB2_Create_Response not in resp:
912 raise ValueError("Failed CreateRequest ! %s" % resp.NTStatus)
913 return resp[SMB2_Create_Response].FileId
914
915 def close_request(self, FileId):
916 """
917 Close the FileId
918 """
919 pkt = SMB2_Close_Request(FileId=FileId)
920 resp = self.ins.sr1(pkt, verbose=0, timeout=self.timeout, chainCC=True)
921 if not resp:
922 raise ValueError("CloseRequest timed out !")
923 if SMB2_Close_Response not in resp:
924 raise ValueError("Failed CloseRequest ! %s" % resp.NTStatus)
925
926 def read_request(self, FileId, Length, Offset=0):
927 """
928 Read request
929 """
930 resp = self.ins.sr1(
931 SMB2_Read_Request(
932 FileId=FileId,
933 Length=Length,
934 Offset=Offset,
935 ),
936 chainCC=True,
937 verbose=0,
938 timeout=self.timeout * 10,
939 )
940 if not resp:
941 raise ValueError("ReadRequest timed out !")
942 if SMB2_Read_Response not in resp:
943 raise ValueError("Failed ReadRequest ! %s" % resp.NTStatus)
944 return resp.Data
945
946 def write_request(self, Data, FileId, Offset=0):
947 """
948 Write request
949 """
950 resp = self.ins.sr1(
951 SMB2_Write_Request(
952 FileId=FileId,
953 Data=Data,
954 Offset=Offset,
955 ),
956 chainCC=True,
957 verbose=0,
958 timeout=self.timeout * 10,
959 )
960 if not resp:
961 raise ValueError("WriteRequest timed out !")
962 if SMB2_Write_Response not in resp:
963 raise ValueError("Failed WriteRequest ! %s" % resp.NTStatus)
964 return resp.Count
965
966 def query_directory(self, FileId, FileName="*"):
967 """
968 Query the Directory with FileId
969 """
970 results = []
971 Flags = "SMB2_RESTART_SCANS"
972 while True:
973 pkt = SMB2_Query_Directory_Request(
974 FileInformationClass="FileIdBothDirectoryInformation",
975 FileId=FileId,
976 FileName=FileName,
977 Flags=Flags,
978 )
979 resp = self.ins.sr1(pkt, verbose=0, timeout=self.timeout, chainCC=True)
980 Flags = 0 # only the first one is RESTART_SCANS
981 if not resp:
982 raise ValueError("QueryDirectory timed out !")
983 if SMB2_Error_Response in resp:
984 break
985 elif SMB2_Query_Directory_Response not in resp:
986 raise ValueError("Failed QueryDirectory ! %s" % resp.NTStatus)
987 res = FileIdBothDirectoryInformation(resp.Output)
988 results.extend(
989 [
990 (
991 x.FileName,
992 x.FileAttributes,
993 x.EndOfFile,
994 x.LastWriteTime,
995 )
996 for x in res.files
997 ]
998 )
999 return results
1000
1001 def query_info(self, FileId, InfoType, FileInfoClass, AdditionalInformation=0):
1002 """
1003 Query the Info
1004 """
1005 pkt = SMB2_Query_Info_Request(
1006 InfoType=InfoType,
1007 FileInfoClass=FileInfoClass,
1008 OutputBufferLength=65535,
1009 FileId=FileId,
1010 AdditionalInformation=AdditionalInformation,
1011 )
1012 resp = self.ins.sr1(pkt, verbose=0, timeout=self.timeout, chainCC=True)
1013 if not resp:
1014 raise ValueError("QueryInfo timed out !")
1015 if SMB2_Query_Info_Response not in resp:
1016 raise ValueError("Failed QueryInfo ! %s" % resp.NTStatus)
1017 return resp.Output
1018
1019 def ioctl(self, CtlCode, Flags="SMB2_0_IOCTL_IS_FSCTL", Input=None):
1020 """
1021 Perform an IOCTL
1022 """
1023 pkt = SMB2_IOCTL_Request(
1024 FileId=SMB2_FILEID(
1025 Persistent=0xFFFFFFFFFFFFFFFF, Volatile=0xFFFFFFFFFFFFFFFF
1026 ),
1027 Flags=Flags,
1028 CtlCode=CtlCode,
1029 )
1030 if Input is not None:
1031 pkt.Input = bytes(Input)
1032 resp = self.ins.sr1(pkt, verbose=0, chainCC=True)
1033 if SMB2_IOCTL_Response not in resp:
1034 raise ValueError("Failed reading IOCTL_Response ! %s" % resp.NTStatus)
1035 return resp.Output
1036
1037 def changenotify(self, FileId):
1038 """
1039 Register change notify
1040 """
1041 pkt = SMB2_Change_Notify_Request(
1042 Flags="SMB2_WATCH_TREE",
1043 OutputBufferLength=65535,
1044 FileId=FileId,
1045 CompletionFilter=0x0FFF,
1046 )
1047 # we can wait forever, not a problem in this one
1048 resp = self.ins.sr1(pkt, verbose=0, chainCC=True)
1049 if SMB2_Change_Notify_Response not in resp:
1050 raise ValueError("Failed ChangeNotify ! %s" % resp.NTStatus)
1051 return resp.Output
1052
1053
1054class SMB_RPC_SOCKET(ObjectPipe, SMB_SOCKET):
1055 """
1056 Extends SMB_SOCKET (which is a wrapper over SMB_Client.smblink) to send
1057 DCE/RPC messages (bind, reqs, etc.)
1058
1059 This is usable as a normal SuperSocket (sr1, etc.) and performs the
1060 wrapping of the DCE/RPC messages into SMB2_Write/Read packets.
1061 """
1062
1063 def __init__(self, smbsock, use_ioctl=True, timeout=3):
1064 self.use_ioctl = use_ioctl
1065 ObjectPipe.__init__(self, "SMB_RPC_SOCKET")
1066 SMB_SOCKET.__init__(self, smbsock, timeout=timeout)
1067
1068 def open_pipe(self, name):
1069 self.PipeFileId = self.create_request(name, mode="rw", type="pipe")
1070
1071 def close_pipe(self):
1072 self.close_request(self.PipeFileId)
1073 self.PipeFileId = None
1074
1075 def send(self, x, is_sr1=True):
1076 # Reminder: this class is an ObjectPipe ! It doesn't act as a real socket
1077 # but just a queue. When someone calls the "send" function, they pipe
1078 # some data that we must send, and tell us if they expect an answer through
1079 # the is_sr1 flag.
1080
1081 # Detect if DCE/RPC is fragmented. Then we must use Read/Write
1082 is_frag = x.pfc_flags & 3 != 3
1083
1084 if self.use_ioctl and is_sr1 and not is_frag and self.session.Dialect >= 0x0210:
1085 # Use IOCTLRequest
1086 pkt = SMB2_IOCTL_Request(
1087 FileId=self.PipeFileId,
1088 Flags="SMB2_0_IOCTL_IS_FSCTL",
1089 CtlCode="FSCTL_PIPE_TRANSCEIVE",
1090 )
1091 pkt.Input = bytes(x)
1092 resp = self.ins.sr1(pkt, verbose=0, chainCC=True)
1093 if SMB2_IOCTL_Response not in resp:
1094 raise ValueError("Failed reading IOCTL_Response ! %s" % resp.NTStatus)
1095 data = bytes(resp.Output)
1096 super(SMB_RPC_SOCKET, self).send(data)
1097
1098 # Handle BUFFER_OVERFLOW (big DCE/RPC response)
1099 while resp.NTStatus == "STATUS_BUFFER_OVERFLOW" or data[3] & 2 != 2:
1100 # Retrieve DCE/RPC full size
1101 resp = self.ins.sr1(
1102 SMB2_Read_Request(
1103 FileId=self.PipeFileId,
1104 ),
1105 verbose=0,
1106 )
1107 data = resp.Data
1108 super(SMB_RPC_SOCKET, self).send(data)
1109 else:
1110 # Use WriteRequest/ReadRequest
1111 pkt = SMB2_Write_Request(
1112 FileId=self.PipeFileId,
1113 )
1114 pkt.Data = bytes(x)
1115 # We send the Write Request
1116 resp = self.ins.sr1(pkt, verbose=0)
1117 if SMB2_Write_Response not in resp:
1118 raise ValueError("Failed sending WriteResponse ! %s" % resp.NTStatus)
1119
1120 # We may not be expecting an answer
1121 if not is_sr1:
1122 return
1123
1124 # If fragmented, only read if it's the last.
1125 if is_frag and not x.pfc_flags.PFC_LAST_FRAG:
1126 return
1127
1128 # We send a Read Request afterwards
1129 resp = self.ins.sr1(
1130 SMB2_Read_Request(
1131 FileId=self.PipeFileId,
1132 ),
1133 verbose=0,
1134 )
1135 if SMB2_Read_Response not in resp:
1136 raise ValueError("Failed reading ReadResponse ! %s" % resp.NTStatus)
1137 super(SMB_RPC_SOCKET, self).send(resp.Data)
1138 # Handle fragmented response
1139 while resp.Data[3] & 2 != 2: # PFC_LAST_FRAG not set
1140 # Retrieve DCE/RPC full size
1141 resp = self.ins.sr1(
1142 SMB2_Read_Request(
1143 FileId=self.PipeFileId,
1144 ),
1145 verbose=0,
1146 )
1147 super(SMB_RPC_SOCKET, self).send(resp.Data)
1148
1149 def close(self):
1150 SMB_SOCKET.close(self)
1151 ObjectPipe.close(self)
1152
1153
1154@conf.commands.register
1155class smbclient(CLIUtil):
1156 r"""
1157 A simple SMB client CLI powered by Scapy
1158
1159 :param target: can be a hostname, the IPv4 or the IPv6 to connect to
1160 :param UPN: the upn to use (DOMAIN/USER, DOMAIN\USER, USER@DOMAIN or USER)
1161 :param guest: use guest mode (over NTLM)
1162 :param ssp: if provided, use this SSP for auth.
1163 :param kerberos_required: require kerberos
1164 :param port: the TCP port. default 445
1165 :param password: if provided, used for auth
1166 :param HashNt: if provided, used for auth (NTLM)
1167 :param HashAes256Sha96: if provided, used for auth (Kerberos)
1168 :param HashAes128Sha96: if provided, used for auth (Kerberos)
1169 :param use_krb5ccname: (bool) if true, the KRB5CCNAME environment variable will
1170 be used if available.
1171 :param use_winssp: (bool) (only works on Windows). Use implicit authentication
1172 through WinSSP.
1173 :param ST: if provided, the service ticket to use (Kerberos)
1174 :param KEY: if provided, the session key associated to the ticket (Kerberos)
1175 :param cli: CLI mode (default True). False to use for scripting
1176
1177 Some additional SMB parameters are available under help(SMB_Client). Some of
1178 them include the following:
1179
1180 :param REQUIRE_ENCRYPTION: requires encryption.
1181 """
1182
1183 def __init__(
1184 self,
1185 target: str,
1186 UPN: str = None,
1187 password: str = None,
1188 guest: bool = False,
1189 kerberos_required: bool = False,
1190 HashNt: bytes = None,
1191 HashAes256Sha96: bytes = None,
1192 HashAes128Sha96: bytes = None,
1193 use_krb5ccname: bool = False,
1194 use_winssp: bool = False,
1195 port: int = 445,
1196 timeout: int = 5,
1197 debug: int = 0,
1198 ssp=None,
1199 ST=None,
1200 KEY=None,
1201 cli=True,
1202 # SMB arguments
1203 REQUIRE_ENCRYPTION=False,
1204 **kwargs,
1205 ):
1206 if cli:
1207 self._depcheck()
1208 assert (
1209 UPN or ssp or guest or use_winssp
1210 ), "Either UPN, ssp or guest must be provided !"
1211 # Do we need to build a SSP?
1212 if ssp is None:
1213 # Create the SSP (only if not guest mode)
1214 if not guest:
1215 ssp = SPNEGOSSP.from_cli_arguments(
1216 UPN=UPN,
1217 target=target,
1218 password=password,
1219 HashNt=HashNt,
1220 HashAes256Sha96=HashAes256Sha96,
1221 HashAes128Sha96=HashAes128Sha96,
1222 ST=ST,
1223 KEY=KEY,
1224 kerberos_required=kerberos_required,
1225 use_krb5ccname=use_krb5ccname,
1226 use_winssp=use_winssp,
1227 )
1228 else:
1229 # Guest mode
1230 ssp = None
1231 # Check if target is IPv4 or IPv6
1232 if ":" in target:
1233 family = socket.AF_INET6
1234 else:
1235 family = socket.AF_INET
1236 # Open socket
1237 sock = socket.socket(family, socket.SOCK_STREAM)
1238 # Configure socket for SMB:
1239 # - TCP KEEPALIVE, TCP_KEEPIDLE and TCP_KEEPINTVL. Against a Windows server this
1240 # isn't necessary, but samba kills the socket VERY fast otherwise.
1241 # - set TCP_NODELAY to disable Nagle's algorithm (we're streaming data)
1242 sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
1243 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1244 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 10)
1245 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10)
1246 # Timeout & connect
1247 sock.settimeout(timeout)
1248 if debug:
1249 print("Connecting to %s:%s" % (target, port))
1250 sock.connect((target, port))
1251 self.extra_create_options = []
1252 # Wrap with the automaton
1253 self.timeout = timeout
1254 kwargs.setdefault("HOST", target)
1255 self.sock = SMB_Client.from_tcpsock(
1256 sock,
1257 ssp=ssp,
1258 debug=debug,
1259 REQUIRE_ENCRYPTION=REQUIRE_ENCRYPTION,
1260 timeout=timeout,
1261 **kwargs,
1262 )
1263 try:
1264 # Wrap with SMB_SOCKET
1265 self.smbsock = SMB_SOCKET(self.sock, timeout=self.timeout)
1266 # Wait for either the atmt to fail, or the smb_sock_ready to timeout
1267 _t = time.time()
1268 while True:
1269 if self.sock.atmt.smb_sock_ready.is_set():
1270 # yay
1271 break
1272 if not self.sock.atmt.isrunning():
1273 status = self.sock.atmt.get("Status")
1274 raise Scapy_Exception(
1275 "%s with status %s"
1276 % (
1277 self.sock.atmt.state.state,
1278 STATUS_ERREF.get(status, hex(status)),
1279 )
1280 )
1281 if time.time() - _t > timeout:
1282 self.sock.close()
1283 raise TimeoutError("The SMB handshake timed out.")
1284 time.sleep(0.1)
1285 except Exception:
1286 # Something bad happened, end the socket/automaton
1287 self.sock.close()
1288 raise
1289
1290 # For some usages, we will also need the RPC wrapper
1291 from scapy.layers.msrpce.rpcclient import DCERPC_Client
1292
1293 self.rpcclient = DCERPC_Client.from_smblink(
1294 self.sock,
1295 ndr64=False,
1296 verb=bool(debug),
1297 )
1298 # We have a valid smb connection !
1299 print(
1300 "%s authentication successful using %s%s !"
1301 % (
1302 SMB_DIALECTS.get(
1303 self.smbsock.session.Dialect,
1304 "SMB %s" % self.smbsock.session.Dialect,
1305 ),
1306 repr(self.smbsock.session.sspcontext),
1307 " as GUEST" if self.smbsock.session.IsGuest else "",
1308 )
1309 )
1310 # Now define some variables for our CLI
1311 self.pwd = pathlib.PureWindowsPath("/")
1312 self.localpwd = pathlib.Path(".").resolve()
1313 self.current_tree = None
1314 self.ls_cache = {} # cache the listing of the current directory
1315 self.sh_cache = [] # cache the shares
1316 # Start CLI
1317 if cli:
1318 self.loop(debug=debug)
1319
1320 def ps1(self):
1321 return r"smb: \%s> " % self.normalize_path(self.pwd)
1322
1323 def close(self):
1324 print("Connection closed")
1325 self.smbsock.close()
1326
1327 def _require_share(self, silent=False):
1328 if self.current_tree is None:
1329 if not silent:
1330 print("No share selected ! Try 'shares' then 'use'.")
1331 return True
1332
1333 def collapse_path(self, path):
1334 # the amount of pathlib.wtf you need to do to resolve .. on all platforms
1335 # is ridiculous
1336 return pathlib.PureWindowsPath(os.path.normpath(path.as_posix()))
1337
1338 def normalize_path(self, path):
1339 """
1340 Normalize path for CIFS usage
1341 """
1342 return str(self.collapse_path(path)).lstrip("\\")
1343
1344 @CLIUtil.addcommand()
1345 def shares(self):
1346 """
1347 List the shares available
1348 """
1349 # Poll cache
1350 if self.sh_cache:
1351 return self.sh_cache
1352 # It's an RPC
1353 self.rpcclient.open_smbpipe("srvsvc")
1354 self.rpcclient.bind(find_dcerpc_interface("srvsvc"))
1355 req = NetrShareEnum_Request(
1356 InfoStruct=LPSHARE_ENUM_STRUCT(
1357 Level=1,
1358 ShareInfo=NDRUnion(
1359 tag=1,
1360 value=SHARE_INFO_1_CONTAINER(Buffer=None),
1361 ),
1362 ),
1363 PreferedMaximumLength=0xFFFFFFFF,
1364 ndr64=self.rpcclient.ndr64,
1365 )
1366 resp = self.rpcclient.sr1_req(req, timeout=self.timeout)
1367 self.rpcclient.close_smbpipe()
1368 if resp.status != 0:
1369 resp.show()
1370 raise ValueError("NetrShareEnum_Request failed !")
1371 results = []
1372 for share in resp.valueof("InfoStruct.ShareInfo.Buffer"):
1373 shi1_type = share.valueof("shi1_type") & 0x0FFFFFFF
1374 results.append(
1375 (
1376 share.valueof("shi1_netname").decode(),
1377 SRVSVC_SHARE_TYPES.get(shi1_type, shi1_type),
1378 share.valueof("shi1_remark").decode(),
1379 )
1380 )
1381 self.sh_cache = results # cache
1382 return results
1383
1384 @CLIUtil.addoutput(shares)
1385 def shares_output(self, results):
1386 """
1387 Print the output of 'shares'
1388 """
1389 print(pretty_list(results, [("ShareName", "ShareType", "Comment")]))
1390
1391 @CLIUtil.addcommand(mono=True)
1392 def use(self, share):
1393 """
1394 Open a share
1395 """
1396 self.current_tree = self.smbsock.tree_connect(share)
1397 self.pwd = pathlib.PureWindowsPath("/")
1398 self.ls_cache.clear()
1399
1400 @CLIUtil.addcomplete(use)
1401 def use_complete(self, share):
1402 """
1403 Auto-complete 'use'
1404 """
1405 return [
1406 x[0] for x in self.shares() if x[0].startswith(share) and x[0] != "IPC$"
1407 ]
1408
1409 def _parsepath(self, arg, remote=True):
1410 """
1411 Parse a path. Returns the parent folder and file name
1412 """
1413 # Find parent directory if it exists
1414 elt = (pathlib.PureWindowsPath if remote else pathlib.Path)(arg)
1415 eltpar = (pathlib.PureWindowsPath if remote else pathlib.Path)(".")
1416 eltname = elt.name
1417 if arg.endswith("/") or arg.endswith("\\"):
1418 eltpar = elt
1419 eltname = ""
1420 elif elt.parent and elt.parent.name or elt.is_absolute():
1421 eltpar = elt.parent
1422 return eltpar, eltname
1423
1424 def _fs_complete(self, arg, cond=None):
1425 """
1426 Return a listing of the remote files for completion purposes
1427 """
1428 if cond is None:
1429 cond = lambda _: True
1430 eltpar, eltname = self._parsepath(arg)
1431 # ls in that directory
1432 try:
1433 files = self.ls(parent=eltpar)
1434 except ValueError:
1435 return []
1436 return [
1437 str(eltpar / x[0])
1438 for x in files
1439 if (
1440 x[0].lower().startswith(eltname.lower())
1441 and x[0] not in [".", ".."]
1442 and cond(x[1])
1443 )
1444 ]
1445
1446 def _dir_complete(self, arg):
1447 """
1448 Return a directories of remote files for completion purposes
1449 """
1450 results = self._fs_complete(
1451 arg,
1452 cond=lambda x: x.FILE_ATTRIBUTE_DIRECTORY,
1453 )
1454 if len(results) == 1 and results[0].startswith(arg):
1455 # skip through folders
1456 return [results[0] + "\\"]
1457 return results
1458
1459 @CLIUtil.addcommand(mono=True)
1460 def ls(self, parent=None):
1461 """
1462 List the files in the remote directory
1463 -t: sort by timestamp
1464 -S: sort by size
1465 -r: reverse while sorting
1466 """
1467 if self._require_share():
1468 return
1469 # Get pwd of the ls
1470 pwd = self.pwd
1471 if parent is not None:
1472 pwd /= parent
1473 pwd = self.normalize_path(pwd)
1474 # Poll the cache
1475 if self.ls_cache and pwd in self.ls_cache:
1476 return self.ls_cache[pwd]
1477 self.smbsock.set_TID(self.current_tree)
1478 # Open folder
1479 fileId = self.smbsock.create_request(
1480 pwd,
1481 type="folder",
1482 extra_create_options=self.extra_create_options,
1483 )
1484 # Query the folder
1485 files = self.smbsock.query_directory(fileId)
1486 # Close the folder
1487 self.smbsock.close_request(fileId)
1488 self.ls_cache[pwd] = files # Store cache
1489 return files
1490
1491 @CLIUtil.addoutput(ls)
1492 def ls_output(self, results, *, t=False, S=False, r=False):
1493 """
1494 Print the output of 'ls'
1495 """
1496 fld = UTCTimeField(
1497 "", None, fmt="<Q", epoch=[1601, 1, 1, 0, 0, 0], custom_scaling=1e7
1498 )
1499 if t:
1500 # Sort by time
1501 results.sort(key=lambda x: -x[3])
1502 if S:
1503 # Sort by size
1504 results.sort(key=lambda x: -x[2])
1505 if r:
1506 # Reverse sort
1507 results = results[::-1]
1508 results = [
1509 (
1510 x[0],
1511 "+".join(y.lstrip("FILE_ATTRIBUTE_") for y in str(x[1]).split("+")),
1512 human_size(x[2]),
1513 fld.i2repr(None, x[3]),
1514 )
1515 for x in results
1516 ]
1517 print(
1518 pretty_list(
1519 results,
1520 [("FileName", "FileAttributes", "EndOfFile", "LastWriteTime")],
1521 sortBy=None,
1522 )
1523 )
1524
1525 @CLIUtil.addcomplete(ls)
1526 def ls_complete(self, folder):
1527 """
1528 Auto-complete ls
1529 """
1530 if self._require_share(silent=True):
1531 return []
1532 return self._dir_complete(folder)
1533
1534 @CLIUtil.addcommand(mono=True)
1535 def cd(self, folder):
1536 """
1537 Change the remote current directory
1538 """
1539 if self._require_share():
1540 return
1541 if not folder:
1542 # show mode
1543 return str(self.pwd)
1544 self.pwd /= folder
1545 self.pwd = self.collapse_path(self.pwd)
1546 self.ls_cache.clear()
1547
1548 @CLIUtil.addcomplete(cd)
1549 def cd_complete(self, folder):
1550 """
1551 Auto-complete cd
1552 """
1553 if self._require_share(silent=True):
1554 return []
1555 return self._dir_complete(folder)
1556
1557 def _lfs_complete(self, arg, cond):
1558 """
1559 Return a listing of local files for completion purposes
1560 """
1561 eltpar, eltname = self._parsepath(arg, remote=False)
1562 eltpar = self.localpwd / eltpar
1563 return [
1564 # trickery so that ../<TAB> works
1565 str(eltpar / x.name)
1566 for x in eltpar.resolve().glob("*")
1567 if (x.name.lower().startswith(eltname.lower()) and cond(x))
1568 ]
1569
1570 @CLIUtil.addoutput(cd)
1571 def cd_output(self, result):
1572 """
1573 Print the output of 'cd'
1574 """
1575 if result:
1576 print(result)
1577
1578 @CLIUtil.addcommand()
1579 def lls(self):
1580 """
1581 List the files in the local directory
1582 """
1583 return list(self.localpwd.glob("*"))
1584
1585 @CLIUtil.addoutput(lls)
1586 def lls_output(self, results):
1587 """
1588 Print the output of 'lls'
1589 """
1590 results = [
1591 (
1592 x.name,
1593 human_size(stat.st_size),
1594 time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(stat.st_mtime)),
1595 )
1596 for x, stat in ((x, x.stat()) for x in results)
1597 ]
1598 print(
1599 pretty_list(results, [("FileName", "File Size", "Last Modification Time")])
1600 )
1601
1602 @CLIUtil.addcommand(mono=True)
1603 def lcd(self, folder):
1604 """
1605 Change the local current directory
1606 """
1607 if not folder:
1608 # show mode
1609 return str(self.localpwd)
1610 self.localpwd /= folder
1611 self.localpwd = self.localpwd.resolve()
1612
1613 @CLIUtil.addcomplete(lcd)
1614 def lcd_complete(self, folder):
1615 """
1616 Auto-complete lcd
1617 """
1618 return self._lfs_complete(folder, lambda x: x.is_dir())
1619
1620 @CLIUtil.addoutput(lcd)
1621 def lcd_output(self, result):
1622 """
1623 Print the output of 'lcd'
1624 """
1625 if result:
1626 print(result)
1627
1628 def _get_file(self, file, fd):
1629 """
1630 Gets the file bytes from a remote host
1631 """
1632 # Get pwd of the ls
1633 fpath = self.pwd / file
1634 self.smbsock.set_TID(self.current_tree)
1635
1636 # Open file
1637 fileId = self.smbsock.create_request(
1638 self.normalize_path(fpath),
1639 type="file",
1640 extra_create_options=[
1641 "FILE_SEQUENTIAL_ONLY",
1642 ]
1643 + self.extra_create_options,
1644 )
1645
1646 # Get the file size
1647 info = FileAllInformation(
1648 self.smbsock.query_info(
1649 FileId=fileId,
1650 InfoType="SMB2_0_INFO_FILE",
1651 FileInfoClass="FileAllInformation",
1652 )
1653 )
1654 length = info.StandardInformation.EndOfFile
1655 offset = 0
1656
1657 # Read the file
1658 while length:
1659 lengthRead = min(self.smbsock.session.MaxReadSize, length)
1660 fd.write(
1661 self.smbsock.read_request(fileId, Length=lengthRead, Offset=offset)
1662 )
1663 offset += lengthRead
1664 length -= lengthRead
1665
1666 # Close the file
1667 self.smbsock.close_request(fileId)
1668 return offset
1669
1670 def _send_file(self, fname, fd):
1671 """
1672 Send the file bytes to a remote host
1673 """
1674 # Get destination file
1675 fpath = self.pwd / fname
1676 self.smbsock.set_TID(self.current_tree)
1677 # Open file
1678 fileId = self.smbsock.create_request(
1679 self.normalize_path(fpath),
1680 type="file",
1681 mode="w",
1682 extra_create_options=self.extra_create_options,
1683 )
1684 # Send the file
1685 offset = 0
1686 while True:
1687 data = fd.read(self.smbsock.session.MaxWriteSize)
1688 if not data:
1689 # end of file
1690 break
1691 offset += self.smbsock.write_request(
1692 Data=data,
1693 FileId=fileId,
1694 Offset=offset,
1695 )
1696 # Close the file
1697 self.smbsock.close_request(fileId)
1698 return offset
1699
1700 def _listr(self, directory, regx=None, max_depth=None, depth=0, _verb=True):
1701 """
1702 Internal recursive function that yields the remote files
1703 """
1704 if max_depth is not None and depth > max_depth:
1705 return
1706 for x in self.ls(parent=directory):
1707 if x[0] in [".", ".."]:
1708 # Discard . and ..
1709 continue
1710 remote = directory / x[0]
1711 try:
1712 if x[1].FILE_ATTRIBUTE_DIRECTORY:
1713 # Sub-directory
1714 yield from self._listr(
1715 remote,
1716 regx=regx,
1717 depth=depth + 1,
1718 max_depth=max_depth,
1719 _verb=_verb,
1720 )
1721 else:
1722 # Sub-file
1723 if regx is None or regx.match(str(remote)):
1724 yield remote
1725 except ValueError as ex:
1726 if _verb:
1727 print(conf.color_theme.red(directory), "->", str(ex))
1728
1729 def _getr(self, directory, root, dest, _verb=True):
1730 """
1731 Internal recursive function to get a directory
1732
1733 :param directory: the remote directory to get
1734 :param root: the root directory to store files into
1735 :param dest: locally, the directory to store any found files (rec)
1736 """
1737 size = 0
1738 if dest is None:
1739 dest = root
1740 if not dest.exists():
1741 dest.mkdir()
1742 # ls the directory
1743 for x in self.ls(parent=directory):
1744 if x[0] in [".", ".."]:
1745 # Discard . and ..
1746 continue
1747 remote = directory / x[0]
1748 local = dest / x[0]
1749 # Word of caution: this check ONLY works because root and local have been
1750 # resolve(). Be careful
1751 if root not in local.parents and local != root:
1752 raise FileNotFoundError
1753 try:
1754 if x[1].FILE_ATTRIBUTE_DIRECTORY:
1755 # Sub-directory
1756 size += self._getr(remote, root, local)
1757 else:
1758 # Sub-file
1759 size += self.get(remote, local)[1]
1760 if _verb:
1761 print(remote)
1762 except ValueError as ex:
1763 if _verb:
1764 print(conf.color_theme.red(remote), "->", str(ex))
1765 return size
1766
1767 @CLIUtil.addcommand(mono=True, globsupport=True)
1768 def get(self, file, _dest=None, _verb=True, *, r=False):
1769 """
1770 Retrieve a file
1771 -r: recursively download a directory
1772 """
1773 if self._require_share():
1774 return
1775 if r:
1776 dirpar, dirname = self._parsepath(file)
1777 return file, self._getr(
1778 dirpar / dirname, # Remotely
1779 self.localpwd / dirname, # Locally
1780 None,
1781 _verb=_verb,
1782 )
1783 else:
1784 fname = pathlib.PureWindowsPath(file).name
1785 # Write the buffer
1786 if _dest is None:
1787 _dest = self.localpwd / fname
1788 if not _dest.parent.exists():
1789 _dest.parent.mkdir()
1790 with _dest.open("wb") as fd:
1791 size = self._get_file(file, fd)
1792 return fname, size
1793
1794 @CLIUtil.addoutput(get)
1795 def get_output(self, info):
1796 """
1797 Print the output of 'get'
1798 """
1799 print("Retrieved '%s' of size %s" % (info[0], human_size(info[1])))
1800
1801 @CLIUtil.addcomplete(get)
1802 def get_complete(self, file):
1803 """
1804 Auto-complete get
1805 """
1806 if self._require_share(silent=True):
1807 return []
1808 return self._fs_complete(file)
1809
1810 @CLIUtil.addcommand()
1811 def tree(self, regx: str = None, max_depth=None):
1812 """
1813 Show the tree of files from the current directory
1814 """
1815 if self._require_share():
1816 return
1817 if regx is not None:
1818 regx = re.compile(regx)
1819 if max_depth is not None:
1820 max_depth = int(max_depth)
1821 return self._listr(self.pwd, regx=regx, max_depth=max_depth)
1822
1823 @CLIUtil.addoutput(tree)
1824 def tree_output(self, result):
1825 """
1826 Print the output of 'tree'
1827 """
1828 if self._require_share(silent=True):
1829 return
1830 for file in result:
1831 print(str(file), flush=True)
1832
1833 @CLIUtil.addcommand()
1834 def server(self, command):
1835 """
1836 Get information about the remote server
1837 """
1838 if command == "network":
1839 # We get the list of interfaces
1840 if self._require_share():
1841 return
1842 resp = self.smbsock.ioctl(CtlCode="FSCTL_QUERY_NETWORK_INTERFACE_INFO")
1843 return (command, resp)
1844 elif command == "sessions":
1845 # We get the list of active connections via RPC
1846 self.rpcclient.open_smbpipe("srvsvc")
1847 self.rpcclient.bind(find_dcerpc_interface("srvsvc"))
1848 ResumeHandle = None
1849 results = []
1850 while True:
1851 req = NetrSessionEnum_Request(
1852 ClientName=None,
1853 UserName=None,
1854 InfoStruct=PSESSION_ENUM_STRUCT(
1855 Level=502,
1856 SessionInfo=NDRUnion(
1857 tag=502,
1858 value=SESSION_INFO_502_CONTAINER(Buffer=None),
1859 ),
1860 ),
1861 PreferedMaximumLength=0xFFFFFFFF,
1862 ResumeHandle=ResumeHandle,
1863 )
1864 resp = self.rpcclient.sr1_req(req, timeout=self.timeout)
1865 resp.show()
1866 ResumeHandle = resp.ResumeHandle
1867 if resp.status in [0, 0x000000EA]: # ERROR_SUCCESS / ERROR_MORE_DATA
1868 results.extend(resp.valueof("InfoStruct.SessionInfo.Buffer"))
1869 if resp.status == 0:
1870 break
1871 else:
1872 break
1873 self.rpcclient.close_smbpipe()
1874 return (command, results)
1875 else:
1876 raise ValueError("Unknown server command: %s" % command)
1877
1878 @CLIUtil.addoutput(server)
1879 def server_output(self, result):
1880 """
1881 Print the output of 'server'
1882 """
1883 command, result = result
1884 if command == "network":
1885 # Show the various interfaces, their IP and capabilities
1886 result.interfaces = [x for x in result.interfaces if x.IfIndex != 0]
1887 result.show()
1888 elif command == "sessions":
1889 # Show the list of active sessions
1890 for sess in result:
1891 print("- cname:", sess.valueof("sesi502_cname"))
1892 print(" username:", sess.valueof("sesi502_username"))
1893 print(" num_opens:", sess.valueof("sesi502_num_opens"))
1894 print(" time:", sess.valueof("sesi502_time"))
1895 print(" idle_time:", sess.valueof("sesi502_idle_time"))
1896 print(" user_flags:", sess.valueof("sesi502_user_flags"))
1897 print(" cltype_name:", sess.valueof("sesi502_cltype_name"))
1898 print(" transport:", sess.valueof("sesi502_transport"))
1899
1900 @CLIUtil.addcomplete(server)
1901 def server_complete(self, line):
1902 """
1903 Auto-complete server
1904 """
1905 return ["network", "sessions"]
1906
1907 @CLIUtil.addcommand(mono=True, globsupport=True)
1908 def cat(self, file):
1909 """
1910 Print a file
1911 """
1912 if self._require_share():
1913 return
1914 # Write the buffer to buffer
1915 buf = io.BytesIO()
1916 self._get_file(file, buf)
1917 return buf.getvalue()
1918
1919 @CLIUtil.addoutput(cat)
1920 def cat_output(self, result):
1921 """
1922 Print the output of 'cat'
1923 """
1924 print(result.decode(errors="backslashreplace"))
1925
1926 @CLIUtil.addcomplete(cat)
1927 def cat_complete(self, file):
1928 """
1929 Auto-complete cat
1930 """
1931 if self._require_share(silent=True):
1932 return []
1933 return self._fs_complete(file)
1934
1935 @CLIUtil.addcommand(mono=True, globsupport=True)
1936 def put(self, file):
1937 """
1938 Upload a file
1939 """
1940 if self._require_share():
1941 return
1942 local_file = self.localpwd / file
1943 if local_file.is_dir():
1944 # Directory
1945 raise ValueError("put on dir not impl")
1946 else:
1947 fname = pathlib.Path(file).name
1948 with local_file.open("rb") as fd:
1949 size = self._send_file(fname, fd)
1950 self.ls_cache.clear()
1951 return fname, size
1952
1953 @CLIUtil.addcomplete(put)
1954 def put_complete(self, folder):
1955 """
1956 Auto-complete put
1957 """
1958 return self._lfs_complete(folder, lambda x: not x.is_dir())
1959
1960 @CLIUtil.addcommand(mono=True)
1961 def rm(self, file):
1962 """
1963 Delete a file
1964 """
1965 if self._require_share():
1966 return
1967 # Get pwd of the ls
1968 fpath = self.pwd / file
1969 self.smbsock.set_TID(self.current_tree)
1970 # Open file
1971 fileId = self.smbsock.create_request(
1972 self.normalize_path(fpath),
1973 type="file",
1974 mode="d",
1975 extra_create_options=self.extra_create_options,
1976 )
1977 # Close the file
1978 self.smbsock.close_request(fileId)
1979 self.ls_cache.clear()
1980 return fpath.name
1981
1982 @CLIUtil.addcomplete(rm)
1983 def rm_complete(self, file):
1984 """
1985 Auto-complete rm
1986 """
1987 if self._require_share(silent=True):
1988 return []
1989 return self._fs_complete(file)
1990
1991 @CLIUtil.addcommand()
1992 def backup(self):
1993 """
1994 Turn on or off backup intent
1995 """
1996 if "FILE_OPEN_FOR_BACKUP_INTENT" in self.extra_create_options:
1997 print("Backup Intent: Off")
1998 self.extra_create_options.remove("FILE_OPEN_FOR_BACKUP_INTENT")
1999 else:
2000 print("Backup Intent: On")
2001 self.extra_create_options.append("FILE_OPEN_FOR_BACKUP_INTENT")
2002
2003 @CLIUtil.addcommand(mono=True)
2004 def watch(self, folder):
2005 """
2006 Watch file changes in folder (recursively)
2007 """
2008 if self._require_share():
2009 return
2010 # Get pwd of the ls
2011 fpath = self.pwd / folder
2012 self.smbsock.set_TID(self.current_tree)
2013 # Open file
2014 fileId = self.smbsock.create_request(
2015 self.normalize_path(fpath),
2016 type="folder",
2017 extra_create_options=self.extra_create_options,
2018 )
2019 print("Watching '%s'" % fpath)
2020 # Watch for changes
2021 try:
2022 while True:
2023 changes = self.smbsock.changenotify(fileId)
2024 for chg in changes:
2025 print(chg.sprintf("%.time%: %Action% %FileName%"))
2026 except KeyboardInterrupt:
2027 pass
2028 print("Cancelled.")
2029
2030 @CLIUtil.addcommand(mono=True)
2031 def getsd(self, file):
2032 """
2033 Get the Security Descriptor
2034 """
2035 if self._require_share():
2036 return
2037 fpath = self.pwd / file
2038 self.smbsock.set_TID(self.current_tree)
2039 # Open file
2040 fileId = self.smbsock.create_request(
2041 self.normalize_path(fpath),
2042 type="",
2043 mode="",
2044 extra_desired_access=["READ_CONTROL", "ACCESS_SYSTEM_SECURITY"],
2045 )
2046 # Get the file size
2047 info = self.smbsock.query_info(
2048 FileId=fileId,
2049 InfoType="SMB2_0_INFO_SECURITY",
2050 FileInfoClass=0,
2051 AdditionalInformation=(
2052 0x00000001
2053 | 0x00000002
2054 | 0x00000004
2055 | 0x00000008
2056 | 0x00000010
2057 | 0x00000020
2058 | 0x00000040
2059 | 0x00010000
2060 ),
2061 )
2062 self.smbsock.close_request(fileId)
2063 return info
2064
2065 @CLIUtil.addcomplete(getsd)
2066 def getsd_complete(self, file):
2067 """
2068 Auto-complete getsd
2069 """
2070 if self._require_share(silent=True):
2071 return []
2072 return self._fs_complete(file)
2073
2074 @CLIUtil.addoutput(getsd)
2075 def getsd_output(self, results):
2076 """
2077 Print the output of 'getsd'
2078 """
2079 sd = SECURITY_DESCRIPTOR(results)
2080 sd.show_print()
2081
2082
2083if __name__ == "__main__":
2084 from scapy.utils import AutoArgparse
2085
2086 AutoArgparse(smbclient)