ruby_smb 1.0.4 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
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