ruby_smb 3.1.7 → 3.2.0
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 +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
|