ruby_smb 3.1.7 → 3.2.0
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/README.md +1 -1
- data/lib/ruby_smb/client/authentication.rb +4 -30
- data/lib/ruby_smb/dcerpc/client.rb +6 -261
- data/lib/ruby_smb/dcerpc/error.rb +10 -0
- data/lib/ruby_smb/dcerpc/icpr/cert_server_request_request.rb +27 -0
- data/lib/ruby_smb/dcerpc/icpr/cert_server_request_response.rb +28 -0
- data/lib/ruby_smb/dcerpc/icpr.rb +84 -0
- data/lib/ruby_smb/dcerpc/ndr.rb +38 -2
- data/lib/ruby_smb/dcerpc/request.rb +4 -0
- data/lib/ruby_smb/dcerpc.rb +245 -12
- data/lib/ruby_smb/error.rb +5 -1
- data/lib/ruby_smb/peer_info.rb +39 -0
- data/lib/ruby_smb/smb1/pipe.rb +21 -1
- data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +5 -1
- data/lib/ruby_smb/smb2/pipe.rb +21 -0
- data/lib/ruby_smb/version.rb +1 -1
- data/spec/lib/ruby_smb/dcerpc/icpr/cert_server_request_request_spec.rb +64 -0
- data/spec/lib/ruby_smb/dcerpc/icpr/cert_server_request_response_spec.rb +71 -0
- data/spec/lib/ruby_smb/dcerpc/icpr/cert_trans_blob_spec.rb +33 -0
- data/spec/lib/ruby_smb/dcerpc_spec.rb +2 -1
- data.tar.gz.sig +0 -0
- metadata +12 -2
- metadata.gz.sig +0 -0
data/lib/ruby_smb/dcerpc.rb
CHANGED
@@ -47,6 +47,7 @@ module RubySMB
|
|
47
47
|
require 'ruby_smb/dcerpc/drsr'
|
48
48
|
require 'ruby_smb/dcerpc/sec_trailer'
|
49
49
|
require 'ruby_smb/dcerpc/dfsnm'
|
50
|
+
require 'ruby_smb/dcerpc/icpr'
|
50
51
|
require 'ruby_smb/dcerpc/request'
|
51
52
|
require 'ruby_smb/dcerpc/response'
|
52
53
|
require 'ruby_smb/dcerpc/rpc_auth3'
|
@@ -55,25 +56,50 @@ module RubySMB
|
|
55
56
|
require 'ruby_smb/dcerpc/print_system'
|
56
57
|
require 'ruby_smb/dcerpc/encrypting_file_system'
|
57
58
|
|
58
|
-
# Bind to the remote server interface endpoint.
|
59
|
+
# Bind to the remote server interface endpoint. It takes care of adding
|
60
|
+
# the necessary authentication verifier if `:auth_level` is set to
|
61
|
+
# anything different than RPC_C_AUTHN_LEVEL_NONE
|
59
62
|
#
|
60
|
-
# @param
|
63
|
+
# @param [Hash] options
|
64
|
+
# @option options [Module] :endpoint the endpoint to bind to. This must be a Dcerpc
|
65
|
+
# class with UUID, VER_MAJOR and VER_MINOR constants defined.
|
66
|
+
# @option options [Integer] :auth_level the authentication level
|
67
|
+
# @option options [Integer] :auth_type the authentication type
|
61
68
|
# @return [BindAck] the BindAck response packet
|
62
69
|
# @raise [Error::InvalidPacket] if an invalid packet is received
|
63
70
|
# @raise [Error::BindError] if the response is not a BindAck packet or if the Bind result code is not ACCEPTANCE
|
71
|
+
# @raise [ArgumentError] if `:auth_type` is unknown
|
72
|
+
# @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
|
64
73
|
def bind(options={})
|
74
|
+
@call_id ||= 1
|
65
75
|
bind_req = Bind.new(options)
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
76
|
+
bind_req.pdu_header.call_id = @call_id
|
77
|
+
|
78
|
+
if options[:auth_level] && options[:auth_level] != RPC_C_AUTHN_LEVEL_NONE
|
79
|
+
case options[:auth_type]
|
80
|
+
when RPC_C_AUTHN_WINNT, RPC_C_AUTHN_DEFAULT
|
81
|
+
@ctx_id = 0
|
82
|
+
@auth_ctx_id_base = rand(0xFFFFFFFF)
|
83
|
+
raise ArgumentError, "NTLM Client not initialized. Username and password must be provided" unless @ntlm_client
|
84
|
+
type1_message = @ntlm_client.init_context
|
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])
|
73
94
|
end
|
74
|
-
|
75
|
-
|
95
|
+
|
96
|
+
send_packet(bind_req)
|
97
|
+
begin
|
98
|
+
dcerpc_response = recv_struct(BindAck)
|
99
|
+
rescue Error::InvalidPacket
|
100
|
+
raise Error::BindError # raise the more context-specific BindError
|
76
101
|
end
|
102
|
+
# TODO: see if BindNack response should be handled too
|
77
103
|
|
78
104
|
res_list = dcerpc_response.p_result_list
|
79
105
|
if res_list.n_results == 0 ||
|
@@ -81,9 +107,216 @@ module RubySMB
|
|
81
107
|
raise Error::BindError,
|
82
108
|
"Bind Failed (Result: #{res_list.p_results[0].result}, Reason: #{res_list.p_results[0].reason})"
|
83
109
|
end
|
84
|
-
|
110
|
+
self.max_buffer_size = dcerpc_response.max_xmit_frag
|
111
|
+
@call_id = dcerpc_response.pdu_header.call_id
|
112
|
+
|
113
|
+
if options[:auth_level] && options[:auth_level] != RPC_C_AUTHN_LEVEL_NONE
|
114
|
+
# The number of legs needed to build the security context is defined
|
115
|
+
# by the security provider
|
116
|
+
# (see [2.2.1.1.7 Security Providers](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/d4097450-c62f-484b-872f-ddf59a7a0d36))
|
117
|
+
case options[:auth_type]
|
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
|
124
|
+
end
|
125
|
+
|
85
126
|
dcerpc_response
|
86
127
|
end
|
87
128
|
|
129
|
+
def max_buffer_size=(value)
|
130
|
+
@tree.client.max_buffer_size = value
|
131
|
+
end
|
132
|
+
|
133
|
+
# Receive a packet from the remote host and parse it according to `struct`
|
134
|
+
#
|
135
|
+
# @param struct [Class] the structure class to parse the response with
|
136
|
+
def recv_struct(struct)
|
137
|
+
raw_response = read
|
138
|
+
begin
|
139
|
+
response = struct.read(raw_response)
|
140
|
+
rescue IOError
|
141
|
+
raise Error::InvalidPacket, "Error reading the #{struct} response"
|
142
|
+
end
|
143
|
+
unless response.pdu_header.ptype == struct::PTYPE
|
144
|
+
raise Error::InvalidPacket, "Not a #{struct} packet"
|
145
|
+
end
|
146
|
+
|
147
|
+
response
|
148
|
+
end
|
149
|
+
|
150
|
+
# Send a packet to the remote host
|
151
|
+
#
|
152
|
+
# @param packet [BinData::Record] the packet to send
|
153
|
+
# @raise [Error::CommunicationError] if socket-related error occurs
|
154
|
+
def send_packet(packet)
|
155
|
+
write(data: packet.to_binary_s)
|
156
|
+
nil
|
157
|
+
end
|
158
|
+
|
159
|
+
# Add the authentication verifier to a Request packet. This includes a
|
160
|
+
# sec trailer and the signature of the packet. This also encrypts the
|
161
|
+
# Request stub if privacy is required (`:auth_level` option is
|
162
|
+
# RPC_C_AUTHN_LEVEL_PKT_PRIVACY).
|
163
|
+
#
|
164
|
+
# @param dcerpc_req [Request] the Request packet to be updated
|
165
|
+
# @param opts [Hash] the authenticaiton options: `:auth_type` and `:auth_level`
|
166
|
+
# @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
|
167
|
+
# @raise [ArgumentError] if `:auth_type` is unknown
|
168
|
+
def set_integrity_privacy(dcerpc_req, auth_level:, auth_type:)
|
169
|
+
dcerpc_req.sec_trailer = {
|
170
|
+
auth_type: auth_type,
|
171
|
+
auth_level: auth_level,
|
172
|
+
auth_context_id: @ctx_id + @auth_ctx_id_base
|
173
|
+
}
|
174
|
+
dcerpc_req.auth_value = ' ' * 16
|
175
|
+
dcerpc_req.pdu_header.auth_length = 16
|
176
|
+
|
177
|
+
data_to_sign = plain_stub = dcerpc_req.stub.to_binary_s + dcerpc_req.auth_pad.to_binary_s
|
178
|
+
if @ntlm_client.flags & NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] != 0
|
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
|
195
|
+
|
196
|
+
signature = @ntlm_client.session.sign_message(data_to_sign)
|
197
|
+
|
198
|
+
unless encrypted_stub.empty?
|
199
|
+
pad_length = dcerpc_req.sec_trailer.auth_pad_length.to_i
|
200
|
+
dcerpc_req.enable_encrypted_stub
|
201
|
+
dcerpc_req.stub = encrypted_stub[0..-(pad_length + 1)]
|
202
|
+
dcerpc_req.auth_pad = encrypted_stub[-(pad_length)..-1]
|
203
|
+
end
|
204
|
+
dcerpc_req.auth_value = signature
|
205
|
+
dcerpc_req.pdu_header.auth_length = signature.size
|
206
|
+
end
|
207
|
+
|
208
|
+
# Process the security context received in a response. It decrypts the
|
209
|
+
# encrypted stub if `:auth_level` is set to anything different than
|
210
|
+
# RPC_C_AUTHN_LEVEL_PKT_PRIVACY. It also checks the packet signature and
|
211
|
+
# raises an InvalidPacket error if it fails. Note that the exception is
|
212
|
+
# disabled by default and can be enabled with the
|
213
|
+
# `:raise_signature_error` option
|
214
|
+
#
|
215
|
+
# @param dcerpc_response [Response] the Response packet
|
216
|
+
# containing the security context to process
|
217
|
+
# @param opts [Hash] the authenticaiton options: `:auth_type` and
|
218
|
+
# `:auth_level`. To enable errors when signature check fails, set the
|
219
|
+
# `:raise_signature_error` option to true
|
220
|
+
# @raise [NotImplementedError] if `:auth_type` is not implemented (yet)
|
221
|
+
# @raise [Error::CommunicationError] if socket-related error occurs
|
222
|
+
def handle_integrity_privacy(dcerpc_response, auth_level:, auth_type:, raise_signature_error: false)
|
223
|
+
decrypted_stub = ''
|
224
|
+
if auth_level == RPC_C_AUTHN_LEVEL_PKT_PRIVACY
|
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]
|
242
|
+
end
|
243
|
+
|
244
|
+
signature = dcerpc_response.auth_value
|
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)
|
250
|
+
if raise_signature_error
|
251
|
+
raise Error::InvalidPacket.new(
|
252
|
+
"Wrong packet signature received (set `raise_signature_error` to false to ignore)"
|
253
|
+
)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
@call_id += 1
|
258
|
+
|
259
|
+
nil
|
260
|
+
end
|
261
|
+
|
262
|
+
# Add the authentication verifier to the packet. This includes a sec
|
263
|
+
# trailer and the actual authentication data.
|
264
|
+
#
|
265
|
+
# @param req [BinData::Record] the request to be updated
|
266
|
+
# @param auth [String] the authentication data
|
267
|
+
# @param auth_type [Integer] the authentication type
|
268
|
+
# @param auth_level [Integer] the authentication level
|
269
|
+
def add_auth_verifier(req, auth, auth_type, auth_level)
|
270
|
+
req.sec_trailer = {
|
271
|
+
auth_type: auth_type,
|
272
|
+
auth_level: auth_level,
|
273
|
+
auth_context_id: @ctx_id + @auth_ctx_id_base
|
274
|
+
}
|
275
|
+
req.auth_value = auth
|
276
|
+
req.pdu_header.auth_length = auth.length
|
277
|
+
|
278
|
+
nil
|
279
|
+
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
|
88
321
|
end
|
89
322
|
end
|
data/lib/ruby_smb/error.rb
CHANGED
@@ -73,6 +73,10 @@ module RubySMB
|
|
73
73
|
module Mixin
|
74
74
|
attr_reader :status_code
|
75
75
|
|
76
|
+
def status_name
|
77
|
+
@status_code.name
|
78
|
+
end
|
79
|
+
|
76
80
|
private
|
77
81
|
|
78
82
|
def status_code=(status_code)
|
@@ -82,7 +86,7 @@ module RubySMB
|
|
82
86
|
when Integer
|
83
87
|
@status_code = WindowsError::NTStatus.find_by_retval(status_code).first
|
84
88
|
if @status_code.nil?
|
85
|
-
@status_code = WindowsError::ErrorCode.new("0x#{status_code.to_s(16).rjust(8, '0')}", status_code, "Unknown status: 0x#{status_code.to_s(16)}")
|
89
|
+
@status_code = WindowsError::ErrorCode.new("0x#{status_code.to_s(16).rjust(8, '0')}", status_code, "Unknown status: 0x#{status_code.to_s(16).rjust(8, '0')}")
|
86
90
|
end
|
87
91
|
else
|
88
92
|
raise ArgumentError, "Status code must be a WindowsError::ErrorCode or an Integer, got #{status_code.class}"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module RubySMB
|
2
|
+
module PeerInfo
|
3
|
+
# Extract and store useful information about the peer/server from the
|
4
|
+
# NTLM Type 2 (challenge) TargetInfo fields.
|
5
|
+
#
|
6
|
+
# @param target_info_str [String] the Target Info string
|
7
|
+
def store_target_info(target_info_str)
|
8
|
+
target_info = Net::NTLM::TargetInfo.new(target_info_str)
|
9
|
+
{
|
10
|
+
Net::NTLM::TargetInfo::MSV_AV_NB_COMPUTER_NAME => :@default_name,
|
11
|
+
Net::NTLM::TargetInfo::MSV_AV_NB_DOMAIN_NAME => :@default_domain,
|
12
|
+
Net::NTLM::TargetInfo::MSV_AV_DNS_COMPUTER_NAME => :@dns_host_name,
|
13
|
+
Net::NTLM::TargetInfo::MSV_AV_DNS_DOMAIN_NAME => :@dns_domain_name,
|
14
|
+
Net::NTLM::TargetInfo::MSV_AV_DNS_TREE_NAME => :@dns_tree_name
|
15
|
+
}.each do |constant, attribute|
|
16
|
+
if target_info.av_pairs[constant]
|
17
|
+
value = target_info.av_pairs[constant].dup
|
18
|
+
value.force_encoding('UTF-16LE')
|
19
|
+
instance_variable_set(attribute, value.encode('UTF-8'))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Extract the peer/server version number from the NTLM Type 2 (challenge)
|
25
|
+
# Version field.
|
26
|
+
#
|
27
|
+
# @param version [String] the version number as a binary string
|
28
|
+
# @return [String] the formatted version number (<major>.<minor>.<build>)
|
29
|
+
def extract_os_version(version)
|
30
|
+
begin
|
31
|
+
os_version = RubySMB::NTLM::OSVersion.read(version)
|
32
|
+
rescue IOError
|
33
|
+
return ''
|
34
|
+
end
|
35
|
+
|
36
|
+
"#{os_version.major}.#{os_version.minor}.#{os_version.build}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/ruby_smb/smb1/pipe.rb
CHANGED
@@ -30,10 +30,18 @@ module RubySMB
|
|
30
30
|
extend RubySMB::Dcerpc::Wkssvc
|
31
31
|
when 'netdfs', '\\netdfs'
|
32
32
|
extend RubySMB::Dcerpc::Dfsnm
|
33
|
+
when 'cert', '\\cert'
|
34
|
+
extend RubySMB::Dcerpc::Icpr
|
33
35
|
end
|
34
36
|
super(tree: tree, response: response, name: name)
|
35
37
|
end
|
36
38
|
|
39
|
+
def bind(options={})
|
40
|
+
@size = 1024
|
41
|
+
@ntlm_client = @tree.client.ntlm_client
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
37
45
|
# Performs a peek operation on the named pipe
|
38
46
|
#
|
39
47
|
# @param peek_size [Integer] Amount of data to peek
|
@@ -98,6 +106,11 @@ module RubySMB
|
|
98
106
|
options.merge!(endpoint: stub_packet.class.name.split('::').at(-2))
|
99
107
|
dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: stub_packet.opnum }, options)
|
100
108
|
dcerpc_request.stub.read(stub_packet.to_binary_s)
|
109
|
+
if options[:auth_level] &&
|
110
|
+
[RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(options[:auth_level])
|
111
|
+
set_integrity_privacy(dcerpc_request, auth_level: options[:auth_level], auth_type: options[:auth_type])
|
112
|
+
end
|
113
|
+
|
101
114
|
trans_nmpipe_request = RubySMB::SMB1::Packet::Trans::TransactNmpipeRequest.new(options)
|
102
115
|
@tree.set_header_fields(trans_nmpipe_request)
|
103
116
|
trans_nmpipe_request.set_fid(@fid)
|
@@ -124,6 +137,10 @@ module RubySMB
|
|
124
137
|
unless dcerpc_response.pdu_header.pfc_flags.first_frag == 1
|
125
138
|
raise RubySMB::Dcerpc::Error::InvalidPacket, "Not the first fragment"
|
126
139
|
end
|
140
|
+
if options[:auth_level] &&
|
141
|
+
[RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(options[:auth_level])
|
142
|
+
handle_integrity_privacy(dcerpc_response, auth_level: options[:auth_level], auth_type: options[:auth_type])
|
143
|
+
end
|
127
144
|
stub_data = dcerpc_response.stub.to_s
|
128
145
|
|
129
146
|
loop do
|
@@ -135,11 +152,14 @@ module RubySMB
|
|
135
152
|
stub_data
|
136
153
|
else
|
137
154
|
dcerpc_response = dcerpc_response_from_raw_response(raw_data)
|
155
|
+
if options[:auth_level] &&
|
156
|
+
[RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(options[:auth_level])
|
157
|
+
handle_integrity_privacy(dcerpc_response, auth_level: options[:auth_level], auth_type: options[:auth_type])
|
158
|
+
end
|
138
159
|
dcerpc_response.stub.to_s
|
139
160
|
end
|
140
161
|
end
|
141
162
|
|
142
|
-
|
143
163
|
private
|
144
164
|
|
145
165
|
def dcerpc_response_from_raw_response(raw_data)
|
@@ -21,7 +21,11 @@ module RubySMB
|
|
21
21
|
uint8 :reserved, label: 'Reserved Space', initial_value: 0x00
|
22
22
|
share_flags :share_flags
|
23
23
|
share_capabilities :capabilities
|
24
|
-
|
24
|
+
choice :maximal_access, label: 'Maximal Access', selection: -> { share_type } do
|
25
|
+
file_access_mask SMB2_SHARE_TYPE_PIPE
|
26
|
+
file_access_mask SMB2_SHARE_TYPE_PRINT
|
27
|
+
directory_access_mask SMB2_SHARE_TYPE_DISK
|
28
|
+
end
|
25
29
|
|
26
30
|
def initialize_instance
|
27
31
|
super
|
data/lib/ruby_smb/smb2/pipe.rb
CHANGED
@@ -27,10 +27,18 @@ module RubySMB
|
|
27
27
|
extend RubySMB::Dcerpc::Wkssvc
|
28
28
|
when 'netdfs', '\\netdfs'
|
29
29
|
extend RubySMB::Dcerpc::Dfsnm
|
30
|
+
when 'cert', '\\cert'
|
31
|
+
extend RubySMB::Dcerpc::Icpr
|
30
32
|
end
|
31
33
|
super(tree: tree, response: response, name: name)
|
32
34
|
end
|
33
35
|
|
36
|
+
def bind(options={})
|
37
|
+
@size = 1024
|
38
|
+
@ntlm_client = @tree.client.ntlm_client
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
34
42
|
# Performs a peek operation on the named pipe
|
35
43
|
#
|
36
44
|
# @param peek_size [Integer] Amount of data to peek
|
@@ -91,6 +99,11 @@ module RubySMB
|
|
91
99
|
options.merge!(endpoint: stub_packet.class.name.split('::').at(-2))
|
92
100
|
dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: stub_packet.opnum }, options)
|
93
101
|
dcerpc_request.stub.read(stub_packet.to_binary_s)
|
102
|
+
if options[:auth_level] &&
|
103
|
+
[RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(options[:auth_level])
|
104
|
+
set_integrity_privacy(dcerpc_request, auth_level: options[:auth_level], auth_type: options[:auth_type])
|
105
|
+
end
|
106
|
+
|
94
107
|
ioctl_send_recv(dcerpc_request, options)
|
95
108
|
end
|
96
109
|
|
@@ -122,6 +135,10 @@ module RubySMB
|
|
122
135
|
unless dcerpc_response.pdu_header.pfc_flags.first_frag == 1
|
123
136
|
raise RubySMB::Dcerpc::Error::InvalidPacket, "Not the first fragment"
|
124
137
|
end
|
138
|
+
if options[:auth_level] &&
|
139
|
+
[RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(options[:auth_level])
|
140
|
+
handle_integrity_privacy(dcerpc_response, auth_level: options[:auth_level], auth_type: options[:auth_type])
|
141
|
+
end
|
125
142
|
stub_data = dcerpc_response.stub.to_s
|
126
143
|
|
127
144
|
loop do
|
@@ -133,6 +150,10 @@ module RubySMB
|
|
133
150
|
stub_data
|
134
151
|
else
|
135
152
|
dcerpc_response = dcerpc_response_from_raw_response(raw_data)
|
153
|
+
if options[:auth_level] &&
|
154
|
+
[RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(options[:auth_level])
|
155
|
+
handle_integrity_privacy(dcerpc_response, auth_level: options[:auth_level], auth_type: options[:auth_type])
|
156
|
+
end
|
136
157
|
dcerpc_response.stub.to_s
|
137
158
|
end
|
138
159
|
end
|
data/lib/ruby_smb/version.rb
CHANGED
@@ -0,0 +1,64 @@
|
|
1
|
+
RSpec.describe RubySMB::Dcerpc::Icpr::CertServerRequestRequest do
|
2
|
+
subject(:packet) { described_class.new }
|
3
|
+
|
4
|
+
it { is_expected.to respond_to :dw_flags }
|
5
|
+
it { is_expected.to respond_to :pwsz_authority }
|
6
|
+
it { is_expected.to respond_to :pdw_request_id }
|
7
|
+
it { is_expected.to respond_to :pctb_attribs }
|
8
|
+
it { is_expected.to respond_to :pctb_request }
|
9
|
+
it { is_expected.to respond_to :opnum }
|
10
|
+
|
11
|
+
it 'is little endian' do
|
12
|
+
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
|
13
|
+
end
|
14
|
+
it 'is a BinData::Record' do
|
15
|
+
expect(packet).to be_a(BinData::Record)
|
16
|
+
end
|
17
|
+
describe '#dw_flags' do
|
18
|
+
it 'is a NdrUint32 structure' do
|
19
|
+
expect(packet.dw_flags).to be_a RubySMB::Dcerpc::Ndr::NdrUint32
|
20
|
+
end
|
21
|
+
end
|
22
|
+
describe '#pwsz_authority' do
|
23
|
+
it 'is a NdrWideStringzPtr structure' do
|
24
|
+
expect(packet.pwsz_authority).to be_a RubySMB::Dcerpc::Ndr::NdrWideStringzPtr
|
25
|
+
end
|
26
|
+
end
|
27
|
+
describe '#pdw_request_id' do
|
28
|
+
it 'is a NdrUint32 structure' do
|
29
|
+
expect(packet.pdw_request_id).to be_a RubySMB::Dcerpc::Ndr::NdrUint32
|
30
|
+
end
|
31
|
+
end
|
32
|
+
describe '#pctb_attribs' do
|
33
|
+
it 'is a CertTransBlob structure' do
|
34
|
+
expect(packet.pctb_attribs).to be_a RubySMB::Dcerpc::Icpr::CertTransBlob
|
35
|
+
end
|
36
|
+
end
|
37
|
+
describe '#pctb_request' do
|
38
|
+
it 'is a CertTransBlob structure' do
|
39
|
+
expect(packet.pctb_request).to be_a RubySMB::Dcerpc::Icpr::CertTransBlob
|
40
|
+
end
|
41
|
+
end
|
42
|
+
describe '#initialize_instance' do
|
43
|
+
it 'sets #opnum to CERT_SERVER_REQUEST constant' do
|
44
|
+
expect(packet.opnum).to eq(RubySMB::Dcerpc::Icpr::CERT_SERVER_REQUEST)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
it 'reads itself' do
|
48
|
+
new_packet = described_class.new({
|
49
|
+
dw_flags: 0,
|
50
|
+
pwsz_authority: 'DC-CA',
|
51
|
+
pdw_request_id: 1,
|
52
|
+
pctb_attribs: { pb: 'ATTRIBUTES'.bytes },
|
53
|
+
pctb_request: { pb: 'REQUEST'.bytes }
|
54
|
+
})
|
55
|
+
expected_output = {
|
56
|
+
dw_flags: 0,
|
57
|
+
pwsz_authority: 'DC-CA'.encode('utf-16le'),
|
58
|
+
pdw_request_id: 1,
|
59
|
+
pctb_attribs: { cb: 10, pb: 'ATTRIBUTES'.bytes },
|
60
|
+
pctb_request: { cb: 7, pb: 'REQUEST'.bytes }
|
61
|
+
}
|
62
|
+
expect(packet.read(new_packet.to_binary_s)).to eq(expected_output)
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
RSpec.describe RubySMB::Dcerpc::Icpr::CertServerRequestResponse do
|
2
|
+
subject(:packet) { described_class.new }
|
3
|
+
|
4
|
+
it { is_expected.to respond_to :pdw_request_id }
|
5
|
+
it { is_expected.to respond_to :pdw_disposition }
|
6
|
+
it { is_expected.to respond_to :pctb_cert }
|
7
|
+
it { is_expected.to respond_to :pctb_encoded_cert }
|
8
|
+
it { is_expected.to respond_to :pctb_disposition_message }
|
9
|
+
it { is_expected.to respond_to :error_status }
|
10
|
+
|
11
|
+
it 'is little endian' do
|
12
|
+
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
|
13
|
+
end
|
14
|
+
it 'is a BinData::Record' do
|
15
|
+
expect(packet).to be_a(BinData::Record)
|
16
|
+
end
|
17
|
+
describe '#pdw_request_id' do
|
18
|
+
it 'is a NdrUint32 structure' do
|
19
|
+
expect(packet.pdw_request_id).to be_a RubySMB::Dcerpc::Ndr::NdrUint32
|
20
|
+
end
|
21
|
+
end
|
22
|
+
describe '#pdw_disposition' do
|
23
|
+
it 'is a NdrUint32 structure' do
|
24
|
+
expect(packet.pdw_disposition).to be_a RubySMB::Dcerpc::Ndr::NdrUint32
|
25
|
+
end
|
26
|
+
end
|
27
|
+
describe '#pctb_cert' do
|
28
|
+
it 'is a CertTransBlob structure' do
|
29
|
+
expect(packet.pctb_cert).to be_a RubySMB::Dcerpc::Icpr::CertTransBlob
|
30
|
+
end
|
31
|
+
end
|
32
|
+
describe '#pctb_encoded_cert' do
|
33
|
+
it 'is a CertTransBlob structure' do
|
34
|
+
expect(packet.pctb_encoded_cert).to be_a RubySMB::Dcerpc::Icpr::CertTransBlob
|
35
|
+
end
|
36
|
+
end
|
37
|
+
describe '#pctb_disposition_message' do
|
38
|
+
it 'is a CertTransBlob structure' do
|
39
|
+
expect(packet.pctb_disposition_message).to be_a RubySMB::Dcerpc::Icpr::CertTransBlob
|
40
|
+
end
|
41
|
+
end
|
42
|
+
describe '#error_status' do
|
43
|
+
it 'is a NdrUint32 structure' do
|
44
|
+
expect(packet.error_status).to be_a RubySMB::Dcerpc::Ndr::NdrUint32
|
45
|
+
end
|
46
|
+
end
|
47
|
+
describe '#initialize_instance' do
|
48
|
+
it 'sets #opnum to CERT_SERVER_REQUEST constant' do
|
49
|
+
expect(packet.opnum).to eq(RubySMB::Dcerpc::Icpr::CERT_SERVER_REQUEST)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
it 'reads itself' do
|
53
|
+
new_packet = described_class.new({
|
54
|
+
pdw_request_id: 1,
|
55
|
+
pdw_disposition: 0,
|
56
|
+
pctb_cert: { pb: 'CERT'.bytes },
|
57
|
+
pctb_encoded_cert: { pb: 'ENCODED_CERT'.bytes },
|
58
|
+
pctb_disposition_message: { pb: 'DISPOSITION_MESSAGE'.bytes },
|
59
|
+
error_status: 0
|
60
|
+
})
|
61
|
+
expected_output = {
|
62
|
+
pdw_request_id: 1,
|
63
|
+
pdw_disposition: 0,
|
64
|
+
pctb_cert: { cb: 4, pb: 'CERT'.bytes },
|
65
|
+
pctb_encoded_cert: { cb: 12, pb: 'ENCODED_CERT'.bytes },
|
66
|
+
pctb_disposition_message: { cb: 19, pb: 'DISPOSITION_MESSAGE'.bytes },
|
67
|
+
error_status: 0
|
68
|
+
}
|
69
|
+
expect(packet.read(new_packet.to_binary_s)).to eq(expected_output)
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
RSpec.describe RubySMB::Dcerpc::Icpr::CertTransBlob do
|
2
|
+
subject(:struct) { described_class.new }
|
3
|
+
|
4
|
+
it { is_expected.to respond_to :cb }
|
5
|
+
it { is_expected.to respond_to :pb }
|
6
|
+
|
7
|
+
it 'is little endian' do
|
8
|
+
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
|
9
|
+
end
|
10
|
+
it 'is a BinData::Record' do
|
11
|
+
expect(struct).to be_a(BinData::Record)
|
12
|
+
end
|
13
|
+
describe '#cb' do
|
14
|
+
it 'is a NdrUint32 structure' do
|
15
|
+
expect(struct.cb).to be_a RubySMB::Dcerpc::Ndr::NdrUint32
|
16
|
+
end
|
17
|
+
end
|
18
|
+
describe '#pb' do
|
19
|
+
it 'is a NdrByteConfArrayPtr structure' do
|
20
|
+
expect(struct.pb).to be_a RubySMB::Dcerpc::Ndr::NdrByteConfArrayPtr
|
21
|
+
end
|
22
|
+
end
|
23
|
+
describe '#buffer' do
|
24
|
+
it 'returns a string' do
|
25
|
+
expect(struct.buffer).to be_a String
|
26
|
+
end
|
27
|
+
end
|
28
|
+
it 'reads itself' do
|
29
|
+
new_struct = described_class.new({ pb: 'BUFFER' })
|
30
|
+
expected_output = { cb: 6, pb: 'BUFFER'.bytes }
|
31
|
+
expect(struct.read(new_struct.to_binary_s)).to eq(expected_output)
|
32
|
+
end
|
33
|
+
end
|
@@ -23,6 +23,7 @@ RSpec.describe RubySMB::Dcerpc do
|
|
23
23
|
allow(RubySMB::Dcerpc::BindAck).to receive(:read).and_return(bind_ack_packet)
|
24
24
|
allow(tree).to receive(:client).and_return(client)
|
25
25
|
allow(client).to receive(:max_buffer_size=)
|
26
|
+
allow(client).to receive(:ntlm_client)
|
26
27
|
end
|
27
28
|
|
28
29
|
it 'creates a Bind packet' do
|
@@ -49,7 +50,7 @@ RSpec.describe RubySMB::Dcerpc do
|
|
49
50
|
|
50
51
|
it 'raises the expected exception when an invalid packet is received' do
|
51
52
|
allow(RubySMB::Dcerpc::BindAck).to receive(:read).and_raise(IOError)
|
52
|
-
expect { pipe.bind(options) }.to raise_error(RubySMB::Dcerpc::Error::
|
53
|
+
expect { pipe.bind(options) }.to raise_error(RubySMB::Dcerpc::Error::BindError)
|
53
54
|
end
|
54
55
|
|
55
56
|
it 'raises the expected exception when it is not a BindAck packet' do
|
data.tar.gz.sig
CHANGED
Binary file
|