ruby_smb 1.0.4 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.travis.yml +3 -2
  5. data/Gemfile +6 -2
  6. data/README.md +35 -47
  7. data/examples/enum_registry_key.rb +28 -0
  8. data/examples/enum_registry_values.rb +30 -0
  9. data/examples/negotiate.rb +51 -8
  10. data/examples/pipes.rb +2 -1
  11. data/examples/read_file_encryption.rb +56 -0
  12. data/examples/read_registry_key_value.rb +32 -0
  13. data/lib/ruby_smb.rb +4 -1
  14. data/lib/ruby_smb/client.rb +207 -18
  15. data/lib/ruby_smb/client/authentication.rb +27 -8
  16. data/lib/ruby_smb/client/encryption.rb +62 -0
  17. data/lib/ruby_smb/client/negotiation.rb +153 -12
  18. data/lib/ruby_smb/client/signing.rb +19 -0
  19. data/lib/ruby_smb/client/tree_connect.rb +4 -4
  20. data/lib/ruby_smb/client/utils.rb +8 -7
  21. data/lib/ruby_smb/client/winreg.rb +46 -0
  22. data/lib/ruby_smb/crypto.rb +30 -0
  23. data/lib/ruby_smb/dcerpc.rb +38 -0
  24. data/lib/ruby_smb/dcerpc/bind.rb +2 -2
  25. data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
  26. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  27. data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
  28. data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
  29. data/lib/ruby_smb/dcerpc/request.rb +28 -9
  30. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
  31. data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
  32. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
  33. data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
  34. data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
  35. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
  36. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
  37. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
  38. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
  39. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
  40. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
  41. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
  42. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
  43. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
  44. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
  45. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
  46. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
  47. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
  48. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
  49. data/lib/ruby_smb/dispatcher/socket.rb +4 -3
  50. data/lib/ruby_smb/error.rb +28 -1
  51. data/lib/ruby_smb/smb1/commands.rb +1 -1
  52. data/lib/ruby_smb/smb1/file.rb +6 -4
  53. data/lib/ruby_smb/smb1/packet/empty_packet.rb +4 -2
  54. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
  55. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
  56. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  57. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
  58. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
  59. data/lib/ruby_smb/smb1/pipe.rb +79 -3
  60. data/lib/ruby_smb/smb1/tree.rb +12 -3
  61. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  62. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  63. data/lib/ruby_smb/smb2/file.rb +25 -43
  64. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  65. data/lib/ruby_smb/smb2/packet.rb +2 -0
  66. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  67. data/lib/ruby_smb/smb2/packet/error_packet.rb +9 -4
  68. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
  69. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +50 -4
  70. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  71. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
  72. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
  73. data/lib/ruby_smb/smb2/pipe.rb +77 -3
  74. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  75. data/lib/ruby_smb/smb2/tree.rb +23 -17
  76. data/lib/ruby_smb/version.rb +1 -1
  77. data/ruby_smb.gemspec +5 -3
  78. data/spec/lib/ruby_smb/client_spec.rb +1441 -61
  79. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  80. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
  81. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
  82. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
  83. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
  84. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
  85. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
  86. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
  87. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
  88. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
  89. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
  90. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
  91. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
  92. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
  93. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
  94. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
  95. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
  96. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
  97. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
  98. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
  99. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
  100. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
  101. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
  102. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
  103. data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
  104. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +2 -2
  105. data/spec/lib/ruby_smb/error_spec.rb +59 -0
  106. data/spec/lib/ruby_smb/smb1/file_spec.rb +9 -1
  107. data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +10 -0
  108. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  109. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  110. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  111. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  112. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +210 -148
  113. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  114. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  115. data/spec/lib/ruby_smb/smb2/file_spec.rb +86 -62
  116. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  117. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  118. data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +29 -2
  119. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  120. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  121. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  122. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  123. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
  124. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +220 -149
  125. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  126. data/spec/lib/ruby_smb/smb2/tree_spec.rb +53 -8
  127. metadata +187 -81
  128. metadata.gz.sig +0 -0
  129. data/lib/ruby_smb/smb1/dcerpc.rb +0 -72
  130. data/lib/ruby_smb/smb2/dcerpc.rb +0 -75
@@ -1,3 +1,5 @@
1
+ require 'ruby_smb/smb2/negotiate_context'
2
+
1
3
  module RubySMB
2
4
  module SMB2
3
5
  module Packet
@@ -8,11 +10,12 @@ module RubySMB
8
10
 
9
11
  endian :little
10
12
  smb2_header :smb2_header
11
- uint16 :structure_size, label: 'Structure Size', initial_value: 65
13
+ uint16 :structure_size, label: 'Structure Size', initial_value: 65
12
14
  smb2_security_mode :security_mode
13
15
  uint16 :dialect_revision, label: 'Dialect Revision'
14
- uint16 :negotiate_context_count, label: 'Negotiate Context Count', initial_value: 0
15
- string :server_guid, label: 'Server GUID', length: 16
16
+ uint16 :negotiate_context_count, label: 'Negotiate Context Count', initial_value: -> { negotiate_context_list.size }, onlyif: -> { has_negotiate_context? }
17
+ uint16 :reserved1, label: 'Reserved', initial_value: 0, onlyif: -> { !has_negotiate_context? }
18
+ string :server_guid, label: 'Server GUID', length: 16
16
19
  smb2_capabilities :capabilities
17
20
  uint32 :max_transact_size, label: 'Max Transaction Size'
18
21
  uint32 :max_read_size, label: 'Max Read Size'
@@ -21,13 +24,56 @@ module RubySMB
21
24
  file_time :server_start_time, label: 'Server Start Time'
22
25
  uint16 :security_buffer_offset, label: 'Offset to Security Buffer'
23
26
  uint16 :security_buffer_length, label: 'Security Buffer Length', initial_value: -> { security_buffer.length }
24
- uint32 :negotiate_context_offset, label: 'Offset to Negotiate Context'
27
+ uint32 :negotiate_context_offset, label: 'Offset to Negotiate Context', onlyif: -> { has_negotiate_context? }
28
+ uint32 :reserved2, label: 'Reserved', initial_value: 0, onlyif: -> { !has_negotiate_context? }
25
29
  string :security_buffer, label: 'Security Buffer', read_length: :security_buffer_length
30
+ string :pad, label: 'Padding', length: -> { pad_length(self.security_buffer) }, onlyif: -> { has_negotiate_context? }
31
+ array :negotiate_context_list, label: 'Negotiate Context List', initial_length: -> { negotiate_context_count }, type: :negotiate_context, onlyif: -> { has_negotiate_context? }
26
32
 
27
33
  def initialize_instance
28
34
  super
29
35
  smb2_header.flags.reply = 1
30
36
  end
37
+
38
+ # Find the first Negotiate Context structure that matches the given
39
+ # context type
40
+ #
41
+ # @param [Integer] the Negotiate Context structure you wish to add
42
+ # @return [NegotiateContext] the Negotiate Context structure or nil if
43
+ # not found
44
+ def find_negotiate_context(type)
45
+ negotiate_context_list.find { |nc| nc.context_type == type }
46
+ end
47
+
48
+ # Adds a Negotiate Context to the #negotiate_context_list
49
+ #
50
+ # @param [NegotiateContext] the Negotiate Context structure you wish to add
51
+ # @return [Array<Fixnum>] the array of all currently added Negotiate Contexts
52
+ # @raise [ArgumentError] if the dialect is not a NegotiateContext structure
53
+ def add_negotiate_context(nc)
54
+ raise ArgumentError, 'Must be a NegotiateContext' unless nc.is_a? NegotiateContext
55
+ previous_element = negotiate_context_list.last || negotiate_context_list
56
+ pad_length = pad_length(previous_element)
57
+ self.negotiate_context_list << nc
58
+ self.negotiate_context_list.last.pad = "\x00" * pad_length
59
+ self.negotiate_context_list
60
+ end
61
+
62
+
63
+ private
64
+
65
+ # Determines the correct length for the padding, so that the next
66
+ # field is 8-byte aligned.
67
+ def pad_length(prev_element)
68
+ offset = (prev_element.abs_offset + prev_element.to_binary_s.length) % 8
69
+ (8 - offset) % 8
70
+ end
71
+
72
+ # Return true if the dialect version requires Negotiate Contexts
73
+ def has_negotiate_context?
74
+ dialect_revision == 0x0311
75
+ end
76
+
31
77
  end
32
78
  end
33
79
  end
@@ -0,0 +1,84 @@
1
+ module RubySMB
2
+ module SMB2
3
+ module Packet
4
+ # An SMB2 TRANSFORM_HEADER Packet as defined in
5
+ # [2.2.41 SMB2 TRANSFORM_HEADER](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/d6ce2327-a4c9-4793-be66-7b5bad2175fa)
6
+ class TransformHeader < BinData::Record
7
+ endian :little
8
+ hide :reserved0
9
+
10
+ endian :little
11
+ bit32 :protocol, label: 'Protocol ID Field', initial_value: 0xFD534D42
12
+ string :signature, label: 'Signature', length: 16
13
+ string :nonce, label: 'Nonce', length: 16
14
+ uint32 :original_message_size, label: 'Original Message Size'
15
+ uint16 :reserved0
16
+ uint16 :flags, label: 'Flags / Encryption Algorithm'
17
+ uint64 :session_id, label: 'Session ID'
18
+ array :encrypted_data, label: 'Encrypted Data', type: :uint8, read_until: :eof
19
+
20
+ def decrypt(key, algorithm: 'AES-128-GCM')
21
+ auth_data = self.to_binary_s[20...52]
22
+ encrypted_data = self.encrypted_data.to_ary.pack('C*')
23
+
24
+ case algorithm
25
+ when 'AES-128-CCM'
26
+ cipher = OpenSSL::CCM.new('AES', key, 16)
27
+ unencrypted_data = cipher.decrypt(encrypted_data + self.signature, self.nonce[0...11], auth_data)
28
+ unless unencrypted_data.length > 0
29
+ raise OpenSSL::Cipher::CipherError # raised for consistency with GCM mode
30
+ end
31
+ when 'AES-128-GCM'
32
+ cipher = OpenSSL::Cipher.new(algorithm).decrypt
33
+ cipher.key = key
34
+ cipher.iv = self.nonce[0...12]
35
+ cipher.auth_data = auth_data
36
+ cipher.auth_tag = self.signature
37
+ unencrypted_data = cipher.update(encrypted_data)
38
+ cipher.final # raises OpenSSL::Cipher::CipherError on signature failure
39
+ else
40
+ raise ArgumentError.new('Invalid algorithm, must be either AES-128-CCM or AES-128-GCM')
41
+ end
42
+
43
+ unencrypted_data[0...self.original_message_size]
44
+ rescue Exception => e
45
+ raise RubySMB::Error::EncryptionError, "Error while decrypting with '#{algorithm}' (#{e.class}: #{e})"
46
+ end
47
+
48
+ def encrypt(unencrypted_data, key, algorithm: 'AES-128-GCM')
49
+ if unencrypted_data.is_a? BinData::Record
50
+ unencrypted_data = unencrypted_data.to_binary_s
51
+ end
52
+
53
+ self.original_message_size.assign(unencrypted_data.length)
54
+
55
+ case algorithm
56
+ when 'AES-128-CCM'
57
+ cipher = OpenSSL::CCM.new('AES', key, 16)
58
+ random_iv = OpenSSL::Random.random_bytes(11)
59
+ self.nonce.assign(random_iv)
60
+ result = cipher.encrypt(unencrypted_data, random_iv, self.to_binary_s[20...52])
61
+ encrypted_data = result[0...-16]
62
+ auth_tag = result[-16..-1]
63
+ when 'AES-128-GCM'
64
+ cipher = OpenSSL::Cipher.new(algorithm).encrypt
65
+ cipher.iv_len = 12
66
+ cipher.key = key
67
+ self.nonce.assign(cipher.random_iv)
68
+ cipher.auth_data = self.to_binary_s[20...52]
69
+ encrypted_data = cipher.update(unencrypted_data) + cipher.final
70
+ auth_tag = cipher.auth_tag
71
+ else
72
+ raise ArgumentError.new('Invalid algorithm, must be either AES-128-CCM or AES-128-GCM')
73
+ end
74
+
75
+ self.encrypted_data.assign(encrypted_data.bytes)
76
+ self.signature.assign(auth_tag)
77
+ nil
78
+ rescue Exception => e
79
+ raise RubySMB::Error::EncryptionError, "Error while encrypting with '#{algorithm}' (#{e.class}: #{e})"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -1,22 +1,108 @@
1
1
  module RubySMB
2
2
  module SMB2
3
3
  module Packet
4
+
5
+
6
+ # An SMB2 RemotedIdentityTreeConnectContext Packet as defined in
7
+ # [2.2.9.2.1 SMB2_REMOTED_IDENTITY_TREE_CONNECT Context](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/ee7ff411-93e0-484f-9f73-31916fee4cb8)
8
+ # TODO: implement helper methods to add each Remote Identity element
9
+ class RemotedIdentityTreeConnectContext < BinData::Record
10
+ endian :little
11
+ uint16 :ticket_type, label: 'Ticket Type', initial_value: 0x0001
12
+ uint16 :ticket_size, label: 'Ticket Size', initial_value: -> { num_bytes }
13
+ uint16 :user, label: 'User'
14
+ uint16 :user_name, label: 'User Name'
15
+ uint16 :domain, label: 'Domain'
16
+ uint16 :groups, label: 'Groups'
17
+ uint16 :restricted_groups, label: 'Restricted Groups'
18
+ uint16 :privileges, label: 'Privileges'
19
+ uint16 :primary_group, label: 'Primary Group'
20
+ uint16 :owner, label: 'Owner'
21
+ uint16 :default_dacl, label: 'Default DACL'
22
+ uint16 :device_groups, label: 'Device Groups'
23
+ uint16 :user_claims, label: 'User Claims'
24
+ uint16 :device_claims, label: 'Device Claims'
25
+ string :ticket_info, label: 'Ticket Info', read_length: -> { ticket_size - ticket_info.rel_offset}
26
+ end
27
+
28
+ # An SMB2 TreeConnectContext Packet as defined in
29
+ # [2.2.9.2 SMB2 TREE_CONNECT_CONTEXT Request Values](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/06eaaabc-caca-4776-9daf-82439e90dacd)
30
+ class TreeConnectContext < BinData::Record
31
+
32
+ # Context Types
33
+
34
+ # This value is reserved.
35
+ SMB2_RESERVED_TREE_CONNECT_CONTEXT_ID = 0x0000
36
+ # The Data field contains remoted identity tree connect context data as
37
+ # specified in section [2.2.9.2.1](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/ee7ff411-93e0-484f-9f73-31916fee4cb8)
38
+ SMB2_REMOTED_IDENTITY_TREE_CONNECT_CONTEXT_ID = 0x0001
39
+
40
+ endian :little
41
+ uint16 :context_type, label: 'Context Type'
42
+ uint16 :data_length, label: 'Data Length', initial_value: -> { data.to_binary_s.size }
43
+ uint32 :reserved, label: 'Reserved'
44
+ choice :data, label: 'Data', selection: -> { context_type } do
45
+ remoted_identity_tree_connect_context SMB2_REMOTED_IDENTITY_TREE_CONNECT_CONTEXT_ID, label: 'Remoted Identity Tree Connect Context'
46
+ end
47
+
48
+ end
49
+
50
+ # An SMB2 TreeConnectRequestExtension Packet as defined in
51
+ # [2.2.9.1 SMB2 TREE_CONNECT Request Extension](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/9ca7328b-b6ca-41a7-9773-0fa237261b76)
52
+ class TreeConnectRequestExtension < BinData::Record
53
+ endian :little
54
+ uint32 :tree_connect_context_offset, label: 'Tree Connect Context Offset', initial_value: -> { tree_connect_contexts.rel_offset }
55
+ uint16 :tree_connect_context_count, label: 'Tree Connect Context Count', initial_value: -> { tree_connect_contexts.size }
56
+ string :reserved, label: 'Reserved', length: 10
57
+ string16 :path, label: 'Path Buffer'
58
+ array :tree_connect_contexts, label: 'Tree Connect Contexts', type: :tree_connect_context, initial_length: -> { tree_connect_context_count }
59
+ end
60
+
4
61
  # An SMB2 TreeConnectRequest Packet as defined in
5
62
  # [2.2.9 SMB2 TREE_CONNECT Request](https://msdn.microsoft.com/en-us/library/cc246567.aspx)
6
63
  class TreeConnectRequest < RubySMB::GenericPacket
7
64
  COMMAND = RubySMB::SMB2::Commands::TREE_CONNECT
8
65
 
66
+ # Flags (SMB 3.1.1 only)
67
+
68
+ # The client has previously connected to the specified cluster share
69
+ # using the SMB dialect of the connection on which the request is received.
70
+ SMB2_TREE_CONNECT_FLAG_CLUSTER_RECONNECT = 0x0001
71
+ # The client can handle synchronous share redirects via a Share Redirect
72
+ # error context response as specified in section [2.2.2.2.2](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/f3073a8b-9f0f-47c0-91e5-ec3be9a49f37).
73
+ SMB2_TREE_CONNECT_FLAG_REDIRECT_TO_OWNER = 0x0002
74
+ # A tree connect request extension, as specified in section
75
+ # [2.2.9.1](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/9ca7328b-b6ca-41a7-9773-0fa237261b76),
76
+ # is present, starting at the Buffer field of this tree connect request.
77
+ SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT = 0x0003
78
+
9
79
  endian :little
10
80
  smb2_header :smb2_header
11
81
  uint16 :structure_size, label: 'Structure Size', initial_value: 9
82
+ # The flags field is only used by SMB 3.1.1, it must be 0 for other versions
12
83
  uint16 :flags, label: 'Flags', initial_value: 0x00
13
- uint16 :path_offset, label: 'Path Offset', initial_value: 0x48
14
- uint16 :path_length, label: 'Path Length', initial_value: -> { path.length }
15
- string :path, label: 'Path Buffer'
16
-
17
- def encode_path(path)
18
- self.path = path.encode('utf-16le')
84
+ # if SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT flag is set, #path_offset
85
+ # will have to be updated with the correct offset of the path name,
86
+ # which is located in the TreeConnect Context.
87
+ uint16 :path_offset, label: 'Path Offset', initial_value: -> do
88
+ if flags == SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT
89
+ tree_connect_request_extension.path.abs_offset
90
+ else
91
+ path.abs_offset
92
+ end
93
+ end
94
+ # if SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT flag is set, #path_length
95
+ # will have to be updated with the correct full share path name,
96
+ # which is located in the TreeConnect Context.
97
+ uint16 :path_length, label: 'Path Length', initial_value: -> do
98
+ if flags == SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT
99
+ tree_connect_request_extension.path.to_binary_s.length
100
+ else
101
+ path.to_binary_s.length
102
+ end
19
103
  end
104
+ string16 :path, label: 'Path Buffer', onlyif: -> { flags != SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT }
105
+ tree_connect_request_extension :tree_connect_request_extension, label: 'Tree Connect Request Extension', onlyif: -> { flags == SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT }
20
106
  end
21
107
  end
22
108
  end
@@ -6,6 +6,14 @@ module RubySMB
6
6
  class TreeConnectResponse < RubySMB::GenericPacket
7
7
  COMMAND = RubySMB::SMB2::Commands::TREE_CONNECT
8
8
 
9
+ # Share Types
10
+ # Physical disk share
11
+ SMB2_SHARE_TYPE_DISK = 0x01
12
+ # Named pipe share
13
+ SMB2_SHARE_TYPE_PIPE = 0x02
14
+ # Printer share
15
+ SMB2_SHARE_TYPE_PRINT = 0x03
16
+
9
17
  endian :little
10
18
  smb2_header :smb2_header
11
19
  uint16 :structure_size, label: 'Structure Size', initial_value: 16
@@ -20,32 +28,6 @@ module RubySMB
20
28
  smb2_header.flags.reply = 1
21
29
  end
22
30
 
23
- # Returns the ACCESS_MASK for the Maximal Share Access Rights. The packet
24
- # defaults this to a {RubySMB::SMB2::BitField::DirectoryAccessMask}. If it is anything other than
25
- # a directory that has been connected to, it will re-cast it as a {RubySMB::SMB2::BitField::FileAccessMask}
26
- #
27
- # @return [RubySMB::SMB2::BitField::DirectoryAccessMask] if a directory was connected to
28
- # @return [RubySMB::SMB2::BitField::FileAccessMask] if anything else was connected to
29
- # @raise [RubySMB::Error::InvalidBitField] if ACCESS_MASK bit field is not valid
30
- def access_rights
31
- if is_directory?
32
- maximal_access
33
- else
34
- mask = maximal_access.to_binary_s
35
- begin
36
- RubySMB::SMB2::BitField::FileAccessMask.read(mask)
37
- rescue IOError
38
- raise RubySMB::Error::InvalidBitField, 'Invalid ACCESS_MASK for the Maximal Share Access Rights'
39
- end
40
- end
41
- end
42
-
43
- # Checks if the remote Tree is a directory
44
- #
45
- # @return [Boolean]
46
- def is_directory?
47
- share_type == 0x01
48
- end
49
31
  end
50
32
  end
51
33
  end
@@ -3,13 +3,24 @@ module RubySMB
3
3
  # Represents a pipe on the Remote server that we can perform
4
4
  # various I/O operations on.
5
5
  class Pipe < File
6
- require 'ruby_smb/smb2/dcerpc'
6
+ require 'ruby_smb/dcerpc'
7
7
 
8
- include RubySMB::SMB2::Dcerpc
8
+ include RubySMB::Dcerpc
9
9
 
10
10
  STATUS_CONNECTED = 0x00000003
11
11
  STATUS_CLOSING = 0x00000004
12
12
 
13
+ def initialize(tree:, response:, name:)
14
+ raise ArgumentError, 'No Name Provided' if name.nil?
15
+ case name
16
+ when 'srvsvc'
17
+ extend RubySMB::Dcerpc::Srvsvc
18
+ when 'winreg'
19
+ extend RubySMB::Dcerpc::Winreg
20
+ end
21
+ super(tree: tree, response: response, name: name)
22
+ end
23
+
13
24
  # Performs a peek operation on the named pipe
14
25
  #
15
26
  # @param peek_size [Integer] Amount of data to peek
@@ -35,7 +46,7 @@ module RubySMB
35
46
  end
36
47
 
37
48
  unless response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW or response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
38
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
49
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
39
50
  end
40
51
  response
41
52
  end
@@ -67,6 +78,69 @@ module RubySMB
67
78
  state == STATUS_CONNECTED
68
79
  end
69
80
 
81
+ def dcerpc_request(stub_packet, options={})
82
+ options.merge!(endpoint: stub_packet.class.name.split('::').at(-2))
83
+ dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: stub_packet.opnum }, options)
84
+ dcerpc_request.stub.read(stub_packet.to_binary_s)
85
+ ioctl_send_recv(dcerpc_request, options)
86
+ end
87
+
88
+ def ioctl_send_recv(action, options={})
89
+ request = set_header_fields(RubySMB::SMB2::Packet::IoctlRequest.new(options))
90
+ request.ctl_code = 0x0011C017
91
+ request.flags.is_fsctl = 0x00000001
92
+ request.buffer = action.to_binary_s
93
+
94
+ ioctl_raw_response = @tree.client.send_recv(request)
95
+ ioctl_response = RubySMB::SMB2::Packet::IoctlResponse.read(ioctl_raw_response)
96
+ unless ioctl_response.valid?
97
+ raise RubySMB::Error::InvalidPacket.new(
98
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
99
+ expected_cmd: RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
100
+ received_proto: ioctl_response.smb2_header.protocol,
101
+ received_cmd: ioctl_response.smb2_header.command
102
+ )
103
+ end
104
+ unless [WindowsError::NTStatus::STATUS_SUCCESS,
105
+ WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW].include?(ioctl_response.status_code)
106
+ raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code
107
+ end
108
+
109
+ raw_data = ioctl_response.output_data
110
+ if ioctl_response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW
111
+ raw_data << read(bytes: @tree.client.max_buffer_size - ioctl_response.output_count)
112
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
113
+ unless dcerpc_response.pdu_header.pfc_flags.first_frag == 1
114
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Not the first fragment"
115
+ end
116
+ stub_data = dcerpc_response.stub.to_s
117
+
118
+ loop do
119
+ break if dcerpc_response.pdu_header.pfc_flags.last_frag == 1
120
+ raw_data = read(bytes: @tree.client.max_buffer_size)
121
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
122
+ stub_data << dcerpc_response.stub.to_s
123
+ end
124
+ stub_data
125
+ else
126
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
127
+ dcerpc_response.stub.to_s
128
+ end
129
+ end
130
+
131
+
132
+ private
133
+
134
+ def dcerpc_response_from_raw_response(raw_data)
135
+ dcerpc_response = RubySMB::Dcerpc::Response.read(raw_data)
136
+ unless dcerpc_response.pdu_header.ptype == RubySMB::Dcerpc::PTypes::RESPONSE
137
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Not a Response packet"
138
+ end
139
+ dcerpc_response
140
+ rescue IOError
141
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Error reading the DCERPC response"
142
+ end
143
+
70
144
  end
71
145
  end
72
146
  end
@@ -6,7 +6,7 @@ module RubySMB
6
6
  endian :little
7
7
  bit32 :protocol, label: 'Protocol ID Field', initial_value: RubySMB::SMB2::SMB2_PROTOCOL_ID
8
8
  uint16 :structure_size, label: 'Header Structure Size', initial_value: 64
9
- uint16 :credit_charge, label: 'Credit Charge', initial_value: 0
9
+ uint16 :credit_charge, label: 'Credit Charge', initial_value: 1
10
10
  nt_status :nt_status, label: 'NT Status', initial_value: 0
11
11
  uint16 :command, label: 'Command'
12
12
  uint16 :credits, label: 'Credit Request/Response'
@@ -23,12 +23,18 @@ module RubySMB
23
23
  # @return [Integer]
24
24
  attr_accessor :id
25
25
 
26
- def initialize(client:, share:, response:)
27
- @client = client
28
- @share = share
29
- @id = response.smb2_header.tree_id
30
- @permissions = response.maximal_access
31
- @share_type = response.share_type
26
+ # Whether or not the share associated with this tree connect needs to be encrypted (SMB 3.x)
27
+ # @!attribute [rw] tree_connect_encrypt_data
28
+ # @return [Boolean]
29
+ attr_accessor :tree_connect_encrypt_data
30
+
31
+ def initialize(client:, share:, response:, encrypt: false)
32
+ @client = client
33
+ @share = share
34
+ @id = response.smb2_header.tree_id
35
+ @permissions = response.maximal_access
36
+ @share_type = response.share_type
37
+ @tree_connect_encrypt_data = encrypt
32
38
  end
33
39
 
34
40
  # Disconnects this Tree from the current session
@@ -38,7 +44,7 @@ module RubySMB
38
44
  def disconnect!
39
45
  request = RubySMB::SMB2::Packet::TreeDisconnectRequest.new
40
46
  request = set_header_fields(request)
41
- raw_response = client.send_recv(request)
47
+ raw_response = client.send_recv(request, encrypt: @tree_connect_encrypt_data)
42
48
  response = RubySMB::SMB2::Packet::TreeDisconnectResponse.read(raw_response)
43
49
  unless response.valid?
44
50
  raise RubySMB::Error::InvalidPacket.new(
@@ -96,7 +102,7 @@ module RubySMB
96
102
  create_request.create_disposition = disposition
97
103
  create_request.name = filename
98
104
 
99
- raw_response = client.send_recv(create_request)
105
+ raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
100
106
  response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
101
107
  unless response.valid?
102
108
  raise RubySMB::Error::InvalidPacket.new(
@@ -107,15 +113,15 @@ module RubySMB
107
113
  )
108
114
  end
109
115
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
110
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
116
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
111
117
  end
112
118
 
113
119
  case @share_type
114
- when 0x01
115
- RubySMB::SMB2::File.new(name: filename, tree: self, response: response)
116
- when 0x02
120
+ when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_DISK
121
+ RubySMB::SMB2::File.new(name: filename, tree: self, response: response, encrypt: @tree_connect_encrypt_data)
122
+ when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_PIPE
117
123
  RubySMB::SMB2::Pipe.new(name: filename, tree: self, response: response)
118
- # when 0x03
124
+ # when RubySMB::SMB2::TreeConnectResponse::SMB2_SHARE_TYPE_PRINT
119
125
  # it's a printer!
120
126
  else
121
127
  raise RubySMB::Error::RubySMBError, 'Unsupported share type'
@@ -148,7 +154,7 @@ module RubySMB
148
154
  files = []
149
155
 
150
156
  loop do
151
- response = client.send_recv(directory_request)
157
+ response = client.send_recv(directory_request, encrypt: @tree_connect_encrypt_data)
152
158
  directory_response = RubySMB::SMB2::Packet::QueryDirectoryResponse.read(response)
153
159
  unless directory_response.valid?
154
160
  raise RubySMB::Error::InvalidPacket.new(
@@ -164,7 +170,7 @@ module RubySMB
164
170
  break if status_code == WindowsError::NTStatus::STATUS_NO_MORE_FILES
165
171
 
166
172
  unless status_code == WindowsError::NTStatus::STATUS_SUCCESS
167
- raise RubySMB::Error::UnexpectedStatusCode, status_code.to_s
173
+ raise RubySMB::Error::UnexpectedStatusCode, status_code
168
174
  end
169
175
 
170
176
  files += directory_response.results(type)
@@ -193,7 +199,7 @@ module RubySMB
193
199
 
194
200
  create_request = open_directory_packet(directory: directory, disposition: disposition,
195
201
  impersonation: impersonation, read: read, write: write, delete: delete)
196
- raw_response = client.send_recv(create_request)
202
+ raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
197
203
  response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
198
204
  unless response.valid?
199
205
  raise RubySMB::Error::InvalidPacket.new(
@@ -204,7 +210,7 @@ module RubySMB
204
210
  )
205
211
  end
206
212
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
207
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
213
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
208
214
  end
209
215
 
210
216
  response