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