1# -*- coding: utf-8 -*-
2# Copyright: (c) 2019, Jordan Borean (@jborean93) <jborean93@gmail.com>
3# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
4
5import binascii
6import socket
7
8from collections import (
9    OrderedDict,
10)
11
12from smbprotocol import (
13    Dialects,
14)
15
16from smbprotocol.connection import (
17    Capabilities,
18    SecurityMode,
19)
20
21from smbprotocol.header import (
22    Commands,
23)
24
25from smbprotocol.structure import (
26    BytesField,
27    EnumField,
28    FlagField,
29    IntField,
30    ListField,
31    Structure,
32    StructureField,
33    UuidField,
34)
35
36
37class CtlCode(object):
38    """
39    [MS-SMB2] v53.0 2017-09-15
40
41    2.2.31 SMB2 IOCTL Request CtlCode
42    The control code of the FSCTL_IOCTL method.
43    """
44    FSCTL_DFS_GET_REFERRALS = 0x00060194
45    FSCTL_PIPE_PEEK = 0x0011400C
46    FSCTL_PIPE_WAIT = 0x00110018
47    FSCTL_PIPE_TRANSCEIVE = 0x0011C017
48    FSCTL_SRV_COPYCHUNK = 0x001440F2
49    FSCTL_SRV_ENUMERATE_SNAPSHOTS = 0x00144064
50    FSCTL_SRV_REQUEST_RESUME_KEY = 0x00140078
51    FSCTL_SRV_READ_HASH = 0x001441bb
52    FSCTL_SRV_COPYCHUNK_WRITE = 0x001480F2
53    FSCTL_LMR_REQUEST_RESILIENCY = 0x001401D4
54    FSCTL_QUERY_NETWORK_INTERFACE_INFO = 0x001401FC
55    FSCTL_SET_REPARSE_POINT = 0x000900A4
56    FSCTL_GET_REPARSE_POINT = 0x000900A8
57    FSCTL_DELETE_REPARSE_POINT = 0x000900AC
58    FSCTL_CREATE_OR_GET_OBJECT_ID = 0x000900C0
59    FSCTL_SET_SPARSE = 0x000900C4
60    FSCTL_QUERY_ALLOCATED_RANGES = 0x000940CF
61    FSCTL_DFS_GET_REFERRALS_EX = 0x000601B0
62    FSCTL_SET_ZERO_DATA = 0x000980C8
63    FSCTL_FILE_LEVEL_TRIM = 0x00098208
64    FSCTL_VALIDATE_NEGOTIATE_INFO = 0x00140204
65
66
67class IOCTLFlags(object):
68    """
69    [MS-SMB2] v53.0 2017-09-15
70
71    2.2.31 SMB2 IOCTL Request Flags
72    A flag that indicates how to process the operation
73    """
74    SMB2_0_IOCTL_IS_IOCTL = 0x00000000
75    SMB2_0_IOCTL_IS_FSCTL = 0x00000001
76
77
78class HashVersion(object):
79    """
80    [MS-SMB2] v53.0 2017-09-15
81
82    2.2.31.2 SRV_READ_HASH Request HashVersion
83    The version number of the algorithm used to create the Content Information.
84    """
85    SRV_HASH_VER_1 = 0x00000001
86    SRV_HASH_VER_2 = 0x00000002
87
88
89class HashRetrievalType(object):
90    """
91    [MS-SMB2] v53.0 2017-09-15
92
93    2.2.31.2 SRV_READ_HASH Request HashRetrievalType
94    Indicates the nature of the Offset field in am SMB2SrvReadHashRequest
95    packet.
96    """
97    SRV_HASH_RETRIEVE_HASH_BASED = 0x00000001
98    SRV_HASH_RETRIEVE_FILE_BASED = 0x00000002
99
100
101class IfCapability(object):
102    """
103    [MS-SMB2] v53.0 2017-09-15
104
105    2.2.32.5 NETWORK_INTERFACE_INFO Response Capability
106    The capabilities of the network interface
107    """
108    RSS_CAPABLE = 0x00000001
109    RDMA_CAPABLE = 0x00000002
110
111
112class SockAddrFamily(object):
113    """
114    [MS-SMB2] v53.0 2017-09-15
115
116    2.2.32.5.1 SOCKADDR_STORAGE Family
117    The address family of the socket.
118    """
119    INTER_NETWORK = 0x0002
120    INTER_NETWORK_V6 = 0x0017
121
122
123class SMB2IOCTLRequest(Structure):
124    """
125    [MS-SMB2] v53.0 2017-09-15
126
127    2.2.31 SMB2 IOCTL Request
128    Send by the client to issue an implementation-specific file system control
129    or device control command across the network.
130    """
131    COMMAND = Commands.SMB2_IOCTL
132
133    def __init__(self):
134        self.fields = OrderedDict([
135            ('structure_size', IntField(size=2, default=57)),
136            ('reserved', IntField(size=2, default=0)),
137            ('ctl_code', EnumField(
138                size=4,
139                enum_type=CtlCode,
140            )),
141            ('file_id', BytesField(size=16)),
142            ('input_offset', IntField(
143                size=4,
144                default=lambda s: self._buffer_offset_value(s)
145            )),
146            ('input_count', IntField(
147                size=4,
148                default=lambda s: len(s['buffer']),
149            )),
150            ('max_input_response', IntField(size=4)),
151            ('output_offset', IntField(
152                size=4,
153                default=lambda s: self._buffer_offset_value(s)
154            )),
155            ('output_count', IntField(size=4, default=0)),
156            ('max_output_response', IntField(size=4)),
157            ('flags', EnumField(
158                size=4,
159                enum_type=IOCTLFlags,
160            )),
161            ('reserved2', IntField(size=4, default=0)),
162            ('buffer', BytesField(
163                size=lambda s: s['input_count'].get_value()
164            ))
165        ])
166        super(SMB2IOCTLRequest, self).__init__()
167
168    def _buffer_offset_value(self, structure):
169        # The offset from the beginning of the SMB2 header to the value of the
170        # buffer, 0 if no buffer is set
171        if len(structure['buffer']) > 0:
172            header_size = 64
173            request_size = structure['structure_size'].get_value()
174            return header_size + request_size - 1
175        else:
176            return 0
177
178
179class SMB2SrvCopyChunkCopy(Structure):
180    """
181    [MS-SMB2] v53.0 2017-09-15
182
183    2.2.31.1 SRV_COPYCHUNK_COPY
184    Sent in an SMB2 IOCTL Request by the client to initiate a server-side copy
185    of data.
186    """
187
188    def __init__(self):
189        self.fields = OrderedDict([
190            ('source_key', BytesField(size=24)),
191            ('chunk_count', IntField(
192                size=4,
193                default=lambda s: len(s['chunks'].get_value())
194            )),
195            ('reserved', IntField(size=4)),
196            ('chunks', ListField(
197                size=lambda s: s['chunk_count'].get_value() * 24,
198                list_count=lambda s: s['chunk_count'].get_value(),
199                list_type=StructureField(
200                    size=24,
201                    structure_type=SMB2SrvCopyChunk
202                )
203            ))
204        ])
205        super(SMB2SrvCopyChunkCopy, self).__init__()
206
207
208class SMB2SrvCopyChunk(Structure):
209    """
210    [MS-SMB2] v53.0 2017-09-15
211
212    2.2.31.1.1 SRC_COPYCHUNK
213    Packet sent in the Chunks array of an SRC_COPY_CHUNK_COPY packet to
214    describe an individual data range to copy.
215    """
216
217    def __init__(self):
218        self.fields = OrderedDict([
219            ('source_offset', IntField(size=8)),
220            ('target_offset', IntField(size=8)),
221            ('length', IntField(size=4)),
222            ('reserved', IntField(size=4))
223        ])
224        super(SMB2SrvCopyChunk, self).__init__()
225
226
227class SMB2SrvReadHashRequest(Structure):
228    """
229    [MS-SMB2] v53.0 2017-09-15
230
231    2.2.31.2 SRC_READ_HASH Request
232    Sent by the client in an SMB2 IOCTL Request to retrieve data from the
233    Content Information File associated with a specified file.
234    Not valid for the SMB 2.0.2 dialect.
235    """
236
237    def __init__(self):
238        self.fields = OrderedDict([
239            ('hash_type', IntField(
240                size=4,
241                default=1  # SRV_HASH_TYPE_PEER_DIST
242            )),
243            ('hash_version', EnumField(
244                size=4,
245                enum_type=HashVersion
246            )),
247            ('hash_retrieval_type', EnumField(
248                size=4,
249                enum_type=HashRetrievalType
250            )),
251            ('length', IntField(size=4)),
252            ('offset', IntField(size=8))
253        ])
254        super(SMB2SrvReadHashRequest, self).__init__()
255
256
257class SMB2SrvNetworkResiliencyRequest(Structure):
258    """
259    [MS-SMB2] v53.0 2017-09-15
260
261    2.2.31.3 NETWORK_RESILIENCY_REQUEST Request
262    Sent by the client to request resiliency for a specified open file.
263    Not valid for the SMB 2.0.2 dialect.
264    """
265
266    def __init__(self):
267        self.fields = OrderedDict([
268            # timeout is in milliseconds
269            ('timeout', IntField(size=4)),
270            ('reserved', IntField(size=4))
271        ])
272        super(SMB2SrvNetworkResiliencyRequest, self).__init__()
273
274
275class SMB2ValidateNegotiateInfoRequest(Structure):
276    """
277    [MS-SMB2] v53.0 2017-09-15
278
279    2.2.31.4 VALIDATE_NEGOTIATE_INFO Request
280    Packet sent to the server to request validation of a previous SMB 2
281    NEGOTIATE request.
282    Only valid for the SMB 3.0 and 3.0.2 dialects.
283    """
284
285    def __init__(self):
286        self.fields = OrderedDict([
287            ('capabilities', FlagField(
288                size=4,
289                flag_type=Capabilities,
290            )),
291            ('guid', UuidField()),
292            ('security_mode', EnumField(
293                size=2,
294                enum_type=SecurityMode,
295            )),
296            ('dialect_count', IntField(
297                size=2,
298                default=lambda s: len(s['dialects'].get_value())
299            )),
300            ('dialects', ListField(
301                size=lambda s: s['dialect_count'].get_value() * 2,
302                list_count=lambda s: s['dialect_count'].get_value(),
303                list_type=EnumField(size=2, enum_type=Dialects),
304            ))
305        ])
306        super(SMB2ValidateNegotiateInfoRequest, self).__init__()
307
308
309class SMB2IOCTLResponse(Structure):
310    """
311    [MS-SMB2] v53.0 2017-09-15
312
313    2.2.32 SMB2 IOCTL Response
314    Sent by the server to transmit the results of a client SMB2 IOCTL Request.
315    """
316    COMMAND = Commands.SMB2_IOCTL
317
318    def __init__(self):
319        self.fields = OrderedDict([
320            ('structure_size', IntField(size=2, default=49)),
321            ('reserved', IntField(size=2, default=0)),
322            ('ctl_code', EnumField(
323                size=4,
324                enum_type=CtlCode,
325            )),
326            ('file_id', BytesField(size=16)),
327            ('input_offset', IntField(size=4)),
328            ('input_count', IntField(size=4)),
329            ('output_offset', IntField(size=4)),
330            ('output_count', IntField(size=4)),
331            ('flags', IntField(size=4, default=0)),
332            ('reserved2', IntField(size=4, default=0)),
333            ('buffer', BytesField(
334                size=lambda s: s['output_count'].get_value(),
335            ))
336        ])
337        super(SMB2IOCTLResponse, self).__init__()
338
339
340class SMB2SrvCopyChunkResponse(Structure):
341    """
342    [MS-SMB2] v53.0 2017-09-15
343
344    2.2.32.1 SRV_COPYCHUNK_RESPONSE
345    """
346
347    def __init__(self):
348        self.fields = OrderedDict([
349            ('chunks_written', IntField(size=4)),
350            ('chunk_bytes_written', IntField(size=4)),
351            ('total_bytes_written', IntField(size=4))
352        ])
353        super(SMB2SrvCopyChunkResponse, self).__init__()
354
355
356class SMB2SrvSnapshotArray(Structure):
357    """
358    [MS-SMB2] v53.0 2017-09-15
359
360    2.2.32.2 SRV_SNAPSHOT_ARRAY
361    Sent by the server in response to an SMB2IOCTLResponse for the
362    FSCTL_SRV_ENUMERATE_SNAPSHOTS request.
363    """
364
365    def __init__(self):
366        # TODO: validate this further when working with actual snapshots
367        self.fields = OrderedDict([
368            ('number_of_snapshots', IntField(size=4)),
369            ('number_of_snapshots_returned', IntField(size=4)),
370            ('snapshot_array_size', IntField(size=4)),
371            ('snapshots', BytesField())
372        ])
373        super(SMB2SrvSnapshotArray, self).__init__()
374
375
376class SMB2SrvRequestResumeKey(Structure):
377    """
378    [MS-SMB2] v53.0 2017-09-15
379
380    2.2.32.3 SRV_REQUEST_RESUME_KEY Response
381    Sent by the server in response to an SMB2IOCTLResponse for the
382    FSCTL_SRV_REQUEST_RESUME_KEY request.
383    """
384
385    def __init__(self):
386        self.fields = OrderedDict([
387            ('resume_key', BytesField(size=24)),
388            ('context_length', IntField(
389                size=4,
390                default=lambda s: len(s['context']),
391            )),
392            ('context', BytesField(
393                size=lambda s: s['context_length'].get_value(),
394            )),
395        ])
396        super(SMB2SrvRequestResumeKey, self).__init__()
397
398
399class SMB2NetworkInterfaceInfo(Structure):
400    """
401    [MS-SMB2] v53.0 2017-09-15
402
403    2.2.32.5 NETWORK_INTERFACE_INFO Response
404    The NETWORK_INTERFACE_INFO returned to the client in an SMB2IOCTLResposne
405    for the FSCTL_QUERY_NETWORK_INTERFACE_INFO.
406
407    Use the pack_multiple and unpack_multiple to handle multiple interfaces
408    that are returned in the SMB2IOCTLResponse
409    """
410
411    def __init__(self):
412        self.fields = OrderedDict([
413            # 0 if no more network interfaces
414            ('next', IntField(size=4)),
415            ('if_index', IntField(size=4)),
416            ('capability', FlagField(
417                size=4,
418                flag_type=IfCapability
419            )),
420            ('reserved', IntField(size=4)),
421            ('link_speed', IntField(size=8)),
422            ('sock_addr_storage', StructureField(
423                size=128,
424                structure_type=SockAddrStorage
425            ))
426        ])
427        super(SMB2NetworkInterfaceInfo, self).__init__()
428
429    @staticmethod
430    def pack_multiple(messages):
431        """
432        Packs a list of SMB2NetworkInterfaceInfo messages and set's the next
433        value accordingly. The byte value returned is then attached to the
434        SMBIOCTLResponse message.
435
436        :param messages: List of SMB2NetworkInterfaceInfo messages
437        :return: bytes of the packed messages
438        """
439        data = b""
440        msg_count = len(messages)
441        for i, msg in enumerate(messages):
442            if i == msg_count - 1:
443                msg['next'] = 0
444            else:
445                msg['next'] = 152
446            data += msg.pack()
447        return data
448
449    @staticmethod
450    def unpack_multiple(data):
451        """
452        Get's a list of SMB2NetworkInterfaceInfo messages from the byte value
453        passed in. This is the raw buffer value that is set on the
454        SMB2IOCTLResponse message.
455
456        :param data: bytes of the messages
457        :return: List of SMB2NetworkInterfaceInfo messages
458        """
459        chunks = []
460        while data:
461            info = SMB2NetworkInterfaceInfo()
462            data = info.unpack(data)
463            chunks.append(info)
464
465        return chunks
466
467
468class SockAddrStorage(Structure):
469    """
470    [MS-SMB2] v53.0 2017-09-15
471
472    2.2.32.5.1 SOCKADDR_STORAGE
473    Socket Address information.
474    """
475
476    def __init__(self):
477        self.fields = OrderedDict([
478            ('family', EnumField(
479                size=2,
480                enum_type=SockAddrFamily
481            )),
482            ('buffer', StructureField(
483                size=lambda s: self._get_buffer_size(s),
484                structure_type=lambda s: self._get_buffer_structure_type(s)
485            )),
486            ('reserved', BytesField(
487                size=lambda s: self._get_reserved_size(s),
488                default=lambda s: b"\x00" * self._get_reserved_size(s)
489            ))
490        ])
491        super(SockAddrStorage, self).__init__()
492
493    def _get_buffer_size(self, structure):
494        if structure['family'].get_value() == SockAddrFamily.INTER_NETWORK:
495            return 14
496        else:
497            return 26
498
499    def _get_buffer_structure_type(self, structure):
500        if structure['family'].get_value() == SockAddrFamily.INTER_NETWORK:
501            return SockAddrIn
502        else:
503            return SockAddrIn6
504
505    def _get_reserved_size(self, structure):
506        if structure['family'].get_value() == SockAddrFamily.INTER_NETWORK:
507            return 112
508        else:
509            return 100
510
511
512class SockAddrIn(Structure):
513    """
514    [MS-SMB2] v53.0 2017-09-15
515
516    2.2.32.5.1.1 SOCKADDR_IN
517    Socket address information for an IPv4 address
518    """
519
520    def __init__(self):
521        self.fields = OrderedDict([
522            ('port', IntField(size=2)),
523            ('ipv4_address', BytesField(size=4)),
524            ('reserved', IntField(size=8))
525        ])
526        super(SockAddrIn, self).__init__()
527
528    def get_ipaddress(self):
529        addr_bytes = self['ipv4_address'].get_value()
530        return socket.inet_ntoa(addr_bytes)
531
532    def set_ipaddress(self, address):
533        # set's the ipv4 address field from the address string passed in, this
534        # needs to be the full ipv4 address including periods, e.g.
535        # 192.168.1.1
536        addr_bytes = socket.inet_aton(address)
537        self['ipv4_address'].set_value(addr_bytes)
538
539
540class SockAddrIn6(Structure):
541    """
542    [MS-SMB2] v53.0 2017-09-15
543
544    2.2.32.5.1.2 SOCKADDR_IN6
545    Socket address information for an IPv6 address
546    """
547
548    def __init__(self):
549        self.fields = OrderedDict([
550            ('port', IntField(size=2)),
551            ('flow_info', IntField(size=4)),
552            ('ipv6_address', BytesField(size=16)),
553            ('scope_id', IntField(size=4))
554        ])
555        super(SockAddrIn6, self).__init__()
556
557    def get_ipaddress(self):
558        # get's the full IPv6 Address, note this is the full address and has
559        # not been concatenated
560        addr_bytes = self['ipv6_address'].get_value()
561        address = binascii.hexlify(addr_bytes).decode('utf-8')
562        return ":".join([address[i:i + 4] for i in range(0, len(address), 4)])
563
564    def set_ipaddress(self, address):
565        # set's the ipv6_address field from the address passed in, note this
566        # needs to be the full ipv6 address,
567        # e.g. fe80:0000:0000:0000:0000:0000:0000:0000 and not any short form
568        address = address.replace(":", "")
569        if len(address) != 32:
570            raise ValueError("When setting an IPv6 address, it must be in the "
571                             "full form without concatenation")
572        self['ipv6_address'].set_value(binascii.unhexlify(address))
573
574
575class SMB2ValidateNegotiateInfoResponse(Structure):
576    """
577    [MS-SMB2] v53.0 2017-09-15
578
579    2.2.32.6 VALIDATE_NEGOTIATE_INFO Response
580    Packet sent by the server on a request validation of SMB 2 negotiate
581    request.
582    """
583
584    def __init__(self):
585        self.fields = OrderedDict([
586            ('capabilities', FlagField(
587                size=4,
588                flag_type=Capabilities,
589            )),
590            ('guid', UuidField()),
591            ('security_mode', EnumField(
592                size=2,
593                enum_type=SecurityMode,
594                enum_strict=False
595            )),
596            ('dialect', EnumField(
597                size=2,
598                enum_type=Dialects
599            ))
600        ])
601        super(SMB2ValidateNegotiateInfoResponse, self).__init__()
602