ruby_smb 3.2.4 → 3.2.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/cortex.yaml +15 -0
- data/examples/dump_secrets_from_sid.rb +1 -1
- data/lib/ruby_smb/dcerpc/alter_context.rb +30 -0
- data/lib/ruby_smb/dcerpc/alter_context_resp.rb +42 -0
- data/lib/ruby_smb/dcerpc/bind.rb +3 -35
- data/lib/ruby_smb/dcerpc/bind_ack.rb +0 -31
- data/lib/ruby_smb/dcerpc/client.rb +4 -0
- data/lib/ruby_smb/dcerpc/drsr.rb +13 -13
- data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_decrypt_file_srv_request.rb +22 -0
- data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_decrypt_file_srv_response.rb +21 -0
- data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_query_recover_agents_request.rb +20 -0
- data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_query_recover_agents_response.rb +21 -0
- data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_query_users_on_file_request.rb +20 -0
- data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_query_users_on_file_response.rb +21 -0
- data/lib/ruby_smb/dcerpc/encrypting_file_system.rb +52 -0
- data/lib/ruby_smb/dcerpc/p_cont_list_t.rb +37 -0
- data/lib/ruby_smb/dcerpc/p_result_list_t.rb +13 -0
- data/lib/ruby_smb/dcerpc/p_result_t.rb +15 -0
- data/lib/ruby_smb/dcerpc/port_any_t.rb +11 -0
- data/lib/ruby_smb/dcerpc/request.rb +8 -3
- data/lib/ruby_smb/dcerpc/response.rb +6 -1
- data/lib/ruby_smb/dcerpc.rb +165 -122
- data/lib/ruby_smb/ntlm/custom/string_encoder.rb +22 -0
- data/lib/ruby_smb/ntlm.rb +1 -1
- data/lib/ruby_smb/version.rb +1 -1
- data/lib/ruby_smb.rb +1 -1
- data/spec/lib/ruby_smb/dcerpc/client_spec.rb +31 -16
- data/spec/lib/ruby_smb/dcerpc/drsr_spec.rb +4 -1
- data/spec/lib/ruby_smb/dcerpc/request_spec.rb +0 -6
- data/spec/lib/ruby_smb/dcerpc/response_spec.rb +0 -6
- data/spec/lib/ruby_smb/dcerpc/sec_trailer_spec.rb +0 -14
- data.tar.gz.sig +0 -0
- metadata +16 -3
- metadata.gz.sig +0 -0
- data/lib/ruby_smb/ntlm/custom/ntlm.rb +0 -19
data/lib/ruby_smb/dcerpc.rb
CHANGED
@@ -51,11 +51,128 @@ module RubySMB
|
|
51
51
|
require 'ruby_smb/dcerpc/request'
|
52
52
|
require 'ruby_smb/dcerpc/response'
|
53
53
|
require 'ruby_smb/dcerpc/rpc_auth3'
|
54
|
+
require 'ruby_smb/dcerpc/port_any_t'
|
55
|
+
require 'ruby_smb/dcerpc/p_cont_list_t'
|
56
|
+
require 'ruby_smb/dcerpc/p_result_t'
|
57
|
+
require 'ruby_smb/dcerpc/p_result_list_t'
|
58
|
+
require 'ruby_smb/dcerpc/alter_context'
|
54
59
|
require 'ruby_smb/dcerpc/bind'
|
55
60
|
require 'ruby_smb/dcerpc/bind_ack'
|
61
|
+
require 'ruby_smb/dcerpc/alter_context_resp'
|
56
62
|
require 'ruby_smb/dcerpc/print_system'
|
57
63
|
require 'ruby_smb/dcerpc/encrypting_file_system'
|
58
64
|
|
65
|
+
# Initialize the auth provider using NTLM. This function should be overriden for other providers (e.g. Kerberos, etc.)
|
66
|
+
# @raise ArgumentError If @ntlm_client isn't initialized with a username and password.
|
67
|
+
# @return Serialized message for initializing the auth provider (NTLM, unless this class is extended/overridden)
|
68
|
+
def auth_provider_init
|
69
|
+
raise ArgumentError, "NTLM Client not initialized. Username and password must be provided" unless @ntlm_client
|
70
|
+
type1_message = @ntlm_client.init_context
|
71
|
+
|
72
|
+
type1_message.serialize
|
73
|
+
end
|
74
|
+
|
75
|
+
# Encrypt the value in dcerpc_req.stub, and add a valid signature to the request.
|
76
|
+
# This function modifies the request object in-place, and does not return anything.
|
77
|
+
# This function should be overriden for other providers (e.g. Kerberos, etc.)
|
78
|
+
# @param dcerpc_req [Request] The Request object to be encrypted and signed in-place
|
79
|
+
def auth_provider_encrypt_and_sign(dcerpc_req)
|
80
|
+
auth_type = dcerpc_req.sec_trailer.auth_type
|
81
|
+
auth_level = dcerpc_req.sec_trailer.auth_level
|
82
|
+
unless [RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT].include?(auth_type)
|
83
|
+
raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
|
84
|
+
end
|
85
|
+
plaintext = dcerpc_req.stub.to_binary_s
|
86
|
+
pad_length = get_auth_padding_length(plaintext.length)
|
87
|
+
dcerpc_req.auth_pad = "\x00" * pad_length
|
88
|
+
data_to_sign = plain_stub_with_padding = dcerpc_req.stub.to_binary_s + dcerpc_req.auth_pad.to_binary_s
|
89
|
+
dcerpc_req.sec_trailer.auth_pad_length = pad_length
|
90
|
+
if @ntlm_client.flags & NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] != 0
|
91
|
+
data_to_sign = dcerpc_req.to_binary_s[0..-(dcerpc_req.pdu_header.auth_length + 1)]
|
92
|
+
end
|
93
|
+
|
94
|
+
if auth_level == RPC_C_AUTHN_LEVEL_PKT_PRIVACY
|
95
|
+
encrypted = @ntlm_client.session.seal_message(plain_stub_with_padding)
|
96
|
+
set_encrypted_packet(dcerpc_req, encrypted, pad_length)
|
97
|
+
end
|
98
|
+
signature = @ntlm_client.session.sign_message(data_to_sign)
|
99
|
+
|
100
|
+
set_signature_on_packet(dcerpc_req, signature)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Get the response's full stub value (which will include the auth-pad)
|
104
|
+
# @param dcerpc_response [Response] The Response object to extract from
|
105
|
+
# @return [String] The full stub, including auth_pad
|
106
|
+
def get_response_full_stub(dcerpc_response)
|
107
|
+
dcerpc_response.stub.to_binary_s + dcerpc_response.auth_pad.to_binary_s
|
108
|
+
end
|
109
|
+
|
110
|
+
# Decrypt the value in dcerpc_req.stub, and validate its signature.
|
111
|
+
# This function modifies the request object in-place, and returns whether the signature was valid.
|
112
|
+
# This function should be overriden for other providers (e.g. Kerberos, etc.)
|
113
|
+
# @param dcerpc_response [Response] The Response packet to decrypt and verify in-place
|
114
|
+
# @raise ArgumentError If the auth type is not NTLM
|
115
|
+
# @return [Boolean] Is the packet's signature valid?
|
116
|
+
def auth_provider_decrypt_and_verify(dcerpc_response)
|
117
|
+
auth_type = dcerpc_response.sec_trailer.auth_type
|
118
|
+
auth_level = dcerpc_response.sec_trailer.auth_level
|
119
|
+
unless [RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT].include?(auth_type)
|
120
|
+
raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
|
121
|
+
end
|
122
|
+
signature = dcerpc_response.auth_value
|
123
|
+
if auth_level == RPC_C_AUTHN_LEVEL_PKT_PRIVACY
|
124
|
+
encrypted_stub = get_response_full_stub(dcerpc_response)
|
125
|
+
plaintext = @ntlm_client.session.unseal_message(encrypted_stub)
|
126
|
+
set_decrypted_packet(dcerpc_response, plaintext)
|
127
|
+
end
|
128
|
+
data_to_check = dcerpc_response.stub.to_binary_s
|
129
|
+
if @ntlm_client.flags & NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] != 0
|
130
|
+
data_to_check = dcerpc_response.to_binary_s[0..-(dcerpc_response.pdu_header.auth_length + 1)]
|
131
|
+
end
|
132
|
+
|
133
|
+
@ntlm_client.session.verify_signature(signature, data_to_check)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Completes local initialisation of @ntlm_client using the server's response
|
137
|
+
#
|
138
|
+
# @param type2_message [String] NTLM type 2 message sent from server
|
139
|
+
# @return [String] Type 3 message to be sent to the server to complete the NTLM handshake
|
140
|
+
def process_ntlm_type2(type2_message)
|
141
|
+
ntlmssp_offset = type2_message.index('NTLMSSP')
|
142
|
+
type2_blob = type2_message.slice(ntlmssp_offset..-1)
|
143
|
+
type2_b64_message = [type2_blob].pack('m')
|
144
|
+
type3_message = @ntlm_client.init_context(type2_b64_message)
|
145
|
+
auth3 = type3_message.serialize
|
146
|
+
|
147
|
+
@session_key = @ntlm_client.session_key
|
148
|
+
auth3
|
149
|
+
end
|
150
|
+
|
151
|
+
# Send a rpc_auth3 PDU that ends the authentication handshake.
|
152
|
+
# This function should be overriden for other providers (e.g. Kerberos, etc.)
|
153
|
+
#
|
154
|
+
# @param response [BindAck] the BindAck response packet
|
155
|
+
# @param options [Hash] Unused by the NTLM auth provider
|
156
|
+
def auth_provider_complete_handshake(response, options)
|
157
|
+
auth3 = process_ntlm_type2(response.auth_value)
|
158
|
+
|
159
|
+
rpc_auth3 = RpcAuth3.new
|
160
|
+
add_auth_verifier(rpc_auth3, auth3)
|
161
|
+
rpc_auth3.pdu_header.call_id = @call_id
|
162
|
+
|
163
|
+
# The server should not respond
|
164
|
+
send_packet(rpc_auth3)
|
165
|
+
@call_id += 1
|
166
|
+
|
167
|
+
nil
|
168
|
+
end
|
169
|
+
|
170
|
+
def force_set_auth_params(auth_type, auth_level)
|
171
|
+
@auth_type = auth_type
|
172
|
+
@auth_level = auth_level
|
173
|
+
end
|
174
|
+
|
175
|
+
|
59
176
|
# Bind to the remote server interface endpoint. It takes care of adding
|
60
177
|
# the necessary authentication verifier if `:auth_level` is set to
|
61
178
|
# anything different than RPC_C_AUTHN_LEVEL_NONE
|
@@ -74,23 +191,16 @@ module RubySMB
|
|
74
191
|
@call_id ||= 1
|
75
192
|
bind_req = Bind.new(options)
|
76
193
|
bind_req.pdu_header.call_id = @call_id
|
194
|
+
auth_type = options.fetch(:auth_type) { RPC_C_AUTHN_WINNT }
|
195
|
+
auth_level = options.fetch(:auth_level) { RPC_C_AUTHN_LEVEL_NONE }
|
77
196
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
auth = type1_message.serialize
|
86
|
-
when RPC_C_AUTHN_GSS_KERBEROS, RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE
|
87
|
-
when RPC_C_AUTHN_GSS_KERBEROS, RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL
|
88
|
-
# TODO
|
89
|
-
raise NotImplementedError
|
90
|
-
else
|
91
|
-
raise ArgumentError, "Unsupported Auth Type: #{options[:auth_type]}"
|
92
|
-
end
|
93
|
-
add_auth_verifier(bind_req, auth, options[:auth_type], options[:auth_level])
|
197
|
+
force_set_auth_params(auth_type, auth_level)
|
198
|
+
|
199
|
+
if @auth_level != RPC_C_AUTHN_LEVEL_NONE
|
200
|
+
@ctx_id = 0
|
201
|
+
@auth_ctx_id_base = rand(0xFFFFFFFF)
|
202
|
+
auth = auth_provider_init
|
203
|
+
add_auth_verifier(bind_req, auth)
|
94
204
|
end
|
95
205
|
|
96
206
|
send_packet(bind_req)
|
@@ -110,17 +220,11 @@ module RubySMB
|
|
110
220
|
self.max_buffer_size = dcerpc_response.max_xmit_frag
|
111
221
|
@call_id = dcerpc_response.pdu_header.call_id
|
112
222
|
|
113
|
-
if
|
223
|
+
if auth_level != RPC_C_AUTHN_LEVEL_NONE
|
114
224
|
# The number of legs needed to build the security context is defined
|
115
225
|
# by the security provider
|
116
|
-
# (see [2.2.1.1.7 Security Providers](https://
|
117
|
-
|
118
|
-
when RPC_C_AUTHN_WINNT
|
119
|
-
send_auth3(dcerpc_response, options[:auth_type], options[:auth_level])
|
120
|
-
when RPC_C_AUTHN_GSS_KERBEROS, RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE
|
121
|
-
# TODO
|
122
|
-
raise NotImplementedError
|
123
|
-
end
|
226
|
+
# (see [2.2.1.1.7 Security Providers](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/d4097450-c62f-484b-872f-ddf59a7a0d36))
|
227
|
+
auth_provider_complete_handshake(dcerpc_response, options)
|
124
228
|
end
|
125
229
|
|
126
230
|
dcerpc_response
|
@@ -156,16 +260,24 @@ module RubySMB
|
|
156
260
|
nil
|
157
261
|
end
|
158
262
|
|
263
|
+
def get_auth_padding_length(plaintext_len)
|
264
|
+
(16 - (plaintext_len % 16)) % 16
|
265
|
+
end
|
266
|
+
|
159
267
|
# Add the authentication verifier to a Request packet. This includes a
|
160
268
|
# sec trailer and the signature of the packet. This also encrypts the
|
161
269
|
# Request stub if privacy is required (`:auth_level` option is
|
162
270
|
# RPC_C_AUTHN_LEVEL_PKT_PRIVACY).
|
163
271
|
#
|
164
272
|
# @param dcerpc_req [Request] the Request packet to be updated
|
165
|
-
# @param opts [Hash] the
|
273
|
+
# @param opts [Hash] the authentication options: `:auth_type` and `:auth_level`
|
166
274
|
# @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
|
167
275
|
# @raise [ArgumentError] if `:auth_type` is unknown
|
168
276
|
def set_integrity_privacy(dcerpc_req, auth_level:, auth_type:)
|
277
|
+
unless [RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(auth_level)
|
278
|
+
raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
|
279
|
+
end
|
280
|
+
|
169
281
|
dcerpc_req.sec_trailer = {
|
170
282
|
auth_type: auth_type,
|
171
283
|
auth_level: auth_level,
|
@@ -174,35 +286,30 @@ module RubySMB
|
|
174
286
|
dcerpc_req.auth_value = ' ' * 16
|
175
287
|
dcerpc_req.pdu_header.auth_length = 16
|
176
288
|
|
177
|
-
|
178
|
-
|
179
|
-
data_to_sign = dcerpc_req.to_binary_s[0..-(dcerpc_req.pdu_header.auth_length + 1)]
|
180
|
-
end
|
181
|
-
|
182
|
-
encrypted_stub = ''
|
183
|
-
if auth_level == RPC_C_AUTHN_LEVEL_PKT_PRIVACY
|
184
|
-
case auth_type
|
185
|
-
when RPC_C_AUTHN_NONE
|
186
|
-
when RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT
|
187
|
-
encrypted_stub = @ntlm_client.session.seal_message(plain_stub)
|
188
|
-
when RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL, RPC_C_AUTHN_GSS_KERBEROS
|
189
|
-
# TODO
|
190
|
-
raise NotImplementedError
|
191
|
-
else
|
192
|
-
raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
|
193
|
-
end
|
194
|
-
end
|
289
|
+
auth_provider_encrypt_and_sign(dcerpc_req)
|
290
|
+
end
|
195
291
|
|
196
|
-
|
292
|
+
def set_signature_on_packet(dcerpc_req, signature)
|
293
|
+
dcerpc_req.auth_value = signature
|
294
|
+
dcerpc_req.pdu_header.auth_length = signature.size
|
295
|
+
end
|
197
296
|
|
297
|
+
def set_encrypted_packet(dcerpc_req, encrypted_stub, pad_length)
|
198
298
|
unless encrypted_stub.empty?
|
199
|
-
pad_length = dcerpc_req.sec_trailer.auth_pad_length.to_i
|
200
299
|
dcerpc_req.enable_encrypted_stub
|
201
|
-
dcerpc_req.stub = encrypted_stub[0..-(pad_length
|
202
|
-
|
300
|
+
dcerpc_req.stub = encrypted_stub[0..-(pad_length+1)]
|
301
|
+
if pad_length != 0
|
302
|
+
dcerpc_req.auth_pad = encrypted_stub[-(pad_length)..-1]
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def set_decrypted_packet(dcerpc_response, decrypted_stub)
|
308
|
+
unless decrypted_stub.empty?
|
309
|
+
pad_length = dcerpc_response.sec_trailer.auth_pad_length.to_i
|
310
|
+
dcerpc_response.stub = decrypted_stub[0..-(pad_length + 1)]
|
311
|
+
dcerpc_response.auth_pad = decrypted_stub[-(pad_length + 1)..-1]
|
203
312
|
end
|
204
|
-
dcerpc_req.auth_value = signature
|
205
|
-
dcerpc_req.pdu_header.auth_length = signature.size
|
206
313
|
end
|
207
314
|
|
208
315
|
# Process the security context received in a response. It decrypts the
|
@@ -214,39 +321,18 @@ module RubySMB
|
|
214
321
|
#
|
215
322
|
# @param dcerpc_response [Response] the Response packet
|
216
323
|
# containing the security context to process
|
217
|
-
# @param opts [Hash] the
|
324
|
+
# @param opts [Hash] the authentication options: `:auth_type` and
|
218
325
|
# `:auth_level`. To enable errors when signature check fails, set the
|
219
326
|
# `:raise_signature_error` option to true
|
220
327
|
# @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
|
221
328
|
# @raise [Error::CommunicationError] if socket-related error occurs
|
222
329
|
def handle_integrity_privacy(dcerpc_response, auth_level:, auth_type:, raise_signature_error: false)
|
223
|
-
|
224
|
-
|
225
|
-
encrypted_stub = dcerpc_response.stub.to_binary_s + dcerpc_response.auth_pad.to_binary_s
|
226
|
-
case auth_type
|
227
|
-
when RPC_C_AUTHN_NONE
|
228
|
-
when RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT
|
229
|
-
decrypted_stub = @ntlm_client.session.unseal_message(encrypted_stub)
|
230
|
-
when RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL, RPC_C_AUTHN_GSS_KERBEROS
|
231
|
-
# TODO
|
232
|
-
raise NotImplementedError
|
233
|
-
else
|
234
|
-
raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
unless decrypted_stub.empty?
|
239
|
-
pad_length = dcerpc_response.sec_trailer.auth_pad_length.to_i
|
240
|
-
dcerpc_response.stub = decrypted_stub[0..-(pad_length + 1)]
|
241
|
-
dcerpc_response.auth_pad = decrypted_stub[-(pad_length)..-1]
|
330
|
+
unless [RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(auth_level)
|
331
|
+
raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
|
242
332
|
end
|
333
|
+
signature_valid = auth_provider_decrypt_and_verify(dcerpc_response)
|
243
334
|
|
244
|
-
|
245
|
-
data_to_check = dcerpc_response.stub.to_binary_s
|
246
|
-
if @ntlm_client.flags & NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] != 0
|
247
|
-
data_to_check = dcerpc_response.to_binary_s[0..-(dcerpc_response.pdu_header.auth_length + 1)]
|
248
|
-
end
|
249
|
-
unless @ntlm_client.session.verify_signature(signature, data_to_check)
|
335
|
+
unless signature_valid
|
250
336
|
if raise_signature_error
|
251
337
|
raise Error::InvalidPacket.new(
|
252
338
|
"Wrong packet signature received (set `raise_signature_error` to false to ignore)"
|
@@ -264,12 +350,10 @@ module RubySMB
|
|
264
350
|
#
|
265
351
|
# @param req [BinData::Record] the request to be updated
|
266
352
|
# @param auth [String] the authentication data
|
267
|
-
|
268
|
-
# @param auth_level [Integer] the authentication level
|
269
|
-
def add_auth_verifier(req, auth, auth_type, auth_level)
|
353
|
+
def add_auth_verifier(req, auth)
|
270
354
|
req.sec_trailer = {
|
271
|
-
auth_type: auth_type,
|
272
|
-
auth_level: auth_level,
|
355
|
+
auth_type: @auth_type,
|
356
|
+
auth_level: @auth_level,
|
273
357
|
auth_context_id: @ctx_id + @auth_ctx_id_base
|
274
358
|
}
|
275
359
|
req.auth_value = auth
|
@@ -277,46 +361,5 @@ module RubySMB
|
|
277
361
|
|
278
362
|
nil
|
279
363
|
end
|
280
|
-
|
281
|
-
def process_ntlm_type2(type2_message)
|
282
|
-
ntlmssp_offset = type2_message.index('NTLMSSP')
|
283
|
-
type2_blob = type2_message.slice(ntlmssp_offset..-1)
|
284
|
-
type2_b64_message = [type2_blob].pack('m')
|
285
|
-
type3_message = @ntlm_client.init_context(type2_b64_message)
|
286
|
-
auth3 = type3_message.serialize
|
287
|
-
|
288
|
-
@session_key = @ntlm_client.session_key
|
289
|
-
auth3
|
290
|
-
end
|
291
|
-
|
292
|
-
# Send a rpc_auth3 PDU that ends the authentication handshake.
|
293
|
-
#
|
294
|
-
# @param response [BindAck] the BindAck response packet
|
295
|
-
# @param auth_type [Integer] the authentication type
|
296
|
-
# @param auth_level [Integer] the authentication level
|
297
|
-
# @raise [ArgumentError] if `:auth_type` is unknown
|
298
|
-
# @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
|
299
|
-
def send_auth3(response, auth_type, auth_level)
|
300
|
-
case auth_type
|
301
|
-
when RPC_C_AUTHN_NONE
|
302
|
-
when RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT
|
303
|
-
auth3 = process_ntlm_type2(response.auth_value)
|
304
|
-
when RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL, RPC_C_AUTHN_GSS_KERBEROS
|
305
|
-
# TODO
|
306
|
-
raise NotImplementedError
|
307
|
-
else
|
308
|
-
raise ArgumentError, "Unsupported Auth Type: #{auth_type}"
|
309
|
-
end
|
310
|
-
|
311
|
-
rpc_auth3 = RpcAuth3.new
|
312
|
-
add_auth_verifier(rpc_auth3, auth3, auth_type, auth_level)
|
313
|
-
rpc_auth3.pdu_header.call_id = @call_id
|
314
|
-
|
315
|
-
# The server should not respond
|
316
|
-
send_packet(rpc_auth3)
|
317
|
-
@call_id += 1
|
318
|
-
|
319
|
-
nil
|
320
|
-
end
|
321
364
|
end
|
322
365
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'net/ntlm'
|
2
|
+
|
3
|
+
module RubySMB
|
4
|
+
module NTLM
|
5
|
+
module Custom
|
6
|
+
module StringEncoder
|
7
|
+
|
8
|
+
def self.prepended(base)
|
9
|
+
base.singleton_class.send(:prepend, ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def encode_utf16le(str)
|
14
|
+
str.dup.force_encoding('UTF-8').encode(Encoding::UTF_16LE, Encoding::UTF_8).force_encoding('ASCII-8BIT')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Net::NTLM::EncodeUtil.send(:prepend, RubySMB::NTLM::Custom::StringEncoder)
|
data/lib/ruby_smb/ntlm.rb
CHANGED
data/lib/ruby_smb/version.rb
CHANGED
data/lib/ruby_smb.rb
CHANGED
@@ -6,7 +6,7 @@ require 'openssl/ccm'
|
|
6
6
|
require 'openssl/cmac'
|
7
7
|
require 'windows_error'
|
8
8
|
require 'windows_error/nt_status'
|
9
|
-
require 'ruby_smb/ntlm/custom/
|
9
|
+
require 'ruby_smb/ntlm/custom/string_encoder'
|
10
10
|
# A packet parsing and manipulation library for the SMB1 and SMB2 protocols
|
11
11
|
#
|
12
12
|
# [[MS-SMB] Server Message Block (SMB) Protocol Version 1](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
|
@@ -9,7 +9,12 @@ RSpec.describe RubySMB::Dcerpc::Client do
|
|
9
9
|
let(:endpoint) { RubySMB::Dcerpc::Samr }
|
10
10
|
|
11
11
|
subject(:client) { described_class.new(host, endpoint) }
|
12
|
-
subject(:auth_client)
|
12
|
+
subject(:auth_client) do
|
13
|
+
result = described_class.new(host, endpoint, username: 'testuser', password: '1234')
|
14
|
+
result.force_set_auth_params(RubySMB::Dcerpc::RPC_C_AUTHN_WINNT, RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
|
15
|
+
|
16
|
+
result
|
17
|
+
end
|
13
18
|
|
14
19
|
it { is_expected.to respond_to :domain }
|
15
20
|
it { is_expected.to respond_to :local_workstation }
|
@@ -135,21 +140,21 @@ RSpec.describe RubySMB::Dcerpc::Client do
|
|
135
140
|
describe '#add_auth_verifier' do
|
136
141
|
let(:req) { RubySMB::Dcerpc::Bind.new }
|
137
142
|
let(:auth_type) { RubySMB::Dcerpc::RPC_C_AUTHN_WINNT }
|
138
|
-
let(:auth_level) {
|
143
|
+
let(:auth_level) { RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY }
|
139
144
|
let(:auth) { 'serialized auth value' }
|
140
145
|
|
141
146
|
it 'sets #auth_value field to the expected value' do
|
142
|
-
auth_client.add_auth_verifier(req, auth
|
147
|
+
auth_client.add_auth_verifier(req, auth)
|
143
148
|
expect(req.auth_value).to eq(auth)
|
144
149
|
end
|
145
150
|
|
146
151
|
it 'sets PDUHeader #auth_length field to the expected value' do
|
147
|
-
auth_client.add_auth_verifier(req, auth
|
152
|
+
auth_client.add_auth_verifier(req, auth)
|
148
153
|
expect(req.pdu_header.auth_length).to eq(auth.length)
|
149
154
|
end
|
150
155
|
|
151
156
|
it 'sets #sec_trailer field to the expected value' do
|
152
|
-
auth_client.add_auth_verifier(req, auth
|
157
|
+
auth_client.add_auth_verifier(req, auth)
|
153
158
|
expect(req.sec_trailer.auth_type).to eq(auth_type)
|
154
159
|
expect(req.sec_trailer.auth_level).to eq(auth_level)
|
155
160
|
expect(req.sec_trailer.auth_context_id).to eq(auth_client.instance_variable_get(:@auth_ctx_id_base))
|
@@ -202,29 +207,29 @@ RSpec.describe RubySMB::Dcerpc::Client do
|
|
202
207
|
end
|
203
208
|
|
204
209
|
it 'add an auth verifier to the RpcAuth3 packet' do
|
205
|
-
auth_client.
|
206
|
-
expect(auth_client).to have_received(:add_auth_verifier).with(rpc_auth3, auth3
|
210
|
+
auth_client.auth_provider_complete_handshake(bindack, auth_type: auth_type, auth_level: auth_level)
|
211
|
+
expect(auth_client).to have_received(:add_auth_verifier).with(rpc_auth3, auth3)
|
207
212
|
end
|
208
213
|
|
209
214
|
it 'sets the PDUHeader #call_id to the expected value' do
|
210
215
|
auth_client.instance_variable_set(:@call_id, 56)
|
211
|
-
auth_client.
|
216
|
+
auth_client.auth_provider_complete_handshake(bindack, auth_type: auth_type, auth_level: auth_level)
|
212
217
|
expect(rpc_auth3.pdu_header.call_id).to eq(56)
|
213
218
|
end
|
214
219
|
|
215
220
|
it 'sends the RpcAuth3 packet' do
|
216
|
-
auth_client.
|
221
|
+
auth_client.auth_provider_complete_handshake(bindack, auth_type: auth_type, auth_level: auth_level)
|
217
222
|
expect(auth_client).to have_received(:send_packet).with(rpc_auth3)
|
218
223
|
end
|
219
224
|
|
220
225
|
it 'increments #call_id' do
|
221
|
-
auth_client.
|
226
|
+
auth_client.auth_provider_complete_handshake(bindack, auth_type: auth_type, auth_level: auth_level)
|
222
227
|
expect(auth_client.instance_variable_get(:@call_id)).to eq(2)
|
223
228
|
end
|
224
229
|
|
225
230
|
context 'with RPC_C_AUTHN_WINNT auth_type' do
|
226
231
|
it 'processes NTLM type2 message' do
|
227
|
-
auth_client.
|
232
|
+
auth_client.auth_provider_complete_handshake(bindack, auth_type: auth_type, auth_level: auth_level)
|
228
233
|
expect(auth_client).to have_received(:process_ntlm_type2).with(auth)
|
229
234
|
end
|
230
235
|
end
|
@@ -300,7 +305,7 @@ RSpec.describe RubySMB::Dcerpc::Client do
|
|
300
305
|
|
301
306
|
context 'with RPC_C_AUTHN_WINNT auth_type' do
|
302
307
|
let(:kwargs) do {
|
303
|
-
auth_level: RubySMB::Dcerpc::
|
308
|
+
auth_level: RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
|
304
309
|
auth_type: RubySMB::Dcerpc::RPC_C_AUTHN_WINNT
|
305
310
|
}
|
306
311
|
end
|
@@ -310,7 +315,7 @@ RSpec.describe RubySMB::Dcerpc::Client do
|
|
310
315
|
allow(client.ntlm_client).to receive(:init_context).and_return(type1_message)
|
311
316
|
allow(type1_message).to receive(:serialize).and_return(auth)
|
312
317
|
allow(client).to receive(:add_auth_verifier)
|
313
|
-
allow(client).to receive(:
|
318
|
+
allow(client).to receive(:auth_provider_complete_handshake)
|
314
319
|
end
|
315
320
|
|
316
321
|
it 'raises an exception if the NTLM client is not initialized' do
|
@@ -320,12 +325,12 @@ RSpec.describe RubySMB::Dcerpc::Client do
|
|
320
325
|
|
321
326
|
it 'adds the auth verifier with a NTLM type1 message' do
|
322
327
|
client.bind(**kwargs)
|
323
|
-
expect(client).to have_received(:add_auth_verifier).with(bind_req, auth
|
328
|
+
expect(client).to have_received(:add_auth_verifier).with(bind_req, auth)
|
324
329
|
end
|
325
330
|
|
326
331
|
it 'sends an auth3 request' do
|
327
332
|
client.bind(**kwargs)
|
328
|
-
expect(client).to have_received(:
|
333
|
+
expect(client).to have_received(:auth_provider_complete_handshake).with(bindack_response, auth_type: kwargs[:auth_type], auth_level: kwargs[:auth_level])
|
329
334
|
end
|
330
335
|
end
|
331
336
|
end
|
@@ -372,10 +377,14 @@ RSpec.describe RubySMB::Dcerpc::Client do
|
|
372
377
|
|
373
378
|
describe '#set_integrity_privacy' do
|
374
379
|
let(:dcerpc_req) do
|
375
|
-
RubySMB::Dcerpc::Request.new(
|
380
|
+
req = RubySMB::Dcerpc::Request.new(
|
376
381
|
{ opnum: RubySMB::Dcerpc::Winreg::REG_ENUM_KEY },
|
377
382
|
{ endpoint: 'Winreg' }
|
378
383
|
)
|
384
|
+
req.sec_trailer.auth_pad_length = 8
|
385
|
+
req.auth_pad = "\x00" * 8
|
386
|
+
|
387
|
+
req
|
379
388
|
end
|
380
389
|
let(:auth_level) { RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY }
|
381
390
|
let(:auth_type) { RubySMB::Dcerpc::RPC_C_AUTHN_WINNT }
|
@@ -656,6 +665,8 @@ RSpec.describe RubySMB::Dcerpc::Client do
|
|
656
665
|
# Make sure the encrypted stub includes the correct pad to make sure sec_trailer is 16-bytes aligned
|
657
666
|
allow(session).to receive(:unseal_message).and_return(decrypted_stub + auth_pad)
|
658
667
|
allow(session).to receive(:verify_signature).and_return true
|
668
|
+
dcerpc_res.sec_trailer.auth_type = auth_type
|
669
|
+
dcerpc_res.sec_trailer.auth_level = auth_level
|
659
670
|
end
|
660
671
|
|
661
672
|
it 'verifies the signature' do
|
@@ -698,6 +709,8 @@ RSpec.describe RubySMB::Dcerpc::Client do
|
|
698
709
|
context 'without RPC_C_AUTHN_LEVEL_PKT_PRIVACY auth_level' do
|
699
710
|
it 'does not encrypt the stub' do
|
700
711
|
plain_stub = dcerpc_res.stub.to_binary_s
|
712
|
+
dcerpc_res.sec_trailer.auth_level = RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_INTEGRITY
|
713
|
+
dcerpc_res.sec_trailer.auth_type = RubySMB::Dcerpc::RPC_C_AUTHN_WINNT
|
701
714
|
auth_client.handle_integrity_privacy(dcerpc_res, auth_level: RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, auth_type: auth_type)
|
702
715
|
expect(dcerpc_res.stub).to eq(plain_stub)
|
703
716
|
expect(session).to_not have_received(:unseal_message)
|
@@ -706,6 +719,8 @@ RSpec.describe RubySMB::Dcerpc::Client do
|
|
706
719
|
|
707
720
|
context 'with an unsupported auth_level' do
|
708
721
|
it 'raises an Argument exception' do
|
722
|
+
dcerpc_res.sec_trailer.auth_level = RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_INTEGRITY
|
723
|
+
dcerpc_res.sec_trailer.auth_type = 88
|
709
724
|
expect { auth_client.handle_integrity_privacy(dcerpc_res, auth_level: auth_level, auth_type: 88) }.to raise_error(ArgumentError)
|
710
725
|
end
|
711
726
|
end
|
@@ -6,7 +6,10 @@ RSpec.describe RubySMB::Dcerpc::Drsr do
|
|
6
6
|
end
|
7
7
|
|
8
8
|
let(:drsr) do
|
9
|
-
RubySMB::Dcerpc::Client.new('1.2.3.4', RubySMB::Dcerpc::Drsr)
|
9
|
+
drsr = RubySMB::Dcerpc::Client.new('1.2.3.4', RubySMB::Dcerpc::Drsr)
|
10
|
+
drsr.force_set_auth_params(RubySMB::Dcerpc::RPC_C_AUTHN_WINNT, RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
|
11
|
+
|
12
|
+
drsr
|
10
13
|
end
|
11
14
|
|
12
15
|
describe described_class::DrsHandle do
|
@@ -134,12 +134,6 @@ RSpec.describe RubySMB::Dcerpc::Request do
|
|
134
134
|
packet.pdu_header.auth_length = 10
|
135
135
|
expect(packet.auth_pad?).to be true
|
136
136
|
end
|
137
|
-
|
138
|
-
it 'makes sure #sec_trailer is 16-bytes aligned with the begining of the PDU body (stub)' do
|
139
|
-
packet.pdu_header.auth_length = 6
|
140
|
-
packet.stub = 'A' * rand(0xFF)
|
141
|
-
expect((packet.sec_trailer.abs_offset - packet.stub.abs_offset) % 16).to eq(0)
|
142
|
-
end
|
143
137
|
end
|
144
138
|
|
145
139
|
describe '#sec_trailer' do
|
@@ -77,12 +77,6 @@ RSpec.describe RubySMB::Dcerpc::Response do
|
|
77
77
|
packet.pdu_header.auth_length = 10
|
78
78
|
expect(packet.auth_pad?).to be true
|
79
79
|
end
|
80
|
-
|
81
|
-
it 'makes sure #sec_trailer is 16-bytes aligned with the begining of the PDU body (stub)' do
|
82
|
-
packet.pdu_header.auth_length = 6
|
83
|
-
packet.stub = 'A' * rand(0xFF)
|
84
|
-
expect((packet.sec_trailer.abs_offset - packet.stub.abs_offset) % 16).to eq(0)
|
85
|
-
end
|
86
80
|
end
|
87
81
|
|
88
82
|
describe '#sec_trailer' do
|
@@ -36,20 +36,6 @@ RSpec.describe RubySMB::Dcerpc::SecTrailer do
|
|
36
36
|
expect(packet.auth_pad_length).to eq(0)
|
37
37
|
end
|
38
38
|
|
39
|
-
context 'when the parent structure has an #auth_pad field' do
|
40
|
-
let(:pad) { 'A' * rand(0xFF) }
|
41
|
-
let(:packet_with_parent) do
|
42
|
-
Class.new(BinData::Record) do
|
43
|
-
string :auth_pad
|
44
|
-
sec_trailer :sec_trailer
|
45
|
-
end.new(auth_pad: pad)
|
46
|
-
end
|
47
|
-
|
48
|
-
it 'has a value equal to the size of the #auth_pad field' do
|
49
|
-
expect(packet_with_parent.sec_trailer.auth_pad_length).to eq(pad.size)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
39
|
context 'when the parent structure does not have an #auth_pad field' do
|
54
40
|
let(:pad) { 'A' * rand(0xFF) }
|
55
41
|
let(:packet_with_parent) do
|
data.tar.gz.sig
CHANGED
Binary file
|