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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/verify.yml +8 -9
- data/README.md +1 -0
- data/cortex.yaml +15 -0
- data/examples/dump_secrets_from_sid.rb +2 -1
- data/lib/ruby_smb/client.rb +6 -4
- data/lib/ruby_smb/dcerpc/alter_context.rb +30 -0
- data/lib/ruby_smb/dcerpc/alter_context_resp.rb +42 -0
- data/lib/ruby_smb/dcerpc/bind.rb +3 -35
- data/lib/ruby_smb/dcerpc/bind_ack.rb +0 -31
- data/lib/ruby_smb/dcerpc/client.rb +10 -4
- data/lib/ruby_smb/dcerpc/drsr.rb +13 -13
- data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_decrypt_file_srv_request.rb +22 -0
- data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_decrypt_file_srv_response.rb +21 -0
- data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_query_recover_agents_request.rb +20 -0
- data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_query_recover_agents_response.rb +21 -0
- data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_query_users_on_file_request.rb +20 -0
- data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_query_users_on_file_response.rb +21 -0
- data/lib/ruby_smb/dcerpc/encrypting_file_system.rb +52 -0
- data/lib/ruby_smb/dcerpc/icpr.rb +3 -2
- data/lib/ruby_smb/dcerpc/ndr.rb +148 -3
- data/lib/ruby_smb/dcerpc/p_cont_list_t.rb +37 -0
- data/lib/ruby_smb/dcerpc/p_result_list_t.rb +13 -0
- data/lib/ruby_smb/dcerpc/p_result_t.rb +15 -0
- data/lib/ruby_smb/dcerpc/port_any_t.rb +11 -0
- data/lib/ruby_smb/dcerpc/request.rb +8 -3
- data/lib/ruby_smb/dcerpc/response.rb +6 -1
- data/lib/ruby_smb/dcerpc/samr.rb +4 -2
- data/lib/ruby_smb/dcerpc.rb +165 -122
- data/lib/ruby_smb/dispatcher/socket.rb +1 -1
- data/lib/ruby_smb/field/file_time.rb +2 -2
- data/lib/ruby_smb/field/utime.rb +2 -2
- data/lib/ruby_smb/generic_packet.rb +2 -2
- data/lib/ruby_smb/gss/provider/ntlm.rb +12 -5
- data/lib/ruby_smb/ntlm/client.rb +4 -0
- data/lib/ruby_smb/ntlm/custom/string_encoder.rb +22 -0
- data/lib/ruby_smb/ntlm.rb +37 -0
- data/lib/ruby_smb/server/server_client/share_io.rb +2 -2
- data/lib/ruby_smb/server/server_client/tree_connect.rb +2 -2
- data/lib/ruby_smb/server/share/provider/disk.rb +2 -2
- data/lib/ruby_smb/server/share/provider/processor.rb +25 -0
- data/lib/ruby_smb/server/share/provider/virtual_disk.rb +3 -3
- data/lib/ruby_smb/server/share/provider.rb +48 -1
- data/lib/ruby_smb/smb1/data_block.rb +2 -2
- data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +1 -1
- data/lib/ruby_smb/smb1/parameter_block.rb +2 -2
- data/lib/ruby_smb/smb1/pipe.rb +4 -0
- data/lib/ruby_smb/smb2/create_context.rb +5 -2
- data/lib/ruby_smb/smb2/packet/negotiate_request.rb +5 -5
- data/lib/ruby_smb/smb2/packet/negotiate_response.rb +1 -1
- data/lib/ruby_smb/smb2/pipe.rb +4 -0
- data/lib/ruby_smb/smb2/tree.rb +3 -1
- data/lib/ruby_smb/utils.rb +15 -0
- data/lib/ruby_smb/version.rb +1 -1
- data/lib/ruby_smb.rb +2 -0
- data/spec/lib/ruby_smb/client_spec.rb +4 -4
- data/spec/lib/ruby_smb/dcerpc/client_spec.rb +31 -16
- data/spec/lib/ruby_smb/dcerpc/drsr_spec.rb +4 -1
- data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +198 -0
- data/spec/lib/ruby_smb/dcerpc/request_spec.rb +0 -6
- data/spec/lib/ruby_smb/dcerpc/response_spec.rb +4 -12
- data/spec/lib/ruby_smb/dcerpc/sec_trailer_spec.rb +0 -14
- data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/read_andx_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/write_andx_request_spec.rb +1 -1
- data/spec/lib/ruby_smb/smb2/create_context/create_context_request_spec.rb +35 -0
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +5 -0
- data.tar.gz.sig +0 -0
- metadata +41 -86
- 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 [
|
|
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 [
|
|
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: '
|
|
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 [
|
|
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 [
|
|
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|
|
data/lib/ruby_smb/smb1/pipe.rb
CHANGED
|
@@ -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
|
|
55
|
-
size +=
|
|
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 [
|
|
32
|
-
# @return [Array<
|
|
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<
|
|
44
|
-
# @return [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<
|
|
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<
|
|
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
|
data/lib/ruby_smb/smb2/pipe.rb
CHANGED
|
@@ -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
|
data/lib/ruby_smb/smb2/tree.rb
CHANGED
|
@@ -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
|
data/lib/ruby_smb/version.rb
CHANGED
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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) {
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
206
|
-
expect(auth_client).to have_received(:add_auth_verifier).with(rpc_auth3, auth3
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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::
|
|
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(:
|
|
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
|
|
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(:
|
|
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
|