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.
@@ -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 options [Hash] the options to pass to the Bind request packet. At least, :endpoint must but provided with an existing Dcerpc class
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
- write(data: bind_req.to_binary_s)
67
- @size = 1024
68
- dcerpc_raw_response = read()
69
- begin
70
- dcerpc_response = BindAck.read(dcerpc_raw_response)
71
- rescue IOError
72
- raise Error::InvalidPacket, "Error reading the DCERPC response"
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
- unless dcerpc_response.pdu_header.ptype == PTypes::BIND_ACK
75
- raise Error::BindError, "Not a BindAck packet"
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
- @tree.client.max_buffer_size = dcerpc_response.max_xmit_frag
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
@@ -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
@@ -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
- file_access_mask :maximal_access, label: 'Maximal Access'
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module RubySMB
2
- VERSION = '3.1.7'.freeze
2
+ VERSION = '3.2.0'.freeze
3
3
  end
@@ -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::InvalidPacket)
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