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[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 the security subsystem indicates that the session
601 # was established by a guest user, Session.SigningRequired
602 # MUST be set to FALSE and Session.IsGuest MUST be set to TRUE."
603 self.session.IsGuest = True
604 self.session.SigningRequired = False
605 elif self.session.Dialect >= 0x0300:
606 if pkt.SessionFlags.ENCRYPT_DATA or self.REQUIRE_ENCRYPTION:
607 self.session.EncryptData = True
608 self.session.SigningRequired = False
609 raise self.AUTHENTICATED(pkt.SecurityBlob)
610 else:
611 if SMB2_Header in pkt:
612 # If required, compute sessions
613 self.session.computeSMBSessionPreauth(
614 bytes(pkt[SMB2_Header]), # session response
615 )
616 # Ongoing auth
617 raise self.NEGOTIATED(pkt.SecurityBlob)
618 elif SMBSession_Setup_AndX_Response_Extended_Security in pkt:
619 # SMB1 non-extended
620 pass
621 elif SMB2_Error_Response in pkt:
622 # Authentication failure
623 self.session.sspcontext.clifailure()
624 # Reset Session preauth (SMB 3.1.1)
625 self.session.SessionPreauthIntegrityHashValue = None
626 if not self.RETRY:
627 raise self.AUTH_FAILED()
628 self.debug(lvl=2, msg="RETRY: %s" % self.RETRY)
629 self.RETRY -= 1
630 raise self.NEGOTIATED()
631
632 @ATMT.state(final=1)
633 def AUTH_FAILED(self):
634 self.smb_sock_ready.set()
635
636 @ATMT.state()
637 def AUTHENTICATED(self, ssp_blob=None):
638 self.session.sspcontext, _, status = self.session.ssp.GSS_Init_sec_context(
639 self.session.sspcontext,
640 input_token=ssp_blob,
641 target_name="cifs/" + self.HOST if self.HOST else None,
642 )
643 if status != GSS_S_COMPLETE:
644 raise ValueError(
645 "Internal error: the SSP completed with error: %s" % status
646 )
647 # Authentication was successful
648 self.session.computeSMBSessionKeys(IsClient=True)
649
650 # DEV: add a condition on AUTHENTICATED with prio=0
651
652 @ATMT.condition(AUTHENTICATED, prio=1)
653 def authenticated_post_actions(self):
654 raise self.SOCKET_BIND()
655
656 # Plain SMB Socket
657
658 @ATMT.state()
659 def SOCKET_BIND(self):
660 self.smb_sock_ready.set()
661
662 @ATMT.condition(SOCKET_BIND)
663 def start_smb_socket(self):
664 raise self.SOCKET_MODE_SMB()
665
666 @ATMT.state()
667 def SOCKET_MODE_SMB(self):
668 pass
669
670 @ATMT.receive_condition(SOCKET_MODE_SMB)
671 def incoming_data_received_smb(self, pkt):
672 raise self.SOCKET_MODE_SMB().action_parameters(pkt)
673
674 @ATMT.action(incoming_data_received_smb)
675 def receive_data_smb(self, pkt):
676 resp = pkt[SMB2_Header].payload
677 if isinstance(resp, SMB2_Error_Response):
678 if pkt.Status == 0x00000103: # STATUS_PENDING
679 # answer is coming later.. just wait...
680 return
681 if pkt.Status == 0x0000010B: # STATUS_NOTIFY_CLEANUP
682 # this is a notify cleanup. ignore
683 return
684 self.update_smbheader(pkt)
685 # Add the status to the response as metadata
686 resp.NTStatus = pkt.sprintf("%SMB2_Header.Status%")
687 self.oi.smbpipe.send(resp)
688
689 @ATMT.ioevent(SOCKET_MODE_SMB, name="smbpipe", as_supersocket="smblink")
690 def outgoing_data_received_smb(self, fd):
691 raise self.SOCKET_MODE_SMB().action_parameters(fd.recv())
692
693 @ATMT.action(outgoing_data_received_smb)
694 def send_data(self, d):
695 self.send(self.smb_header.copy() / d)
696
697
698class SMB_SOCKET(SuperSocket):
699 """
700 Mid-level wrapper over SMB_Client.smblink that provides some basic SMB
701 client functions, such as tree connect, directory query, etc.
702 """
703
704 def __init__(self, smbsock, use_ioctl=True, timeout=3):
705 self.ins = smbsock
706 self.timeout = timeout
707 if not self.ins.atmt.smb_sock_ready.wait(timeout=timeout):
708 # If we have a SSP, tell it we failed.
709 if self.session.sspcontext:
710 self.session.sspcontext.clifailure()
711 raise TimeoutError(
712 "The SMB handshake timed out ! (enable debug=1 for logs)"
713 )
714 if self.ins.atmt.ErrorStatus:
715 raise Scapy_Exception(
716 "SMB Session Setup failed: %s" % self.ins.atmt.ErrorStatus
717 )
718
719 @classmethod
720 def from_tcpsock(cls, sock, **kwargs):
721 """
722 Wraps the tcp socket in a SMB_Client.smblink first, then into the
723 SMB_SOCKET/SMB_RPC_SOCKET
724 """
725 return cls(
726 use_ioctl=kwargs.pop("use_ioctl", True),
727 timeout=kwargs.pop("timeout", 3),
728 smbsock=SMB_Client.from_tcpsock(sock, **kwargs),
729 )
730
731 @property
732 def session(self):
733 return self.ins.atmt.session
734
735 def set_TID(self, TID):
736 """
737 Set the TID (Tree ID).
738 This can be called before sending a packet
739 """
740 self.ins.atmt.smb_header.TID = TID
741
742 def get_TID(self):
743 """
744 Get the current TID from the underlying socket
745 """
746 return self.ins.atmt.smb_header.TID
747
748 def tree_connect(self, name):
749 """
750 Send a TreeConnect request
751 """
752 resp = self.ins.sr1(
753 SMB2_Tree_Connect_Request(
754 Buffer=[
755 (
756 "Path",
757 "\\\\%s\\%s"
758 % (
759 self.session.sspcontext.ServerHostname,
760 name,
761 ),
762 )
763 ]
764 ),
765 chainCC=True,
766 verbose=False,
767 timeout=self.timeout,
768 )
769 if not resp:
770 raise ValueError("TreeConnect timed out !")
771 if SMB2_Tree_Connect_Response not in resp:
772 raise ValueError("Failed TreeConnect ! %s" % resp.NTStatus)
773 # [MS-SMB2] sect 3.2.5.5
774 if self.session.Dialect >= 0x0300:
775 if resp.ShareFlags.ENCRYPT_DATA and self.session.SupportsEncryption:
776 self.session.TreeEncryptData = True
777 else:
778 self.session.TreeEncryptData = False
779 return self.get_TID()
780
781 def tree_disconnect(self):
782 """
783 Send a TreeDisconnect request
784 """
785 resp = self.ins.sr1(
786 SMB2_Tree_Disconnect_Request(),
787 verbose=False,
788 timeout=self.timeout,
789 chainCC=True,
790 )
791 if not resp:
792 raise ValueError("TreeDisconnect timed out !")
793 if SMB2_Tree_Disconnect_Response not in resp:
794 raise ValueError("Failed TreeDisconnect ! %s" % resp.NTStatus)
795
796 def create_request(
797 self,
798 name,
799 mode="r",
800 type="pipe",
801 extra_create_options=[],
802 extra_desired_access=[],
803 ):
804 """
805 Open a file/pipe by its name
806
807 :param name: the name of the file or named pipe. e.g. 'srvsvc'
808 """
809 ShareAccess = []
810 DesiredAccess = []
811 # Common params depending on the access
812 if "r" in mode:
813 ShareAccess.append("FILE_SHARE_READ")
814 DesiredAccess.extend(["FILE_READ_DATA", "FILE_READ_ATTRIBUTES"])
815 if "w" in mode:
816 ShareAccess.append("FILE_SHARE_WRITE")
817 DesiredAccess.extend(["FILE_WRITE_DATA", "FILE_WRITE_ATTRIBUTES"])
818 if "d" in mode:
819 ShareAccess.append("FILE_SHARE_DELETE")
820 # Params depending on the type
821 FileAttributes = []
822 CreateOptions = []
823 CreateContexts = []
824 CreateDisposition = "FILE_OPEN"
825 if type == "folder":
826 FileAttributes.append("FILE_ATTRIBUTE_DIRECTORY")
827 CreateOptions.append("FILE_DIRECTORY_FILE")
828 elif type in ["file", "pipe"]:
829 CreateOptions = ["FILE_NON_DIRECTORY_FILE"]
830 if "r" in mode:
831 DesiredAccess.extend(["FILE_READ_EA", "READ_CONTROL", "SYNCHRONIZE"])
832 if "w" in mode:
833 CreateDisposition = "FILE_OVERWRITE_IF"
834 DesiredAccess.append("FILE_WRITE_EA")
835 if "d" in mode:
836 DesiredAccess.append("DELETE")
837 CreateOptions.append("FILE_DELETE_ON_CLOSE")
838 if type == "file":
839 FileAttributes.append("FILE_ATTRIBUTE_NORMAL")
840 elif type:
841 raise ValueError("Unknown type: %s" % type)
842 # [MS-SMB2] 3.2.4.3.8
843 RequestedOplockLevel = 0
844 if self.session.Dialect >= 0x0300:
845 RequestedOplockLevel = "SMB2_OPLOCK_LEVEL_LEASE"
846 elif self.session.Dialect >= 0x0210 and type == "file":
847 RequestedOplockLevel = "SMB2_OPLOCK_LEVEL_LEASE"
848 # SMB 3.X
849 if self.session.Dialect >= 0x0300 and type in ["file", "folder"]:
850 CreateContexts.extend(
851 [
852 # [SMB2] sect 3.2.4.3.5
853 SMB2_Create_Context(
854 Name=b"DH2Q",
855 Data=SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2(
856 CreateGuid=RandUUID()._fix()
857 ),
858 ),
859 # [SMB2] sect 3.2.4.3.9
860 SMB2_Create_Context(
861 Name=b"MxAc",
862 ),
863 # [SMB2] sect 3.2.4.3.10
864 SMB2_Create_Context(
865 Name=b"QFid",
866 ),
867 # [SMB2] sect 3.2.4.3.8
868 SMB2_Create_Context(
869 Name=b"RqLs",
870 Data=SMB2_CREATE_REQUEST_LEASE_V2(LeaseKey=RandUUID()._fix()),
871 ),
872 ]
873 )
874 elif self.session.Dialect == 0x0210 and type == "file":
875 CreateContexts.extend(
876 [
877 # [SMB2] sect 3.2.4.3.8
878 SMB2_Create_Context(
879 Name=b"RqLs",
880 Data=SMB2_CREATE_REQUEST_LEASE(LeaseKey=RandUUID()._fix()),
881 ),
882 ]
883 )
884 # Extra options
885 if extra_create_options:
886 CreateOptions.extend(extra_create_options)
887 if extra_desired_access:
888 DesiredAccess.extend(extra_desired_access)
889 # Request
890 resp = self.ins.sr1(
891 SMB2_Create_Request(
892 ImpersonationLevel="Impersonation",
893 DesiredAccess="+".join(DesiredAccess),
894 CreateDisposition=CreateDisposition,
895 CreateOptions="+".join(CreateOptions),
896 ShareAccess="+".join(ShareAccess),
897 FileAttributes="+".join(FileAttributes),
898 CreateContexts=CreateContexts,
899 RequestedOplockLevel=RequestedOplockLevel,
900 Name=name,
901 ),
902 chainCC=True,
903 verbose=0,
904 timeout=self.timeout,
905 )
906 if not resp:
907 raise ValueError("CreateRequest timed out !")
908 if SMB2_Create_Response not in resp:
909 raise ValueError("Failed CreateRequest ! %s" % resp.NTStatus)
910 return resp[SMB2_Create_Response].FileId
911
912 def close_request(self, FileId):
913 """
914 Close the FileId
915 """
916 pkt = SMB2_Close_Request(FileId=FileId)
917 resp = self.ins.sr1(pkt, verbose=0, timeout=self.timeout, chainCC=True)
918 if not resp:
919 raise ValueError("CloseRequest timed out !")
920 if SMB2_Close_Response not in resp:
921 raise ValueError("Failed CloseRequest ! %s" % resp.NTStatus)
922
923 def read_request(self, FileId, Length, Offset=0):
924 """
925 Read request
926 """
927 resp = self.ins.sr1(
928 SMB2_Read_Request(
929 FileId=FileId,
930 Length=Length,
931 Offset=Offset,
932 ),
933 chainCC=True,
934 verbose=0,
935 timeout=self.timeout * 10,
936 )
937 if not resp:
938 raise ValueError("ReadRequest timed out !")
939 if SMB2_Read_Response not in resp:
940 raise ValueError("Failed ReadRequest ! %s" % resp.NTStatus)
941 return resp.Data
942
943 def write_request(self, Data, FileId, Offset=0):
944 """
945 Write request
946 """
947 resp = self.ins.sr1(
948 SMB2_Write_Request(
949 FileId=FileId,
950 Data=Data,
951 Offset=Offset,
952 ),
953 chainCC=True,
954 verbose=0,
955 timeout=self.timeout * 10,
956 )
957 if not resp:
958 raise ValueError("WriteRequest timed out !")
959 if SMB2_Write_Response not in resp:
960 raise ValueError("Failed WriteRequest ! %s" % resp.NTStatus)
961 return resp.Count
962
963 def query_directory(self, FileId, FileName="*"):
964 """
965 Query the Directory with FileId
966 """
967 results = []
968 Flags = "SMB2_RESTART_SCANS"
969 while True:
970 pkt = SMB2_Query_Directory_Request(
971 FileInformationClass="FileIdBothDirectoryInformation",
972 FileId=FileId,
973 FileName=FileName,
974 Flags=Flags,
975 )
976 resp = self.ins.sr1(pkt, verbose=0, timeout=self.timeout, chainCC=True)
977 Flags = 0 # only the first one is RESTART_SCANS
978 if not resp:
979 raise ValueError("QueryDirectory timed out !")
980 if SMB2_Error_Response in resp:
981 break
982 elif SMB2_Query_Directory_Response not in resp:
983 raise ValueError("Failed QueryDirectory ! %s" % resp.NTStatus)
984 res = FileIdBothDirectoryInformation(resp.Output)
985 results.extend(
986 [
987 (
988 x.FileName,
989 x.FileAttributes,
990 x.EndOfFile,
991 x.LastWriteTime,
992 )
993 for x in res.files
994 ]
995 )
996 return results
997
998 def query_info(self, FileId, InfoType, FileInfoClass, AdditionalInformation=0):
999 """
1000 Query the Info
1001 """
1002 pkt = SMB2_Query_Info_Request(
1003 InfoType=InfoType,
1004 FileInfoClass=FileInfoClass,
1005 OutputBufferLength=65535,
1006 FileId=FileId,
1007 AdditionalInformation=AdditionalInformation,
1008 )
1009 resp = self.ins.sr1(pkt, verbose=0, timeout=self.timeout, chainCC=True)
1010 if not resp:
1011 raise ValueError("QueryInfo timed out !")
1012 if SMB2_Query_Info_Response not in resp:
1013 raise ValueError("Failed QueryInfo ! %s" % resp.NTStatus)
1014 return resp.Output
1015
1016 def ioctl(self, CtlCode, Flags="SMB2_0_IOCTL_IS_FSCTL", Input=None):
1017 """
1018 Perform an IOCTL
1019 """
1020 pkt = SMB2_IOCTL_Request(
1021 FileId=SMB2_FILEID(
1022 Persistent=0xFFFFFFFFFFFFFFFF, Volatile=0xFFFFFFFFFFFFFFFF
1023 ),
1024 Flags=Flags,
1025 CtlCode=CtlCode,
1026 )
1027 if Input is not None:
1028 pkt.Input = bytes(Input)
1029 resp = self.ins.sr1(pkt, verbose=0, chainCC=True)
1030 if SMB2_IOCTL_Response not in resp:
1031 raise ValueError("Failed reading IOCTL_Response ! %s" % resp.NTStatus)
1032 return resp.Output
1033
1034 def changenotify(self, FileId):
1035 """
1036 Register change notify
1037 """
1038 pkt = SMB2_Change_Notify_Request(
1039 Flags="SMB2_WATCH_TREE",
1040 OutputBufferLength=65535,
1041 FileId=FileId,
1042 CompletionFilter=0x0FFF,
1043 )
1044 # we can wait forever, not a problem in this one
1045 resp = self.ins.sr1(pkt, verbose=0, chainCC=True)
1046 if SMB2_Change_Notify_Response not in resp:
1047 raise ValueError("Failed ChangeNotify ! %s" % resp.NTStatus)
1048 return resp.Output
1049
1050
1051class SMB_RPC_SOCKET(ObjectPipe, SMB_SOCKET):
1052 """
1053 Extends SMB_SOCKET (which is a wrapper over SMB_Client.smblink) to send
1054 DCE/RPC messages (bind, reqs, etc.)
1055
1056 This is usable as a normal SuperSocket (sr1, etc.) and performs the
1057 wrapping of the DCE/RPC messages into SMB2_Write/Read packets.
1058 """
1059
1060 def __init__(self, smbsock, use_ioctl=True, timeout=3):
1061 self.use_ioctl = use_ioctl
1062 ObjectPipe.__init__(self, "SMB_RPC_SOCKET")
1063 SMB_SOCKET.__init__(self, smbsock, timeout=timeout)
1064
1065 def open_pipe(self, name):
1066 self.PipeFileId = self.create_request(name, mode="rw", type="pipe")
1067
1068 def close_pipe(self):
1069 self.close_request(self.PipeFileId)
1070 self.PipeFileId = None
1071
1072 def send(self, x, is_sr1=True):
1073 # Reminder: this class is an ObjectPipe ! It doesn't act as a real socket
1074 # but just a queue. When someone calls the "send" function, they pipe
1075 # some data that we must send, and tell us if they expect an answer through
1076 # the is_sr1 flag.
1077
1078 # Detect if DCE/RPC is fragmented. Then we must use Read/Write
1079 is_frag = x.pfc_flags & 3 != 3
1080
1081 if self.use_ioctl and is_sr1 and not is_frag and self.session.Dialect >= 0x0210:
1082 # Use IOCTLRequest
1083 pkt = SMB2_IOCTL_Request(
1084 FileId=self.PipeFileId,
1085 Flags="SMB2_0_IOCTL_IS_FSCTL",
1086 CtlCode="FSCTL_PIPE_TRANSCEIVE",
1087 )
1088 pkt.Input = bytes(x)
1089 resp = self.ins.sr1(pkt, verbose=0, chainCC=True)
1090 if SMB2_IOCTL_Response not in resp:
1091 raise ValueError("Failed reading IOCTL_Response ! %s" % resp.NTStatus)
1092 data = bytes(resp.Output)
1093 super(SMB_RPC_SOCKET, self).send(data)
1094
1095 # Handle BUFFER_OVERFLOW (big DCE/RPC response)
1096 while resp.NTStatus == "STATUS_BUFFER_OVERFLOW" or data[3] & 2 != 2:
1097 # Retrieve DCE/RPC full size
1098 resp = self.ins.sr1(
1099 SMB2_Read_Request(
1100 FileId=self.PipeFileId,
1101 ),
1102 verbose=0,
1103 )
1104 data = resp.Data
1105 super(SMB_RPC_SOCKET, self).send(data)
1106 else:
1107 # Use WriteRequest/ReadRequest
1108 pkt = SMB2_Write_Request(
1109 FileId=self.PipeFileId,
1110 )
1111 pkt.Data = bytes(x)
1112 # We send the Write Request
1113 resp = self.ins.sr1(pkt, verbose=0)
1114 if SMB2_Write_Response not in resp:
1115 raise ValueError("Failed sending WriteResponse ! %s" % resp.NTStatus)
1116
1117 # We may not be expecting an answer
1118 if not is_sr1:
1119 return
1120
1121 # If fragmented, only read if it's the last.
1122 if is_frag and not x.pfc_flags.PFC_LAST_FRAG:
1123 return
1124
1125 # We send a Read Request afterwards
1126 resp = self.ins.sr1(
1127 SMB2_Read_Request(
1128 FileId=self.PipeFileId,
1129 ),
1130 verbose=0,
1131 )
1132 if SMB2_Read_Response not in resp:
1133 raise ValueError("Failed reading ReadResponse ! %s" % resp.NTStatus)
1134 super(SMB_RPC_SOCKET, self).send(resp.Data)
1135 # Handle fragmented response
1136 while resp.Data[3] & 2 != 2: # PFC_LAST_FRAG not set
1137 # Retrieve DCE/RPC full size
1138 resp = self.ins.sr1(
1139 SMB2_Read_Request(
1140 FileId=self.PipeFileId,
1141 ),
1142 verbose=0,
1143 )
1144 super(SMB_RPC_SOCKET, self).send(resp.Data)
1145
1146 def close(self):
1147 SMB_SOCKET.close(self)
1148 ObjectPipe.close(self)
1149
1150
1151@conf.commands.register
1152class smbclient(CLIUtil):
1153 r"""
1154 A simple SMB client CLI powered by Scapy
1155
1156 :param target: can be a hostname, the IPv4 or the IPv6 to connect to
1157 :param UPN: the upn to use (DOMAIN/USER, DOMAIN\USER, USER@DOMAIN or USER)
1158 :param guest: use guest mode (over NTLM)
1159 :param ssp: if provided, use this SSP for auth.
1160 :param kerberos_required: require kerberos
1161 :param port: the TCP port. default 445
1162 :param password: if provided, used for auth
1163 :param HashNt: if provided, used for auth (NTLM)
1164 :param HashAes256Sha96: if provided, used for auth (Kerberos)
1165 :param HashAes128Sha96: if provided, used for auth (Kerberos)
1166 :param use_krb5ccname: (bool) if true, the KRB5CCNAME environment variable will
1167 be used if available.
1168 :param use_winssp: (bool) (only works on Windows). Use implicit authentication
1169 through WinSSP.
1170 :param ST: if provided, the service ticket to use (Kerberos)
1171 :param KEY: if provided, the session key associated to the ticket (Kerberos)
1172 :param cli: CLI mode (default True). False to use for scripting
1173
1174 Some additional SMB parameters are available under help(SMB_Client). Some of
1175 them include the following:
1176
1177 :param REQUIRE_ENCRYPTION: requires encryption.
1178 """
1179
1180 def __init__(
1181 self,
1182 target: str,
1183 UPN: str = None,
1184 password: str = None,
1185 guest: bool = False,
1186 kerberos_required: bool = False,
1187 HashNt: bytes = None,
1188 HashAes256Sha96: bytes = None,
1189 HashAes128Sha96: bytes = None,
1190 use_krb5ccname: bool = False,
1191 use_winssp: bool = False,
1192 port: int = 445,
1193 timeout: int = 5,
1194 debug: int = 0,
1195 ssp=None,
1196 ST=None,
1197 KEY=None,
1198 cli=True,
1199 # SMB arguments
1200 REQUIRE_ENCRYPTION=False,
1201 **kwargs,
1202 ):
1203 if cli:
1204 self._depcheck()
1205 assert (
1206 UPN or ssp or guest or use_winssp
1207 ), "Either UPN, ssp or guest must be provided !"
1208 # Do we need to build a SSP?
1209 if ssp is None:
1210 # Create the SSP (only if not guest mode)
1211 if not guest:
1212 ssp = SPNEGOSSP.from_cli_arguments(
1213 UPN=UPN,
1214 target=target,
1215 password=password,
1216 HashNt=HashNt,
1217 HashAes256Sha96=HashAes256Sha96,
1218 HashAes128Sha96=HashAes128Sha96,
1219 ST=ST,
1220 KEY=KEY,
1221 kerberos_required=kerberos_required,
1222 use_krb5ccname=use_krb5ccname,
1223 use_winssp=use_winssp,
1224 )
1225 else:
1226 # Guest mode
1227 ssp = None
1228 # Check if target is IPv4 or IPv6
1229 if ":" in target:
1230 family = socket.AF_INET6
1231 else:
1232 family = socket.AF_INET
1233 # Open socket
1234 sock = socket.socket(family, socket.SOCK_STREAM)
1235 # Configure socket for SMB:
1236 # - TCP KEEPALIVE, TCP_KEEPIDLE and TCP_KEEPINTVL. Against a Windows server this
1237 # isn't necessary, but samba kills the socket VERY fast otherwise.
1238 # - set TCP_NODELAY to disable Nagle's algorithm (we're streaming data)
1239 sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
1240 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
1241 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 10)
1242 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10)
1243 # Timeout & connect
1244 sock.settimeout(timeout)
1245 if debug:
1246 print("Connecting to %s:%s" % (target, port))
1247 sock.connect((target, port))
1248 self.extra_create_options = []
1249 # Wrap with the automaton
1250 self.timeout = timeout
1251 kwargs.setdefault("HOST", target)
1252 self.sock = SMB_Client.from_tcpsock(
1253 sock,
1254 ssp=ssp,
1255 debug=debug,
1256 REQUIRE_ENCRYPTION=REQUIRE_ENCRYPTION,
1257 timeout=timeout,
1258 **kwargs,
1259 )
1260 try:
1261 # Wrap with SMB_SOCKET
1262 self.smbsock = SMB_SOCKET(self.sock, timeout=self.timeout)
1263 # Wait for either the atmt to fail, or the smb_sock_ready to timeout
1264 _t = time.time()
1265 while True:
1266 if self.sock.atmt.smb_sock_ready.is_set():
1267 # yay
1268 break
1269 if not self.sock.atmt.isrunning():
1270 status = self.sock.atmt.get("Status")
1271 raise Scapy_Exception(
1272 "%s with status %s"
1273 % (
1274 self.sock.atmt.state.state,
1275 STATUS_ERREF.get(status, hex(status)),
1276 )
1277 )
1278 if time.time() - _t > timeout:
1279 self.sock.close()
1280 raise TimeoutError("The SMB handshake timed out.")
1281 time.sleep(0.1)
1282 except Exception:
1283 # Something bad happened, end the socket/automaton
1284 self.sock.close()
1285 raise
1286
1287 # For some usages, we will also need the RPC wrapper
1288 from scapy.layers.msrpce.rpcclient import DCERPC_Client
1289
1290 self.rpcclient = DCERPC_Client.from_smblink(
1291 self.sock,
1292 ndr64=False,
1293 verb=bool(debug),
1294 )
1295 # We have a valid smb connection !
1296 print(
1297 "%s authentication successful using %s%s !"
1298 % (
1299 SMB_DIALECTS.get(
1300 self.smbsock.session.Dialect,
1301 "SMB %s" % self.smbsock.session.Dialect,
1302 ),
1303 repr(self.smbsock.session.sspcontext),
1304 " as GUEST" if self.smbsock.session.IsGuest else "",
1305 )
1306 )
1307 # Now define some variables for our CLI
1308 self.pwd = pathlib.PureWindowsPath("/")
1309 self.localpwd = pathlib.Path(".").resolve()
1310 self.current_tree = None
1311 self.ls_cache = {} # cache the listing of the current directory
1312 self.sh_cache = [] # cache the shares
1313 # Start CLI
1314 if cli:
1315 self.loop(debug=debug)
1316
1317 def ps1(self):
1318 return r"smb: \%s> " % self.normalize_path(self.pwd)
1319
1320 def close(self):
1321 print("Connection closed")
1322 self.smbsock.close()
1323
1324 def _require_share(self, silent=False):
1325 if self.current_tree is None:
1326 if not silent:
1327 print("No share selected ! Try 'shares' then 'use'.")
1328 return True
1329
1330 def collapse_path(self, path):
1331 # the amount of pathlib.wtf you need to do to resolve .. on all platforms
1332 # is ridiculous
1333 return pathlib.PureWindowsPath(os.path.normpath(path.as_posix()))
1334
1335 def normalize_path(self, path):
1336 """
1337 Normalize path for CIFS usage
1338 """
1339 return str(self.collapse_path(path)).lstrip("\\")
1340
1341 @CLIUtil.addcommand()
1342 def shares(self):
1343 """
1344 List the shares available
1345 """
1346 # Poll cache
1347 if self.sh_cache:
1348 return self.sh_cache
1349 # It's an RPC
1350 self.rpcclient.open_smbpipe("srvsvc")
1351 self.rpcclient.bind(find_dcerpc_interface("srvsvc"))
1352 req = NetrShareEnum_Request(
1353 InfoStruct=LPSHARE_ENUM_STRUCT(
1354 Level=1,
1355 ShareInfo=NDRUnion(
1356 tag=1,
1357 value=SHARE_INFO_1_CONTAINER(Buffer=None),
1358 ),
1359 ),
1360 PreferedMaximumLength=0xFFFFFFFF,
1361 ndr64=self.rpcclient.ndr64,
1362 )
1363 resp = self.rpcclient.sr1_req(req, timeout=self.timeout)
1364 self.rpcclient.close_smbpipe()
1365 if resp.status != 0:
1366 resp.show()
1367 raise ValueError("NetrShareEnum_Request failed !")
1368 results = []
1369 for share in resp.valueof("InfoStruct.ShareInfo.Buffer"):
1370 shi1_type = share.valueof("shi1_type") & 0x0FFFFFFF
1371 results.append(
1372 (
1373 share.valueof("shi1_netname").decode(),
1374 SRVSVC_SHARE_TYPES.get(shi1_type, shi1_type),
1375 share.valueof("shi1_remark").decode(),
1376 )
1377 )
1378 self.sh_cache = results # cache
1379 return results
1380
1381 @CLIUtil.addoutput(shares)
1382 def shares_output(self, results):
1383 """
1384 Print the output of 'shares'
1385 """
1386 print(pretty_list(results, [("ShareName", "ShareType", "Comment")]))
1387
1388 @CLIUtil.addcommand(mono=True)
1389 def use(self, share):
1390 """
1391 Open a share
1392 """
1393 self.current_tree = self.smbsock.tree_connect(share)
1394 self.pwd = pathlib.PureWindowsPath("/")
1395 self.ls_cache.clear()
1396
1397 @CLIUtil.addcomplete(use)
1398 def use_complete(self, share):
1399 """
1400 Auto-complete 'use'
1401 """
1402 return [
1403 x[0] for x in self.shares() if x[0].startswith(share) and x[0] != "IPC$"
1404 ]
1405
1406 def _parsepath(self, arg, remote=True):
1407 """
1408 Parse a path. Returns the parent folder and file name
1409 """
1410 # Find parent directory if it exists
1411 elt = (pathlib.PureWindowsPath if remote else pathlib.Path)(arg)
1412 eltpar = (pathlib.PureWindowsPath if remote else pathlib.Path)(".")
1413 eltname = elt.name
1414 if arg.endswith("/") or arg.endswith("\\"):
1415 eltpar = elt
1416 eltname = ""
1417 elif elt.parent and elt.parent.name or elt.is_absolute():
1418 eltpar = elt.parent
1419 return eltpar, eltname
1420
1421 def _fs_complete(self, arg, cond=None):
1422 """
1423 Return a listing of the remote files for completion purposes
1424 """
1425 if cond is None:
1426 cond = lambda _: True
1427 eltpar, eltname = self._parsepath(arg)
1428 # ls in that directory
1429 try:
1430 files = self.ls(parent=eltpar)
1431 except ValueError:
1432 return []
1433 return [
1434 str(eltpar / x[0])
1435 for x in files
1436 if (
1437 x[0].lower().startswith(eltname.lower())
1438 and x[0] not in [".", ".."]
1439 and cond(x[1])
1440 )
1441 ]
1442
1443 def _dir_complete(self, arg):
1444 """
1445 Return a directories of remote files for completion purposes
1446 """
1447 results = self._fs_complete(
1448 arg,
1449 cond=lambda x: x.FILE_ATTRIBUTE_DIRECTORY,
1450 )
1451 if len(results) == 1 and results[0].startswith(arg):
1452 # skip through folders
1453 return [results[0] + "\\"]
1454 return results
1455
1456 @CLIUtil.addcommand(mono=True)
1457 def ls(self, parent=None):
1458 """
1459 List the files in the remote directory
1460 -t: sort by timestamp
1461 -S: sort by size
1462 -r: reverse while sorting
1463 """
1464 if self._require_share():
1465 return
1466 # Get pwd of the ls
1467 pwd = self.pwd
1468 if parent is not None:
1469 pwd /= parent
1470 pwd = self.normalize_path(pwd)
1471 # Poll the cache
1472 if self.ls_cache and pwd in self.ls_cache:
1473 return self.ls_cache[pwd]
1474 self.smbsock.set_TID(self.current_tree)
1475 # Open folder
1476 fileId = self.smbsock.create_request(
1477 pwd,
1478 type="folder",
1479 extra_create_options=self.extra_create_options,
1480 )
1481 # Query the folder
1482 files = self.smbsock.query_directory(fileId)
1483 # Close the folder
1484 self.smbsock.close_request(fileId)
1485 self.ls_cache[pwd] = files # Store cache
1486 return files
1487
1488 @CLIUtil.addoutput(ls)
1489 def ls_output(self, results, *, t=False, S=False, r=False):
1490 """
1491 Print the output of 'ls'
1492 """
1493 fld = UTCTimeField(
1494 "", None, fmt="<Q", epoch=[1601, 1, 1, 0, 0, 0], custom_scaling=1e7
1495 )
1496 if t:
1497 # Sort by time
1498 results.sort(key=lambda x: -x[3])
1499 if S:
1500 # Sort by size
1501 results.sort(key=lambda x: -x[2])
1502 if r:
1503 # Reverse sort
1504 results = results[::-1]
1505 results = [
1506 (
1507 x[0],
1508 "+".join(y.lstrip("FILE_ATTRIBUTE_") for y in str(x[1]).split("+")),
1509 human_size(x[2]),
1510 fld.i2repr(None, x[3]),
1511 )
1512 for x in results
1513 ]
1514 print(
1515 pretty_list(
1516 results,
1517 [("FileName", "FileAttributes", "EndOfFile", "LastWriteTime")],
1518 sortBy=None,
1519 )
1520 )
1521
1522 @CLIUtil.addcomplete(ls)
1523 def ls_complete(self, folder):
1524 """
1525 Auto-complete ls
1526 """
1527 if self._require_share(silent=True):
1528 return []
1529 return self._dir_complete(folder)
1530
1531 @CLIUtil.addcommand(mono=True)
1532 def cd(self, folder):
1533 """
1534 Change the remote current directory
1535 """
1536 if self._require_share():
1537 return
1538 if not folder:
1539 # show mode
1540 return str(self.pwd)
1541 self.pwd /= folder
1542 self.pwd = self.collapse_path(self.pwd)
1543 self.ls_cache.clear()
1544
1545 @CLIUtil.addcomplete(cd)
1546 def cd_complete(self, folder):
1547 """
1548 Auto-complete cd
1549 """
1550 if self._require_share(silent=True):
1551 return []
1552 return self._dir_complete(folder)
1553
1554 def _lfs_complete(self, arg, cond):
1555 """
1556 Return a listing of local files for completion purposes
1557 """
1558 eltpar, eltname = self._parsepath(arg, remote=False)
1559 eltpar = self.localpwd / eltpar
1560 return [
1561 # trickery so that ../<TAB> works
1562 str(eltpar / x.name)
1563 for x in eltpar.resolve().glob("*")
1564 if (x.name.lower().startswith(eltname.lower()) and cond(x))
1565 ]
1566
1567 @CLIUtil.addoutput(cd)
1568 def cd_output(self, result):
1569 """
1570 Print the output of 'cd'
1571 """
1572 if result:
1573 print(result)
1574
1575 @CLIUtil.addcommand()
1576 def lls(self):
1577 """
1578 List the files in the local directory
1579 """
1580 return list(self.localpwd.glob("*"))
1581
1582 @CLIUtil.addoutput(lls)
1583 def lls_output(self, results):
1584 """
1585 Print the output of 'lls'
1586 """
1587 results = [
1588 (
1589 x.name,
1590 human_size(stat.st_size),
1591 time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(stat.st_mtime)),
1592 )
1593 for x, stat in ((x, x.stat()) for x in results)
1594 ]
1595 print(
1596 pretty_list(results, [("FileName", "File Size", "Last Modification Time")])
1597 )
1598
1599 @CLIUtil.addcommand(mono=True)
1600 def lcd(self, folder):
1601 """
1602 Change the local current directory
1603 """
1604 if not folder:
1605 # show mode
1606 return str(self.localpwd)
1607 self.localpwd /= folder
1608 self.localpwd = self.localpwd.resolve()
1609
1610 @CLIUtil.addcomplete(lcd)
1611 def lcd_complete(self, folder):
1612 """
1613 Auto-complete lcd
1614 """
1615 return self._lfs_complete(folder, lambda x: x.is_dir())
1616
1617 @CLIUtil.addoutput(lcd)
1618 def lcd_output(self, result):
1619 """
1620 Print the output of 'lcd'
1621 """
1622 if result:
1623 print(result)
1624
1625 def _get_file(self, file, fd):
1626 """
1627 Gets the file bytes from a remote host
1628 """
1629 # Get pwd of the ls
1630 fpath = self.pwd / file
1631 self.smbsock.set_TID(self.current_tree)
1632
1633 # Open file
1634 fileId = self.smbsock.create_request(
1635 self.normalize_path(fpath),
1636 type="file",
1637 extra_create_options=[
1638 "FILE_SEQUENTIAL_ONLY",
1639 ]
1640 + self.extra_create_options,
1641 )
1642
1643 # Get the file size
1644 info = FileAllInformation(
1645 self.smbsock.query_info(
1646 FileId=fileId,
1647 InfoType="SMB2_0_INFO_FILE",
1648 FileInfoClass="FileAllInformation",
1649 )
1650 )
1651 length = info.StandardInformation.EndOfFile
1652 offset = 0
1653
1654 # Read the file
1655 while length:
1656 lengthRead = min(self.smbsock.session.MaxReadSize, length)
1657 fd.write(
1658 self.smbsock.read_request(fileId, Length=lengthRead, Offset=offset)
1659 )
1660 offset += lengthRead
1661 length -= lengthRead
1662
1663 # Close the file
1664 self.smbsock.close_request(fileId)
1665 return offset
1666
1667 def _send_file(self, fname, fd):
1668 """
1669 Send the file bytes to a remote host
1670 """
1671 # Get destination file
1672 fpath = self.pwd / fname
1673 self.smbsock.set_TID(self.current_tree)
1674 # Open file
1675 fileId = self.smbsock.create_request(
1676 self.normalize_path(fpath),
1677 type="file",
1678 mode="w",
1679 extra_create_options=self.extra_create_options,
1680 )
1681 # Send the file
1682 offset = 0
1683 while True:
1684 data = fd.read(self.smbsock.session.MaxWriteSize)
1685 if not data:
1686 # end of file
1687 break
1688 offset += self.smbsock.write_request(
1689 Data=data,
1690 FileId=fileId,
1691 Offset=offset,
1692 )
1693 # Close the file
1694 self.smbsock.close_request(fileId)
1695 return offset
1696
1697 def _listr(self, directory, regx=None, max_depth=None, depth=0, _verb=True):
1698 """
1699 Internal recursive function that yields the remote files
1700 """
1701 if max_depth is not None and depth > max_depth:
1702 return
1703 for x in self.ls(parent=directory):
1704 if x[0] in [".", ".."]:
1705 # Discard . and ..
1706 continue
1707 remote = directory / x[0]
1708 try:
1709 if x[1].FILE_ATTRIBUTE_DIRECTORY:
1710 # Sub-directory
1711 yield from self._listr(
1712 remote,
1713 regx=regx,
1714 depth=depth + 1,
1715 max_depth=max_depth,
1716 _verb=_verb,
1717 )
1718 else:
1719 # Sub-file
1720 if regx is None or regx.match(str(remote)):
1721 yield remote
1722 except ValueError as ex:
1723 if _verb:
1724 print(conf.color_theme.red(directory), "->", str(ex))
1725
1726 def _getr(self, directory, _root, _verb=True):
1727 """
1728 Internal recursive function to get a directory
1729
1730 :param directory: the remote directory to get
1731 :param _root: locally, the directory to store any found files
1732 """
1733 size = 0
1734 if not _root.exists():
1735 _root.mkdir()
1736 # ls the directory
1737 for x in self.ls(parent=directory):
1738 if x[0] in [".", ".."]:
1739 # Discard . and ..
1740 continue
1741 remote = directory / x[0]
1742 local = _root / x[0]
1743 try:
1744 if x[1].FILE_ATTRIBUTE_DIRECTORY:
1745 # Sub-directory
1746 size += self._getr(remote, local)
1747 else:
1748 # Sub-file
1749 size += self.get(remote, local)[1]
1750 if _verb:
1751 print(remote)
1752 except ValueError as ex:
1753 if _verb:
1754 print(conf.color_theme.red(remote), "->", str(ex))
1755 return size
1756
1757 @CLIUtil.addcommand(mono=True, globsupport=True)
1758 def get(self, file, _dest=None, _verb=True, *, r=False):
1759 """
1760 Retrieve a file
1761 -r: recursively download a directory
1762 """
1763 if self._require_share():
1764 return
1765 if r:
1766 dirpar, dirname = self._parsepath(file)
1767 return file, self._getr(
1768 dirpar / dirname, # Remotely
1769 _root=self.localpwd / dirname, # Locally
1770 _verb=_verb,
1771 )
1772 else:
1773 fname = pathlib.PureWindowsPath(file).name
1774 # Write the buffer
1775 if _dest is None:
1776 _dest = self.localpwd / fname
1777 if not _dest.parent.exists():
1778 _dest.parent.mkdir()
1779 with _dest.open("wb") as fd:
1780 size = self._get_file(file, fd)
1781 return fname, size
1782
1783 @CLIUtil.addoutput(get)
1784 def get_output(self, info):
1785 """
1786 Print the output of 'get'
1787 """
1788 print("Retrieved '%s' of size %s" % (info[0], human_size(info[1])))
1789
1790 @CLIUtil.addcomplete(get)
1791 def get_complete(self, file):
1792 """
1793 Auto-complete get
1794 """
1795 if self._require_share(silent=True):
1796 return []
1797 return self._fs_complete(file)
1798
1799 @CLIUtil.addcommand()
1800 def tree(self, regx: str = None, max_depth=None):
1801 """
1802 Show the tree of files from the current directory
1803 """
1804 if self._require_share():
1805 return
1806 if regx is not None:
1807 regx = re.compile(regx)
1808 if max_depth is not None:
1809 max_depth = int(max_depth)
1810 return self._listr(self.pwd, regx=regx, max_depth=max_depth)
1811
1812 @CLIUtil.addoutput(tree)
1813 def tree_output(self, result):
1814 """
1815 Print the output of 'tree'
1816 """
1817 if self._require_share(silent=True):
1818 return
1819 for file in result:
1820 print(str(file), flush=True)
1821
1822 @CLIUtil.addcommand()
1823 def server(self, command):
1824 """
1825 Get information about the remote server
1826 """
1827 if command == "network":
1828 # We get the list of interfaces
1829 if self._require_share():
1830 return
1831 resp = self.smbsock.ioctl(CtlCode="FSCTL_QUERY_NETWORK_INTERFACE_INFO")
1832 return (command, resp)
1833 elif command == "sessions":
1834 # We get the list of active connections via RPC
1835 self.rpcclient.open_smbpipe("srvsvc")
1836 self.rpcclient.bind(find_dcerpc_interface("srvsvc"))
1837 ResumeHandle = None
1838 results = []
1839 while True:
1840 req = NetrSessionEnum_Request(
1841 ClientName=None,
1842 UserName=None,
1843 InfoStruct=PSESSION_ENUM_STRUCT(
1844 Level=502,
1845 SessionInfo=NDRUnion(
1846 tag=502,
1847 value=SESSION_INFO_502_CONTAINER(Buffer=None),
1848 ),
1849 ),
1850 PreferedMaximumLength=0xFFFFFFFF,
1851 ResumeHandle=ResumeHandle,
1852 )
1853 resp = self.rpcclient.sr1_req(req, timeout=self.timeout)
1854 resp.show()
1855 ResumeHandle = resp.ResumeHandle
1856 if resp.status in [0, 0x000000EA]: # ERROR_SUCCESS / ERROR_MORE_DATA
1857 results.extend(resp.valueof("InfoStruct.SessionInfo.Buffer"))
1858 if resp.status == 0:
1859 break
1860 else:
1861 break
1862 self.rpcclient.close_smbpipe()
1863 return (command, results)
1864 else:
1865 raise ValueError("Unknown server command: %s" % command)
1866
1867 @CLIUtil.addoutput(server)
1868 def server_output(self, result):
1869 """
1870 Print the output of 'server'
1871 """
1872 command, result = result
1873 if command == "network":
1874 # Show the various interfaces, their IP and capabilities
1875 result.interfaces = [x for x in result.interfaces if x.IfIndex != 0]
1876 result.show()
1877 elif command == "sessions":
1878 # Show the list of active sessions
1879 for sess in result:
1880 print("- cname:", sess.valueof("sesi502_cname"))
1881 print(" username:", sess.valueof("sesi502_username"))
1882 print(" num_opens:", sess.valueof("sesi502_num_opens"))
1883 print(" time:", sess.valueof("sesi502_time"))
1884 print(" idle_time:", sess.valueof("sesi502_idle_time"))
1885 print(" user_flags:", sess.valueof("sesi502_user_flags"))
1886 print(" cltype_name:", sess.valueof("sesi502_cltype_name"))
1887 print(" transport:", sess.valueof("sesi502_transport"))
1888
1889 @CLIUtil.addcomplete(server)
1890 def server_complete(self, line):
1891 """
1892 Auto-complete server
1893 """
1894 return ["network", "sessions"]
1895
1896 @CLIUtil.addcommand(mono=True, globsupport=True)
1897 def cat(self, file):
1898 """
1899 Print a file
1900 """
1901 if self._require_share():
1902 return
1903 # Write the buffer to buffer
1904 buf = io.BytesIO()
1905 self._get_file(file, buf)
1906 return buf.getvalue()
1907
1908 @CLIUtil.addoutput(cat)
1909 def cat_output(self, result):
1910 """
1911 Print the output of 'cat'
1912 """
1913 print(result.decode(errors="backslashreplace"))
1914
1915 @CLIUtil.addcomplete(cat)
1916 def cat_complete(self, file):
1917 """
1918 Auto-complete cat
1919 """
1920 if self._require_share(silent=True):
1921 return []
1922 return self._fs_complete(file)
1923
1924 @CLIUtil.addcommand(mono=True, globsupport=True)
1925 def put(self, file):
1926 """
1927 Upload a file
1928 """
1929 if self._require_share():
1930 return
1931 local_file = self.localpwd / file
1932 if local_file.is_dir():
1933 # Directory
1934 raise ValueError("put on dir not impl")
1935 else:
1936 fname = pathlib.Path(file).name
1937 with local_file.open("rb") as fd:
1938 size = self._send_file(fname, fd)
1939 self.ls_cache.clear()
1940 return fname, size
1941
1942 @CLIUtil.addcomplete(put)
1943 def put_complete(self, folder):
1944 """
1945 Auto-complete put
1946 """
1947 return self._lfs_complete(folder, lambda x: not x.is_dir())
1948
1949 @CLIUtil.addcommand(mono=True)
1950 def rm(self, file):
1951 """
1952 Delete a file
1953 """
1954 if self._require_share():
1955 return
1956 # Get pwd of the ls
1957 fpath = self.pwd / file
1958 self.smbsock.set_TID(self.current_tree)
1959 # Open file
1960 fileId = self.smbsock.create_request(
1961 self.normalize_path(fpath),
1962 type="file",
1963 mode="d",
1964 extra_create_options=self.extra_create_options,
1965 )
1966 # Close the file
1967 self.smbsock.close_request(fileId)
1968 self.ls_cache.clear()
1969 return fpath.name
1970
1971 @CLIUtil.addcomplete(rm)
1972 def rm_complete(self, file):
1973 """
1974 Auto-complete rm
1975 """
1976 if self._require_share(silent=True):
1977 return []
1978 return self._fs_complete(file)
1979
1980 @CLIUtil.addcommand()
1981 def backup(self):
1982 """
1983 Turn on or off backup intent
1984 """
1985 if "FILE_OPEN_FOR_BACKUP_INTENT" in self.extra_create_options:
1986 print("Backup Intent: Off")
1987 self.extra_create_options.remove("FILE_OPEN_FOR_BACKUP_INTENT")
1988 else:
1989 print("Backup Intent: On")
1990 self.extra_create_options.append("FILE_OPEN_FOR_BACKUP_INTENT")
1991
1992 @CLIUtil.addcommand(mono=True)
1993 def watch(self, folder):
1994 """
1995 Watch file changes in folder (recursively)
1996 """
1997 if self._require_share():
1998 return
1999 # Get pwd of the ls
2000 fpath = self.pwd / folder
2001 self.smbsock.set_TID(self.current_tree)
2002 # Open file
2003 fileId = self.smbsock.create_request(
2004 self.normalize_path(fpath),
2005 type="folder",
2006 extra_create_options=self.extra_create_options,
2007 )
2008 print("Watching '%s'" % fpath)
2009 # Watch for changes
2010 try:
2011 while True:
2012 changes = self.smbsock.changenotify(fileId)
2013 for chg in changes:
2014 print(chg.sprintf("%.time%: %Action% %FileName%"))
2015 except KeyboardInterrupt:
2016 pass
2017 print("Cancelled.")
2018
2019 @CLIUtil.addcommand(mono=True)
2020 def getsd(self, file):
2021 """
2022 Get the Security Descriptor
2023 """
2024 if self._require_share():
2025 return
2026 fpath = self.pwd / file
2027 self.smbsock.set_TID(self.current_tree)
2028 # Open file
2029 fileId = self.smbsock.create_request(
2030 self.normalize_path(fpath),
2031 type="",
2032 mode="",
2033 extra_desired_access=["READ_CONTROL", "ACCESS_SYSTEM_SECURITY"],
2034 )
2035 # Get the file size
2036 info = self.smbsock.query_info(
2037 FileId=fileId,
2038 InfoType="SMB2_0_INFO_SECURITY",
2039 FileInfoClass=0,
2040 AdditionalInformation=(
2041 0x00000001
2042 | 0x00000002
2043 | 0x00000004
2044 | 0x00000008
2045 | 0x00000010
2046 | 0x00000020
2047 | 0x00000040
2048 | 0x00010000
2049 ),
2050 )
2051 self.smbsock.close_request(fileId)
2052 return info
2053
2054 @CLIUtil.addcomplete(getsd)
2055 def getsd_complete(self, file):
2056 """
2057 Auto-complete getsd
2058 """
2059 if self._require_share(silent=True):
2060 return []
2061 return self._fs_complete(file)
2062
2063 @CLIUtil.addoutput(getsd)
2064 def getsd_output(self, results):
2065 """
2066 Print the output of 'getsd'
2067 """
2068 sd = SECURITY_DESCRIPTOR(results)
2069 sd.show_print()
2070
2071
2072if __name__ == "__main__":
2073 from scapy.utils import AutoArgparse
2074
2075 AutoArgparse(smbclient)