ruby_smb 2.0.2 → 2.0.3
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.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
|