ruby_smb 3.1.5 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/verify.yml +27 -4
  4. data/README.md +1 -2
  5. data/lib/ruby_smb/client/authentication.rb +4 -30
  6. data/lib/ruby_smb/dcerpc/client.rb +6 -261
  7. data/lib/ruby_smb/dcerpc/dfsnm/netr_dfs_add_std_root_request.rb +24 -0
  8. data/lib/ruby_smb/dcerpc/dfsnm/netr_dfs_add_std_root_response.rb +21 -0
  9. data/lib/ruby_smb/dcerpc/dfsnm/netr_dfs_remove_std_root_request.rb +23 -0
  10. data/lib/ruby_smb/dcerpc/dfsnm/netr_dfs_remove_std_root_response.rb +21 -0
  11. data/lib/ruby_smb/dcerpc/dfsnm.rb +84 -0
  12. data/lib/ruby_smb/dcerpc/error.rb +21 -0
  13. data/lib/ruby_smb/dcerpc/icpr/cert_server_request_request.rb +27 -0
  14. data/lib/ruby_smb/dcerpc/icpr/cert_server_request_response.rb +28 -0
  15. data/lib/ruby_smb/dcerpc/icpr.rb +84 -0
  16. data/lib/ruby_smb/dcerpc/ndr.rb +38 -2
  17. data/lib/ruby_smb/dcerpc/request.rb +10 -1
  18. data/lib/ruby_smb/dcerpc.rb +246 -12
  19. data/lib/ruby_smb/error.rb +25 -11
  20. data/lib/ruby_smb/peer_info.rb +39 -0
  21. data/lib/ruby_smb/signing.rb +2 -0
  22. data/lib/ruby_smb/smb1/pipe.rb +23 -1
  23. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +5 -1
  24. data/lib/ruby_smb/smb2/pipe.rb +23 -0
  25. data/lib/ruby_smb/version.rb +1 -1
  26. data/spec/lib/ruby_smb/dcerpc/dfsnm/netr_dfs_add_std_root_request_spec.rb +57 -0
  27. data/spec/lib/ruby_smb/dcerpc/dfsnm/netr_dfs_add_std_root_response_spec.rb +34 -0
  28. data/spec/lib/ruby_smb/dcerpc/dfsnm/netr_dfs_remove_std_root_request_spec.rb +49 -0
  29. data/spec/lib/ruby_smb/dcerpc/dfsnm/netr_dfs_remove_std_root_response_spec.rb +34 -0
  30. data/spec/lib/ruby_smb/dcerpc/icpr/cert_server_request_request_spec.rb +64 -0
  31. data/spec/lib/ruby_smb/dcerpc/icpr/cert_server_request_response_spec.rb +71 -0
  32. data/spec/lib/ruby_smb/dcerpc/icpr/cert_trans_blob_spec.rb +33 -0
  33. data/spec/lib/ruby_smb/dcerpc_spec.rb +2 -1
  34. data/spec/spec_helper.rb +6 -8
  35. data/spec/support/openssl.conf +14 -0
  36. data.tar.gz.sig +0 -0
  37. metadata +27 -2
  38. metadata.gz.sig +0 -0
@@ -0,0 +1,28 @@
1
+ require 'ruby_smb/dcerpc/ndr'
2
+
3
+ module RubySMB
4
+ module Dcerpc
5
+ module Icpr
6
+
7
+ # [3.2.4.1.1 CertServerRequest (Opnum 0)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-icpr/0c6f150e-3ead-4006-b37f-ebbf9e2cf2e7)
8
+ class CertServerRequestResponse < BinData::Record
9
+ attr_reader :opnum
10
+
11
+ endian :little
12
+
13
+ ndr_uint32 :pdw_request_id
14
+ ndr_uint32 :pdw_disposition
15
+ cert_trans_blob :pctb_cert
16
+ cert_trans_blob :pctb_encoded_cert
17
+ cert_trans_blob :pctb_disposition_message
18
+ ndr_uint32 :error_status
19
+
20
+ def initialize_instance
21
+ super
22
+ @opnum = CERT_SERVER_REQUEST
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,84 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ module Icpr
4
+
5
+ # [3.2.4.1 ICertPassage Interface](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-icpr/d98e6cfb-87ba-4915-b3ec-a1b7c6129a53)
6
+ UUID = '91ae6020-9e3c-11cf-8d7c-00aa00c091be'
7
+ VER_MAJOR = 0
8
+ VER_MINOR = 0
9
+
10
+ # Operation numbers
11
+ CERT_SERVER_REQUEST = 0x0000
12
+
13
+ # Disposition constants, see
14
+ # [3.2.1.4.2.1 ICertRequestD::Request (Opnum 3)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/dbb2e78f-7630-4615-92c4-6734fccfc5a6)
15
+ CR_DISP_ISSUED = 0x0003
16
+ CR_DISP_UNDER_SUBMISSION = 0x0005
17
+
18
+ # [2.2.2.2 CERTTRANSBLOB](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/d6bee093-d862-4122-8f2b-7b49102097dc)
19
+ # (actually defined in MS-WCCE)
20
+ class CertTransBlob < Ndr::NdrStruct
21
+ endian :little
22
+ default_parameter byte_align: 4
23
+
24
+ ndr_uint32 :cb, initial_value: -> { pb.length }
25
+ ndr_byte_conf_array_ptr :pb
26
+
27
+ def buffer
28
+ pb.to_ary.pack('C*')
29
+ end
30
+ end
31
+
32
+ require 'ruby_smb/dcerpc/icpr/cert_server_request_request'
33
+ require 'ruby_smb/dcerpc/icpr/cert_server_request_response'
34
+
35
+ def cert_server_request(attributes:, authority:, csr:)
36
+ cert_server_request_request = CertServerRequestRequest.new(
37
+ pwsz_authority: authority,
38
+ pctb_attribs: { pb: (attributes.map { |k,v| "#{k}:#{v}" }.join("\n").encode('UTF-16le').force_encoding('ASCII-8bit') + "\x00\x00".b) },
39
+ pctb_request: { pb: csr.to_der }
40
+ )
41
+
42
+ response = dcerpc_request(
43
+ cert_server_request_request,
44
+ auth_level: RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
45
+ auth_type: RubySMB::Dcerpc::RPC_C_AUTHN_WINNT
46
+ )
47
+ begin
48
+ cert_server_request_response = CertServerRequestResponse.read(response)
49
+ rescue IOError
50
+ raise RubySMB::Dcerpc::Error::InvalidPacket, 'Error reading CertServerRequestResponse'
51
+ end
52
+
53
+ ret = {
54
+ disposition: cert_server_request_response.pdw_disposition.value,
55
+ disposition_message: cert_server_request_response.pctb_disposition_message.buffer.chomp("\x00\x00").force_encoding('utf-16le').encode,
56
+ status: {
57
+ CR_DISP_ISSUED => :issued,
58
+ CR_DISP_UNDER_SUBMISSION => :submitted,
59
+ }.fetch(cert_server_request_response.pdw_disposition.value, :error)
60
+ }
61
+
62
+ # note: error_status == RPC_S_BINDING_HAS_NO_AUTH when not properly bound
63
+ if ret[:status] == :error
64
+ unless cert_server_request_response.error_status == WindowsError::NTStatus::STATUS_SUCCESS
65
+ error_status = cert_server_request_response.error_status.value
66
+ status_code = WindowsError::Win32.find_by_retval(error_status).first
67
+ if status_code.nil? && (fault_name = RubySMB::Dcerpc::Fault::Status.name(error_status))
68
+ status_code = WindowsError::ErrorCode.new(fault_name.to_s, error_status, 'DCERPC fault')
69
+ end
70
+ raise RubySMB::Dcerpc::Error::IcprError.new(
71
+ "Error returned with cert_server_request: #{status_code || "0x#{error_status.to_s(16).rjust(8, '0')}"}",
72
+ status_code: status_code
73
+ )
74
+ end
75
+ else
76
+ ret[:certificate] = OpenSSL::X509::Certificate.new(cert_server_request_response.pctb_encoded_cert.buffer)
77
+ end
78
+
79
+ ret
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -1208,14 +1208,50 @@ module RubySMB::Dcerpc::Ndr
1208
1208
  extend PointerClassPlugin
1209
1209
  end
1210
1210
 
1211
+
1212
+ # ArrayPtr definitions:
1213
+ # If the type is Ndr*ArrayPtr, it's a ConfVarArray (Uni-dimensional Conformant-varying Array)
1214
+ # If the type is Ndr*ConfArrayPtr, it's a ConfArray (Uni-dimensional Conformant Array)
1211
1215
  class NdrByteArrayPtr < NdrConfVarArray
1212
1216
  default_parameters type: :ndr_uint8
1213
1217
  extend PointerClassPlugin
1218
+
1219
+ def assign(val)
1220
+ val = val.bytes if val.is_a?(String)
1221
+ super(val.to_ary)
1222
+ end
1214
1223
  end
1215
1224
 
1216
- class NdrUint16ArrayPtr < NdrConfVarArray
1217
- default_parameters type: :ndr_uint16
1225
+ class NdrByteConfArrayPtr < NdrConfArray
1226
+ default_parameters type: :ndr_uint8
1218
1227
  extend PointerClassPlugin
1228
+
1229
+ def assign(val)
1230
+ val = val.bytes if val.is_a?(String)
1231
+ super(val.to_ary)
1232
+ end
1233
+ end
1234
+
1235
+ %i[ Uint8 Uint16 Uint32 Uint64 ].each do |klass|
1236
+ new_klass_name = "Ndr#{klass}ArrayPtr"
1237
+ unless self.const_defined?(new_klass_name)
1238
+ new_klass = Class.new(NdrConfVarArray) do
1239
+ default_parameters type: "ndr_#{klass}".downcase.to_sym
1240
+ extend PointerClassPlugin
1241
+ end
1242
+ self.const_set(new_klass_name, new_klass)
1243
+ BinData::RegisteredClasses.register(new_klass_name, new_klass)
1244
+ end
1245
+
1246
+ new_klass_name = "Ndr#{klass}ConfArrayPtr"
1247
+ unless self.const_defined?(new_klass_name)
1248
+ new_klass = Class.new(NdrConfArray) do
1249
+ default_parameters type: "ndr_#{klass}".downcase.to_sym
1250
+ extend PointerClassPlugin
1251
+ end
1252
+ self.const_set(new_klass_name, new_klass)
1253
+ BinData::RegisteredClasses.register(new_klass_name, new_klass)
1254
+ end
1219
1255
  end
1220
1256
 
1221
1257
  class NdrFileTimePtr < NdrFileTime
@@ -90,7 +90,16 @@ module RubySMB
90
90
  drs_domain_controller_info_request Drsr::DRS_DOMAIN_CONTROLLER_INFO
91
91
  drs_crack_names_request Drsr::DRS_CRACK_NAMES
92
92
  drs_get_nc_changes_request Drsr::DRS_GET_NC_CHANGES
93
- string :default
93
+ string :default
94
+ end
95
+ choice 'Dfsnm', selection: -> { opnum } do
96
+ netr_dfs_add_std_root_request Dfsnm::NETR_DFS_ADD_STD_ROOT
97
+ netr_dfs_remove_std_root_request Dfsnm::NETR_DFS_REMOVE_STD_ROOT
98
+ string :default
99
+ end
100
+ choice 'Icpr', selection: -> { opnum } do
101
+ cert_server_request_request Icpr::CERT_SERVER_REQUEST
102
+ string :default
94
103
  end
95
104
  string :default
96
105
  end
@@ -46,6 +46,8 @@ module RubySMB
46
46
  require 'ruby_smb/dcerpc/epm'
47
47
  require 'ruby_smb/dcerpc/drsr'
48
48
  require 'ruby_smb/dcerpc/sec_trailer'
49
+ require 'ruby_smb/dcerpc/dfsnm'
50
+ require 'ruby_smb/dcerpc/icpr'
49
51
  require 'ruby_smb/dcerpc/request'
50
52
  require 'ruby_smb/dcerpc/response'
51
53
  require 'ruby_smb/dcerpc/rpc_auth3'
@@ -54,25 +56,50 @@ module RubySMB
54
56
  require 'ruby_smb/dcerpc/print_system'
55
57
  require 'ruby_smb/dcerpc/encrypting_file_system'
56
58
 
57
- # 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
58
62
  #
59
- # @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
60
68
  # @return [BindAck] the BindAck response packet
61
69
  # @raise [Error::InvalidPacket] if an invalid packet is received
62
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)
63
73
  def bind(options={})
74
+ @call_id ||= 1
64
75
  bind_req = Bind.new(options)
65
- write(data: bind_req.to_binary_s)
66
- @size = 1024
67
- dcerpc_raw_response = read()
68
- begin
69
- dcerpc_response = BindAck.read(dcerpc_raw_response)
70
- rescue IOError
71
- 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])
72
94
  end
73
- unless dcerpc_response.pdu_header.ptype == PTypes::BIND_ACK
74
- 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
75
101
  end
102
+ # TODO: see if BindNack response should be handled too
76
103
 
77
104
  res_list = dcerpc_response.p_result_list
78
105
  if res_list.n_results == 0 ||
@@ -80,9 +107,216 @@ module RubySMB
80
107
  raise Error::BindError,
81
108
  "Bind Failed (Result: #{res_list.p_results[0].result}, Reason: #{res_list.p_results[0].reason})"
82
109
  end
83
- @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
+
84
126
  dcerpc_response
85
127
  end
86
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
87
321
  end
88
322
  end
@@ -70,20 +70,34 @@ module RubySMB
70
70
 
71
71
  # Raised when a response packet has a NTStatus code that was unexpected.
72
72
  class UnexpectedStatusCode < RubySMBError
73
- attr_reader :status_code
73
+ module Mixin
74
+ attr_reader :status_code
74
75
 
75
- def initialize(status_code)
76
- case status_code
77
- when WindowsError::ErrorCode
78
- @status_code = status_code
79
- when Integer
80
- @status_code = WindowsError::NTStatus.find_by_retval(status_code).first
81
- if @status_code.nil?
82
- @status_code = WindowsError::ErrorCode.new("0x#{status_code.to_s(16)}", status_code, "Unknown 0x#{status_code.to_s(16)}")
76
+ def status_name
77
+ @status_code.name
78
+ end
79
+
80
+ private
81
+
82
+ def status_code=(status_code)
83
+ case status_code
84
+ when WindowsError::ErrorCode
85
+ @status_code = status_code
86
+ when Integer
87
+ @status_code = WindowsError::NTStatus.find_by_retval(status_code).first
88
+ if @status_code.nil?
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')}")
90
+ end
91
+ else
92
+ raise ArgumentError, "Status code must be a WindowsError::ErrorCode or an Integer, got #{status_code.class}"
83
93
  end
84
- else
85
- raise ArgumentError, "Status code must be a WindowsError::ErrorCode or an Integer, got #{status_code.class}"
86
94
  end
95
+ end
96
+
97
+ include Mixin
98
+
99
+ def initialize(status_code)
100
+ self.status_code = status_code
87
101
  super
88
102
  end
89
103
 
@@ -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
@@ -53,6 +53,8 @@ module RubySMB
53
53
  def self.smb2_sign(packet, session_key)
54
54
  packet.smb2_header.flags.signed = 1
55
55
  packet.smb2_header.signature = "\x00" * 16
56
+ # OpenSSL 3 raises exceptions if the session key is an empty string
57
+ session_key = session_key == '' ? ("\x00" * 16).b : session_key
56
58
  hmac = OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA256'), session_key, packet.to_binary_s)
57
59
  packet.smb2_header.signature = hmac[0, 16]
58
60
 
@@ -28,10 +28,20 @@ module RubySMB
28
28
  extend RubySMB::Dcerpc::Samr
29
29
  when 'wkssvc', '\\wkssvc'
30
30
  extend RubySMB::Dcerpc::Wkssvc
31
+ when 'netdfs', '\\netdfs'
32
+ extend RubySMB::Dcerpc::Dfsnm
33
+ when 'cert', '\\cert'
34
+ extend RubySMB::Dcerpc::Icpr
31
35
  end
32
36
  super(tree: tree, response: response, name: name)
33
37
  end
34
38
 
39
+ def bind(options={})
40
+ @size = 1024
41
+ @ntlm_client = @tree.client.ntlm_client
42
+ super
43
+ end
44
+
35
45
  # Performs a peek operation on the named pipe
36
46
  #
37
47
  # @param peek_size [Integer] Amount of data to peek
@@ -96,6 +106,11 @@ module RubySMB
96
106
  options.merge!(endpoint: stub_packet.class.name.split('::').at(-2))
97
107
  dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: stub_packet.opnum }, options)
98
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
+
99
114
  trans_nmpipe_request = RubySMB::SMB1::Packet::Trans::TransactNmpipeRequest.new(options)
100
115
  @tree.set_header_fields(trans_nmpipe_request)
101
116
  trans_nmpipe_request.set_fid(@fid)
@@ -122,6 +137,10 @@ module RubySMB
122
137
  unless dcerpc_response.pdu_header.pfc_flags.first_frag == 1
123
138
  raise RubySMB::Dcerpc::Error::InvalidPacket, "Not the first fragment"
124
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
125
144
  stub_data = dcerpc_response.stub.to_s
126
145
 
127
146
  loop do
@@ -133,11 +152,14 @@ module RubySMB
133
152
  stub_data
134
153
  else
135
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
136
159
  dcerpc_response.stub.to_s
137
160
  end
138
161
  end
139
162
 
140
-
141
163
  private
142
164
 
143
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