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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.travis.yml +3 -2
- data/Gemfile +6 -2
- data/README.md +35 -47
- data/examples/enum_registry_key.rb +28 -0
- data/examples/enum_registry_values.rb +30 -0
- data/examples/negotiate.rb +51 -8
- data/examples/pipes.rb +2 -1
- data/examples/read_file_encryption.rb +56 -0
- data/examples/read_registry_key_value.rb +32 -0
- data/lib/ruby_smb.rb +4 -1
- data/lib/ruby_smb/client.rb +207 -18
- data/lib/ruby_smb/client/authentication.rb +27 -8
- data/lib/ruby_smb/client/encryption.rb +62 -0
- data/lib/ruby_smb/client/negotiation.rb +153 -12
- data/lib/ruby_smb/client/signing.rb +19 -0
- data/lib/ruby_smb/client/tree_connect.rb +4 -4
- data/lib/ruby_smb/client/utils.rb +8 -7
- data/lib/ruby_smb/client/winreg.rb +46 -0
- data/lib/ruby_smb/crypto.rb +30 -0
- data/lib/ruby_smb/dcerpc.rb +38 -0
- data/lib/ruby_smb/dcerpc/bind.rb +2 -2
- data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
- data/lib/ruby_smb/dcerpc/error.rb +3 -0
- data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
- data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
- data/lib/ruby_smb/dcerpc/request.rb +28 -9
- data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
- data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
- data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
- data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
- data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
- data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
- data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
- data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
- data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
- data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
- data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
- data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
- data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
- data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
- data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
- data/lib/ruby_smb/dispatcher/socket.rb +4 -3
- data/lib/ruby_smb/error.rb +28 -1
- data/lib/ruby_smb/smb1/commands.rb +1 -1
- data/lib/ruby_smb/smb1/file.rb +6 -4
- data/lib/ruby_smb/smb1/packet/empty_packet.rb +4 -2
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
- data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
- data/lib/ruby_smb/smb1/pipe.rb +79 -3
- data/lib/ruby_smb/smb1/tree.rb +12 -3
- data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
- data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
- data/lib/ruby_smb/smb2/file.rb +25 -43
- data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
- data/lib/ruby_smb/smb2/packet.rb +2 -0
- data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
- data/lib/ruby_smb/smb2/packet/error_packet.rb +9 -4
- data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
- data/lib/ruby_smb/smb2/packet/negotiate_response.rb +50 -4
- data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
- data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
- data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
- data/lib/ruby_smb/smb2/pipe.rb +77 -3
- data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
- data/lib/ruby_smb/smb2/tree.rb +23 -17
- data/lib/ruby_smb/version.rb +1 -1
- data/ruby_smb.gemspec +5 -3
- data/spec/lib/ruby_smb/client_spec.rb +1441 -61
- data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
- data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
- data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
- data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
- data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
- data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
- data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
- data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
- data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +2 -2
- data/spec/lib/ruby_smb/error_spec.rb +59 -0
- data/spec/lib/ruby_smb/smb1/file_spec.rb +9 -1
- data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +10 -0
- data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
- data/spec/lib/ruby_smb/smb1/pipe_spec.rb +210 -148
- data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
- data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
- data/spec/lib/ruby_smb/smb2/file_spec.rb +86 -62
- data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
- data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
- data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +29 -2
- data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
- data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
- data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
- data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
- data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
- data/spec/lib/ruby_smb/smb2/pipe_spec.rb +220 -149
- data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +53 -8
- metadata +187 -81
- metadata.gz.sig +0 -0
- data/lib/ruby_smb/smb1/dcerpc.rb +0 -72
- data/lib/ruby_smb/smb2/dcerpc.rb +0 -75
@@ -0,0 +1,62 @@
|
|
1
|
+
module RubySMB
|
2
|
+
class Client
|
3
|
+
# Contains the methods for handling encryption / decryption
|
4
|
+
module Encryption
|
5
|
+
def smb3_encrypt(data)
|
6
|
+
unless @client_encryption_key
|
7
|
+
case @dialect
|
8
|
+
when '0x0300', '0x0302'
|
9
|
+
@client_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
10
|
+
@session_key,
|
11
|
+
"SMB2AESCCM\x00",
|
12
|
+
"ServerIn \x00"
|
13
|
+
)
|
14
|
+
when '0x0311'
|
15
|
+
@client_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
16
|
+
@session_key,
|
17
|
+
"SMBC2SCipherKey\x00",
|
18
|
+
@preauth_integrity_hash_value
|
19
|
+
)
|
20
|
+
else
|
21
|
+
raise RubySMB::Error::EncryptionError.new('Dialect is incompatible with SMBv3 encryption')
|
22
|
+
end
|
23
|
+
######
|
24
|
+
# DEBUG
|
25
|
+
#puts "Client encryption key = #{@client_encryption_key.each_byte.map {|e| '%02x' % e}.join}"
|
26
|
+
######
|
27
|
+
end
|
28
|
+
|
29
|
+
th = RubySMB::SMB2::Packet::TransformHeader.new(flags: 1, session_id: @session_id)
|
30
|
+
th.encrypt(data, @client_encryption_key, algorithm: @encryption_algorithm)
|
31
|
+
th
|
32
|
+
end
|
33
|
+
|
34
|
+
def smb3_decrypt(th)
|
35
|
+
unless @server_encryption_key
|
36
|
+
case @dialect
|
37
|
+
when '0x0300', '0x0302'
|
38
|
+
@server_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
39
|
+
@session_key,
|
40
|
+
"SMB2AESCCM\x00",
|
41
|
+
"ServerOut\x00"
|
42
|
+
)
|
43
|
+
when '0x0311'
|
44
|
+
@server_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
45
|
+
@session_key,
|
46
|
+
"SMBS2CCipherKey\x00",
|
47
|
+
@preauth_integrity_hash_value
|
48
|
+
)
|
49
|
+
else
|
50
|
+
raise RubySMB::Error::EncryptionError.new('Dialect is incompatible with SMBv3 decryption')
|
51
|
+
end
|
52
|
+
######
|
53
|
+
# DEBUG
|
54
|
+
#puts "Server encryption key = #{@server_encryption_key.each_byte.map {|e| '%02x' % e}.join}"
|
55
|
+
######
|
56
|
+
end
|
57
|
+
|
58
|
+
th.decrypt(@server_encryption_key, algorithm: @encryption_algorithm)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -17,10 +17,28 @@ module RubySMB
|
|
17
17
|
# internally to be able to retrieve the negotiated dialect later on.
|
18
18
|
# This is only valid for SMB1.
|
19
19
|
response_packet.dialects = request_packet.dialects if response_packet.respond_to? :dialects=
|
20
|
-
parse_negotiate_response(response_packet)
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
version = parse_negotiate_response(response_packet)
|
21
|
+
if @dialect == '0x0311'
|
22
|
+
update_preauth_hash(request_packet)
|
23
|
+
update_preauth_hash(response_packet)
|
24
|
+
end
|
25
|
+
|
26
|
+
# If the response contains the SMB2 wildcard revision number dialect;
|
27
|
+
# it indicates that the server implements SMB 2.1 or future dialect
|
28
|
+
# revisions and expects the client to send a subsequent SMB2 Negotiate
|
29
|
+
# request to negotiate the actual SMB 2 Protocol revision to be used.
|
30
|
+
# The wildcard revision number is sent only in response to a
|
31
|
+
# multi-protocol negotiate request with the "SMB 2.???" dialect string.
|
32
|
+
if @dialect == '0x02ff'
|
33
|
+
self.smb2_message_id += 1
|
34
|
+
version = negotiate
|
35
|
+
end
|
36
|
+
version
|
37
|
+
rescue RubySMB::Error::InvalidPacket, Errno::ECONNRESET, RubySMB::Error::CommunicationError => e
|
38
|
+
version = request_packet.packet_smb_version
|
39
|
+
version = 'SMB3' if version == 'SMB2' && !@smb2 && @smb3
|
40
|
+
version = 'SMB2 or SMB3' if version == 'SMB2' && @smb2 && @smb3
|
41
|
+
error = "Unable to negotiate #{version} with the remote host: #{e.message}"
|
24
42
|
raise RubySMB::Error::NegotiationFailure, error
|
25
43
|
end
|
26
44
|
|
@@ -32,8 +50,8 @@ module RubySMB
|
|
32
50
|
def negotiate_request
|
33
51
|
if smb1
|
34
52
|
smb1_negotiate_request
|
35
|
-
|
36
|
-
|
53
|
+
else
|
54
|
+
smb2_3_negotiate_request
|
37
55
|
end
|
38
56
|
end
|
39
57
|
|
@@ -50,7 +68,7 @@ module RubySMB
|
|
50
68
|
packet = RubySMB::SMB1::Packet::NegotiateResponseExtended.read raw_data
|
51
69
|
response = packet if packet.valid?
|
52
70
|
end
|
53
|
-
if smb2 && response.nil?
|
71
|
+
if (smb2 || smb3) && response.nil?
|
54
72
|
packet = RubySMB::SMB2::Packet::NegotiateResponse.read raw_data
|
55
73
|
response = packet if packet.valid?
|
56
74
|
end
|
@@ -95,26 +113,93 @@ module RubySMB
|
|
95
113
|
when RubySMB::SMB1::Packet::NegotiateResponseExtended
|
96
114
|
self.smb1 = true
|
97
115
|
self.smb2 = false
|
116
|
+
self.smb3 = false
|
98
117
|
self.signing_required = packet.parameter_block.security_mode.security_signatures_required == 1
|
99
118
|
self.dialect = packet.negotiated_dialect.to_s
|
100
119
|
# MaxBufferSize is largest message server will receive, measured from start of the SMB header. Subtract 260
|
101
120
|
# for protocol overhead. Then this value can be used for max read/write size without having to factor in
|
102
121
|
# protocol overhead every time.
|
103
122
|
self.server_max_buffer_size = packet.parameter_block.max_buffer_size - 260
|
123
|
+
self.negotiated_smb_version = 1
|
124
|
+
self.session_encrypt_data = false
|
104
125
|
'SMB1'
|
105
126
|
when RubySMB::SMB2::Packet::NegotiateResponse
|
106
127
|
self.smb1 = false
|
107
|
-
|
108
|
-
|
128
|
+
unless packet.dialect_revision.to_i == 0x02ff
|
129
|
+
self.smb2 = packet.dialect_revision.to_i >= 0x0200 && packet.dialect_revision.to_i < 0x0300
|
130
|
+
self.smb3 = packet.dialect_revision.to_i >= 0x0300 && packet.dialect_revision.to_i < 0x0400
|
131
|
+
end
|
132
|
+
self.signing_required = packet.security_mode.signing_required == 1 if self.smb2 || self.smb3
|
109
133
|
self.dialect = "0x%04x" % packet.dialect_revision
|
110
134
|
self.server_max_read_size = packet.max_read_size
|
111
135
|
self.server_max_write_size = packet.max_write_size
|
112
136
|
self.server_max_transact_size = packet.max_transact_size
|
113
137
|
# This value is used in SMB1 only but calculate a valid value anyway
|
114
138
|
self.server_max_buffer_size = [self.server_max_read_size, self.server_max_write_size, self.server_max_transact_size].min
|
115
|
-
|
139
|
+
self.negotiated_smb_version = self.smb2 ? 2 : 3
|
140
|
+
self.server_guid = packet.server_guid
|
141
|
+
self.server_start_time = packet.server_start_time.to_time if packet.server_start_time != 0
|
142
|
+
self.server_system_time = packet.system_time.to_time if packet.system_time != 0
|
143
|
+
case self.dialect
|
144
|
+
when '0x02ff'
|
145
|
+
when '0x0300', '0x0302'
|
146
|
+
if packet&.capabilities&.encryption == 1
|
147
|
+
self.encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM]
|
148
|
+
end
|
149
|
+
self.session_encrypt_data = self.session_encrypt_data && !self.encryption_algorithm.nil?
|
150
|
+
when '0x0311'
|
151
|
+
parse_smb3_capabilities(packet)
|
152
|
+
self.session_encrypt_data = self.session_encrypt_data && !self.encryption_algorithm.nil?
|
153
|
+
else
|
154
|
+
self.session_encrypt_data = false
|
155
|
+
end
|
156
|
+
return "SMB#{self.negotiated_smb_version}"
|
157
|
+
else
|
158
|
+
error = 'Unable to negotiate with remote host'
|
159
|
+
if packet.status_code == WindowsError::NTStatus::STATUS_NOT_SUPPORTED
|
160
|
+
error << ", SMB2" if @smb2
|
161
|
+
error << ", SMB3" if @smb3
|
162
|
+
error << ' not supported'
|
163
|
+
end
|
164
|
+
raise RubySMB::Error::NegotiationFailure, error
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def parse_smb3_capabilities(response_packet)
|
169
|
+
nc = response_packet.find_negotiate_context(
|
170
|
+
RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
171
|
+
)
|
172
|
+
@preauth_integrity_hash_algorithm = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[nc&.data&.hash_algorithms&.first]
|
173
|
+
unless @preauth_integrity_hash_algorithm
|
174
|
+
raise RubySMB::Error::EncryptionError.new(
|
175
|
+
'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
|
176
|
+
)
|
177
|
+
end
|
178
|
+
# Set the encryption the client will use, prioritizing AES_128_GCM over AES_128_CCM
|
179
|
+
nc = response_packet.find_negotiate_context(
|
180
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
181
|
+
)
|
182
|
+
@server_encryption_algorithms = nc&.data&.ciphers&.to_ary
|
183
|
+
if @server_encryption_algorithms.nil? || @server_encryption_algorithms.empty?
|
184
|
+
raise RubySMB::Error::EncryptionError.new(
|
185
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
186
|
+
)
|
187
|
+
end
|
188
|
+
if @server_encryption_algorithms.include?(RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM)
|
189
|
+
@encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM]
|
190
|
+
else
|
191
|
+
@encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[@server_encryption_algorithms.first]
|
192
|
+
end
|
193
|
+
unless @encryption_algorithm
|
194
|
+
raise RubySMB::Error::EncryptionError.new(
|
195
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
196
|
+
)
|
116
197
|
end
|
117
198
|
|
199
|
+
nc = response_packet.find_negotiate_context(
|
200
|
+
RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
201
|
+
)
|
202
|
+
@server_compression_algorithms = nc&.data&.compression_algorithms&.to_ary || []
|
118
203
|
end
|
119
204
|
|
120
205
|
# Create a {RubySMB::SMB1::Packet::NegotiateRequest} packet with the
|
@@ -127,10 +212,17 @@ module RubySMB
|
|
127
212
|
# while being guaranteed to work with any modern Windows system. We can get more sophisticated
|
128
213
|
# with switching this on and off at a later date if the need arises.
|
129
214
|
packet.smb_header.flags2.extended_security = 1
|
215
|
+
# Recent Mac OS X requires the unicode flag to be set on the Negotiate
|
216
|
+
# SMB Header request, even if this packet does not contain string fields
|
217
|
+
# (see Flags2 SMB_FLAGS2_UNICODE definition in "2.2.3.1 The SMB Header"
|
218
|
+
# documentation:
|
219
|
+
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/69a29f73-de0c-45a6-a1aa-8ceeea42217f
|
220
|
+
packet.smb_header.flags2.unicode = 1
|
130
221
|
# There is no real good reason to ever send an SMB1 Negotiate packet
|
131
222
|
# to Negotiate strictly SMB2, but the protocol WILL support it
|
132
223
|
packet.add_dialect(SMB1_DIALECT_SMB1_DEFAULT) if smb1
|
133
224
|
packet.add_dialect(SMB1_DIALECT_SMB2_DEFAULT) if smb2
|
225
|
+
packet.add_dialect(SMB1_DIALECT_SMB2_WILDCARD) if smb2 || smb3
|
134
226
|
packet
|
135
227
|
end
|
136
228
|
|
@@ -139,11 +231,60 @@ module RubySMB
|
|
139
231
|
# may want to communicate over SMB1
|
140
232
|
#
|
141
233
|
# @ return [RubySMB::SMB2::Packet::NegotiateRequest] a completed SMB2 Negotiate Request packet
|
142
|
-
def
|
234
|
+
def smb2_3_negotiate_request
|
143
235
|
packet = RubySMB::SMB2::Packet::NegotiateRequest.new
|
144
236
|
packet.security_mode.signing_enabled = 1
|
145
|
-
packet.add_dialect(SMB2_DIALECT_DEFAULT)
|
146
237
|
packet.client_guid = SecureRandom.random_bytes(16)
|
238
|
+
packet.set_dialects(SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)}) if smb2
|
239
|
+
packet = add_smb3_to_negotiate_request(packet) if smb3
|
240
|
+
packet
|
241
|
+
end
|
242
|
+
|
243
|
+
# This adds SMBv3 specific information: SMBv3 supported dialects,
|
244
|
+
# encryption capability, Negotiate Contexts if the dialect requires them
|
245
|
+
#
|
246
|
+
# @param packet [RubySMB::SMB2::Packet::NegotiateRequest] the NegotiateRequest
|
247
|
+
# to add SMB3 specific info to
|
248
|
+
# @param dialects [Array<String>] the dialects to negotiate. This must be
|
249
|
+
# an array of strings. Default is SMB3_DIALECT_DEFAULT
|
250
|
+
# @return [RubySMB::SMB2::Packet::NegotiateRequest] a completed SMB3 Negotiate Request packet
|
251
|
+
# @raise [ArgumentError] if dialects is not an array of strings
|
252
|
+
def add_smb3_to_negotiate_request(packet, dialects = SMB3_DIALECT_DEFAULT)
|
253
|
+
dialects.each do |dialect|
|
254
|
+
raise ArgumentError, 'Must be an array of strings' unless dialect.is_a? String
|
255
|
+
packet.add_dialect(dialect.to_i(16))
|
256
|
+
end
|
257
|
+
packet.capabilities.encryption = 1
|
258
|
+
|
259
|
+
if packet.dialects.include?(0x0311)
|
260
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
261
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
262
|
+
)
|
263
|
+
nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
264
|
+
nc.data.salt = SecureRandom.random_bytes(32)
|
265
|
+
packet.add_negotiate_context(nc)
|
266
|
+
|
267
|
+
@preauth_integrity_hash_value = "\x00" * 64
|
268
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
269
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
270
|
+
)
|
271
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
|
272
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
273
|
+
packet.add_negotiate_context(nc)
|
274
|
+
|
275
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
276
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
277
|
+
)
|
278
|
+
# Adding all possible compression algorithm even if we don't support
|
279
|
+
# them yet. This will force the server to disclose the support
|
280
|
+
# algorithms in the repsonse.
|
281
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
|
282
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
|
283
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
|
284
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
|
285
|
+
packet.add_negotiate_context(nc)
|
286
|
+
end
|
287
|
+
|
147
288
|
packet
|
148
289
|
end
|
149
290
|
end
|
@@ -40,6 +40,25 @@ module RubySMB
|
|
40
40
|
end
|
41
41
|
packet
|
42
42
|
end
|
43
|
+
|
44
|
+
def smb3_sign(packet)
|
45
|
+
if !session_key.empty? && (signing_required || packet.is_a?(RubySMB::SMB2::Packet::TreeConnectRequest))
|
46
|
+
case @dialect
|
47
|
+
when '0x0300', '0x0302'
|
48
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(@session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
|
49
|
+
when '0x0311'
|
50
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(@session_key, "SMBSigningKey\x00", @preauth_integrity_hash_value)
|
51
|
+
else
|
52
|
+
raise RubySMB::Error::SigningError.new('Dialect is incompatible with SMBv3 signing')
|
53
|
+
end
|
54
|
+
|
55
|
+
packet.smb2_header.flags.signed = 1
|
56
|
+
packet.smb2_header.signature = "\x00" * 16
|
57
|
+
hmac = OpenSSL::CMAC.digest('AES', signing_key, packet.to_binary_s)
|
58
|
+
packet.smb2_header.signature = hmac[0, 16]
|
59
|
+
end
|
60
|
+
packet
|
61
|
+
end
|
43
62
|
end
|
44
63
|
end
|
45
64
|
end
|
@@ -38,7 +38,7 @@ module RubySMB
|
|
38
38
|
)
|
39
39
|
end
|
40
40
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
41
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
41
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
42
42
|
end
|
43
43
|
RubySMB::SMB1::Tree.new(client: self, share: share, response: response)
|
44
44
|
end
|
@@ -55,7 +55,7 @@ module RubySMB
|
|
55
55
|
def smb2_tree_connect(share)
|
56
56
|
request = RubySMB::SMB2::Packet::TreeConnectRequest.new
|
57
57
|
request.smb2_header.tree_id = 65_535
|
58
|
-
request.
|
58
|
+
request.path = share
|
59
59
|
raw_response = send_recv(request)
|
60
60
|
response = RubySMB::SMB2::Packet::TreeConnectResponse.read(raw_response)
|
61
61
|
smb2_tree_from_response(share, response)
|
@@ -78,9 +78,9 @@ module RubySMB
|
|
78
78
|
)
|
79
79
|
end
|
80
80
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
81
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
81
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
82
82
|
end
|
83
|
-
RubySMB::SMB2::Tree.new(client: self, share: share, response: response)
|
83
|
+
RubySMB::SMB2::Tree.new(client: self, share: share, response: response, encrypt: response.share_flags.encrypt == 1)
|
84
84
|
end
|
85
85
|
end
|
86
86
|
end
|
@@ -40,27 +40,28 @@ module RubySMB
|
|
40
40
|
end
|
41
41
|
|
42
42
|
#Writes data to an open file handle
|
43
|
-
def write(file_id, offset = 0, data = '', do_recv = true)
|
43
|
+
def write(file_id = last_file_id, offset = 0, data = '', do_recv = true)
|
44
44
|
@open_files[file_id].send_recv_write(data: data, offset: offset)
|
45
45
|
end
|
46
46
|
|
47
|
-
def read(file_id, offset = 0, length = last_file.size)
|
47
|
+
def read(file_id = last_file_id, offset = 0, length = last_file.size, do_recv = true)
|
48
48
|
data = @open_files[file_id].send_recv_read(read_length: length, offset: offset)
|
49
49
|
data.bytes
|
50
50
|
end
|
51
51
|
|
52
|
-
def delete(path)
|
53
|
-
|
52
|
+
def delete(path, tree_id = last_tree_id, do_recv = true)
|
53
|
+
tree = @tree_connects.detect{ |tree| tree.id == tree_id }
|
54
|
+
file = tree.open_file(filename: path.sub(/^\\/, ''), delete: true)
|
54
55
|
file.delete
|
55
56
|
file.close
|
56
57
|
end
|
57
58
|
|
58
|
-
def close(file_id, tree_id)
|
59
|
+
def close(file_id = last_file_id, tree_id = last_tree_id, do_recv = true)
|
59
60
|
@open_files[file_id].close
|
60
61
|
end
|
61
62
|
|
62
|
-
def tree_disconnect(
|
63
|
-
@tree_connects.detect{|tree| tree.id ==
|
63
|
+
def tree_disconnect(tree_id = last_tree_id, do_recv = true)
|
64
|
+
@tree_connects.detect{|tree| tree.id == tree_id }.disconnect!
|
64
65
|
end
|
65
66
|
|
66
67
|
def native_os
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module RubySMB
|
2
|
+
class Client
|
3
|
+
module Winreg
|
4
|
+
|
5
|
+
def connect_to_winreg(host)
|
6
|
+
share = "\\\\#{host}\\IPC$"
|
7
|
+
tree = @tree_connects.find {|tree| tree.share == share}
|
8
|
+
tree = tree_connect(share) unless tree
|
9
|
+
named_pipe = tree.open_file(filename: "winreg", write: true, read: true)
|
10
|
+
if block_given?
|
11
|
+
res = yield named_pipe
|
12
|
+
named_pipe.close
|
13
|
+
res
|
14
|
+
else
|
15
|
+
named_pipe
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def has_registry_key?(host, key)
|
20
|
+
connect_to_winreg(host) do |named_pipe|
|
21
|
+
named_pipe.has_registry_key?(key)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def read_registry_key_value(host, key, value_name)
|
26
|
+
connect_to_winreg(host) do |named_pipe|
|
27
|
+
named_pipe.read_registry_key_value(key, value_name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def enum_registry_key(host, key)
|
32
|
+
connect_to_winreg(host) do |named_pipe|
|
33
|
+
named_pipe.enum_registry_key(key)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def enum_registry_values(host, key)
|
38
|
+
connect_to_winreg(host) do |named_pipe|
|
39
|
+
named_pipe.enum_registry_values(key)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module RubySMB
|
2
|
+
module Crypto
|
3
|
+
module KDF
|
4
|
+
def self.counter_mode(ki, label, context, length: 128)
|
5
|
+
digest = OpenSSL::Digest.new('SHA256')
|
6
|
+
r = 32
|
7
|
+
|
8
|
+
n = length / 256
|
9
|
+
n = 1 if n == 0
|
10
|
+
|
11
|
+
raise ArgumentError if n > 2**r - 1
|
12
|
+
result = ""
|
13
|
+
|
14
|
+
n.times do |i|
|
15
|
+
input = [i + 1].pack('L>')
|
16
|
+
input << label
|
17
|
+
input << "\x00"
|
18
|
+
input << context
|
19
|
+
input << [length].pack('L>')
|
20
|
+
k = OpenSSL::HMAC.digest(digest, ki, input)
|
21
|
+
result << k
|
22
|
+
end
|
23
|
+
|
24
|
+
return result[0...(length / 8)]
|
25
|
+
rescue OpenSSL::OpenSSLError => e
|
26
|
+
raise RubySMB::Error::EncryptionError, "Crypto::KDF.counter_mode OpenSSL error: #{e.message}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|