ruby_smb 3.1.5 → 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.
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