ruby_smb 2.0.2 → 2.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/examples/anonymous_auth.rb +3 -3
- data/examples/append_file.rb +10 -8
- data/examples/authenticate.rb +9 -5
- data/examples/delete_file.rb +8 -6
- data/examples/enum_registry_key.rb +5 -4
- data/examples/enum_registry_values.rb +5 -4
- data/examples/list_directory.rb +8 -6
- data/examples/negotiate_with_netbios_service.rb +9 -5
- data/examples/net_share_enum_all.rb +6 -4
- data/examples/pipes.rb +11 -12
- data/examples/query_service_status.rb +64 -0
- data/examples/read_file.rb +8 -6
- data/examples/read_registry_key_value.rb +6 -5
- data/examples/rename_file.rb +9 -7
- data/examples/tree_connect.rb +7 -5
- data/examples/write_file.rb +9 -7
- data/lib/ruby_smb/client.rb +72 -43
- data/lib/ruby_smb/client/negotiation.rb +1 -0
- data/lib/ruby_smb/dcerpc.rb +2 -0
- data/lib/ruby_smb/dcerpc/error.rb +3 -0
- data/lib/ruby_smb/dcerpc/ndr.rb +209 -44
- data/lib/ruby_smb/dcerpc/request.rb +13 -0
- data/lib/ruby_smb/dcerpc/rpc_security_attributes.rb +34 -0
- data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +9 -6
- data/lib/ruby_smb/dcerpc/svcctl.rb +479 -0
- data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request.rb +48 -0
- data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response.rb +26 -0
- data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request.rb +25 -0
- data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response.rb +26 -0
- data/lib/ruby_smb/dcerpc/svcctl/control_service_request.rb +26 -0
- data/lib/ruby_smb/dcerpc/svcctl/control_service_response.rb +26 -0
- data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request.rb +35 -0
- data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response.rb +23 -0
- data/lib/ruby_smb/dcerpc/svcctl/open_service_w_request.rb +31 -0
- data/lib/ruby_smb/dcerpc/svcctl/open_service_w_response.rb +23 -0
- data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request.rb +25 -0
- data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response.rb +44 -0
- data/lib/ruby_smb/dcerpc/svcctl/query_service_status_request.rb +23 -0
- data/lib/ruby_smb/dcerpc/svcctl/query_service_status_response.rb +27 -0
- data/lib/ruby_smb/dcerpc/svcctl/service_status.rb +25 -0
- data/lib/ruby_smb/dcerpc/svcctl/start_service_w_request.rb +27 -0
- data/lib/ruby_smb/dcerpc/svcctl/start_service_w_response.rb +25 -0
- data/lib/ruby_smb/dcerpc/winreg.rb +98 -17
- data/lib/ruby_smb/dcerpc/winreg/create_key_request.rb +73 -0
- data/lib/ruby_smb/dcerpc/winreg/create_key_response.rb +36 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +1 -1
- data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +1 -1
- data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +1 -1
- data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +4 -4
- data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +1 -1
- data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +7 -6
- data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +10 -10
- data/lib/ruby_smb/dcerpc/winreg/save_key_request.rb +37 -0
- data/lib/ruby_smb/dcerpc/winreg/save_key_response.rb +23 -0
- data/lib/ruby_smb/dispatcher/base.rb +1 -1
- data/lib/ruby_smb/dispatcher/socket.rb +1 -1
- data/lib/ruby_smb/field/stringz16.rb +17 -1
- data/lib/ruby_smb/nbss/session_header.rb +4 -4
- data/lib/ruby_smb/smb1/file.rb +2 -10
- data/lib/ruby_smb/smb1/pipe.rb +2 -0
- data/lib/ruby_smb/smb2/file.rb +25 -17
- data/lib/ruby_smb/smb2/pipe.rb +3 -0
- data/lib/ruby_smb/smb2/tree.rb +9 -3
- data/lib/ruby_smb/version.rb +1 -1
- data/spec/lib/ruby_smb/client_spec.rb +161 -60
- data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +1396 -77
- data/spec/lib/ruby_smb/dcerpc/rpc_security_attributes_spec.rb +161 -0
- data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +49 -12
- data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request_spec.rb +191 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request_spec.rb +30 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_request_spec.rb +39 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request_spec.rb +78 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_request_spec.rb +59 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response_spec.rb +152 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_request_spec.rb +30 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/service_status_spec.rb +72 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_request_spec.rb +46 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_response_spec.rb +30 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl_spec.rb +512 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/create_key_request_spec.rb +110 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/create_key_response_spec.rb +44 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +0 -4
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +9 -4
- data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +0 -4
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +17 -17
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +11 -23
- data/spec/lib/ruby_smb/dcerpc/winreg/save_key_request_spec.rb +57 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/save_key_response_spec.rb +22 -0
- data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +215 -41
- data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +10 -10
- data/spec/lib/ruby_smb/field/stringz16_spec.rb +12 -0
- data/spec/lib/ruby_smb/nbss/session_header_spec.rb +4 -11
- data/spec/lib/ruby_smb/smb1/pipe_spec.rb +7 -0
- data/spec/lib/ruby_smb/smb2/file_spec.rb +60 -6
- data/spec/lib/ruby_smb/smb2/pipe_spec.rb +7 -0
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +35 -1
- metadata +72 -2
- metadata.gz.sig +0 -0
@@ -13,9 +13,10 @@ module RubySMB
|
|
13
13
|
|
14
14
|
rpc_hkey :hkey
|
15
15
|
rrp_unicode_string :lp_value_name
|
16
|
-
string :
|
16
|
+
string :pad1, length: -> { pad_length(self.lp_value_name) }
|
17
17
|
ndr_lp_dword :lp_type
|
18
|
-
|
18
|
+
ndr_lp_byte_array :lp_data
|
19
|
+
string :pad2, length: -> { pad_length(self.lp_data) }
|
19
20
|
ndr_lp_dword :lpcb_data
|
20
21
|
ndr_lp_dword :lpcb_len
|
21
22
|
|
@@ -24,10 +25,10 @@ module RubySMB
|
|
24
25
|
@opnum = REG_QUERY_VALUE
|
25
26
|
end
|
26
27
|
|
27
|
-
# Determines the correct length for the padding
|
28
|
-
#
|
29
|
-
def pad_length
|
30
|
-
offset = (
|
28
|
+
# Determines the correct length for the padding, so that the next
|
29
|
+
# field is 4-byte aligned.
|
30
|
+
def pad_length(prev_element)
|
31
|
+
offset = (prev_element.abs_offset + prev_element.to_binary_s.length) % 4
|
31
32
|
(4 - offset) % 4
|
32
33
|
end
|
33
34
|
end
|
@@ -9,22 +9,22 @@ module RubySMB
|
|
9
9
|
|
10
10
|
endian :little
|
11
11
|
|
12
|
-
ndr_lp_dword
|
13
|
-
|
14
|
-
string
|
15
|
-
ndr_lp_dword
|
16
|
-
ndr_lp_dword
|
17
|
-
uint32
|
12
|
+
ndr_lp_dword :lp_type
|
13
|
+
ndr_lp_byte_array :lp_data
|
14
|
+
string :pad, length: -> { pad_length(self.lp_data) }
|
15
|
+
ndr_lp_dword :lpcb_data
|
16
|
+
ndr_lp_dword :lpcb_len
|
17
|
+
uint32 :error_status
|
18
18
|
|
19
19
|
def initialize_instance
|
20
20
|
super
|
21
21
|
@opnum = REG_QUERY_VALUE
|
22
22
|
end
|
23
23
|
|
24
|
-
# Determines the correct length for the padding
|
25
|
-
#
|
26
|
-
def pad_length
|
27
|
-
offset = (
|
24
|
+
# Determines the correct length for the padding, so that the next
|
25
|
+
# field is 4-byte aligned.
|
26
|
+
def pad_length(prev_element)
|
27
|
+
offset = (prev_element.abs_offset + prev_element.to_binary_s.length) % 4
|
28
28
|
(4 - offset) % 4
|
29
29
|
end
|
30
30
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module RubySMB
|
2
|
+
module Dcerpc
|
3
|
+
module Winreg
|
4
|
+
|
5
|
+
class RpcHkey < Ndr::NdrContextHandle; end
|
6
|
+
|
7
|
+
# This class represents a BaseRegSaveKey Request Packet as defined in
|
8
|
+
# [3.1.5.20 BaseRegSaveKey (Opnum 20)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rrp/f022247d-6ef1-4f46-b195-7f60654f4a0d)
|
9
|
+
class SaveKeyRequest < BinData::Record
|
10
|
+
attr_reader :opnum
|
11
|
+
|
12
|
+
endian :little
|
13
|
+
|
14
|
+
rpc_hkey :hkey
|
15
|
+
rrp_unicode_string :lp_file
|
16
|
+
string :pad, length: -> { pad_length(self.lp_file) }
|
17
|
+
prpc_security_attributes :lp_security_attributes
|
18
|
+
|
19
|
+
def initialize_instance
|
20
|
+
super
|
21
|
+
@opnum = REG_SAVE_KEY
|
22
|
+
end
|
23
|
+
|
24
|
+
# Determines the correct length for the padding, so that the next
|
25
|
+
# field is 4-byte aligned.
|
26
|
+
def pad_length(prev_element)
|
27
|
+
offset = (prev_element.abs_offset + prev_element.to_binary_s.length) % 4
|
28
|
+
(4 - offset) % 4
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module RubySMB
|
2
|
+
module Dcerpc
|
3
|
+
module Winreg
|
4
|
+
|
5
|
+
# This class represents a BaseRegSaveKey Response Packet as defined in
|
6
|
+
# [3.1.5.20 BaseRegSaveKey (Opnum 20)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rrp/f022247d-6ef1-4f46-b195-7f60654f4a0d)
|
7
|
+
class SaveKeyResponse < BinData::Record
|
8
|
+
attr_reader :opnum
|
9
|
+
|
10
|
+
endian :little
|
11
|
+
|
12
|
+
uint32 :error_status
|
13
|
+
|
14
|
+
def initialize_instance
|
15
|
+
super
|
16
|
+
@opnum = REG_CREATE_KEY
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -9,7 +9,7 @@ module RubySMB
|
|
9
9
|
def nbss(packet)
|
10
10
|
nbss = RubySMB::Nbss::SessionHeader.new
|
11
11
|
nbss.session_packet_type = RubySMB::Nbss::SESSION_MESSAGE
|
12
|
-
nbss.
|
12
|
+
nbss.stream_protocol_length = packet.do_num_bytes
|
13
13
|
nbss.to_binary_s
|
14
14
|
end
|
15
15
|
|
@@ -74,7 +74,7 @@ module RubySMB
|
|
74
74
|
raise ::RubySMB::Error::NetBiosSessionService, 'NBSS Header is missing'
|
75
75
|
end
|
76
76
|
|
77
|
-
length = nbss_header.
|
77
|
+
length = nbss_header.stream_protocol_length
|
78
78
|
data = full_response ? nbss_header.to_binary_s : ''
|
79
79
|
if length > 0
|
80
80
|
if IO.select([@tcp_socket], nil, nil, @read_timeout).nil?
|
@@ -25,12 +25,17 @@ module RubySMB
|
|
25
25
|
# @see BinData::Stringz
|
26
26
|
def read_and_return_value(io)
|
27
27
|
max_length = eval_parameter(:max_length)
|
28
|
+
if max_length && max_length % 2 != 0
|
29
|
+
raise ArgumentError, "[Stringz16] #max_length should be a multiple of "\
|
30
|
+
"two, since it is Unicode (got #{max_length})"
|
31
|
+
end
|
28
32
|
str = ''
|
29
33
|
i = 0
|
30
34
|
ch = nil
|
31
35
|
|
32
36
|
# read until double NULL-byte or we have read in the max number of bytes
|
33
|
-
|
37
|
+
loop do
|
38
|
+
break if ch == "\0\0" || (max_length && i == max_length)
|
34
39
|
ch = io.readbytes(2)
|
35
40
|
str << ch
|
36
41
|
i += 2
|
@@ -46,6 +51,17 @@ module RubySMB
|
|
46
51
|
def truncate_after_first_zero_byte!(str)
|
47
52
|
str.sub!(/([^\0]*\0\0\0).*/, '\1')
|
48
53
|
end
|
54
|
+
|
55
|
+
def trim_to!(str, max_length = nil)
|
56
|
+
if max_length
|
57
|
+
max_length = 2 if max_length < 2
|
58
|
+
str.slice!(max_length..-1)
|
59
|
+
if str.length == max_length && str[-2, 2] != "\0\0"
|
60
|
+
str[-2, 2] = "\0\0"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
49
65
|
end
|
50
66
|
end
|
51
67
|
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
module RubySMB
|
2
2
|
module Nbss
|
3
3
|
# Representation of the NetBIOS Session Service Header as defined in
|
4
|
-
# [
|
4
|
+
# SMB: [2.1 Transport](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb/f906c680-330c-43ae-9a71-f854e24aeee6)
|
5
|
+
# SMB2: [2.1 Transport](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/1dfacde4-b5c7-4494-8a14-a09d3ab4cc83)
|
5
6
|
class SessionHeader < BinData::Record
|
6
7
|
endian :big
|
7
8
|
|
8
|
-
uint8
|
9
|
-
|
10
|
-
bit17 :packet_length, label: 'Packet Length'
|
9
|
+
uint8 :session_packet_type, label: 'Session Packet Type', initial_value: 0
|
10
|
+
uint24 :stream_protocol_length, label: 'Stream Protocol Length'
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
data/lib/ruby_smb/smb1/file.rb
CHANGED
@@ -106,11 +106,7 @@ module RubySMB
|
|
106
106
|
# @raise [RubySMB::Error::InvalidPacket] if the response packet is not valid
|
107
107
|
# @raise [RubySMB::Error::UnexpectedStatusCode] if the response NTStatus is not STATUS_SUCCESS
|
108
108
|
def read(bytes: @size, offset: 0)
|
109
|
-
atomic_read_size =
|
110
|
-
@tree.client.max_buffer_size
|
111
|
-
else
|
112
|
-
bytes
|
113
|
-
end
|
109
|
+
atomic_read_size = [bytes, @tree.client.max_buffer_size].min
|
114
110
|
remaining_bytes = bytes
|
115
111
|
data = ''
|
116
112
|
|
@@ -227,11 +223,7 @@ module RubySMB
|
|
227
223
|
total_bytes_written = 0
|
228
224
|
|
229
225
|
loop do
|
230
|
-
atomic_write_size =
|
231
|
-
@tree.client.max_buffer_size
|
232
|
-
else
|
233
|
-
bytes
|
234
|
-
end
|
226
|
+
atomic_write_size = [bytes, @tree.client.max_buffer_size].min
|
235
227
|
write_request = write_packet(data: buffer.slice!(0, atomic_write_size), offset: offset)
|
236
228
|
raw_response = @tree.client.send_recv(write_request)
|
237
229
|
response = RubySMB::SMB1::Packet::WriteAndxResponse.read(raw_response)
|
data/lib/ruby_smb/smb1/pipe.rb
CHANGED
data/lib/ruby_smb/smb2/file.rb
CHANGED
@@ -115,13 +115,15 @@ module RubySMB
|
|
115
115
|
# @raise [RubySMB::Error::InvalidPacket] if the response is not a ReadResponse packet
|
116
116
|
# @raise [RubySMB::Error::UnexpectedStatusCode] if the response NTStatus is not STATUS_SUCCESS
|
117
117
|
def read(bytes: size, offset: 0)
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
118
|
+
max_read = tree.client.server_max_read_size
|
119
|
+
max_read = 65536 unless tree.client.server_supports_multi_credit
|
120
|
+
atomic_read_size = [bytes, max_read].min
|
121
|
+
credit_charge = 0
|
122
|
+
if tree.client.server_supports_multi_credit
|
123
|
+
credit_charge = (atomic_read_size - 1) / 65536 + 1
|
124
|
+
end
|
123
125
|
|
124
|
-
read_request = read_packet(read_length: atomic_read_size, offset: offset)
|
126
|
+
read_request = read_packet(read_length: atomic_read_size, offset: offset, credit_charge: credit_charge)
|
125
127
|
raw_response = tree.client.send_recv(read_request, encrypt: @tree_connect_encrypt_data)
|
126
128
|
response = RubySMB::SMB2::Packet::ReadResponse.read(raw_response)
|
127
129
|
unless response.valid?
|
@@ -142,9 +144,9 @@ module RubySMB
|
|
142
144
|
|
143
145
|
while remaining_bytes > 0
|
144
146
|
offset += atomic_read_size
|
145
|
-
atomic_read_size = remaining_bytes if remaining_bytes <
|
147
|
+
atomic_read_size = remaining_bytes if remaining_bytes < max_read
|
146
148
|
|
147
|
-
read_request = read_packet(read_length: atomic_read_size, offset: offset)
|
149
|
+
read_request = read_packet(read_length: atomic_read_size, offset: offset, credit_charge: credit_charge)
|
148
150
|
raw_response = tree.client.send_recv(read_request, encrypt: @tree_connect_encrypt_data)
|
149
151
|
response = RubySMB::SMB2::Packet::ReadResponse.read(raw_response)
|
150
152
|
unless response.valid?
|
@@ -169,11 +171,13 @@ module RubySMB
|
|
169
171
|
#
|
170
172
|
# @param bytes [Integer] the number of bytes to read
|
171
173
|
# @param offset [Integer] the byte offset in the file to start reading from
|
174
|
+
# @param credit_charge [Integer] the number of credits that this request consumes
|
172
175
|
# @return [RubySMB::SMB2::Packet::ReadRequest] the data read from the file
|
173
|
-
def read_packet(read_length: 0, offset: 0)
|
176
|
+
def read_packet(read_length: 0, offset: 0, credit_charge: 1)
|
174
177
|
read_request = set_header_fields(RubySMB::SMB2::Packet::ReadRequest.new)
|
175
178
|
read_request.read_length = read_length
|
176
179
|
read_request.offset = offset
|
180
|
+
read_request.smb2_header.credit_charge = credit_charge
|
177
181
|
read_request
|
178
182
|
end
|
179
183
|
|
@@ -240,16 +244,18 @@ module RubySMB
|
|
240
244
|
# @return [WindowsError::ErrorCode] the NTStatus code returned from the operation
|
241
245
|
# @raise [RubySMB::Error::InvalidPacket] if the response is not a WriteResponse packet
|
242
246
|
def write(data:'', offset: 0)
|
247
|
+
max_write = tree.client.server_max_write_size
|
248
|
+
max_write = 65536 unless tree.client.server_supports_multi_credit
|
243
249
|
buffer = data.dup
|
244
250
|
bytes = data.length
|
245
|
-
atomic_write_size =
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
251
|
+
atomic_write_size = [bytes, max_write].min
|
252
|
+
credit_charge = 0
|
253
|
+
if tree.client.server_supports_multi_credit
|
254
|
+
credit_charge = (atomic_write_size - 1) / 65536 + 1
|
255
|
+
end
|
250
256
|
|
251
257
|
while buffer.length > 0 do
|
252
|
-
write_request = write_packet(data: buffer.slice!(0,atomic_write_size), offset: offset)
|
258
|
+
write_request = write_packet(data: buffer.slice!(0, atomic_write_size), offset: offset, credit_charge: credit_charge)
|
253
259
|
raw_response = tree.client.send_recv(write_request, encrypt: @tree_connect_encrypt_data)
|
254
260
|
response = RubySMB::SMB2::Packet::WriteResponse.read(raw_response)
|
255
261
|
unless response.valid?
|
@@ -262,7 +268,7 @@ module RubySMB
|
|
262
268
|
end
|
263
269
|
status = response.smb2_header.nt_status.to_nt_status
|
264
270
|
|
265
|
-
offset+= atomic_write_size
|
271
|
+
offset += atomic_write_size
|
266
272
|
return status unless status == WindowsError::NTStatus::STATUS_SUCCESS
|
267
273
|
end
|
268
274
|
|
@@ -273,11 +279,13 @@ module RubySMB
|
|
273
279
|
#
|
274
280
|
# @param data [String] the data to write to the file
|
275
281
|
# @param offset [Integer] the offset in the file to start writing from
|
282
|
+
# @param credit_charge [Integer] the number of credits that this request consumes
|
276
283
|
# @return []RubySMB::SMB2::Packet::WriteRequest] the request packet
|
277
|
-
def write_packet(data:'', offset: 0)
|
284
|
+
def write_packet(data:'', offset: 0, credit_charge: 1)
|
278
285
|
write_request = set_header_fields(RubySMB::SMB2::Packet::WriteRequest.new)
|
279
286
|
write_request.write_offset = offset
|
280
287
|
write_request.buffer = data
|
288
|
+
write_request.smb2_header.credit_charge = credit_charge
|
281
289
|
write_request
|
282
290
|
end
|
283
291
|
|
data/lib/ruby_smb/smb2/pipe.rb
CHANGED
@@ -17,6 +17,8 @@ module RubySMB
|
|
17
17
|
extend RubySMB::Dcerpc::Srvsvc
|
18
18
|
when 'winreg'
|
19
19
|
extend RubySMB::Dcerpc::Winreg
|
20
|
+
when 'svcctl'
|
21
|
+
extend RubySMB::Dcerpc::Svcctl
|
20
22
|
end
|
21
23
|
super(tree: tree, response: response, name: name)
|
22
24
|
end
|
@@ -89,6 +91,7 @@ module RubySMB
|
|
89
91
|
request = set_header_fields(RubySMB::SMB2::Packet::IoctlRequest.new(options))
|
90
92
|
request.ctl_code = 0x0011C017
|
91
93
|
request.flags.is_fsctl = 0x00000001
|
94
|
+
# TODO: handle fragmentation when the request size > MAX_XMIT_FRAG
|
92
95
|
request.buffer = action.to_binary_s
|
93
96
|
|
94
97
|
ioctl_raw_response = @tree.client.send_recv(request)
|
data/lib/ruby_smb/smb2/tree.rb
CHANGED
@@ -147,12 +147,19 @@ module RubySMB
|
|
147
147
|
directory_request.file_information_class = type::CLASS_LEVEL
|
148
148
|
directory_request.file_id = file_id
|
149
149
|
directory_request.name = pattern
|
150
|
-
|
150
|
+
|
151
|
+
max_read = client.server_max_read_size
|
152
|
+
max_read = 65536 unless client.server_supports_multi_credit
|
153
|
+
credit_charge = 0
|
154
|
+
if client.server_supports_multi_credit
|
155
|
+
credit_charge = (max_read - 1) / 65536 + 1
|
156
|
+
end
|
157
|
+
directory_request.output_length = max_read
|
158
|
+
directory_request.smb2_header.credit_charge = credit_charge
|
151
159
|
|
152
160
|
directory_request = set_header_fields(directory_request)
|
153
161
|
|
154
162
|
files = []
|
155
|
-
|
156
163
|
loop do
|
157
164
|
response = client.send_recv(directory_request, encrypt: @tree_connect_encrypt_data)
|
158
165
|
directory_response = RubySMB::SMB2::Packet::QueryDirectoryResponse.read(response)
|
@@ -256,7 +263,6 @@ module RubySMB
|
|
256
263
|
# @return [RubySMB::SMB2::Packet] the modified packet.
|
257
264
|
def set_header_fields(request)
|
258
265
|
request.smb2_header.tree_id = id
|
259
|
-
request.smb2_header.credit_charge = 1
|
260
266
|
request.smb2_header.credits = 256
|
261
267
|
request
|
262
268
|
end
|
data/lib/ruby_smb/version.rb
CHANGED
@@ -65,6 +65,8 @@ RSpec.describe RubySMB::Client do
|
|
65
65
|
it { is_expected.to respond_to :verify_signature }
|
66
66
|
it { is_expected.to respond_to :auth_user }
|
67
67
|
it { is_expected.to respond_to :last_file_id }
|
68
|
+
it { is_expected.to respond_to :pid }
|
69
|
+
it { is_expected.to respond_to :server_supports_multi_credit }
|
68
70
|
|
69
71
|
describe '#initialize' do
|
70
72
|
it 'should raise an ArgumentError without a valid dispatcher' do
|
@@ -141,6 +143,18 @@ RSpec.describe RubySMB::Client do
|
|
141
143
|
it 'sets the max_buffer_size to MAX_BUFFER_SIZE' do
|
142
144
|
expect(client.max_buffer_size).to eq RubySMB::Client::MAX_BUFFER_SIZE
|
143
145
|
end
|
146
|
+
|
147
|
+
it 'sets the server_supports_multi_credit to false' do
|
148
|
+
expect(client.server_supports_multi_credit).to be false
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'sets the pid to a random value' do
|
152
|
+
100.times do
|
153
|
+
previous_pid = client.pid
|
154
|
+
client = described_class.new(dispatcher, username: username, password: password)
|
155
|
+
expect(client.pid).to_not eq(previous_pid)
|
156
|
+
end
|
157
|
+
end
|
144
158
|
end
|
145
159
|
|
146
160
|
describe '#echo' do
|
@@ -184,11 +198,13 @@ RSpec.describe RubySMB::Client do
|
|
184
198
|
describe '#send_recv' do
|
185
199
|
let(:smb1_request) { RubySMB::SMB1::Packet::TreeConnectRequest.new }
|
186
200
|
let(:smb2_request) { RubySMB::SMB2::Packet::TreeConnectRequest.new }
|
201
|
+
let(:smb2_header) { RubySMB::SMB2::SMB2Header.new }
|
187
202
|
|
188
203
|
before(:each) do
|
189
204
|
allow(client).to receive(:is_status_pending?).and_return(false)
|
190
205
|
allow(dispatcher).to receive(:send_packet).and_return(nil)
|
191
206
|
allow(dispatcher).to receive(:recv_packet).and_return('A')
|
207
|
+
allow(RubySMB::SMB2::SMB2Header).to receive(:read).and_return(smb2_header)
|
192
208
|
end
|
193
209
|
|
194
210
|
context 'when signing' do
|
@@ -199,9 +215,10 @@ RSpec.describe RubySMB::Client do
|
|
199
215
|
|
200
216
|
context 'with an SMB2 packet' do
|
201
217
|
it 'does not sign a SessionSetupRequest packet' do
|
218
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(false)
|
202
219
|
expect(smb2_client).to_not receive(:smb2_sign)
|
203
220
|
expect(smb2_client).to_not receive(:smb3_sign)
|
204
|
-
|
221
|
+
smb2_client.send_recv(RubySMB::SMB2::Packet::SessionSetupRequest.new)
|
205
222
|
end
|
206
223
|
|
207
224
|
it 'calls #smb2_sign if it is an SMB2 client' do
|
@@ -229,6 +246,29 @@ RSpec.describe RubySMB::Client do
|
|
229
246
|
expect(smb1_client).to_not receive(:is_status_pending?)
|
230
247
|
smb1_client.send_recv(smb1_request)
|
231
248
|
end
|
249
|
+
|
250
|
+
it 'set the #uid SMB header when #user_id is defined' do
|
251
|
+
smb1_client.user_id = 333
|
252
|
+
smb1_client.send_recv(smb1_request)
|
253
|
+
expect(smb1_request.smb_header.uid).to eq(333)
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'does not set the #uid SMB header when #user_id is not defined' do
|
257
|
+
smb1_client.send_recv(smb1_request)
|
258
|
+
expect(smb1_request.smb_header.uid).to eq(0)
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'set the #pid SMB header when #pid is defined' do
|
262
|
+
smb1_client.pid = 333
|
263
|
+
smb1_client.send_recv(smb1_request)
|
264
|
+
expect(smb1_request.smb_header.pid_low).to eq(333)
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'does not set the #pid SMB header when #pid is not defined' do
|
268
|
+
smb1_client.pid = nil
|
269
|
+
smb1_client.send_recv(smb1_request)
|
270
|
+
expect(smb1_request.smb_header.pid_low).to eq(0)
|
271
|
+
end
|
232
272
|
end
|
233
273
|
|
234
274
|
context 'with SMB2' do
|
@@ -251,10 +291,8 @@ RSpec.describe RubySMB::Client do
|
|
251
291
|
context 'with a SessionSetupRequest' do
|
252
292
|
it 'does not encrypt/decrypt' do
|
253
293
|
request = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
254
|
-
expect(smb3_client).
|
255
|
-
expect(smb3_client).
|
256
|
-
expect(dispatcher).to receive(:send_packet).with(request)
|
257
|
-
expect(dispatcher).to receive(:recv_packet)
|
294
|
+
expect(smb3_client).to receive(:send_packet).with(request, encrypt: false)
|
295
|
+
expect(smb3_client).to receive(:recv_packet).with(encrypt: false)
|
258
296
|
smb3_client.send_recv(request)
|
259
297
|
end
|
260
298
|
end
|
@@ -262,17 +300,15 @@ RSpec.describe RubySMB::Client do
|
|
262
300
|
context 'with a NegotiateRequest' do
|
263
301
|
it 'does not encrypt/decrypt' do
|
264
302
|
request = RubySMB::SMB2::Packet::NegotiateRequest.new
|
265
|
-
expect(smb3_client).
|
266
|
-
expect(smb3_client).
|
267
|
-
expect(dispatcher).to receive(:send_packet).with(request)
|
268
|
-
expect(dispatcher).to receive(:recv_packet)
|
303
|
+
expect(smb3_client).to receive(:send_packet).with(request, encrypt: false)
|
304
|
+
expect(smb3_client).to receive(:recv_packet).with(encrypt: false)
|
269
305
|
smb3_client.send_recv(request)
|
270
306
|
end
|
271
307
|
end
|
272
308
|
|
273
309
|
it 'encrypts and decrypts' do
|
274
|
-
expect(smb3_client).to receive(:
|
275
|
-
expect(smb3_client).to receive(:
|
310
|
+
expect(smb3_client).to receive(:send_packet).with(smb2_request, encrypt: true)
|
311
|
+
expect(smb3_client).to receive(:recv_packet).with(encrypt: true)
|
276
312
|
smb3_client.send_recv(smb2_request)
|
277
313
|
end
|
278
314
|
|
@@ -280,12 +316,39 @@ RSpec.describe RubySMB::Client do
|
|
280
316
|
it 'waits 1 second and reads/decrypts again' do
|
281
317
|
allow(smb3_client).to receive(:is_status_pending?).and_return(true, false)
|
282
318
|
expect(smb3_client).to receive(:sleep).with(1)
|
283
|
-
expect(smb3_client).to receive(:
|
284
|
-
expect(smb3_client).to receive(:
|
319
|
+
expect(smb3_client).to receive(:send_packet).with(smb2_request, encrypt: true)
|
320
|
+
expect(smb3_client).to receive(:recv_packet).with(encrypt: true).twice
|
285
321
|
smb3_client.send_recv(smb2_request)
|
286
322
|
end
|
287
323
|
end
|
288
324
|
end
|
325
|
+
|
326
|
+
it 'increments the sequence counter if signing is required and the session key exist' do
|
327
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(false)
|
328
|
+
smb2_client.signing_required = true
|
329
|
+
smb2_client.session_key = 'key'
|
330
|
+
smb2_client.sequence_counter = 0
|
331
|
+
smb2_client.send_recv(smb2_request)
|
332
|
+
expect(smb2_client.sequence_counter).to eq(1)
|
333
|
+
end
|
334
|
+
|
335
|
+
it 'updates #msb2_message_id with SMB2 header #credit_charge if the dialect is not 0x0202' do
|
336
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(false)
|
337
|
+
smb2_client.smb2_message_id = 0
|
338
|
+
smb2_client.dialect = '0x0210'
|
339
|
+
smb2_header.credit_charge = 5
|
340
|
+
smb2_client.send_recv(smb2_request)
|
341
|
+
expect(smb2_client.smb2_message_id).to eq(5)
|
342
|
+
end
|
343
|
+
|
344
|
+
it 'does not update #msb2_message_id with SMB2 header #credit_charge if the dialect is 0x0202' do
|
345
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(false)
|
346
|
+
smb2_client.smb2_message_id = 0
|
347
|
+
smb2_client.dialect = '0x0202'
|
348
|
+
smb2_header.credit_charge = 5
|
349
|
+
smb2_client.send_recv(smb2_request)
|
350
|
+
expect(smb2_client.smb2_message_id).to eq(1)
|
351
|
+
end
|
289
352
|
end
|
290
353
|
|
291
354
|
describe '#is_status_pending?' do
|
@@ -297,17 +360,17 @@ RSpec.describe RubySMB::Client do
|
|
297
360
|
}
|
298
361
|
|
299
362
|
it 'returns true when the response has a STATUS_PENDING status code and the async_command flag set' do
|
300
|
-
expect(client.is_status_pending?(response.
|
363
|
+
expect(client.is_status_pending?(response.smb2_header)).to be true
|
301
364
|
end
|
302
365
|
|
303
366
|
it 'returns false when the response has a STATUS_PENDING status code and the async_command flag not set' do
|
304
367
|
response.smb2_header.flags.async_command = 0
|
305
|
-
expect(client.is_status_pending?(response.
|
368
|
+
expect(client.is_status_pending?(response.smb2_header)).to be false
|
306
369
|
end
|
307
370
|
|
308
371
|
it 'returns false when the response has no STATUS_PENDING status code but the async_command flag set' do
|
309
372
|
response.smb2_header.nt_status= WindowsError::NTStatus::STATUS_SUCCESS.value
|
310
|
-
expect(client.is_status_pending?(response.
|
373
|
+
expect(client.is_status_pending?(response.smb2_header)).to be false
|
311
374
|
end
|
312
375
|
end
|
313
376
|
|
@@ -344,35 +407,47 @@ RSpec.describe RubySMB::Client do
|
|
344
407
|
end
|
345
408
|
end
|
346
409
|
|
347
|
-
describe '#
|
410
|
+
describe '#send_packet' do
|
348
411
|
let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
|
349
412
|
before :example do
|
350
413
|
allow(dispatcher).to receive(:send_packet)
|
351
414
|
client.dialect = '0x0300'
|
352
415
|
end
|
353
416
|
|
354
|
-
it '
|
355
|
-
expect(client).
|
356
|
-
client.
|
417
|
+
it 'does not encrypt the packet' do
|
418
|
+
expect(client).to_not receive(:smb3_encrypt)
|
419
|
+
client.send_packet(packet)
|
357
420
|
end
|
358
421
|
|
359
|
-
it '
|
360
|
-
|
361
|
-
expect
|
362
|
-
RubySMB::Error::EncryptionError,
|
363
|
-
"Error while encrypting #{packet.class.name} packet (SMB 0x0300): Error"
|
364
|
-
)
|
422
|
+
it 'sends the packet through the dispatcher' do
|
423
|
+
client.send_packet(packet)
|
424
|
+
expect(dispatcher).to have_received(:send_packet).with(packet)
|
365
425
|
end
|
366
426
|
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
427
|
+
context 'with encryption' do
|
428
|
+
it 'creates a Transform request' do
|
429
|
+
expect(client).to receive(:smb3_encrypt).with(packet.to_binary_s)
|
430
|
+
client.send_packet(packet, encrypt: true)
|
431
|
+
end
|
432
|
+
|
433
|
+
it 'raises an EncryptionError exception if an error occurs while encrypting' do
|
434
|
+
allow(client).to receive(:smb3_encrypt).and_raise(RubySMB::Error::RubySMBError.new('Error'))
|
435
|
+
expect { client.send_packet(packet, encrypt: true) }.to raise_error(
|
436
|
+
RubySMB::Error::EncryptionError,
|
437
|
+
"Error while encrypting #{packet.class.name} packet (SMB 0x0300): Error"
|
438
|
+
)
|
439
|
+
end
|
440
|
+
|
441
|
+
it 'sends the encrypted packet' do
|
442
|
+
encrypted_packet = double('Encrypted packet')
|
443
|
+
allow(client).to receive(:smb3_encrypt).and_return(encrypted_packet)
|
444
|
+
client.send_packet(packet, encrypt: true)
|
445
|
+
expect(dispatcher).to have_received(:send_packet).with(encrypted_packet)
|
446
|
+
end
|
372
447
|
end
|
373
448
|
end
|
374
449
|
|
375
|
-
describe '#
|
450
|
+
describe '#recv_packet' do
|
376
451
|
let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
|
377
452
|
before :example do
|
378
453
|
allow(dispatcher).to receive(:recv_packet).and_return(packet.to_binary_s)
|
@@ -381,42 +456,49 @@ RSpec.describe RubySMB::Client do
|
|
381
456
|
end
|
382
457
|
|
383
458
|
it 'reads the response packet' do
|
384
|
-
client.
|
459
|
+
client.recv_packet
|
385
460
|
expect(dispatcher).to have_received(:recv_packet)
|
386
461
|
end
|
387
462
|
|
388
|
-
it 'raises an
|
463
|
+
it 'raises an CommunicationError exception if an error occurs while receiving the response' do
|
389
464
|
allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
|
390
|
-
expect { client.
|
391
|
-
RubySMB::Error::EncryptionError,
|
392
|
-
'Communication error with the remote host: RubySMB::Error::CommunicationError. '\
|
393
|
-
'The server supports encryption but was not able to handle the encrypted request.'
|
394
|
-
)
|
465
|
+
expect { client.recv_packet }.to raise_error(RubySMB::Error::CommunicationError)
|
395
466
|
end
|
396
467
|
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
468
|
+
context 'with encryption' do
|
469
|
+
it 'raises an EncryptionError exception if an error occurs while receiving the response' do
|
470
|
+
allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
|
471
|
+
expect { client.recv_packet(encrypt: true) }.to raise_error(
|
472
|
+
RubySMB::Error::EncryptionError,
|
473
|
+
'Communication error with the remote host: RubySMB::Error::CommunicationError. '\
|
474
|
+
'The server supports encryption but was not able to handle the encrypted request.'
|
475
|
+
)
|
476
|
+
end
|
401
477
|
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
478
|
+
it 'parses the response as a Transform response packet' do
|
479
|
+
expect(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).with(packet.to_binary_s)
|
480
|
+
client.recv_packet(encrypt: true)
|
481
|
+
end
|
406
482
|
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
expect(client).to have_received(:smb3_decrypt).with(transform)
|
412
|
-
end
|
483
|
+
it 'raises an InvalidPacket exception if an error occurs while parsing the response' do
|
484
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_raise(IOError)
|
485
|
+
expect { client.recv_packet(encrypt: true) }.to raise_error(RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet')
|
486
|
+
end
|
413
487
|
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
488
|
+
it 'decrypts the Transform response packet' do
|
489
|
+
transform = double('Transform header packet')
|
490
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_return(transform)
|
491
|
+
client.recv_packet(encrypt: true)
|
492
|
+
expect(client).to have_received(:smb3_decrypt).with(transform)
|
493
|
+
end
|
494
|
+
|
495
|
+
it 'raises an EncryptionError exception if an error occurs while decrypting' do
|
496
|
+
allow(client).to receive(:smb3_decrypt).and_raise(RubySMB::Error::RubySMBError)
|
497
|
+
expect { client.recv_packet(encrypt: true) }.to raise_error(
|
498
|
+
RubySMB::Error::EncryptionError,
|
499
|
+
'Error while decrypting RubySMB::SMB2::Packet::TransformHeader packet (SMB 0x0300}): RubySMB::Error::RubySMBError'
|
500
|
+
)
|
501
|
+
end
|
420
502
|
end
|
421
503
|
end
|
422
504
|
|
@@ -667,7 +749,7 @@ RSpec.describe RubySMB::Client do
|
|
667
749
|
expect(session_packet.session_header.session_packet_type).to eq RubySMB::Nbss::SESSION_REQUEST
|
668
750
|
expect(session_packet.called_name).to eq called_name
|
669
751
|
expect(session_packet.calling_name).to eq calling_name
|
670
|
-
expect(session_packet.session_header.
|
752
|
+
expect(session_packet.session_header.stream_protocol_length).to eq(
|
671
753
|
session_packet.called_name.to_binary_s.size + session_packet.calling_name.to_binary_s.size
|
672
754
|
)
|
673
755
|
end
|
@@ -1036,6 +1118,19 @@ RSpec.describe RubySMB::Client do
|
|
1036
1118
|
it 'returns the string \'SMB2\'' do
|
1037
1119
|
expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
|
1038
1120
|
end
|
1121
|
+
|
1122
|
+
it 'sets #server_supports_multi_credit to true when the response has #large_mtu capability set' do
|
1123
|
+
smb2_response.capabilities.large_mtu = 1
|
1124
|
+
client.parse_negotiate_response(smb2_response)
|
1125
|
+
expect(client.server_supports_multi_credit).to be true
|
1126
|
+
end
|
1127
|
+
|
1128
|
+
it 'sets #server_supports_multi_credit to false when the dialect is 0x0202' do
|
1129
|
+
smb2_response.dialect_revision = 0x0202
|
1130
|
+
smb2_response.capabilities.large_mtu = 1 # just to make sure it won't affect the result
|
1131
|
+
client.parse_negotiate_response(smb2_response)
|
1132
|
+
expect(client.server_supports_multi_credit).to be false
|
1133
|
+
end
|
1039
1134
|
end
|
1040
1135
|
|
1041
1136
|
context 'when SMB3 was negotiated' do
|
@@ -1060,6 +1155,12 @@ RSpec.describe RubySMB::Client do
|
|
1060
1155
|
expect(client.parse_negotiate_response(smb3_response)).to eq ('SMB3')
|
1061
1156
|
end
|
1062
1157
|
|
1158
|
+
it 'sets #server_supports_multi_credit to true when the response has #large_mtu capability set' do
|
1159
|
+
smb3_response.capabilities.large_mtu = 1
|
1160
|
+
client.parse_negotiate_response(smb3_response)
|
1161
|
+
expect(client.server_supports_multi_credit).to be true
|
1162
|
+
end
|
1163
|
+
|
1063
1164
|
context 'when the server supports encryption' do
|
1064
1165
|
before :example do
|
1065
1166
|
smb3_response.capabilities.encryption = 1
|