ruby_smb 3.2.0 → 3.3.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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/verify.yml +8 -9
  4. data/README.md +1 -0
  5. data/cortex.yaml +15 -0
  6. data/examples/dump_secrets_from_sid.rb +2 -1
  7. data/lib/ruby_smb/client.rb +6 -4
  8. data/lib/ruby_smb/dcerpc/alter_context.rb +30 -0
  9. data/lib/ruby_smb/dcerpc/alter_context_resp.rb +42 -0
  10. data/lib/ruby_smb/dcerpc/bind.rb +3 -35
  11. data/lib/ruby_smb/dcerpc/bind_ack.rb +0 -31
  12. data/lib/ruby_smb/dcerpc/client.rb +10 -4
  13. data/lib/ruby_smb/dcerpc/drsr.rb +13 -13
  14. data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_decrypt_file_srv_request.rb +22 -0
  15. data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_decrypt_file_srv_response.rb +21 -0
  16. data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_query_recover_agents_request.rb +20 -0
  17. data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_query_recover_agents_response.rb +21 -0
  18. data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_query_users_on_file_request.rb +20 -0
  19. data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_query_users_on_file_response.rb +21 -0
  20. data/lib/ruby_smb/dcerpc/encrypting_file_system.rb +52 -0
  21. data/lib/ruby_smb/dcerpc/icpr.rb +3 -2
  22. data/lib/ruby_smb/dcerpc/ndr.rb +148 -3
  23. data/lib/ruby_smb/dcerpc/p_cont_list_t.rb +37 -0
  24. data/lib/ruby_smb/dcerpc/p_result_list_t.rb +13 -0
  25. data/lib/ruby_smb/dcerpc/p_result_t.rb +15 -0
  26. data/lib/ruby_smb/dcerpc/port_any_t.rb +11 -0
  27. data/lib/ruby_smb/dcerpc/request.rb +8 -3
  28. data/lib/ruby_smb/dcerpc/response.rb +6 -1
  29. data/lib/ruby_smb/dcerpc/samr.rb +4 -2
  30. data/lib/ruby_smb/dcerpc.rb +165 -122
  31. data/lib/ruby_smb/dispatcher/socket.rb +1 -1
  32. data/lib/ruby_smb/field/file_time.rb +2 -2
  33. data/lib/ruby_smb/field/utime.rb +2 -2
  34. data/lib/ruby_smb/generic_packet.rb +2 -2
  35. data/lib/ruby_smb/gss/provider/ntlm.rb +12 -5
  36. data/lib/ruby_smb/ntlm/client.rb +4 -0
  37. data/lib/ruby_smb/ntlm/custom/string_encoder.rb +22 -0
  38. data/lib/ruby_smb/ntlm.rb +37 -0
  39. data/lib/ruby_smb/server/server_client/share_io.rb +2 -2
  40. data/lib/ruby_smb/server/server_client/tree_connect.rb +2 -2
  41. data/lib/ruby_smb/server/share/provider/disk.rb +2 -2
  42. data/lib/ruby_smb/server/share/provider/processor.rb +25 -0
  43. data/lib/ruby_smb/server/share/provider/virtual_disk.rb +3 -3
  44. data/lib/ruby_smb/server/share/provider.rb +48 -1
  45. data/lib/ruby_smb/smb1/data_block.rb +2 -2
  46. data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +1 -1
  47. data/lib/ruby_smb/smb1/parameter_block.rb +2 -2
  48. data/lib/ruby_smb/smb1/pipe.rb +4 -0
  49. data/lib/ruby_smb/smb2/create_context.rb +5 -2
  50. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +5 -5
  51. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +1 -1
  52. data/lib/ruby_smb/smb2/pipe.rb +4 -0
  53. data/lib/ruby_smb/smb2/tree.rb +3 -1
  54. data/lib/ruby_smb/utils.rb +15 -0
  55. data/lib/ruby_smb/version.rb +1 -1
  56. data/lib/ruby_smb.rb +2 -0
  57. data/spec/lib/ruby_smb/client_spec.rb +4 -4
  58. data/spec/lib/ruby_smb/dcerpc/client_spec.rb +31 -16
  59. data/spec/lib/ruby_smb/dcerpc/drsr_spec.rb +4 -1
  60. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +198 -0
  61. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +0 -6
  62. data/spec/lib/ruby_smb/dcerpc/response_spec.rb +4 -12
  63. data/spec/lib/ruby_smb/dcerpc/sec_trailer_spec.rb +0 -14
  64. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +2 -2
  65. data/spec/lib/ruby_smb/smb1/packet/read_andx_request_spec.rb +2 -2
  66. data/spec/lib/ruby_smb/smb1/packet/write_andx_request_spec.rb +1 -1
  67. data/spec/lib/ruby_smb/smb2/create_context/create_context_request_spec.rb +35 -0
  68. data/spec/lib/ruby_smb/smb2/tree_spec.rb +5 -0
  69. data.tar.gz.sig +0 -0
  70. metadata +41 -86
  71. metadata.gz.sig +0 -0
@@ -6,9 +6,51 @@ module RubySMB
6
6
  # type and name. It is shared across all client connections and
7
7
  # sessions.
8
8
  class Base
9
+ Hook = Struct.new(:request_class, :location, :callback)
10
+
9
11
  # @param [String] name The name of this share.
10
- def initialize(name)
12
+ def initialize(name, hooks: nil)
11
13
  @name = name
14
+ @hooks = hooks || []
15
+ end
16
+
17
+ # Add a hook to be called when the specified request class is processed. Any hook that was previously
18
+ # installed for the request class and location will be removed. A hook installed with a location of :before
19
+ # will be called with the session and request as the only two arguments. The return value, if provided, will
20
+ # replace the request that is to be processed. A hook installed with a location of :after will be called with
21
+ # the session, request and response as the only three arguments. The return value, if provided, will replace
22
+ # the response that is to be sent to the client.
23
+ #
24
+ # @param [RubySMB::GenericPacket] request_class The request class to register the hook for.
25
+ # @param [Proc] callback The routine to be executed when the request class is being processed.
26
+ # @param [Symbol] location When the callback should be invoked. Must be either :before or :after.
27
+ def add_hook(request_class, callback: nil, location: :before, &block)
28
+ unless %i[ before after ].include?(location)
29
+ raise ArgumentError, 'the location argument must be :before or :after'
30
+ end
31
+
32
+ unless callback.nil? ^ block.nil?
33
+ raise ArgumentError, 'either a callback or a block must be specified'
34
+ end
35
+
36
+ # Remove any hooks that were previously installed, this enforces that only one hook can be present at a time
37
+ # for any particular request class and location combination.
38
+ remove_hook(request_class, location: location)
39
+ @hooks << Hook.new(request_class, location, callback || block)
40
+
41
+ nil
42
+ end
43
+
44
+ # Remove a hook for the specified request class.
45
+ #
46
+ # @param [RubySMB::GenericPacket] request_class The request class to register the hook for.
47
+ # @param [Symbol] location When the callback should be invoked.
48
+ def remove_hook(request_class, location: :before)
49
+ @hooks.filter! do |hook|
50
+ hook.request_class == request_class && hook.location == location
51
+ end
52
+
53
+ nil
12
54
  end
13
55
 
14
56
  # Create a new, session-specific processor instance for this share.
@@ -28,6 +70,11 @@ module RubySMB
28
70
  # @!attribute [r] name
29
71
  # @return [String]
30
72
  attr_accessor :name
73
+
74
+ # The hooks installed for this share.
75
+ # @!attribute [r] hooks
76
+ # @return [Array]
77
+ attr_accessor :hooks
31
78
  end
32
79
  end
33
80
  end
@@ -11,7 +11,7 @@ module RubySMB
11
11
  # Class method to stub byte count calculation during
12
12
  # lazy evaluation.
13
13
  #
14
- # @return [Fixnum] will always return 0
14
+ # @return [Integer] will always return 0
15
15
  def self.calculate_byte_count
16
16
  0
17
17
  end
@@ -28,7 +28,7 @@ module RubySMB
28
28
  # Calculates the size of the other fields in the DataBlock
29
29
  # in Bytes.
30
30
  #
31
- # @return [Fixnum] The size of the DataBlock in Words
31
+ # @return [Integer] The size of the DataBlock in Words
32
32
  def calculate_byte_count
33
33
  total_count = 0
34
34
  self.class.data_fields.each do |field_name|
@@ -35,7 +35,7 @@ module RubySMB
35
35
  end
36
36
 
37
37
  uint64 :allocation_size, label: 'Allocation Size'
38
- smb_ext_file_attributes :ext_file_attributes, label: 'Extented File Attributes'
38
+ smb_ext_file_attributes :ext_file_attributes, label: 'Extended File Attributes'
39
39
  share_access :share_access, label: 'Share Access'
40
40
  # The following constants are defined in RubySMB::Dispositions
41
41
  uint32 :create_disposition, label: 'Create Disposition'
@@ -11,7 +11,7 @@ module RubySMB
11
11
  # Class method to stub word count calculation during
12
12
  # lazy evaluation.
13
13
  #
14
- # @param [Fixnum] will always return 0
14
+ # @param [Integer] will always return 0
15
15
  def self.calculate_word_count
16
16
  0
17
17
  end
@@ -28,7 +28,7 @@ module RubySMB
28
28
  # Calculates the size of the other fields in the ParameterBlock
29
29
  # in Words.
30
30
  #
31
- # @return [Fixnum] The size of the ParameterBlock in Words
31
+ # @return [Integer] The size of the ParameterBlock in Words
32
32
  def calculate_word_count
33
33
  total_count = 0
34
34
  self.class.parameter_fields.each do |field_name|
@@ -147,6 +147,10 @@ module RubySMB
147
147
  break if dcerpc_response.pdu_header.pfc_flags.last_frag == 1
148
148
  raw_data = read(bytes: @tree.client.max_buffer_size)
149
149
  dcerpc_response = dcerpc_response_from_raw_response(raw_data)
150
+ if options[:auth_level] &&
151
+ [RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(options[:auth_level])
152
+ handle_integrity_privacy(dcerpc_response, auth_level: options[:auth_level], auth_type: options[:auth_type])
153
+ end
150
154
  stub_data << dcerpc_response.stub.to_s
151
155
  end
152
156
  stub_data
@@ -51,8 +51,11 @@ module RubySMB
51
51
  def calc_buffer_size
52
52
  align = 8
53
53
  size = 0
54
- size += name_length + ((align - name_length % align) % align)
55
- size += data_length + ((align - data_length % align) % align)
54
+ size += name_length
55
+ size += ((align - size % align) % align) if name_offset < data_offset
56
+ size += data_length
57
+ size += ((align - size % align) % align) if data_offset < name_offset
58
+ size += ((align - size % align) % align) if next_offset != 0
56
59
  size
57
60
  end
58
61
 
@@ -28,8 +28,8 @@ module RubySMB
28
28
 
29
29
  # Adds a dialect to the Dialects array
30
30
  #
31
- # @param [Fixnum] the numeric code for the dialect you wish to add
32
- # @return [Array<Fixnum>] the array of all currently selected dialects
31
+ # @param [Integer] the numeric code for the dialect you wish to add
32
+ # @return [Array<Integer>] the array of all currently selected dialects
33
33
  # @raise [ArgumentError] if the dialect is not an Integer
34
34
  def add_dialect(dialect)
35
35
  raise ArgumentError, 'Must be a number' unless dialect.is_a? Integer
@@ -40,8 +40,8 @@ module RubySMB
40
40
  # the dialect_count field appropriately. Will erase any previously set
41
41
  # dialects.
42
42
  #
43
- # @param [Array<Fixnum>] the array of dialects to set
44
- # @return [Array<Fixnum>] the current value of the dialects array
43
+ # @param [Array<Integer>] the array of dialects to set
44
+ # @return [Array<Integer>] the current value of the dialects array
45
45
  def set_dialects(add_dialects = [])
46
46
  self.dialects = []
47
47
  add_dialects.each do |dialect|
@@ -53,7 +53,7 @@ module RubySMB
53
53
  # Adds a Negotiate Context to the #negotiate_context_list
54
54
  #
55
55
  # @param [NegotiateContext] the Negotiate Context structure you wish to add
56
- # @return [Array<Fixnum>] the array of all currently added Negotiate Contexts
56
+ # @return [Array<Integer>] the array of all currently added Negotiate Contexts
57
57
  # @raise [ArgumentError] if the dialect is not a NegotiateContext structure
58
58
  def add_negotiate_context(nc)
59
59
  raise ArgumentError, 'Must be a NegotiateContext' unless nc.is_a? NegotiateContext
@@ -48,7 +48,7 @@ module RubySMB
48
48
  # Adds a Negotiate Context to the #negotiate_context_list
49
49
  #
50
50
  # @param [NegotiateContext] the Negotiate Context structure you wish to add
51
- # @return [Array<Fixnum>] the array of all currently added Negotiate Contexts
51
+ # @return [Array<Integer>] the array of all currently added Negotiate Contexts
52
52
  # @raise [ArgumentError] if the dialect is not a NegotiateContext structure
53
53
  def add_negotiate_context(nc)
54
54
  raise ArgumentError, 'Must be a NegotiateContext' unless nc.is_a? NegotiateContext
@@ -145,6 +145,10 @@ module RubySMB
145
145
  break if dcerpc_response.pdu_header.pfc_flags.last_frag == 1
146
146
  raw_data = read(bytes: @tree.client.max_buffer_size)
147
147
  dcerpc_response = dcerpc_response_from_raw_response(raw_data)
148
+ if options[:auth_level] &&
149
+ [RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(options[:auth_level])
150
+ handle_integrity_privacy(dcerpc_response, auth_level: options[:auth_level], auth_type: options[:auth_type])
151
+ end
148
152
  stub_data << dcerpc_response.stub.to_s
149
153
  end
150
154
  stub_data
@@ -85,6 +85,7 @@ module RubySMB
85
85
  # @raise [RubySMB::Error::InvalidPacket] if the response is not a QueryDirectoryResponse packet
86
86
  def list(directory: nil, pattern: '*', type: RubySMB::Fscc::FileInformation::FileIdFullDirectoryInformation)
87
87
  create_response = open_directory(directory: directory)
88
+ opened_directory = RubySMB::SMB2::File.new(tree: self, response: create_response, name: directory)
88
89
  file_id = create_response.file_id
89
90
 
90
91
  directory_request = RubySMB::SMB2::Packet::QueryDirectoryRequest.new
@@ -127,8 +128,9 @@ module RubySMB
127
128
  # Reset the message id so the client can update appropriately.
128
129
  directory_request.smb2_header.message_id = 0
129
130
  end
130
-
131
131
  files
132
+ ensure
133
+ opened_directory.close if opened_directory
132
134
  end
133
135
 
134
136
  # 'Opens' a directory file on the remote end, using a CreateRequest. This
@@ -0,0 +1,15 @@
1
+ module RubySMB
2
+ module Utils
3
+
4
+ def self.safe_encode(str, encoding)
5
+ str.encode(encoding)
6
+ rescue EncodingError
7
+ if str.encoding == ::Encoding::ASCII_8BIT
8
+ str.dup.force_encoding(encoding)
9
+ else
10
+ raise
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module RubySMB
2
- VERSION = '3.2.0'.freeze
2
+ VERSION = '3.3.0'.freeze
3
3
  end
data/lib/ruby_smb.rb CHANGED
@@ -6,11 +6,13 @@ 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/string_encoder'
9
10
  # A packet parsing and manipulation library for the SMB1 and SMB2 protocols
10
11
  #
11
12
  # [[MS-SMB] Server Message Block (SMB) Protocol Version 1](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
12
13
  # [[MS-SMB2] Server Message Block (SMB) Protocol Versions 2 and 3](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
13
14
  module RubySMB
15
+ require 'ruby_smb/utils'
14
16
  require 'ruby_smb/error'
15
17
  require 'ruby_smb/create_actions'
16
18
  require 'ruby_smb/dispositions'
@@ -2701,7 +2701,7 @@ RSpec.describe RubySMB::Client do
2701
2701
  session_key,
2702
2702
  "SMB2AESCCM\x00",
2703
2703
  "ServerIn \x00",
2704
- {length: 128}
2704
+ length: 128
2705
2705
  ).and_call_original
2706
2706
  client.smb3_encrypt(data)
2707
2707
  end
@@ -2717,7 +2717,7 @@ RSpec.describe RubySMB::Client do
2717
2717
  session_key,
2718
2718
  "SMBC2SCipherKey\x00",
2719
2719
  '',
2720
- {length: 128}
2720
+ length: 128
2721
2721
  ).and_call_original
2722
2722
  client.smb3_encrypt(data)
2723
2723
  end
@@ -2791,7 +2791,7 @@ RSpec.describe RubySMB::Client do
2791
2791
  session_key,
2792
2792
  "SMB2AESCCM\x00",
2793
2793
  "ServerOut\x00",
2794
- {length: 128}
2794
+ length: 128
2795
2795
  ).and_call_original
2796
2796
  client.smb3_decrypt(transform_packet)
2797
2797
  end
@@ -2807,7 +2807,7 @@ RSpec.describe RubySMB::Client do
2807
2807
  session_key,
2808
2808
  "SMBS2CCipherKey\x00",
2809
2809
  '',
2810
- {length: 128}
2810
+ length: 128
2811
2811
  ).and_call_original
2812
2812
  client.smb3_decrypt(transform_packet)
2813
2813
  end
@@ -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) { described_class.new(host, endpoint, username: 'testuser', password: '1234') }
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) { 0 }
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, auth_type, auth_level)
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, auth_type, auth_level)
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, auth_type, auth_level)
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.send_auth3(bindack, auth_type, auth_level)
206
- expect(auth_client).to have_received(:add_auth_verifier).with(rpc_auth3, auth3, auth_type, auth_level)
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.send_auth3(bindack, auth_type, auth_level)
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.send_auth3(bindack, auth_type, auth_level)
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.send_auth3(bindack, auth_type, auth_level)
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.send_auth3(bindack, auth_type, auth_level)
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::RPC_C_AUTHN_LEVEL_DEFAULT,
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(:send_auth3)
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, kwargs[:auth_type], kwargs[:auth_level])
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(:send_auth3).with(bindack_response, kwargs[:auth_type], kwargs[:auth_level])
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
@@ -4146,3 +4146,201 @@ RSpec.describe 'Alignment' do
4146
4146
  end
4147
4147
  end
4148
4148
  end
4149
+
4150
+ RSpec.describe RubySMB::Dcerpc::Ndr::TypeSerialization1CommonTypeHeader do
4151
+ it 'is a BinData::Record class' do
4152
+ expect(described_class).to be < BinData::Record
4153
+ end
4154
+ it 'has :byte_align parameter set to the expected value' do
4155
+ expect(described_class.default_parameters[:byte_align]).to eq(8)
4156
+ end
4157
+
4158
+ subject { described_class.new }
4159
+
4160
+ it { is_expected.to respond_to :version }
4161
+ it { is_expected.to respond_to :endianness }
4162
+ it { is_expected.to respond_to :common_header_length }
4163
+ it { is_expected.to respond_to :filler }
4164
+
4165
+ context 'with #version' do
4166
+ it 'is a BinData::Uint8' do
4167
+ expect(subject.version).to be_a BinData::Uint8
4168
+ end
4169
+ it 'returns 1 by default' do
4170
+ expect(subject.version).to eq(1)
4171
+ end
4172
+ end
4173
+
4174
+ context 'with #endianness' do
4175
+ it 'is a BinData::Uint8' do
4176
+ expect(subject.endianness).to be_a BinData::Uint8
4177
+ end
4178
+ it 'returns 0x10 by default' do
4179
+ expect(subject.endianness).to eq(0x10)
4180
+ end
4181
+ end
4182
+
4183
+ context 'with #common_header_length' do
4184
+ it 'is a BinData::Uint16le' do
4185
+ expect(subject.common_header_length).to be_a BinData::Uint16le
4186
+ end
4187
+ it 'returns 8 by default' do
4188
+ expect(subject.common_header_length).to eq(8)
4189
+ end
4190
+ end
4191
+
4192
+ context 'with #filler' do
4193
+ it 'is a BinData::Uint32le' do
4194
+ expect(subject.filler).to be_a BinData::Uint32le
4195
+ end
4196
+ it 'returns 0xCCCCCCCC by default' do
4197
+ expect(subject.filler).to eq(0xCCCCCCCC)
4198
+ end
4199
+ end
4200
+
4201
+ it 'reads itself' do
4202
+ values = {version: 4, endianness: 0x33, common_header_length: 44, filler: 0xFFFFFFFF}
4203
+ struct_instance = described_class.new(values)
4204
+ expect(described_class.read(struct_instance.to_binary_s)).to eq(values)
4205
+ end
4206
+ end
4207
+
4208
+ RSpec.describe RubySMB::Dcerpc::Ndr::TypeSerialization1PrivateHeader do
4209
+ it 'is a BinData::Record class' do
4210
+ expect(described_class).to be < BinData::Record
4211
+ end
4212
+ it 'has :byte_align parameter set to the expected value' do
4213
+ expect(described_class.default_parameters[:byte_align]).to eq(8)
4214
+ end
4215
+
4216
+ subject { described_class.new }
4217
+
4218
+ it { is_expected.to respond_to :object_buffer_length }
4219
+ it { is_expected.to respond_to :filler }
4220
+
4221
+ context 'with #object_buffer_length' do
4222
+ it 'is a BinData::Uint32le' do
4223
+ expect(subject.object_buffer_length).to be_a BinData::Uint32le
4224
+ end
4225
+ it 'calls its parent\'s #field_length method to set its default value' do
4226
+ test_struct = Class.new(BinData::Record) do
4227
+ endian :little
4228
+ type_serialization1_private_header :header
4229
+
4230
+ def field_length(obj);end
4231
+ end.new
4232
+ expect(test_struct).to receive(:field_length).and_return(4)
4233
+ expect(test_struct.header.object_buffer_length).to eq(4)
4234
+ end
4235
+ it 'sets the default value to 0 when its parent doesn\'t implemet #field_length' do
4236
+ expect(subject.object_buffer_length).to eq(0)
4237
+ end
4238
+ end
4239
+
4240
+ context 'with #filler' do
4241
+ it 'is a BinData::Uint32le' do
4242
+ expect(subject.filler).to be_a BinData::Uint32le
4243
+ end
4244
+ it 'returns 0x00000000 by default' do
4245
+ expect(subject.filler).to eq(0x00000000)
4246
+ end
4247
+ end
4248
+
4249
+ it 'reads itself' do
4250
+ values = {object_buffer_length: 4, filler: 0xFFFFFFFF}
4251
+ struct_instance = described_class.new(values)
4252
+ expect(described_class.read(struct_instance.to_binary_s)).to eq(values)
4253
+ end
4254
+ end
4255
+
4256
+ RSpec.describe RubySMB::Dcerpc::Ndr::TypeSerialization1 do
4257
+ it 'is a BinData::Record class' do
4258
+ expect(described_class).to be < BinData::Record
4259
+ end
4260
+ it 'has :byte_align parameter set to the expected value' do
4261
+ expect(described_class.default_parameters[:byte_align]).to eq(8)
4262
+ end
4263
+
4264
+ subject { described_class.new }
4265
+
4266
+ it { is_expected.to respond_to :common_header }
4267
+
4268
+ context 'with #common_header' do
4269
+ it 'is a TypeSerialization1CommonTypeHeader structure' do
4270
+ expect(subject.common_header).to be_a RubySMB::Dcerpc::Ndr::TypeSerialization1CommonTypeHeader
4271
+ end
4272
+ end
4273
+
4274
+ it 'reads itself' do
4275
+ values = {
4276
+ common_header: {version: 4, endianness: 0x33, common_header_length: 44, filler: 0xFFFFFFFF}
4277
+ }
4278
+ struct_instance = described_class.new(values)
4279
+ expect(described_class.read(struct_instance.to_binary_s)).to eq(values)
4280
+ end
4281
+
4282
+ context 'when inherited' do
4283
+ let(:test_struct) do
4284
+ Class.new(RubySMB::Dcerpc::Ndr::NdrStruct) do
4285
+ default_parameters byte_align: 8
4286
+ endian :little
4287
+
4288
+ rpc_unicode_string :full_name
4289
+ ndr_uint32 :user_id
4290
+ end
4291
+ end
4292
+ before :example do
4293
+ BinData::RegisteredClasses.register('test_struct', test_struct)
4294
+ end
4295
+ after :example do
4296
+ BinData::RegisteredClasses.unregister('test_struct')
4297
+ end
4298
+
4299
+ subject do
4300
+ Class.new(described_class) do
4301
+ default_parameter byte_align: 8
4302
+ endian :little
4303
+
4304
+ test_struct :data1
4305
+ uint32 :value1, initial_value: 5
4306
+ uint32 :value2, initial_value: 6
4307
+ test_struct :data2
4308
+ uint32 :value3, initial_value: 7
4309
+ test_struct :data3
4310
+ end.new
4311
+ end
4312
+
4313
+ it { is_expected.to respond_to :common_header }
4314
+ it { is_expected.to respond_to :private_header1 }
4315
+ it { is_expected.to respond_to :data1 }
4316
+ it { is_expected.to respond_to :value1 }
4317
+ it { is_expected.to respond_to :value2 }
4318
+ it { is_expected.to respond_to :private_header2 }
4319
+ it { is_expected.to respond_to :data2 }
4320
+ it { is_expected.to respond_to :value3 }
4321
+ it { is_expected.to respond_to :private_header3 }
4322
+ it { is_expected.to respond_to :data3 }
4323
+
4324
+ it 'sets the expected default values' do
4325
+ data_obj = test_struct.new
4326
+ expect(subject.data1).to eq(data_obj)
4327
+ expect(subject.value1).to eq(5)
4328
+ expect(subject.value2).to eq(6)
4329
+ expect(subject.data2).to eq(data_obj)
4330
+ expect(subject.value3).to eq(7)
4331
+ expect(subject.data3).to eq(data_obj)
4332
+ end
4333
+
4334
+ it 'sets the correct private header\'s object_buffer_length field value' do
4335
+ data1 = test_struct.new(full_name: 'test string')
4336
+ subject.data1 = data1
4337
+ expect(subject.private_header1.object_buffer_length).to eq(data1.num_bytes)
4338
+ data2 = test_struct.new(full_name: 'another test string')
4339
+ subject.data2 = data2
4340
+ expect(subject.private_header2.object_buffer_length).to eq(data2.num_bytes)
4341
+ data3 = test_struct.new(full_name: 'more string!!!')
4342
+ subject.data3 = data3
4343
+ expect(subject.private_header3.object_buffer_length).to eq(data3.num_bytes)
4344
+ end
4345
+ end
4346
+ end
@@ -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