ruby_smb 1.0.3 → 2.0.1
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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.travis.yml +3 -2
- data/Gemfile +6 -2
- data/README.md +35 -47
- data/examples/enum_registry_key.rb +28 -0
- data/examples/enum_registry_values.rb +30 -0
- data/examples/negotiate.rb +51 -8
- data/examples/pipes.rb +2 -1
- data/examples/read_file_encryption.rb +56 -0
- data/examples/read_registry_key_value.rb +32 -0
- data/lib/ruby_smb.rb +4 -1
- data/lib/ruby_smb/client.rb +233 -22
- data/lib/ruby_smb/client/authentication.rb +70 -33
- data/lib/ruby_smb/client/echo.rb +20 -2
- data/lib/ruby_smb/client/encryption.rb +62 -0
- data/lib/ruby_smb/client/negotiation.rb +172 -24
- data/lib/ruby_smb/client/signing.rb +19 -0
- data/lib/ruby_smb/client/tree_connect.rb +24 -18
- data/lib/ruby_smb/client/utils.rb +8 -7
- data/lib/ruby_smb/client/winreg.rb +46 -0
- data/lib/ruby_smb/crypto.rb +30 -0
- data/lib/ruby_smb/dcerpc.rb +38 -0
- data/lib/ruby_smb/dcerpc/bind.rb +2 -2
- data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
- data/lib/ruby_smb/dcerpc/error.rb +3 -0
- data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
- data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
- data/lib/ruby_smb/dcerpc/request.rb +28 -9
- data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
- data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
- data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
- data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
- data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
- data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
- data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
- data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
- data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
- data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
- data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
- data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
- data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
- data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
- data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
- data/lib/ruby_smb/dispatcher/socket.rb +4 -3
- data/lib/ruby_smb/error.rb +68 -2
- data/lib/ruby_smb/generic_packet.rb +33 -4
- data/lib/ruby_smb/smb1/commands.rb +1 -1
- data/lib/ruby_smb/smb1/file.rb +66 -15
- data/lib/ruby_smb/smb1/packet/close_request.rb +2 -5
- data/lib/ruby_smb/smb1/packet/close_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/echo_request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/echo_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/empty_packet.rb +10 -1
- data/lib/ruby_smb/smb1/packet/logoff_request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/logoff_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/negotiate_request.rb +2 -5
- data/lib/ruby_smb/smb1/packet/negotiate_response.rb +3 -7
- data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +4 -4
- data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/nt_create_andx_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/nt_trans/create_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/nt_trans/request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/nt_trans/response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/read_andx_request.rb +2 -5
- data/lib/ruby_smb/smb1/packet/read_andx_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +3 -2
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +2 -5
- data/lib/ruby_smb/smb1/packet/session_setup_response.rb +3 -2
- data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_request.rb +0 -1
- data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_response.rb +3 -2
- data/lib/ruby_smb/smb1/packet/trans/request.rb +2 -5
- data/lib/ruby_smb/smb1/packet/trans/response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_response.rb +1 -1
- data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +8 -2
- data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +8 -2
- data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +2 -4
- data/lib/ruby_smb/smb1/packet/trans2/response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/tree_connect_response.rb +13 -3
- data/lib/ruby_smb/smb1/packet/tree_disconnect_request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/tree_disconnect_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/write_andx_request.rb +3 -6
- data/lib/ruby_smb/smb1/packet/write_andx_response.rb +2 -1
- data/lib/ruby_smb/smb1/pipe.rb +87 -6
- data/lib/ruby_smb/smb1/tree.rb +50 -3
- data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
- data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
- data/lib/ruby_smb/smb2/file.rb +103 -25
- data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
- data/lib/ruby_smb/smb2/packet.rb +2 -0
- data/lib/ruby_smb/smb2/packet/close_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/close_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
- data/lib/ruby_smb/smb2/packet/create_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/create_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/echo_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/echo_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/error_packet.rb +15 -3
- data/lib/ruby_smb/smb2/packet/ioctl_request.rb +2 -5
- data/lib/ruby_smb/smb2/packet/ioctl_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/logoff_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/logoff_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -17
- data/lib/ruby_smb/smb2/packet/negotiate_response.rb +52 -5
- data/lib/ruby_smb/smb2/packet/query_directory_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/query_directory_response.rb +8 -2
- data/lib/ruby_smb/smb2/packet/read_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/read_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/session_setup_request.rb +2 -5
- data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/set_info_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/set_info_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
- data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +93 -10
- data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +10 -22
- data/lib/ruby_smb/smb2/packet/tree_disconnect_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/tree_disconnect_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/write_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/write_response.rb +2 -1
- data/lib/ruby_smb/smb2/pipe.rb +86 -12
- data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
- data/lib/ruby_smb/smb2/tree.rb +65 -21
- data/lib/ruby_smb/version.rb +1 -1
- data/ruby_smb.gemspec +5 -3
- data/spec/lib/ruby_smb/client_spec.rb +1612 -108
- data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
- data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
- data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
- data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
- data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
- data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
- data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
- data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
- data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +2 -2
- data/spec/lib/ruby_smb/error_spec.rb +59 -0
- data/spec/lib/ruby_smb/generic_packet_spec.rb +52 -4
- data/spec/lib/ruby_smb/smb1/file_spec.rb +191 -2
- data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +68 -0
- data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +11 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +11 -2
- data/spec/lib/ruby_smb/smb1/packet/tree_connect_response_spec.rb +40 -0
- data/spec/lib/ruby_smb/smb1/pipe_spec.rb +272 -149
- data/spec/lib/ruby_smb/smb1/tree_spec.rb +44 -7
- data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
- data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
- data/spec/lib/ruby_smb/smb2/file_spec.rb +323 -6
- data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
- data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
- data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +78 -0
- data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
- data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
- data/spec/lib/ruby_smb/smb2/packet/query_directory_response_spec.rb +8 -0
- data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
- data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
- data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -22
- data/spec/lib/ruby_smb/smb2/pipe_spec.rb +286 -149
- data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +261 -2
- metadata +191 -83
- metadata.gz.sig +0 -0
- data/lib/ruby_smb/smb1/dcerpc.rb +0 -67
- data/lib/ruby_smb/smb2/dcerpc.rb +0 -70
- data/spec/lib/ruby_smb/smb1/packet/error_packet_spec.rb +0 -37
@@ -4,6 +4,8 @@ module RubySMB
|
|
4
4
|
# An SMB2 Write Request Packet as defined in
|
5
5
|
# [2.2.21 SMB2 WRITE Request](https://msdn.microsoft.com/en-us/library/cc246532.aspx)
|
6
6
|
class WriteRequest < RubySMB::GenericPacket
|
7
|
+
COMMAND = RubySMB::SMB2::Commands::WRITE
|
8
|
+
|
7
9
|
endian :little
|
8
10
|
|
9
11
|
smb2_header :smb2_header
|
@@ -19,10 +21,6 @@ module RubySMB
|
|
19
21
|
uint32 :flags, label: 'Flags'
|
20
22
|
string :buffer, label: 'Write Data Buffer'
|
21
23
|
|
22
|
-
def initialize_instance
|
23
|
-
super
|
24
|
-
smb2_header.command = RubySMB::SMB2::Commands::WRITE
|
25
|
-
end
|
26
24
|
end
|
27
25
|
end
|
28
26
|
end
|
@@ -4,6 +4,8 @@ module RubySMB
|
|
4
4
|
# An SMB2 Write Response Packet as defined in
|
5
5
|
# [2.2.22 SMB2 WRITE Response](https://msdn.microsoft.com/en-us/library/cc246533.aspx)
|
6
6
|
class WriteResponse < RubySMB::GenericPacket
|
7
|
+
COMMAND = RubySMB::SMB2::Commands::WRITE
|
8
|
+
|
7
9
|
endian :little
|
8
10
|
|
9
11
|
smb2_header :smb2_header
|
@@ -17,7 +19,6 @@ module RubySMB
|
|
17
19
|
|
18
20
|
def initialize_instance
|
19
21
|
super
|
20
|
-
smb2_header.command = RubySMB::SMB2::Commands::WRITE
|
21
22
|
smb2_header.flags.reply = 1
|
22
23
|
end
|
23
24
|
end
|
data/lib/ruby_smb/smb2/pipe.rb
CHANGED
@@ -3,18 +3,29 @@ module RubySMB
|
|
3
3
|
# Represents a pipe on the Remote server that we can perform
|
4
4
|
# various I/O operations on.
|
5
5
|
class Pipe < File
|
6
|
-
require 'ruby_smb/
|
6
|
+
require 'ruby_smb/dcerpc'
|
7
7
|
|
8
|
-
include RubySMB::
|
8
|
+
include RubySMB::Dcerpc
|
9
9
|
|
10
10
|
STATUS_CONNECTED = 0x00000003
|
11
11
|
STATUS_CLOSING = 0x00000004
|
12
12
|
|
13
|
+
def initialize(tree:, response:, name:)
|
14
|
+
raise ArgumentError, 'No Name Provided' if name.nil?
|
15
|
+
case name
|
16
|
+
when 'srvsvc'
|
17
|
+
extend RubySMB::Dcerpc::Srvsvc
|
18
|
+
when 'winreg'
|
19
|
+
extend RubySMB::Dcerpc::Winreg
|
20
|
+
end
|
21
|
+
super(tree: tree, response: response, name: name)
|
22
|
+
end
|
23
|
+
|
13
24
|
# Performs a peek operation on the named pipe
|
14
25
|
#
|
15
26
|
# @param peek_size [Integer] Amount of data to peek
|
16
27
|
# @return [RubySMB::SMB2::Packet::IoctlResponse]
|
17
|
-
# @raise [RubySMB::Error::InvalidPacket] if not a valid
|
28
|
+
# @raise [RubySMB::Error::InvalidPacket] if not a valid FIoctlResponse response
|
18
29
|
# @raise [RubySMB::Error::UnexpectedStatusCode] If status is not STATUS_BUFFER_OVERFLOW or STATUS_SUCCESS
|
19
30
|
def peek(peek_size: 0)
|
20
31
|
packet = RubySMB::SMB2::Packet::IoctlRequest.new
|
@@ -24,18 +35,18 @@ module RubySMB
|
|
24
35
|
packet.max_output_response = 16 + peek_size
|
25
36
|
packet = set_header_fields(packet)
|
26
37
|
raw_response = @tree.client.send_recv(packet)
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
38
|
+
response = RubySMB::SMB2::Packet::IoctlResponse.read(raw_response)
|
39
|
+
unless response.valid?
|
40
|
+
raise RubySMB::Error::InvalidPacket.new(
|
41
|
+
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
42
|
+
expected_cmd: RubySMB::SMB2::Packet::IoctlResponse::COMMAND,
|
43
|
+
received_proto: response.smb2_header.protocol,
|
44
|
+
received_cmd: response.smb2_header.command
|
45
|
+
)
|
31
46
|
end
|
32
47
|
|
33
48
|
unless response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW or response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
34
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
35
|
-
end
|
36
|
-
|
37
|
-
unless response.smb2_header.command == RubySMB::SMB2::Commands::IOCTL
|
38
|
-
raise RubySMB::Error::InvalidPacket, 'Not an IoctlResponse packet'
|
49
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
39
50
|
end
|
40
51
|
response
|
41
52
|
end
|
@@ -67,6 +78,69 @@ module RubySMB
|
|
67
78
|
state == STATUS_CONNECTED
|
68
79
|
end
|
69
80
|
|
81
|
+
def dcerpc_request(stub_packet, options={})
|
82
|
+
options.merge!(endpoint: stub_packet.class.name.split('::').at(-2))
|
83
|
+
dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: stub_packet.opnum }, options)
|
84
|
+
dcerpc_request.stub.read(stub_packet.to_binary_s)
|
85
|
+
ioctl_send_recv(dcerpc_request, options)
|
86
|
+
end
|
87
|
+
|
88
|
+
def ioctl_send_recv(action, options={})
|
89
|
+
request = set_header_fields(RubySMB::SMB2::Packet::IoctlRequest.new(options))
|
90
|
+
request.ctl_code = 0x0011C017
|
91
|
+
request.flags.is_fsctl = 0x00000001
|
92
|
+
request.buffer = action.to_binary_s
|
93
|
+
|
94
|
+
ioctl_raw_response = @tree.client.send_recv(request)
|
95
|
+
ioctl_response = RubySMB::SMB2::Packet::IoctlResponse.read(ioctl_raw_response)
|
96
|
+
unless ioctl_response.valid?
|
97
|
+
raise RubySMB::Error::InvalidPacket.new(
|
98
|
+
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
99
|
+
expected_cmd: RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
|
100
|
+
received_proto: ioctl_response.smb2_header.protocol,
|
101
|
+
received_cmd: ioctl_response.smb2_header.command
|
102
|
+
)
|
103
|
+
end
|
104
|
+
unless [WindowsError::NTStatus::STATUS_SUCCESS,
|
105
|
+
WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW].include?(ioctl_response.status_code)
|
106
|
+
raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code
|
107
|
+
end
|
108
|
+
|
109
|
+
raw_data = ioctl_response.output_data
|
110
|
+
if ioctl_response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW
|
111
|
+
raw_data << read(bytes: @tree.client.max_buffer_size - ioctl_response.output_count)
|
112
|
+
dcerpc_response = dcerpc_response_from_raw_response(raw_data)
|
113
|
+
unless dcerpc_response.pdu_header.pfc_flags.first_frag == 1
|
114
|
+
raise RubySMB::Dcerpc::Error::InvalidPacket, "Not the first fragment"
|
115
|
+
end
|
116
|
+
stub_data = dcerpc_response.stub.to_s
|
117
|
+
|
118
|
+
loop do
|
119
|
+
break if dcerpc_response.pdu_header.pfc_flags.last_frag == 1
|
120
|
+
raw_data = read(bytes: @tree.client.max_buffer_size)
|
121
|
+
dcerpc_response = dcerpc_response_from_raw_response(raw_data)
|
122
|
+
stub_data << dcerpc_response.stub.to_s
|
123
|
+
end
|
124
|
+
stub_data
|
125
|
+
else
|
126
|
+
dcerpc_response = dcerpc_response_from_raw_response(raw_data)
|
127
|
+
dcerpc_response.stub.to_s
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def dcerpc_response_from_raw_response(raw_data)
|
135
|
+
dcerpc_response = RubySMB::Dcerpc::Response.read(raw_data)
|
136
|
+
unless dcerpc_response.pdu_header.ptype == RubySMB::Dcerpc::PTypes::RESPONSE
|
137
|
+
raise RubySMB::Dcerpc::Error::InvalidPacket, "Not a Response packet"
|
138
|
+
end
|
139
|
+
dcerpc_response
|
140
|
+
rescue IOError
|
141
|
+
raise RubySMB::Dcerpc::Error::InvalidPacket, "Error reading the DCERPC response"
|
142
|
+
end
|
143
|
+
|
70
144
|
end
|
71
145
|
end
|
72
146
|
end
|
@@ -6,7 +6,7 @@ module RubySMB
|
|
6
6
|
endian :little
|
7
7
|
bit32 :protocol, label: 'Protocol ID Field', initial_value: RubySMB::SMB2::SMB2_PROTOCOL_ID
|
8
8
|
uint16 :structure_size, label: 'Header Structure Size', initial_value: 64
|
9
|
-
uint16 :credit_charge, label: 'Credit Charge', initial_value:
|
9
|
+
uint16 :credit_charge, label: 'Credit Charge', initial_value: 1
|
10
10
|
nt_status :nt_status, label: 'NT Status', initial_value: 0
|
11
11
|
uint16 :command, label: 'Command'
|
12
12
|
uint16 :credits, label: 'Credit Request/Response'
|
data/lib/ruby_smb/smb2/tree.rb
CHANGED
@@ -23,22 +23,37 @@ module RubySMB
|
|
23
23
|
# @return [Integer]
|
24
24
|
attr_accessor :id
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
# Whether or not the share associated with this tree connect needs to be encrypted (SMB 3.x)
|
27
|
+
# @!attribute [rw] tree_connect_encrypt_data
|
28
|
+
# @return [Boolean]
|
29
|
+
attr_accessor :tree_connect_encrypt_data
|
30
|
+
|
31
|
+
def initialize(client:, share:, response:, encrypt: false)
|
32
|
+
@client = client
|
33
|
+
@share = share
|
34
|
+
@id = response.smb2_header.tree_id
|
35
|
+
@permissions = response.maximal_access
|
36
|
+
@share_type = response.share_type
|
37
|
+
@tree_connect_encrypt_data = encrypt
|
32
38
|
end
|
33
39
|
|
34
40
|
# Disconnects this Tree from the current session
|
35
41
|
#
|
36
42
|
# @return [WindowsError::ErrorCode] the NTStatus sent back by the server.
|
43
|
+
# @raise [RubySMB::Error::InvalidPacket] if the response is not a TreeDisconnectResponse packet
|
37
44
|
def disconnect!
|
38
45
|
request = RubySMB::SMB2::Packet::TreeDisconnectRequest.new
|
39
46
|
request = set_header_fields(request)
|
40
|
-
raw_response = client.send_recv(request)
|
47
|
+
raw_response = client.send_recv(request, encrypt: @tree_connect_encrypt_data)
|
41
48
|
response = RubySMB::SMB2::Packet::TreeDisconnectResponse.read(raw_response)
|
49
|
+
unless response.valid?
|
50
|
+
raise RubySMB::Error::InvalidPacket.new(
|
51
|
+
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
52
|
+
expected_cmd: RubySMB::SMB2::Packet::TreeDisconnectResponse::COMMAND,
|
53
|
+
received_proto: response.smb2_header.protocol,
|
54
|
+
received_cmd: response.smb2_header.command
|
55
|
+
)
|
56
|
+
end
|
42
57
|
response.status_code
|
43
58
|
end
|
44
59
|
|
@@ -87,22 +102,29 @@ module RubySMB
|
|
87
102
|
create_request.create_disposition = disposition
|
88
103
|
create_request.name = filename
|
89
104
|
|
90
|
-
raw_response = client.send_recv(create_request)
|
105
|
+
raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
|
91
106
|
response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
|
92
|
-
|
93
|
-
|
94
|
-
|
107
|
+
unless response.valid?
|
108
|
+
raise RubySMB::Error::InvalidPacket.new(
|
109
|
+
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
110
|
+
expected_cmd: RubySMB::SMB2::Packet::CreateResponse::COMMAND,
|
111
|
+
received_proto: response.smb2_header.protocol,
|
112
|
+
received_cmd: response.smb2_header.command
|
113
|
+
)
|
114
|
+
end
|
115
|
+
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
116
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
95
117
|
end
|
96
118
|
|
97
119
|
case @share_type
|
98
|
-
when
|
99
|
-
RubySMB::SMB2::File.new(name: filename, tree: self, response: response)
|
100
|
-
when
|
120
|
+
when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_DISK
|
121
|
+
RubySMB::SMB2::File.new(name: filename, tree: self, response: response, encrypt: @tree_connect_encrypt_data)
|
122
|
+
when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_PIPE
|
101
123
|
RubySMB::SMB2::Pipe.new(name: filename, tree: self, response: response)
|
102
|
-
# when
|
124
|
+
# when RubySMB::SMB2::TreeConnectResponse::SMB2_SHARE_TYPE_PRINT
|
103
125
|
# it's a printer!
|
104
126
|
else
|
105
|
-
raise RubySMB::Error::RubySMBError
|
127
|
+
raise RubySMB::Error::RubySMBError, 'Unsupported share type'
|
106
128
|
end
|
107
129
|
end
|
108
130
|
|
@@ -116,6 +138,7 @@ module RubySMB
|
|
116
138
|
# @param pattern [String] search pattern
|
117
139
|
# @param type [Class] file information class
|
118
140
|
# @return [Array] array of directory structures
|
141
|
+
# @raise [RubySMB::Error::InvalidPacket] if the response is not a QueryDirectoryResponse packet
|
119
142
|
def list(directory: nil, pattern: '*', type: RubySMB::Fscc::FileInformation::FileIdFullDirectoryInformation)
|
120
143
|
create_response = open_directory(directory: directory)
|
121
144
|
file_id = create_response.file_id
|
@@ -131,16 +154,23 @@ module RubySMB
|
|
131
154
|
files = []
|
132
155
|
|
133
156
|
loop do
|
134
|
-
response = client.send_recv(directory_request)
|
157
|
+
response = client.send_recv(directory_request, encrypt: @tree_connect_encrypt_data)
|
135
158
|
directory_response = RubySMB::SMB2::Packet::QueryDirectoryResponse.read(response)
|
159
|
+
unless directory_response.valid?
|
160
|
+
raise RubySMB::Error::InvalidPacket.new(
|
161
|
+
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
162
|
+
expected_cmd: RubySMB::SMB2::Packet::QueryDirectoryResponse::COMMAND,
|
163
|
+
received_proto: directory_response.smb2_header.protocol,
|
164
|
+
received_cmd: directory_response.smb2_header.command
|
165
|
+
)
|
166
|
+
end
|
136
167
|
|
137
168
|
status_code = directory_response.smb2_header.nt_status.to_nt_status
|
138
169
|
|
139
170
|
break if status_code == WindowsError::NTStatus::STATUS_NO_MORE_FILES
|
140
171
|
|
141
172
|
unless status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
142
|
-
|
143
|
-
raise RubySMB::Error::UnexpectedStatusCode, status_code.to_s
|
173
|
+
raise RubySMB::Error::UnexpectedStatusCode, status_code
|
144
174
|
end
|
145
175
|
|
146
176
|
files += directory_response.results(type)
|
@@ -162,14 +192,28 @@ module RubySMB
|
|
162
192
|
# @param write [Boolean] whether to request write access
|
163
193
|
# @param delete [Boolean] whether to request delete access
|
164
194
|
# @return [RubySMB::SMB2::Packet::CreateResponse] the response packet returned from the server
|
195
|
+
# @raise [RubySMB::Error::InvalidPacket] if the response is not a CreateResponse packet
|
165
196
|
def open_directory(directory: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
|
166
197
|
impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE,
|
167
198
|
read: true, write: false, delete: false)
|
168
199
|
|
169
200
|
create_request = open_directory_packet(directory: directory, disposition: disposition,
|
170
201
|
impersonation: impersonation, read: read, write: write, delete: delete)
|
171
|
-
raw_response = client.send_recv(create_request)
|
172
|
-
RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
|
202
|
+
raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
|
203
|
+
response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
|
204
|
+
unless response.valid?
|
205
|
+
raise RubySMB::Error::InvalidPacket.new(
|
206
|
+
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
207
|
+
expected_cmd: RubySMB::SMB2::Packet::CreateResponse::COMMAND,
|
208
|
+
received_proto: response.smb2_header.protocol,
|
209
|
+
received_cmd: response.smb2_header.command
|
210
|
+
)
|
211
|
+
end
|
212
|
+
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
213
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
214
|
+
end
|
215
|
+
|
216
|
+
response
|
173
217
|
end
|
174
218
|
|
175
219
|
# Creates the Packet for the #open_directory method.
|
data/lib/ruby_smb/version.rb
CHANGED
data/ruby_smb.gemspec
CHANGED
@@ -6,8 +6,8 @@ require 'ruby_smb/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = 'ruby_smb'
|
8
8
|
spec.version = RubySMB::VERSION
|
9
|
-
spec.authors = ['David Maloney', 'James Lee', 'Dev Mohanty', 'Christophe De La Fuente']
|
10
|
-
spec.email = ['
|
9
|
+
spec.authors = ['Metasploit Hackers', 'David Maloney', 'James Lee', 'Dev Mohanty', 'Christophe De La Fuente']
|
10
|
+
spec.email = ['msfdev@metasploit.com']
|
11
11
|
spec.summary = 'A pure Ruby implementation of the SMB Protocol Family'
|
12
12
|
spec.description = ''
|
13
13
|
spec.homepage = 'https://github.com/rapid7/ruby_smb'
|
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.platform = Gem::Platform::RUBY
|
27
27
|
end
|
28
28
|
|
29
|
-
spec.required_ruby_version = '>= 2.
|
29
|
+
spec.required_ruby_version = '>= 2.5'
|
30
30
|
|
31
31
|
spec.add_development_dependency 'bundler'
|
32
32
|
spec.add_development_dependency 'fivemat'
|
@@ -36,4 +36,6 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.add_runtime_dependency 'rubyntlm'
|
37
37
|
spec.add_runtime_dependency 'windows_error'
|
38
38
|
spec.add_runtime_dependency 'bindata'
|
39
|
+
spec.add_runtime_dependency 'openssl-ccm'
|
40
|
+
spec.add_runtime_dependency 'openssl-cmac'
|
39
41
|
end
|
@@ -6,9 +6,65 @@ RSpec.describe RubySMB::Client do
|
|
6
6
|
let(:username) { 'msfadmin' }
|
7
7
|
let(:password) { 'msfpasswd' }
|
8
8
|
subject(:client) { described_class.new(dispatcher, username: username, password: password) }
|
9
|
-
let(:smb1_client) { described_class.new(dispatcher, smb2: false, username: username, password: password) }
|
10
|
-
let(:smb2_client) { described_class.new(dispatcher, smb1: false, username: username, password: password) }
|
9
|
+
let(:smb1_client) { described_class.new(dispatcher, smb2: false, smb3: false, username: username, password: password) }
|
10
|
+
let(:smb2_client) { described_class.new(dispatcher, smb1: false, smb3: false, username: username, password: password) }
|
11
|
+
let(:smb3_client) { described_class.new(dispatcher, smb1: false, smb2: false, username: username, password: password) }
|
11
12
|
let(:empty_packet) { RubySMB::SMB1::Packet::EmptyPacket.new }
|
13
|
+
let(:error_packet) { RubySMB::SMB2::Packet::ErrorPacket.new }
|
14
|
+
|
15
|
+
it { is_expected.to respond_to :dispatcher }
|
16
|
+
it { is_expected.to respond_to :domain }
|
17
|
+
it { is_expected.to respond_to :local_workstation }
|
18
|
+
it { is_expected.to respond_to :ntlm_client }
|
19
|
+
it { is_expected.to respond_to :password }
|
20
|
+
it { is_expected.to respond_to :peer_native_os }
|
21
|
+
it { is_expected.to respond_to :peer_native_lm }
|
22
|
+
it { is_expected.to respond_to :primary_domain }
|
23
|
+
it { is_expected.to respond_to :default_name }
|
24
|
+
it { is_expected.to respond_to :default_domain }
|
25
|
+
it { is_expected.to respond_to :dns_host_name }
|
26
|
+
it { is_expected.to respond_to :dns_domain_name }
|
27
|
+
it { is_expected.to respond_to :dns_tree_name }
|
28
|
+
it { is_expected.to respond_to :os_version }
|
29
|
+
it { is_expected.to respond_to :dialect }
|
30
|
+
it { is_expected.to respond_to :sequence_counter }
|
31
|
+
it { is_expected.to respond_to :session_id }
|
32
|
+
it { is_expected.to respond_to :signing_required }
|
33
|
+
it { is_expected.to respond_to :smb1 }
|
34
|
+
it { is_expected.to respond_to :smb2 }
|
35
|
+
it { is_expected.to respond_to :smb3 }
|
36
|
+
it { is_expected.to respond_to :smb2_message_id }
|
37
|
+
it { is_expected.to respond_to :username }
|
38
|
+
it { is_expected.to respond_to :user_id }
|
39
|
+
it { is_expected.to respond_to :max_buffer_size }
|
40
|
+
it { is_expected.to respond_to :server_max_buffer_size }
|
41
|
+
it { is_expected.to respond_to :server_max_write_size }
|
42
|
+
it { is_expected.to respond_to :server_max_read_size }
|
43
|
+
it { is_expected.to respond_to :server_max_transact_size }
|
44
|
+
it { is_expected.to respond_to :preauth_integrity_hash_algorithm }
|
45
|
+
it { is_expected.to respond_to :preauth_integrity_hash_value }
|
46
|
+
it { is_expected.to respond_to :encryption_algorithm }
|
47
|
+
it { is_expected.to respond_to :client_encryption_key }
|
48
|
+
it { is_expected.to respond_to :server_encryption_key }
|
49
|
+
it { is_expected.to respond_to :session_encrypt_data }
|
50
|
+
it { is_expected.to respond_to :server_encryption_algorithms }
|
51
|
+
it { is_expected.to respond_to :server_compression_algorithms }
|
52
|
+
it { is_expected.to respond_to :negotiated_smb_version }
|
53
|
+
it { is_expected.to respond_to :session_key }
|
54
|
+
it { is_expected.to respond_to :tree_connects }
|
55
|
+
it { is_expected.to respond_to :open_files }
|
56
|
+
it { is_expected.to respond_to :use_ntlmv2 }
|
57
|
+
it { is_expected.to respond_to :usentlm2_session }
|
58
|
+
it { is_expected.to respond_to :send_lm }
|
59
|
+
it { is_expected.to respond_to :use_lanman_key }
|
60
|
+
it { is_expected.to respond_to :send_ntlm }
|
61
|
+
it { is_expected.to respond_to :spnopt }
|
62
|
+
it { is_expected.to respond_to :evasion_opts }
|
63
|
+
it { is_expected.to respond_to :native_os }
|
64
|
+
it { is_expected.to respond_to :native_lm }
|
65
|
+
it { is_expected.to respond_to :verify_signature }
|
66
|
+
it { is_expected.to respond_to :auth_user }
|
67
|
+
it { is_expected.to respond_to :last_file_id }
|
12
68
|
|
13
69
|
describe '#initialize' do
|
14
70
|
it 'should raise an ArgumentError without a valid dispatcher' do
|
@@ -25,14 +81,21 @@ RSpec.describe RubySMB::Client do
|
|
25
81
|
|
26
82
|
it 'accepts an argument to disable smb1 support' do
|
27
83
|
expect(smb2_client.smb1).to be false
|
84
|
+
expect(smb3_client.smb1).to be false
|
28
85
|
end
|
29
86
|
|
30
87
|
it 'accepts an argument to disable smb2 support' do
|
31
88
|
expect(smb1_client.smb2).to be false
|
89
|
+
expect(smb3_client.smb2).to be false
|
32
90
|
end
|
33
91
|
|
34
|
-
it '
|
35
|
-
expect
|
92
|
+
it 'accepts an argument to disable smb3 support' do
|
93
|
+
expect(smb1_client.smb3).to be false
|
94
|
+
expect(smb2_client.smb3).to be false
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'raises an exception if SMB1, SMB2 and SMB3 are disabled' do
|
98
|
+
expect { described_class.new(dispatcher, smb1: false, smb2: false, smb3: false, username: username, password: password) }.to raise_error(ArgumentError, 'You must enable at least one Protocol')
|
36
99
|
end
|
37
100
|
|
38
101
|
it 'sets the username attribute' do
|
@@ -43,6 +106,11 @@ RSpec.describe RubySMB::Client do
|
|
43
106
|
expect(client.password).to eq password
|
44
107
|
end
|
45
108
|
|
109
|
+
it 'sets the session_encrypt_data attribute' do
|
110
|
+
client = described_class.new(dispatcher, username: username, password: password, always_encrypt: true)
|
111
|
+
expect(client.session_encrypt_data).to eq true
|
112
|
+
end
|
113
|
+
|
46
114
|
it 'creates an NTLM client' do
|
47
115
|
expect(client.ntlm_client).to be_a Net::NTLM::Client
|
48
116
|
end
|
@@ -57,7 +125,7 @@ RSpec.describe RubySMB::Client do
|
|
57
125
|
expect(opt[:workstation]).to eq(local_workstation)
|
58
126
|
expect(opt[:domain]).to eq(domain)
|
59
127
|
flags = Net::NTLM::Client::DEFAULT_FLAGS |
|
60
|
-
Net::NTLM::FLAGS[:TARGET_INFO] | 0x02000000
|
128
|
+
Net::NTLM::FLAGS[:TARGET_INFO] | 0x02000000 ^ Net::NTLM::FLAGS[:OEM]
|
61
129
|
expect(opt[:flags]).to eq(flags)
|
62
130
|
end
|
63
131
|
|
@@ -75,28 +143,280 @@ RSpec.describe RubySMB::Client do
|
|
75
143
|
end
|
76
144
|
end
|
77
145
|
|
146
|
+
describe '#echo' do
|
147
|
+
let(:response) { double('Echo Response') }
|
148
|
+
before :example do
|
149
|
+
allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_SUCCESS)
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'with SMB1' do
|
153
|
+
it 'calls #smb1_echo with the expected arguments' do
|
154
|
+
allow(smb1_client).to receive(:smb1_echo).and_return(response)
|
155
|
+
count = 3
|
156
|
+
data = 'testing...'
|
157
|
+
smb1_client.echo(count: count, data: data)
|
158
|
+
expect(smb1_client).to have_received(:smb1_echo).with(count: count, data: data)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'with SMB2' do
|
163
|
+
it 'calls #smb2_echo without arguments' do
|
164
|
+
allow(smb2_client).to receive(:smb2_echo).and_return(response)
|
165
|
+
smb2_client.echo
|
166
|
+
expect(smb2_client).to have_received(:smb2_echo)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context 'with SMB3' do
|
171
|
+
it 'calls #smb2_echo without arguments' do
|
172
|
+
allow(smb3_client).to receive(:smb2_echo).and_return(response)
|
173
|
+
smb3_client.echo
|
174
|
+
expect(smb3_client).to have_received(:smb2_echo)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'returns the expected status code' do
|
179
|
+
allow(smb2_client).to receive(:smb2_echo).and_return(response)
|
180
|
+
expect(smb2_client.echo).to eq(WindowsError::NTStatus::STATUS_SUCCESS)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
78
184
|
describe '#send_recv' do
|
79
185
|
let(:smb1_request) { RubySMB::SMB1::Packet::TreeConnectRequest.new }
|
80
186
|
let(:smb2_request) { RubySMB::SMB2::Packet::TreeConnectRequest.new }
|
81
187
|
|
82
188
|
before(:each) do
|
83
|
-
|
84
|
-
|
189
|
+
allow(client).to receive(:is_status_pending?).and_return(false)
|
190
|
+
allow(dispatcher).to receive(:send_packet).and_return(nil)
|
191
|
+
allow(dispatcher).to receive(:recv_packet).and_return('A')
|
85
192
|
end
|
86
193
|
|
87
|
-
|
88
|
-
|
89
|
-
|
194
|
+
context 'when signing' do
|
195
|
+
it 'calls #smb1_sign if it is an SMB1 packet' do
|
196
|
+
expect(client).to receive(:smb1_sign).with(smb1_request).and_call_original
|
197
|
+
client.send_recv(smb1_request)
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'with an SMB2 packet' do
|
201
|
+
it 'does not sign a SessionSetupRequest packet' do
|
202
|
+
expect(smb2_client).to_not receive(:smb2_sign)
|
203
|
+
expect(smb2_client).to_not receive(:smb3_sign)
|
204
|
+
client.send_recv(RubySMB::SMB2::Packet::SessionSetupRequest.new)
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'calls #smb2_sign if it is an SMB2 client' do
|
208
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(false)
|
209
|
+
expect(smb2_client).to receive(:smb2_sign).with(smb2_request).and_call_original
|
210
|
+
smb2_client.send_recv(smb2_request)
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'calls #smb3_sign if it is an SMB3 client' do
|
214
|
+
allow(smb3_client).to receive(:is_status_pending?).and_return(false)
|
215
|
+
expect(smb3_client).to receive(:smb3_sign).with(smb2_request).and_call_original
|
216
|
+
smb3_client.send_recv(smb2_request)
|
217
|
+
end
|
218
|
+
end
|
90
219
|
end
|
91
220
|
|
92
|
-
it '
|
93
|
-
expect(
|
221
|
+
it 'sends the expected packet and gets the response' do
|
222
|
+
expect(dispatcher).to receive(:send_packet).with(smb1_request)
|
223
|
+
expect(dispatcher).to receive(:recv_packet)
|
94
224
|
client.send_recv(smb1_request)
|
95
225
|
end
|
96
226
|
|
97
|
-
|
98
|
-
|
99
|
-
|
227
|
+
context 'with SMB1' do
|
228
|
+
it 'does not check if it is a STATUS_PENDING response' do
|
229
|
+
expect(smb1_client).to_not receive(:is_status_pending?)
|
230
|
+
smb1_client.send_recv(smb1_request)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
context 'with SMB2' do
|
235
|
+
context 'when receiving a STATUS_PENDING response' do
|
236
|
+
it 'waits 1 second and reads/decrypts again' do
|
237
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(true, false)
|
238
|
+
expect(smb2_client).to receive(:sleep).with(1)
|
239
|
+
expect(dispatcher).to receive(:recv_packet).twice
|
240
|
+
smb2_client.send_recv(smb2_request)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
context 'with SMB3 and encryption' do
|
246
|
+
before :example do
|
247
|
+
smb3_client.dialect = '0x0300'
|
248
|
+
allow(smb3_client).to receive(:is_status_pending?).and_return(false)
|
249
|
+
end
|
250
|
+
|
251
|
+
context 'with a SessionSetupRequest' do
|
252
|
+
it 'does not encrypt/decrypt' do
|
253
|
+
request = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
254
|
+
expect(smb3_client).to_not receive(:send_encrypt).with(request)
|
255
|
+
expect(smb3_client).to_not receive(:recv_encrypt)
|
256
|
+
expect(dispatcher).to receive(:send_packet).with(request)
|
257
|
+
expect(dispatcher).to receive(:recv_packet)
|
258
|
+
smb3_client.send_recv(request)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
context 'with a NegotiateRequest' do
|
263
|
+
it 'does not encrypt/decrypt' do
|
264
|
+
request = RubySMB::SMB2::Packet::NegotiateRequest.new
|
265
|
+
expect(smb3_client).to_not receive(:send_encrypt).with(request)
|
266
|
+
expect(smb3_client).to_not receive(:recv_encrypt)
|
267
|
+
expect(dispatcher).to receive(:send_packet).with(request)
|
268
|
+
expect(dispatcher).to receive(:recv_packet)
|
269
|
+
smb3_client.send_recv(request)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'encrypts and decrypts' do
|
274
|
+
expect(smb3_client).to receive(:send_encrypt).with(smb2_request)
|
275
|
+
expect(smb3_client).to receive(:recv_encrypt)
|
276
|
+
smb3_client.send_recv(smb2_request)
|
277
|
+
end
|
278
|
+
|
279
|
+
context 'when receiving a STATUS_PENDING response' do
|
280
|
+
it 'waits 1 second and reads/decrypts again' do
|
281
|
+
allow(smb3_client).to receive(:is_status_pending?).and_return(true, false)
|
282
|
+
expect(smb3_client).to receive(:sleep).with(1)
|
283
|
+
expect(smb3_client).to receive(:send_encrypt).with(smb2_request)
|
284
|
+
expect(smb3_client).to receive(:recv_encrypt).twice
|
285
|
+
smb3_client.send_recv(smb2_request)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
describe '#is_status_pending?' do
|
292
|
+
let(:response) {
|
293
|
+
res = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
294
|
+
res.smb2_header.nt_status= WindowsError::NTStatus::STATUS_PENDING.value
|
295
|
+
res.smb2_header.flags.async_command = 1
|
296
|
+
res
|
297
|
+
}
|
298
|
+
|
299
|
+
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.to_binary_s)).to be true
|
301
|
+
end
|
302
|
+
|
303
|
+
it 'returns false when the response has a STATUS_PENDING status code and the async_command flag not set' do
|
304
|
+
response.smb2_header.flags.async_command = 0
|
305
|
+
expect(client.is_status_pending?(response.to_binary_s)).to be false
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'returns false when the response has no STATUS_PENDING status code but the async_command flag set' do
|
309
|
+
response.smb2_header.nt_status= WindowsError::NTStatus::STATUS_SUCCESS.value
|
310
|
+
expect(client.is_status_pending?(response.to_binary_s)).to be false
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
describe '#can_be_encrypted?' do
|
315
|
+
it 'returns true if the packet can be encrypted' do
|
316
|
+
packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
|
317
|
+
expect(client.can_be_encrypted?(packet)).to be true
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'returns false if it is an SMB1 packet' do
|
321
|
+
packet = RubySMB::SMB1::Packet::LogoffRequest.new
|
322
|
+
expect(client.can_be_encrypted?(packet)).to be false
|
323
|
+
end
|
324
|
+
|
325
|
+
[RubySMB::SMB2::Packet::SessionSetupRequest, RubySMB::SMB2::Packet::NegotiateRequest].each do |klass|
|
326
|
+
it "returns false if the packet is a #{klass}" do
|
327
|
+
packet = klass.new
|
328
|
+
expect(client.can_be_encrypted?(packet)).to be false
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
describe '#encryption_supported?' do
|
334
|
+
['0x0300', '0x0302', '0x0311'].each do |dialect|
|
335
|
+
it "returns true if the dialect is #{dialect}" do
|
336
|
+
client.dialect = dialect
|
337
|
+
expect(client.encryption_supported?).to be true
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
it "returns false if the dialect does not support encryption" do
|
342
|
+
client.dialect = '0x0202'
|
343
|
+
expect(client.encryption_supported?).to be false
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
describe '#send_encrypt' do
|
348
|
+
let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
|
349
|
+
before :example do
|
350
|
+
allow(dispatcher).to receive(:send_packet)
|
351
|
+
client.dialect = '0x0300'
|
352
|
+
end
|
353
|
+
|
354
|
+
it 'creates a Transform request' do
|
355
|
+
expect(client).to receive(:smb3_encrypt).with(packet.to_binary_s)
|
356
|
+
client.send_encrypt(packet)
|
357
|
+
end
|
358
|
+
|
359
|
+
it 'raises an EncryptionError exception if an error occurs while encrypting' do
|
360
|
+
allow(client).to receive(:smb3_encrypt).and_raise(RubySMB::Error::RubySMBError.new('Error'))
|
361
|
+
expect { client.send_encrypt(packet) }.to raise_error(
|
362
|
+
RubySMB::Error::EncryptionError,
|
363
|
+
"Error while encrypting #{packet.class.name} packet (SMB 0x0300): Error"
|
364
|
+
)
|
365
|
+
end
|
366
|
+
|
367
|
+
it 'sends the encrypted packet' do
|
368
|
+
encrypted_packet = double('Encrypted packet')
|
369
|
+
allow(client).to receive(:smb3_encrypt).and_return(encrypted_packet)
|
370
|
+
client.send_encrypt(packet)
|
371
|
+
expect(dispatcher).to have_received(:send_packet).with(encrypted_packet)
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
describe '#recv_encrypt' do
|
376
|
+
let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
|
377
|
+
before :example do
|
378
|
+
allow(dispatcher).to receive(:recv_packet).and_return(packet.to_binary_s)
|
379
|
+
client.dialect = '0x0300'
|
380
|
+
allow(client).to receive(:smb3_decrypt)
|
381
|
+
end
|
382
|
+
|
383
|
+
it 'reads the response packet' do
|
384
|
+
client.recv_encrypt
|
385
|
+
expect(dispatcher).to have_received(:recv_packet)
|
386
|
+
end
|
387
|
+
|
388
|
+
it 'raises an EncryptionError exception if an error occurs while receiving the response' do
|
389
|
+
allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
|
390
|
+
expect { client.recv_encrypt }.to raise_error(
|
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
|
+
)
|
395
|
+
end
|
396
|
+
|
397
|
+
it 'parses the response as a Transform response packet' do
|
398
|
+
expect(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).with(packet.to_binary_s)
|
399
|
+
client.recv_encrypt
|
400
|
+
end
|
401
|
+
|
402
|
+
it 'raises an InvalidPacket exception if an error occurs while parsing the response' do
|
403
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_raise(IOError)
|
404
|
+
expect { client.recv_encrypt }.to raise_error(RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet')
|
405
|
+
end
|
406
|
+
|
407
|
+
it 'decrypts the Transform response packet' do
|
408
|
+
transform = double('Transform header packet')
|
409
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_return(transform)
|
410
|
+
client.recv_encrypt
|
411
|
+
expect(client).to have_received(:smb3_decrypt).with(transform)
|
412
|
+
end
|
413
|
+
|
414
|
+
it 'raises an EncryptionError exception if an error occurs while decrypting' do
|
415
|
+
allow(client).to receive(:smb3_decrypt).and_raise(RubySMB::Error::RubySMBError)
|
416
|
+
expect { client.recv_encrypt }.to raise_error(
|
417
|
+
RubySMB::Error::EncryptionError,
|
418
|
+
'Error while decrypting RubySMB::SMB2::Packet::TransformHeader packet (SMB 0x0300}): RubySMB::Error::RubySMBError'
|
419
|
+
)
|
100
420
|
end
|
101
421
|
end
|
102
422
|
|
@@ -153,6 +473,132 @@ RSpec.describe RubySMB::Client do
|
|
153
473
|
end
|
154
474
|
end
|
155
475
|
|
476
|
+
describe '#logoff!' do
|
477
|
+
context 'with SMB1' do
|
478
|
+
let(:raw_response) { double('Raw response') }
|
479
|
+
let(:logoff_response) {
|
480
|
+
RubySMB::SMB1::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB1::Commands::SMB_COM_LOGOFF} )
|
481
|
+
}
|
482
|
+
before :example do
|
483
|
+
allow(smb1_client).to receive(:send_recv).and_return(raw_response)
|
484
|
+
allow(RubySMB::SMB1::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
485
|
+
allow(smb1_client).to receive(:wipe_state!)
|
486
|
+
end
|
487
|
+
|
488
|
+
it 'creates a LogoffRequest packet' do
|
489
|
+
expect(RubySMB::SMB1::Packet::LogoffRequest).to receive(:new).and_call_original
|
490
|
+
smb1_client.logoff!
|
491
|
+
end
|
492
|
+
|
493
|
+
it 'calls #send_recv' do
|
494
|
+
expect(smb1_client).to receive(:send_recv)
|
495
|
+
smb1_client.logoff!
|
496
|
+
end
|
497
|
+
|
498
|
+
it 'reads the raw response as a LogoffResponse packet' do
|
499
|
+
expect(RubySMB::SMB1::Packet::LogoffResponse).to receive(:read).with(raw_response)
|
500
|
+
smb1_client.logoff!
|
501
|
+
end
|
502
|
+
|
503
|
+
it 'raise an InvalidPacket exception when the response is an empty packet' do
|
504
|
+
allow(RubySMB::SMB1::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB1::Packet::EmptyPacket.new)
|
505
|
+
expect {smb1_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
506
|
+
end
|
507
|
+
|
508
|
+
it 'raise an InvalidPacket exception when the response is not valid' do
|
509
|
+
allow(logoff_response).to receive(:valid?).and_return(false)
|
510
|
+
expect {smb1_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
511
|
+
end
|
512
|
+
|
513
|
+
it 'calls #wipe_state!' do
|
514
|
+
expect(smb1_client).to receive(:wipe_state!)
|
515
|
+
smb1_client.logoff!
|
516
|
+
end
|
517
|
+
|
518
|
+
it 'returns the expected status code' do
|
519
|
+
logoff_response.smb_header.nt_status = WindowsError::NTStatus::STATUS_PENDING.value
|
520
|
+
allow(RubySMB::SMB1::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
521
|
+
expect(smb1_client.logoff!).to eq(WindowsError::NTStatus::STATUS_PENDING)
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
context 'with SMB2' do
|
526
|
+
let(:raw_response) { double('Raw response') }
|
527
|
+
let(:logoff_response) {
|
528
|
+
RubySMB::SMB2::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB2::Commands::LOGOFF} )
|
529
|
+
}
|
530
|
+
before :example do
|
531
|
+
allow(smb2_client).to receive(:send_recv).and_return(raw_response)
|
532
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
533
|
+
allow(smb2_client).to receive(:wipe_state!)
|
534
|
+
end
|
535
|
+
|
536
|
+
it 'creates a LogoffRequest packet' do
|
537
|
+
expect(RubySMB::SMB2::Packet::LogoffRequest).to receive(:new).and_call_original
|
538
|
+
smb2_client.logoff!
|
539
|
+
end
|
540
|
+
|
541
|
+
it 'calls #send_recv' do
|
542
|
+
expect(smb2_client).to receive(:send_recv)
|
543
|
+
smb2_client.logoff!
|
544
|
+
end
|
545
|
+
|
546
|
+
it 'reads the raw response as a LogoffResponse packet' do
|
547
|
+
expect(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).with(raw_response)
|
548
|
+
smb2_client.logoff!
|
549
|
+
end
|
550
|
+
|
551
|
+
it 'raise an InvalidPacket exception when the response is an error packet' do
|
552
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB2::Packet::ErrorPacket.new)
|
553
|
+
expect {smb2_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
554
|
+
end
|
555
|
+
|
556
|
+
it 'raise an InvalidPacket exception when the response is not a LOGOFF command' do
|
557
|
+
logoff_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
|
558
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
559
|
+
expect {smb2_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
context 'with SMB3' do
|
564
|
+
let(:raw_response) { double('Raw response') }
|
565
|
+
let(:logoff_response) {
|
566
|
+
RubySMB::SMB2::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB2::Commands::LOGOFF} )
|
567
|
+
}
|
568
|
+
before :example do
|
569
|
+
allow(smb3_client).to receive(:send_recv).and_return(raw_response)
|
570
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
571
|
+
allow(smb3_client).to receive(:wipe_state!)
|
572
|
+
end
|
573
|
+
|
574
|
+
it 'creates a LogoffRequest packet' do
|
575
|
+
expect(RubySMB::SMB2::Packet::LogoffRequest).to receive(:new).and_call_original
|
576
|
+
smb3_client.logoff!
|
577
|
+
end
|
578
|
+
|
579
|
+
it 'calls #send_recv' do
|
580
|
+
expect(smb3_client).to receive(:send_recv)
|
581
|
+
smb3_client.logoff!
|
582
|
+
end
|
583
|
+
|
584
|
+
it 'reads the raw response as a LogoffResponse packet' do
|
585
|
+
expect(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).with(raw_response)
|
586
|
+
smb3_client.logoff!
|
587
|
+
end
|
588
|
+
|
589
|
+
it 'raise an InvalidPacket exception when the response is an error packet' do
|
590
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB2::Packet::ErrorPacket.new)
|
591
|
+
expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
592
|
+
end
|
593
|
+
|
594
|
+
it 'raise an InvalidPacket exception when the response is not a LOGOFF command' do
|
595
|
+
logoff_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
|
596
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
597
|
+
expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
598
|
+
end
|
599
|
+
end
|
600
|
+
end
|
601
|
+
|
156
602
|
context 'NetBIOS Session Service' do
|
157
603
|
describe '#session_request' do
|
158
604
|
let(:session_header) { RubySMB::Nbss::SessionHeader.new }
|
@@ -197,6 +643,11 @@ RSpec.describe RubySMB::Client do
|
|
197
643
|
allow(dispatcher).to receive(:recv_packet).and_return(negative_session_response.to_binary_s)
|
198
644
|
expect { client.session_request }.to raise_error(RubySMB::Error::NetBiosSessionService)
|
199
645
|
end
|
646
|
+
|
647
|
+
it 'raises an InvalidPacket exception when an error occurs while reading' do
|
648
|
+
allow(RubySMB::Nbss::SessionHeader).to receive(:read).and_raise(IOError)
|
649
|
+
expect { client.session_request }.to raise_error(RubySMB::Error::InvalidPacket)
|
650
|
+
end
|
200
651
|
end
|
201
652
|
|
202
653
|
describe '#session_request_packet' do
|
@@ -271,7 +722,8 @@ RSpec.describe RubySMB::Client do
|
|
271
722
|
smb1_extended_response.to_binary_s
|
272
723
|
}
|
273
724
|
|
274
|
-
let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new }
|
725
|
+
let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x200) }
|
726
|
+
let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x300) }
|
275
727
|
|
276
728
|
describe '#smb1_negotiate_request' do
|
277
729
|
it 'returns an SMB1 Negotiate Request packet' do
|
@@ -279,33 +731,158 @@ RSpec.describe RubySMB::Client do
|
|
279
731
|
end
|
280
732
|
|
281
733
|
it 'sets the default SMB1 Dialect' do
|
282
|
-
expect(client.smb1_negotiate_request.dialects).to include(
|
734
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
735
|
+
buffer_format: 2,
|
736
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
|
737
|
+
)
|
283
738
|
end
|
284
739
|
|
285
740
|
it 'sets the SMB2.02 dialect if SMB2 support is enabled' do
|
286
|
-
expect(client.smb1_negotiate_request.dialects).to include(
|
741
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
742
|
+
buffer_format: 2,
|
743
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
|
744
|
+
)
|
287
745
|
end
|
288
746
|
|
289
747
|
it 'excludes the SMB2.02 Dialect if SMB2 support is disabled' do
|
290
|
-
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
748
|
+
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
749
|
+
buffer_format: 2,
|
750
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
|
751
|
+
)
|
291
752
|
end
|
292
753
|
|
293
754
|
it 'excludes the default SMB1 Dialect if SMB1 support is disabled' do
|
294
|
-
expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
|
755
|
+
expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
|
756
|
+
buffer_format: 2,
|
757
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
|
758
|
+
)
|
759
|
+
end
|
760
|
+
|
761
|
+
it 'sets the SMB wildcard dialect if SMB2 support is enabled' do
|
762
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
763
|
+
buffer_format: 2,
|
764
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
765
|
+
)
|
766
|
+
end
|
767
|
+
|
768
|
+
it 'sets the SMB wildcard dialect if SMB3 support is enabled' do
|
769
|
+
expect(smb3_client.smb1_negotiate_request.dialects).to include(
|
770
|
+
buffer_format: 2,
|
771
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
772
|
+
)
|
773
|
+
end
|
774
|
+
|
775
|
+
it 'excludes the SMB wildcard dialect if both SMB2 and SMB3 supports are disabled' do
|
776
|
+
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
777
|
+
buffer_format: 2,
|
778
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
779
|
+
)
|
295
780
|
end
|
296
781
|
end
|
297
782
|
|
298
|
-
describe '#
|
783
|
+
describe '#smb2_3_negotiate_request' do
|
299
784
|
it 'return an SMB2 Negotiate Request packet' do
|
300
|
-
expect(client.
|
785
|
+
expect(client.smb2_3_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
|
786
|
+
end
|
787
|
+
|
788
|
+
it 'sets the default SMB2 Dialect if SMB2 support is enabled' do
|
789
|
+
expect(client.smb2_3_negotiate_request.dialects).to include(
|
790
|
+
*(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
791
|
+
)
|
301
792
|
end
|
302
793
|
|
303
|
-
it '
|
304
|
-
expect(
|
794
|
+
it 'does not set the default SMB2 Dialect if SMB2 support is disabled' do
|
795
|
+
expect(smb3_client.smb2_3_negotiate_request.dialects).to_not include(
|
796
|
+
*(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
797
|
+
)
|
305
798
|
end
|
306
799
|
|
307
800
|
it 'sets the Message ID to 0' do
|
308
|
-
expect(client.
|
801
|
+
expect(client.smb2_3_negotiate_request.smb2_header.message_id).to eq 0
|
802
|
+
end
|
803
|
+
|
804
|
+
it 'adds SMB3 dialects if if SMB3 support is enabled' do
|
805
|
+
expect(client.smb2_3_negotiate_request.dialects).to include(
|
806
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
807
|
+
)
|
808
|
+
end
|
809
|
+
|
810
|
+
it 'does not set the default SMB3 Dialect if SMB3 support is disabled' do
|
811
|
+
expect(smb2_client.smb2_3_negotiate_request.dialects).to_not include(
|
812
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
813
|
+
)
|
814
|
+
end
|
815
|
+
end
|
816
|
+
|
817
|
+
describe '#add_smb3_to_negotiate_request' do
|
818
|
+
let(:negotiate_request) { RubySMB::SMB2::Packet::NegotiateRequest.new }
|
819
|
+
|
820
|
+
it 'adds the default SMB3 dialects' do
|
821
|
+
expect(client.add_smb3_to_negotiate_request(negotiate_request).dialects).to include(
|
822
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
823
|
+
)
|
824
|
+
end
|
825
|
+
|
826
|
+
it 'raises the expected exception when the dialects is not an array of strings' do
|
827
|
+
dialects = ['0x0300', 0x0302, '0x0311']
|
828
|
+
expect { client.add_smb3_to_negotiate_request(negotiate_request, dialects) }.to raise_error(ArgumentError)
|
829
|
+
end
|
830
|
+
|
831
|
+
it 'sets encryption capability flag' do
|
832
|
+
expect(client.add_smb3_to_negotiate_request(negotiate_request).capabilities.encryption).to eq(1)
|
833
|
+
end
|
834
|
+
|
835
|
+
context 'when the negotiate packet includes the 0x0311 dialect' do
|
836
|
+
before :example do
|
837
|
+
client.add_smb3_to_negotiate_request(negotiate_request, ['0x0311'])
|
838
|
+
end
|
839
|
+
|
840
|
+
it 'adds 3 Negotiate Contexts' do
|
841
|
+
expect(negotiate_request.negotiate_context_info.negotiate_context_count).to eq(3)
|
842
|
+
end
|
843
|
+
|
844
|
+
it 'adds a Preauth Integrity Negotiate Context with the expected hash algorithms' do
|
845
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
846
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
847
|
+
end
|
848
|
+
expect(nc.length).to eq(1)
|
849
|
+
expect(nc.first.data.hash_algorithms).to eq([RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512])
|
850
|
+
end
|
851
|
+
|
852
|
+
it 'adds Encryption Negotiate Contexts with the expected encryption algorithms' do
|
853
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
854
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
855
|
+
end
|
856
|
+
expect(nc.length).to eq(1)
|
857
|
+
expect(nc.first.data.ciphers).to eq(
|
858
|
+
[
|
859
|
+
RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM,
|
860
|
+
RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
861
|
+
]
|
862
|
+
)
|
863
|
+
end
|
864
|
+
|
865
|
+
it 'adds Compression Negotiate Contexts with the expected compression algorithms' do
|
866
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
867
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
868
|
+
end
|
869
|
+
expect(nc.length).to eq(1)
|
870
|
+
expect(nc.first.data.compression_algorithms).to eq(
|
871
|
+
[
|
872
|
+
RubySMB::SMB2::CompressionCapabilities::LZNT1,
|
873
|
+
RubySMB::SMB2::CompressionCapabilities::LZ77,
|
874
|
+
RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman,
|
875
|
+
RubySMB::SMB2::CompressionCapabilities::Pattern_V1
|
876
|
+
]
|
877
|
+
)
|
878
|
+
end
|
879
|
+
end
|
880
|
+
|
881
|
+
context 'when the negotiate packet does not include the 0x0311 dialect' do
|
882
|
+
it 'does not add any Negotiate Context' do
|
883
|
+
client.add_smb3_to_negotiate_request(negotiate_request, ['0x0300', '0x0302'])
|
884
|
+
expect(negotiate_request.negotiate_context_list?). to be false
|
885
|
+
end
|
309
886
|
end
|
310
887
|
end
|
311
888
|
|
@@ -320,10 +897,15 @@ RSpec.describe RubySMB::Client do
|
|
320
897
|
client.negotiate_request
|
321
898
|
end
|
322
899
|
|
323
|
-
it 'calls #
|
324
|
-
expect(smb2_client).to receive(:
|
900
|
+
it 'calls #smb2_3_negotiate_request if SMB2 is enabled' do
|
901
|
+
expect(smb2_client).to receive(:smb2_3_negotiate_request)
|
325
902
|
smb2_client.negotiate_request
|
326
903
|
end
|
904
|
+
|
905
|
+
it 'calls #smb2_3_negotiate_request if SMB3 is enabled' do
|
906
|
+
expect(smb3_client).to receive(:smb2_3_negotiate_request)
|
907
|
+
smb3_client.negotiate_request
|
908
|
+
end
|
327
909
|
end
|
328
910
|
|
329
911
|
describe '#negotiate_response' do
|
@@ -332,10 +914,15 @@ RSpec.describe RubySMB::Client do
|
|
332
914
|
expect(smb1_client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
|
333
915
|
end
|
334
916
|
|
335
|
-
it 'raises an exception if the
|
917
|
+
it 'raises an exception if the response is not a SMB packet' do
|
336
918
|
expect { smb1_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
|
337
919
|
end
|
338
920
|
|
921
|
+
it 'raises an InvalidPacket error if the response is not a valid response' do
|
922
|
+
empty_packet.smb_header.command = RubySMB::SMB2::Commands::NEGOTIATE
|
923
|
+
expect { smb1_client.negotiate_response(empty_packet.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
924
|
+
end
|
925
|
+
|
339
926
|
it 'considers the response invalid if it is not an actual Negotiate Response' do
|
340
927
|
bogus_response = smb1_extended_response
|
341
928
|
bogus_response.smb_header.command = 0xff
|
@@ -357,14 +944,36 @@ RSpec.describe RubySMB::Client do
|
|
357
944
|
it 'raises an exception if the Response is invalid' do
|
358
945
|
expect { smb2_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
|
359
946
|
end
|
947
|
+
|
948
|
+
it 'considers the response invalid if it is not an actual Negotiate Response' do
|
949
|
+
bogus_response = smb2_response
|
950
|
+
bogus_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
|
951
|
+
expect { smb2_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
952
|
+
end
|
953
|
+
end
|
954
|
+
|
955
|
+
context 'with only SMB3' do
|
956
|
+
it 'returns a properly formed packet' do
|
957
|
+
expect(smb3_client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
|
958
|
+
end
|
959
|
+
|
960
|
+
it 'raises an exception if the Response is invalid' do
|
961
|
+
expect { smb3_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
|
962
|
+
end
|
963
|
+
|
964
|
+
it 'considers the response invalid if it is not an actual Negotiate Response' do
|
965
|
+
bogus_response = smb2_response
|
966
|
+
bogus_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
|
967
|
+
expect { smb3_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
968
|
+
end
|
360
969
|
end
|
361
970
|
|
362
|
-
context 'with SMB1 and
|
971
|
+
context 'with SMB1, SMB2 and SMB3 enabled' do
|
363
972
|
it 'returns an SMB1 NegotiateResponse if it looks like SMB1' do
|
364
973
|
expect(client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
|
365
974
|
end
|
366
975
|
|
367
|
-
it 'returns an SMB2 NegotiateResponse if it looks like SMB2' do
|
976
|
+
it 'returns an SMB2 NegotiateResponse if it looks like SMB2 or SMB3' do
|
368
977
|
expect(client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
|
369
978
|
end
|
370
979
|
end
|
@@ -372,9 +981,10 @@ RSpec.describe RubySMB::Client do
|
|
372
981
|
|
373
982
|
describe '#parse_negotiate_response' do
|
374
983
|
context 'when SMB1 was Negotiated' do
|
375
|
-
it 'turns off SMB2 support' do
|
984
|
+
it 'turns off SMB2 and SMB3 support' do
|
376
985
|
client.parse_negotiate_response(smb1_extended_response)
|
377
986
|
expect(client.smb2).to be false
|
987
|
+
expect(client.smb3).to be false
|
378
988
|
end
|
379
989
|
|
380
990
|
it 'sets whether or not signing is required' do
|
@@ -383,67 +993,427 @@ RSpec.describe RubySMB::Client do
|
|
383
993
|
expect(client.signing_required).to be true
|
384
994
|
end
|
385
995
|
|
386
|
-
it 'sets #dialect to the negotiated dialect' do
|
387
|
-
smb1_extended_response.dialects = [
|
388
|
-
RubySMB::SMB1::Dialect.new(dialect_string: 'A'),
|
389
|
-
RubySMB::SMB1::Dialect.new(dialect_string: 'B'),
|
390
|
-
RubySMB::SMB1::Dialect.new(dialect_string: 'C'),
|
391
|
-
]
|
392
|
-
smb1_extended_response.parameter_block.dialect_index = 1
|
393
|
-
client.parse_negotiate_response(smb1_extended_response)
|
394
|
-
expect(client.dialect).to eq 'B'
|
395
|
-
end
|
996
|
+
it 'sets #dialect to the negotiated dialect' do
|
997
|
+
smb1_extended_response.dialects = [
|
998
|
+
RubySMB::SMB1::Dialect.new(dialect_string: 'A'),
|
999
|
+
RubySMB::SMB1::Dialect.new(dialect_string: 'B'),
|
1000
|
+
RubySMB::SMB1::Dialect.new(dialect_string: 'C'),
|
1001
|
+
]
|
1002
|
+
smb1_extended_response.parameter_block.dialect_index = 1
|
1003
|
+
client.parse_negotiate_response(smb1_extended_response)
|
1004
|
+
expect(client.dialect).to eq 'B'
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
it 'returns the string \'SMB1\'' do
|
1008
|
+
expect(client.parse_negotiate_response(smb1_extended_response)).to eq ('SMB1')
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
it 'sets #negotiated_smb_version to 1' do
|
1012
|
+
client.parse_negotiate_response(smb1_extended_response)
|
1013
|
+
expect(client.negotiated_smb_version).to eq(1)
|
1014
|
+
end
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
context 'when SMB2 was negotiated' do
|
1018
|
+
it 'turns off SMB1 and SMB3 support' do
|
1019
|
+
client.parse_negotiate_response(smb2_response)
|
1020
|
+
expect(client.smb1).to be false
|
1021
|
+
expect(client.smb3).to be false
|
1022
|
+
end
|
1023
|
+
|
1024
|
+
it 'sets whether or not signing is required' do
|
1025
|
+
smb2_response.security_mode.signing_required = 1
|
1026
|
+
client.parse_negotiate_response(smb2_response)
|
1027
|
+
expect(client.signing_required).to be true
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
it 'sets #dialect to the negotiated dialect' do
|
1031
|
+
smb2_response.dialect_revision = 2
|
1032
|
+
client.parse_negotiate_response(smb2_response)
|
1033
|
+
expect(client.dialect).to eq '0x0002'
|
1034
|
+
end
|
1035
|
+
|
1036
|
+
it 'returns the string \'SMB2\'' do
|
1037
|
+
expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
|
1038
|
+
end
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
context 'when SMB3 was negotiated' do
|
1042
|
+
it 'turns off SMB1 and SMB2 support' do
|
1043
|
+
client.parse_negotiate_response(smb3_response)
|
1044
|
+
expect(client.smb1).to be false
|
1045
|
+
expect(client.smb2).to be false
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
it 'sets whether or not signing is required' do
|
1049
|
+
smb3_response.security_mode.signing_required = 1
|
1050
|
+
client.parse_negotiate_response(smb3_response)
|
1051
|
+
expect(client.signing_required).to be true
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
it 'sets #dialect to the negotiated dialect' do
|
1055
|
+
client.parse_negotiate_response(smb3_response)
|
1056
|
+
expect(client.dialect).to eq '0x0300'
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
it 'returns the string \'SMB2\'' do
|
1060
|
+
expect(client.parse_negotiate_response(smb3_response)).to eq ('SMB3')
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
context 'when the server supports encryption' do
|
1064
|
+
before :example do
|
1065
|
+
smb3_response.capabilities.encryption = 1
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
it 'keeps session encryption enabled if it was already' do
|
1069
|
+
client.session_encrypt_data = true
|
1070
|
+
client.parse_negotiate_response(smb3_response)
|
1071
|
+
expect(client.session_encrypt_data).to be true
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
it 'keeps session encryption disabled if it was already' do
|
1075
|
+
client.session_encrypt_data = false
|
1076
|
+
client.parse_negotiate_response(smb3_response)
|
1077
|
+
expect(client.session_encrypt_data).to be false
|
1078
|
+
end
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
context 'when the server does not support encryption' do
|
1082
|
+
before :example do
|
1083
|
+
smb3_response.capabilities.encryption = 0
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
it 'disables session encryption if it was already enabled' do
|
1087
|
+
client.session_encrypt_data = true
|
1088
|
+
client.parse_negotiate_response(smb3_response)
|
1089
|
+
expect(client.session_encrypt_data).to be false
|
1090
|
+
end
|
1091
|
+
|
1092
|
+
it 'keeps session encryption disabled if it was already' do
|
1093
|
+
client.session_encrypt_data = false
|
1094
|
+
client.parse_negotiate_response(smb3_response)
|
1095
|
+
expect(client.session_encrypt_data).to be false
|
1096
|
+
end
|
1097
|
+
end
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
context 'when the response contains the SMB2 wildcard revision number dialect' do
|
1101
|
+
it 'only turns off SMB1 support' do
|
1102
|
+
smb2_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x02ff)
|
1103
|
+
client.parse_negotiate_response(smb2_response)
|
1104
|
+
expect(client.smb1).to be false
|
1105
|
+
expect(client.smb2).to be true
|
1106
|
+
expect(client.smb3).to be true
|
1107
|
+
end
|
1108
|
+
end
|
1109
|
+
|
1110
|
+
context 'when the negotiation failed' do
|
1111
|
+
context 'with a STATUS_NOT_SUPPORTED status code' do
|
1112
|
+
before :example do
|
1113
|
+
error_packet.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED.value
|
1114
|
+
end
|
1115
|
+
|
1116
|
+
it 'raises the expected exception with SMB2' do
|
1117
|
+
expect { smb2_client.parse_negotiate_response(error_packet) }.to raise_error(
|
1118
|
+
RubySMB::Error::NegotiationFailure,
|
1119
|
+
'Unable to negotiate with remote host, SMB2 not supported'
|
1120
|
+
)
|
1121
|
+
end
|
1122
|
+
|
1123
|
+
it 'raises the expected exception with SMB3' do
|
1124
|
+
expect { smb3_client.parse_negotiate_response(error_packet) }.to raise_error(
|
1125
|
+
RubySMB::Error::NegotiationFailure,
|
1126
|
+
'Unable to negotiate with remote host, SMB3 not supported'
|
1127
|
+
)
|
1128
|
+
end
|
1129
|
+
end
|
1130
|
+
|
1131
|
+
context 'with an unknown status code' do
|
1132
|
+
it 'raises the expected exception' do
|
1133
|
+
expect { client.parse_negotiate_response(empty_packet) }.to raise_error(
|
1134
|
+
RubySMB::Error::NegotiationFailure,
|
1135
|
+
'Unable to negotiate with remote host'
|
1136
|
+
)
|
1137
|
+
end
|
1138
|
+
end
|
1139
|
+
end
|
1140
|
+
end
|
1141
|
+
|
1142
|
+
describe '#negotiate' do
|
1143
|
+
let(:request_packet) { client.smb1_negotiate_request }
|
1144
|
+
before :example do
|
1145
|
+
allow(client).to receive(:negotiate_request)
|
1146
|
+
allow(client).to receive(:send_recv)
|
1147
|
+
allow(client).to receive(:negotiate_response)
|
1148
|
+
allow(client).to receive(:parse_negotiate_response)
|
1149
|
+
end
|
1150
|
+
|
1151
|
+
it 'calls the backing methods' do
|
1152
|
+
expect(client).to receive(:negotiate_request)
|
1153
|
+
expect(client).to receive(:send_recv)
|
1154
|
+
expect(client).to receive(:negotiate_response)
|
1155
|
+
expect(client).to receive(:parse_negotiate_response)
|
1156
|
+
client.negotiate
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
context 'with SMB1' do
|
1160
|
+
it 'sets the response-packet #dialects array with the dialects sent in the request' do
|
1161
|
+
request_packet = client.smb1_negotiate_request
|
1162
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1163
|
+
allow(client).to receive(:negotiate_response).and_return(smb1_extended_response)
|
1164
|
+
expect(smb1_extended_response).to receive(:dialects=).with(request_packet.dialects)
|
1165
|
+
client.negotiate
|
1166
|
+
end
|
1167
|
+
end
|
1168
|
+
|
1169
|
+
['0x0300', '0x0302'].each do |dialect|
|
1170
|
+
context "with #{dialect} dialect" do
|
1171
|
+
before :example do
|
1172
|
+
client.dialect = dialect
|
1173
|
+
end
|
1174
|
+
|
1175
|
+
it 'sets the expected encryption algorithm' do
|
1176
|
+
client.negotiate
|
1177
|
+
expect(client.encryption_algorithm).to eq(RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM])
|
1178
|
+
end
|
1179
|
+
end
|
1180
|
+
end
|
1181
|
+
|
1182
|
+
context "with 0x0311 dialect" do
|
1183
|
+
it 'calls #parse_smb3_encryption_data' do
|
1184
|
+
client.dialect = '0x0311'
|
1185
|
+
request_packet = client.smb2_3_negotiate_request
|
1186
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1187
|
+
allow(client).to receive(:negotiate_response).and_return(smb3_response)
|
1188
|
+
expect(client).to receive(:parse_smb3_encryption_data).with(request_packet, smb3_response)
|
1189
|
+
client.negotiate
|
1190
|
+
end
|
1191
|
+
end
|
1192
|
+
|
1193
|
+
context 'with a wildcard revision number response' do
|
1194
|
+
before :example do
|
1195
|
+
client.dialect = '0x02ff'
|
1196
|
+
allow(client).to receive(:smb2_message_id=) do
|
1197
|
+
client.dialect = '0x0202'
|
1198
|
+
end
|
1199
|
+
end
|
1200
|
+
|
1201
|
+
it 'increments the message ID' do
|
1202
|
+
expect(client).to receive(:smb2_message_id=).with(1)
|
1203
|
+
client.negotiate
|
1204
|
+
end
|
1205
|
+
|
1206
|
+
it 're-negotiates' do
|
1207
|
+
expect(client).to receive(:negotiate_request).twice
|
1208
|
+
expect(client).to receive(:send_recv).twice
|
1209
|
+
expect(client).to receive(:negotiate_response).twice
|
1210
|
+
expect(client).to receive(:parse_negotiate_response).twice
|
1211
|
+
client.negotiate
|
1212
|
+
end
|
1213
|
+
end
|
1214
|
+
|
1215
|
+
context 'when an error occurs' do
|
1216
|
+
before :example do
|
1217
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1218
|
+
allow(client).to receive(:send_recv).and_raise(RubySMB::Error::InvalidPacket)
|
1219
|
+
client.smb1 = false
|
1220
|
+
client.smb2 = false
|
1221
|
+
client.smb3 = false
|
1222
|
+
end
|
1223
|
+
|
1224
|
+
context 'with SMB1' do
|
1225
|
+
let(:request_packet) { client.smb1_negotiate_request }
|
1226
|
+
|
1227
|
+
it 'raise the expected exception' do
|
1228
|
+
client.smb1 = true
|
1229
|
+
expect { client.negotiate }.to raise_error(
|
1230
|
+
RubySMB::Error::NegotiationFailure,
|
1231
|
+
"Unable to negotiate SMB1 with the remote host: RubySMB::Error::InvalidPacket"
|
1232
|
+
)
|
1233
|
+
end
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
context 'with SMB2' do
|
1237
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
1238
|
+
|
1239
|
+
it 'raise the expected exception' do
|
1240
|
+
client.smb2 = true
|
1241
|
+
expect { client.negotiate }.to raise_error(
|
1242
|
+
RubySMB::Error::NegotiationFailure,
|
1243
|
+
"Unable to negotiate SMB2 with the remote host: RubySMB::Error::InvalidPacket"
|
1244
|
+
)
|
1245
|
+
end
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
context 'with SMB3' do
|
1249
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
396
1250
|
|
397
|
-
|
398
|
-
|
1251
|
+
it 'raise the expected exception' do
|
1252
|
+
client.smb3 = true
|
1253
|
+
expect { client.negotiate }.to raise_error(
|
1254
|
+
RubySMB::Error::NegotiationFailure,
|
1255
|
+
"Unable to negotiate SMB3 with the remote host: RubySMB::Error::InvalidPacket"
|
1256
|
+
)
|
1257
|
+
end
|
399
1258
|
end
|
400
1259
|
end
|
401
1260
|
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
1261
|
+
describe '#parse_smb3_encryption_data' do
|
1262
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
1263
|
+
let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311) }
|
1264
|
+
let(:nc_encryption) do
|
1265
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1266
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1267
|
+
)
|
1268
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
|
1269
|
+
nc
|
406
1270
|
end
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
1271
|
+
let(:nc_integrity) do
|
1272
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1273
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1274
|
+
)
|
1275
|
+
nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
1276
|
+
nc
|
412
1277
|
end
|
413
1278
|
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
1279
|
+
before :example do
|
1280
|
+
allow(smb3_client).to receive(:update_preauth_hash)
|
1281
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1282
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
418
1283
|
end
|
419
1284
|
|
420
|
-
|
421
|
-
|
1285
|
+
context 'when selecting the integrity hash algorithm' do
|
1286
|
+
context 'with one algorithm' do
|
1287
|
+
it 'selects the expected algorithm' do
|
1288
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1289
|
+
expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
|
1290
|
+
end
|
1291
|
+
end
|
1292
|
+
|
1293
|
+
context 'with multiple algorithms' do
|
1294
|
+
it 'selects the first algorithm' do
|
1295
|
+
nc = smb3_response.find_negotiate_context(
|
1296
|
+
RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1297
|
+
)
|
1298
|
+
nc.data.hash_algorithms << 3
|
1299
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1300
|
+
expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
|
1301
|
+
end
|
1302
|
+
end
|
1303
|
+
|
1304
|
+
context 'without integrity negotiate context' do
|
1305
|
+
it 'raises the expected exception' do
|
1306
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1307
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1308
|
+
expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
|
1309
|
+
RubySMB::Error::EncryptionError,
|
1310
|
+
'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
|
1311
|
+
)
|
1312
|
+
end
|
1313
|
+
end
|
1314
|
+
|
1315
|
+
context 'with an unknown integrity hash algorithm' do
|
1316
|
+
it 'raises the expected exception' do
|
1317
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1318
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1319
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1320
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1321
|
+
)
|
1322
|
+
nc.data.hash_algorithms << 5
|
1323
|
+
smb3_response.add_negotiate_context(nc)
|
1324
|
+
expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
|
1325
|
+
RubySMB::Error::EncryptionError,
|
1326
|
+
'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
|
1327
|
+
)
|
1328
|
+
end
|
1329
|
+
end
|
422
1330
|
end
|
423
|
-
end
|
424
|
-
end
|
425
1331
|
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
end
|
1332
|
+
context 'when selecting the encryption algorithm' do
|
1333
|
+
context 'with one algorithm' do
|
1334
|
+
it 'selects the expected algorithm' do
|
1335
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1336
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
|
1337
|
+
end
|
1338
|
+
end
|
434
1339
|
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
1340
|
+
context 'with multiple algorithms' do
|
1341
|
+
it 'selects the AES-128-GCM algorithm if included' do
|
1342
|
+
nc = smb3_response.find_negotiate_context(
|
1343
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1344
|
+
)
|
1345
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
1346
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1347
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-GCM')
|
1348
|
+
end
|
1349
|
+
|
1350
|
+
it 'selects the first algorithm if AES-128-GCM is not included' do
|
1351
|
+
nc = smb3_response.find_negotiate_context(
|
1352
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1353
|
+
)
|
1354
|
+
nc.data.ciphers << 3
|
1355
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1356
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
|
1357
|
+
end
|
1358
|
+
|
1359
|
+
it 'keep tracks of the server supported algorithms' do
|
1360
|
+
nc = smb3_response.find_negotiate_context(
|
1361
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1362
|
+
)
|
1363
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
1364
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1365
|
+
expect(smb3_client.server_encryption_algorithms).to eq([1, 2])
|
1366
|
+
end
|
1367
|
+
end
|
1368
|
+
|
1369
|
+
context 'without encryption context' do
|
1370
|
+
it 'raises the expected exception' do
|
1371
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1372
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
1373
|
+
expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
|
1374
|
+
RubySMB::Error::EncryptionError,
|
1375
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
1376
|
+
)
|
1377
|
+
end
|
1378
|
+
end
|
1379
|
+
|
1380
|
+
context 'with an unknown encryption algorithm' do
|
1381
|
+
it 'raises the expected exception' do
|
1382
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1383
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
1384
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1385
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1386
|
+
)
|
1387
|
+
nc.data.ciphers << 14
|
1388
|
+
smb3_response.add_negotiate_context(nc)
|
1389
|
+
expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
|
1390
|
+
RubySMB::Error::EncryptionError,
|
1391
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
1392
|
+
)
|
1393
|
+
end
|
1394
|
+
end
|
1395
|
+
end
|
1396
|
+
|
1397
|
+
context 'when selecting the compression algorithm' do
|
1398
|
+
it 'keep tracks of the server supported algorithms' do
|
1399
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1400
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
1401
|
+
)
|
1402
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
|
1403
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
|
1404
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
|
1405
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
|
1406
|
+
smb3_response.add_negotiate_context(nc)
|
1407
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1408
|
+
expect(smb3_client.server_compression_algorithms).to eq([1, 2, 3, 4])
|
1409
|
+
end
|
1410
|
+
end
|
443
1411
|
|
444
|
-
|
445
|
-
|
446
|
-
|
1412
|
+
it 'updates the preauth hash' do
|
1413
|
+
expect(smb3_client).to receive(:update_preauth_hash).with(request_packet)
|
1414
|
+
expect(smb3_client).to receive(:update_preauth_hash).with(smb3_response)
|
1415
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1416
|
+
end
|
447
1417
|
end
|
448
1418
|
end
|
449
1419
|
end
|
@@ -624,7 +1594,7 @@ RSpec.describe RubySMB::Client do
|
|
624
1594
|
expect { smb1_client.smb1_ntlmssp_challenge_packet(response.to_binary_s) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
|
625
1595
|
end
|
626
1596
|
|
627
|
-
it '
|
1597
|
+
it 'raise an InvalidPacket exception when the response is not valid' do
|
628
1598
|
expect { smb1_client.smb1_ntlmssp_challenge_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
629
1599
|
end
|
630
1600
|
end
|
@@ -645,7 +1615,7 @@ RSpec.describe RubySMB::Client do
|
|
645
1615
|
expect(smb1_client.smb1_ntlmssp_final_packet(response.to_binary_s)).to eq response
|
646
1616
|
end
|
647
1617
|
|
648
|
-
it '
|
1618
|
+
it 'raise an InvalidPacket exception when the response is not valid' do
|
649
1619
|
expect { smb1_client.smb1_ntlmssp_final_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
650
1620
|
end
|
651
1621
|
end
|
@@ -717,12 +1687,7 @@ RSpec.describe RubySMB::Client do
|
|
717
1687
|
expect(smb1_client.smb1_anonymous_auth_response(anonymous_response.to_binary_s)).to eq anonymous_response
|
718
1688
|
end
|
719
1689
|
|
720
|
-
it '
|
721
|
-
empty_packet.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP
|
722
|
-
expect(smb1_client.smb1_anonymous_auth_response(empty_packet.to_binary_s)).to eq empty_packet
|
723
|
-
end
|
724
|
-
|
725
|
-
it 'raises an InvalidPacket error if the command is wrong' do
|
1690
|
+
it 'raise an InvalidPacket exception when the response is not valid' do
|
726
1691
|
anonymous_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_NEGOTIATE
|
727
1692
|
expect { smb1_client.smb1_anonymous_auth_response(anonymous_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
728
1693
|
end
|
@@ -775,6 +1740,39 @@ RSpec.describe RubySMB::Client do
|
|
775
1740
|
smb2_client.smb2_authenticate
|
776
1741
|
expect(smb2_client.os_version).to eq '6.1.7601'
|
777
1742
|
end
|
1743
|
+
|
1744
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1745
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1746
|
+
smb2_client.dialect = dialect
|
1747
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1748
|
+
smb2_client.smb2_authenticate
|
1749
|
+
end
|
1750
|
+
end
|
1751
|
+
|
1752
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1753
|
+
smb2_client.dialect = '0x0311'
|
1754
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(response_packet)
|
1755
|
+
smb2_client.smb2_authenticate
|
1756
|
+
end
|
1757
|
+
|
1758
|
+
context 'when setting the session_encrypt_data parameter' do
|
1759
|
+
before :example do
|
1760
|
+
smb2_client.smb3 = true
|
1761
|
+
smb2_client.session_encrypt_data = false
|
1762
|
+
end
|
1763
|
+
|
1764
|
+
it 'sets the session_encrypt_data parameter to true if the server requires encryption' do
|
1765
|
+
final_response_packet.session_flags.encrypt_data = 1
|
1766
|
+
smb2_client.smb2_authenticate
|
1767
|
+
expect(smb2_client.session_encrypt_data).to be true
|
1768
|
+
end
|
1769
|
+
|
1770
|
+
it 'does not set the session_encrypt_data parameter if the server does not require encryption' do
|
1771
|
+
final_response_packet.session_flags.encrypt_data = 0
|
1772
|
+
smb2_client.smb2_authenticate
|
1773
|
+
expect(smb2_client.session_encrypt_data).to be false
|
1774
|
+
end
|
1775
|
+
end
|
778
1776
|
end
|
779
1777
|
|
780
1778
|
describe '#smb2_ntlmssp_negotiate_packet' do
|
@@ -790,20 +1788,34 @@ RSpec.describe RubySMB::Client do
|
|
790
1788
|
smb2_client.smb2_ntlmssp_negotiate_packet
|
791
1789
|
end
|
792
1790
|
|
793
|
-
it '
|
794
|
-
expect(smb2_client.smb2_ntlmssp_negotiate_packet.
|
795
|
-
end
|
796
|
-
|
797
|
-
it 'increments client#smb2_message_id' do
|
798
|
-
expect { smb2_client.smb2_ntlmssp_negotiate_packet }.to change(smb2_client, :smb2_message_id).to(2)
|
1791
|
+
it 'enables signing' do
|
1792
|
+
expect(smb2_client.smb2_ntlmssp_negotiate_packet.security_mode.signing_enabled).to eq 1
|
799
1793
|
end
|
800
1794
|
end
|
801
1795
|
|
802
1796
|
describe '#smb2_ntlmssp_negotiate' do
|
1797
|
+
before :example do
|
1798
|
+
allow(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
|
1799
|
+
allow(smb2_client).to receive(:send_recv)
|
1800
|
+
end
|
1801
|
+
|
803
1802
|
it 'sends the request packet and receives a response' do
|
804
|
-
expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
|
805
|
-
expect(
|
806
|
-
|
1803
|
+
expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
|
1804
|
+
expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
|
1805
|
+
smb2_client.smb2_ntlmssp_negotiate
|
1806
|
+
end
|
1807
|
+
|
1808
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1809
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1810
|
+
smb2_client.dialect = dialect
|
1811
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1812
|
+
smb2_client.smb2_ntlmssp_negotiate
|
1813
|
+
end
|
1814
|
+
end
|
1815
|
+
|
1816
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1817
|
+
smb2_client.dialect = '0x0311'
|
1818
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
|
807
1819
|
smb2_client.smb2_ntlmssp_negotiate
|
808
1820
|
end
|
809
1821
|
end
|
@@ -829,7 +1841,7 @@ RSpec.describe RubySMB::Client do
|
|
829
1841
|
expect { smb2_client.smb2_ntlmssp_challenge_packet(response.to_binary_s) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
|
830
1842
|
end
|
831
1843
|
|
832
|
-
it '
|
1844
|
+
it 'raise an InvalidPacket exception when the response is not valid' do
|
833
1845
|
expect { smb2_client.smb2_ntlmssp_challenge_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
834
1846
|
end
|
835
1847
|
end
|
@@ -861,13 +1873,35 @@ RSpec.describe RubySMB::Client do
|
|
861
1873
|
it 'sets the session ID on the request packet' do
|
862
1874
|
expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).smb2_header.session_id).to eq session_id
|
863
1875
|
end
|
1876
|
+
|
1877
|
+
it 'enables signing' do
|
1878
|
+
expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).security_mode.signing_enabled).to eq 1
|
1879
|
+
end
|
864
1880
|
end
|
865
1881
|
|
866
1882
|
describe '#smb2_ntlmssp_authenticate' do
|
1883
|
+
before :example do
|
1884
|
+
allow(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
|
1885
|
+
allow(smb2_client).to receive(:send_recv)
|
1886
|
+
end
|
1887
|
+
|
867
1888
|
it 'sends the request packet and receives a response' do
|
868
|
-
expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
|
869
|
-
expect(
|
870
|
-
|
1889
|
+
expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
|
1890
|
+
expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
|
1891
|
+
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
1892
|
+
end
|
1893
|
+
|
1894
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1895
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1896
|
+
smb2_client.dialect = dialect
|
1897
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1898
|
+
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
1899
|
+
end
|
1900
|
+
end
|
1901
|
+
|
1902
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1903
|
+
smb2_client.dialect = '0x0311'
|
1904
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
|
871
1905
|
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
872
1906
|
end
|
873
1907
|
end
|
@@ -888,7 +1922,7 @@ RSpec.describe RubySMB::Client do
|
|
888
1922
|
expect(smb2_client.smb2_ntlmssp_final_packet(response.to_binary_s)).to eq response
|
889
1923
|
end
|
890
1924
|
|
891
|
-
it '
|
1925
|
+
it 'raise an InvalidPacket exception when the response is not valid' do
|
892
1926
|
expect { smb2_client.smb2_ntlmssp_final_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
893
1927
|
end
|
894
1928
|
end
|
@@ -1003,6 +2037,108 @@ RSpec.describe RubySMB::Client do
|
|
1003
2037
|
end
|
1004
2038
|
end
|
1005
2039
|
end
|
2040
|
+
|
2041
|
+
describe '#smb3_sign' do
|
2042
|
+
context 'if signing is required and we have a session key' do
|
2043
|
+
let(:request) {
|
2044
|
+
packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
2045
|
+
packet.smb2_header.flags.signed = 1
|
2046
|
+
packet.smb2_header.signature = "\x00" * 16
|
2047
|
+
packet
|
2048
|
+
}
|
2049
|
+
let(:session_key) { 'Session Key' }
|
2050
|
+
before :example do
|
2051
|
+
smb3_client.session_key = session_key
|
2052
|
+
smb3_client.signing_required = true
|
2053
|
+
end
|
2054
|
+
|
2055
|
+
['0x0300', '0x0302'].each do |dialect|
|
2056
|
+
context "with #{dialect} dialect" do
|
2057
|
+
it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
|
2058
|
+
smb3_client.dialect = dialect
|
2059
|
+
fake_hash = "\x34\xc0\x40\xfe\x87\xcf\x49\x3d\x37\x87\x52\xd0\xd5\xf5\xfb\x86".b
|
2060
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
|
2061
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
|
2062
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2063
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2064
|
+
end
|
2065
|
+
end
|
2066
|
+
end
|
2067
|
+
|
2068
|
+
context "with 0x0311 dialect" do
|
2069
|
+
it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
|
2070
|
+
smb3_client.dialect = '0x0311'
|
2071
|
+
preauth_integrity_hash_value = 'Preauth Integrity Hash'
|
2072
|
+
fake_hash = "\x0e\x49\x6f\x8e\x74\x7c\xf2\xa0\x88\x5e\x9d\x54\xff\x0d\x0d\xfa".b
|
2073
|
+
smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
|
2074
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
|
2075
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
|
2076
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2077
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2078
|
+
end
|
2079
|
+
end
|
2080
|
+
|
2081
|
+
context 'with an incompatible dialect' do
|
2082
|
+
it 'raises the expected exception' do
|
2083
|
+
smb3_client.dialect = '0x0202'
|
2084
|
+
expect { smb3_client.smb3_sign(request) }.to raise_error(
|
2085
|
+
RubySMB::Error::SigningError,
|
2086
|
+
'Dialect is incompatible with SMBv3 signing'
|
2087
|
+
)
|
2088
|
+
end
|
2089
|
+
end
|
2090
|
+
end
|
2091
|
+
|
2092
|
+
context 'if signing is not required but it is a TreeConnectRequest and we have a session key' do
|
2093
|
+
let(:request) {
|
2094
|
+
packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
|
2095
|
+
packet.smb2_header.flags.signed = 1
|
2096
|
+
packet.smb2_header.signature = "\x00" * 16
|
2097
|
+
packet
|
2098
|
+
}
|
2099
|
+
let(:session_key) { 'Session Key' }
|
2100
|
+
before :example do
|
2101
|
+
smb3_client.session_key = session_key
|
2102
|
+
smb3_client.signing_required = false
|
2103
|
+
end
|
2104
|
+
|
2105
|
+
['0x0300', '0x0302'].each do |dialect|
|
2106
|
+
context "with #{dialect} dialect" do
|
2107
|
+
it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
|
2108
|
+
smb3_client.dialect = dialect
|
2109
|
+
fake_hash = "\x34\x9e\x28\xb9\x50\x08\x34\x31\xc0\x83\x9d\xba\x56\xa5\x70\xa4".b
|
2110
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
|
2111
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
|
2112
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2113
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2114
|
+
end
|
2115
|
+
end
|
2116
|
+
end
|
2117
|
+
|
2118
|
+
context "with 0x0311 dialect" do
|
2119
|
+
it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
|
2120
|
+
smb3_client.dialect = '0x0311'
|
2121
|
+
preauth_integrity_hash_value = 'Preauth Integrity Hash'
|
2122
|
+
fake_hash = "\x83\xd9\x31\x39\x60\x46\xbe\x1e\x29\x34\xc8\xcf\x8c\x8e\xb4\x73".b
|
2123
|
+
smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
|
2124
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
|
2125
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
|
2126
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2127
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2128
|
+
end
|
2129
|
+
end
|
2130
|
+
|
2131
|
+
context 'with an incompatible dialect' do
|
2132
|
+
it 'raises the expected exception' do
|
2133
|
+
smb3_client.dialect = '0x0202'
|
2134
|
+
expect { smb3_client.smb3_sign(request) }.to raise_error(
|
2135
|
+
RubySMB::Error::SigningError,
|
2136
|
+
'Dialect is incompatible with SMBv3 signing'
|
2137
|
+
)
|
2138
|
+
end
|
2139
|
+
end
|
2140
|
+
end
|
2141
|
+
end
|
1006
2142
|
end
|
1007
2143
|
|
1008
2144
|
context '#increment_smb_message_id' do
|
@@ -1056,7 +2192,10 @@ RSpec.describe RubySMB::Client do
|
|
1056
2192
|
|
1057
2193
|
it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
|
1058
2194
|
response.smb_header.nt_status = 0xc0000015
|
1059
|
-
expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
|
2195
|
+
expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
|
2196
|
+
RubySMB::Error::UnexpectedStatusCode,
|
2197
|
+
'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
|
2198
|
+
)
|
1060
2199
|
end
|
1061
2200
|
|
1062
2201
|
it 'creates a new Tree from itself, the share path, and the response packet' do
|
@@ -1077,11 +2216,14 @@ RSpec.describe RubySMB::Client do
|
|
1077
2216
|
}
|
1078
2217
|
|
1079
2218
|
describe '#smb2_tree_connect' do
|
1080
|
-
it 'builds and sends
|
2219
|
+
it 'builds and sends the expected TreeconnectRequest for the supplied share' do
|
1081
2220
|
allow(RubySMB::SMB2::Packet::TreeConnectRequest).to receive(:new).and_return(request)
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
2221
|
+
expect(smb2_client).to receive(:send_recv) do |req|
|
2222
|
+
expect(req).to eq(request)
|
2223
|
+
expect(req.smb2_header.tree_id).to eq(65_535)
|
2224
|
+
expect(req.path).to eq(path.encode('UTF-16LE'))
|
2225
|
+
response.to_binary_s
|
2226
|
+
end
|
1085
2227
|
smb2_client.smb2_tree_connect(path)
|
1086
2228
|
end
|
1087
2229
|
|
@@ -1100,11 +2242,20 @@ RSpec.describe RubySMB::Client do
|
|
1100
2242
|
|
1101
2243
|
it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
|
1102
2244
|
response.smb2_header.nt_status = 0xc0000015
|
1103
|
-
expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
|
2245
|
+
expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
|
2246
|
+
RubySMB::Error::UnexpectedStatusCode,
|
2247
|
+
'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
|
2248
|
+
)
|
1104
2249
|
end
|
1105
2250
|
|
1106
2251
|
it 'creates a new Tree from itself, the share path, and the response packet' do
|
1107
|
-
expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response)
|
2252
|
+
expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: false)
|
2253
|
+
smb2_client.smb2_tree_from_response(path, response)
|
2254
|
+
end
|
2255
|
+
|
2256
|
+
it 'creates a new with encryption set if the response requires it' do
|
2257
|
+
response.share_flags.encrypt = 1
|
2258
|
+
expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: true)
|
1108
2259
|
smb2_client.smb2_tree_from_response(path, response)
|
1109
2260
|
end
|
1110
2261
|
end
|
@@ -1199,6 +2350,12 @@ RSpec.describe RubySMB::Client do
|
|
1199
2350
|
expect(smb1_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
|
1200
2351
|
expect(smb1_client.echo).to eq WindowsError::NTStatus::STATUS_ABANDONED
|
1201
2352
|
end
|
2353
|
+
|
2354
|
+
it 'raise an InvalidPacket exception when the response is not valid' do
|
2355
|
+
echo_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX
|
2356
|
+
allow(smb1_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
|
2357
|
+
expect { smb1_client.echo }.to raise_error(RubySMB::Error::InvalidPacket)
|
2358
|
+
end
|
1202
2359
|
end
|
1203
2360
|
|
1204
2361
|
context 'with SMB2' do
|
@@ -1210,8 +2367,355 @@ RSpec.describe RubySMB::Client do
|
|
1210
2367
|
expect(smb2_client).to receive(:send_recv).with(echo_request).and_return(echo_response.to_binary_s)
|
1211
2368
|
expect(smb2_client.smb2_echo).to eq echo_response
|
1212
2369
|
end
|
2370
|
+
|
2371
|
+
it 'raise an InvalidPacket exception when the response is not valid' do
|
2372
|
+
echo_response.smb2_header.command = RubySMB::SMB2::Commands::SESSION_SETUP
|
2373
|
+
allow(smb2_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
|
2374
|
+
expect { smb2_client.smb2_echo }.to raise_error(RubySMB::Error::InvalidPacket)
|
2375
|
+
end
|
2376
|
+
end
|
2377
|
+
end
|
2378
|
+
|
2379
|
+
context 'Winreg' do
|
2380
|
+
describe '#connect_to_winreg' do
|
2381
|
+
let(:host) { '1.2.3.4' }
|
2382
|
+
let(:share) { "\\\\#{host}\\IPC$" }
|
2383
|
+
let(:ipc_tree) { double('IPC$ tree') }
|
2384
|
+
let(:named_pipe) { double('Named pipe') }
|
2385
|
+
before :example do
|
2386
|
+
allow(ipc_tree).to receive_messages(
|
2387
|
+
:share => share,
|
2388
|
+
:open_file => named_pipe
|
2389
|
+
)
|
2390
|
+
allow(client).to receive(:tree_connect).and_return(ipc_tree)
|
2391
|
+
end
|
2392
|
+
|
2393
|
+
context 'when the client is already connected to the IPC$ share' do
|
2394
|
+
before :example do
|
2395
|
+
client.tree_connects << ipc_tree
|
2396
|
+
allow(ipc_tree).to receive(:share).and_return(share)
|
2397
|
+
end
|
2398
|
+
|
2399
|
+
it 'does not connect to the already connected tree' do
|
2400
|
+
client.connect_to_winreg(host)
|
2401
|
+
expect(client).to_not have_received(:tree_connect)
|
2402
|
+
end
|
2403
|
+
end
|
2404
|
+
|
2405
|
+
it 'calls #tree_connect' do
|
2406
|
+
client.connect_to_winreg(host)
|
2407
|
+
expect(client).to have_received(:tree_connect).with(share)
|
2408
|
+
end
|
2409
|
+
|
2410
|
+
it 'open \'winreg\' file on the IPC$ Tree' do
|
2411
|
+
client.connect_to_winreg(host)
|
2412
|
+
expect(ipc_tree).to have_received(:open_file).with(filename: "winreg", write: true, read: true)
|
2413
|
+
end
|
2414
|
+
|
2415
|
+
it 'returns the expected opened named pipe' do
|
2416
|
+
expect(client.connect_to_winreg(host)).to eq(named_pipe)
|
2417
|
+
end
|
2418
|
+
|
2419
|
+
context 'when a block is given' do
|
2420
|
+
before :example do
|
2421
|
+
allow(named_pipe).to receive(:close)
|
2422
|
+
end
|
2423
|
+
|
2424
|
+
it 'yields the expected named_pipe' do
|
2425
|
+
client.connect_to_winreg(host) do |np|
|
2426
|
+
expect(np).to eq(named_pipe)
|
2427
|
+
end
|
2428
|
+
end
|
2429
|
+
|
2430
|
+
it 'closes the named pipe' do
|
2431
|
+
client.connect_to_winreg(host) { |np| }
|
2432
|
+
expect(named_pipe).to have_received(:close)
|
2433
|
+
end
|
2434
|
+
|
2435
|
+
it 'returns the block return value' do
|
2436
|
+
result = double('Result')
|
2437
|
+
expect(client.connect_to_winreg(host) { |np| result }).to eq(result)
|
2438
|
+
end
|
2439
|
+
end
|
2440
|
+
end
|
2441
|
+
|
2442
|
+
describe '#has_registry_key?' do
|
2443
|
+
let(:host) { '1.2.3.4' }
|
2444
|
+
let(:key) { 'HKLM\\Registry\\Key' }
|
2445
|
+
let(:named_pipe) { double('Named pipe') }
|
2446
|
+
let(:result) { double('Result') }
|
2447
|
+
before :example do
|
2448
|
+
allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
|
2449
|
+
allow(named_pipe).to receive(:has_registry_key?).and_return(result)
|
2450
|
+
end
|
2451
|
+
|
2452
|
+
it 'calls #connect_to_winreg to wrap the main logic around' do
|
2453
|
+
client.has_registry_key?(host, key)
|
2454
|
+
expect(client).to have_received(:connect_to_winreg).with(host)
|
2455
|
+
end
|
2456
|
+
|
2457
|
+
it 'calls Pipe #has_registry_key?' do
|
2458
|
+
client.has_registry_key?(host, key)
|
2459
|
+
expect(named_pipe).to have_received(:has_registry_key?).with(key)
|
2460
|
+
end
|
2461
|
+
end
|
2462
|
+
|
2463
|
+
describe '#read_registry_key_value' do
|
2464
|
+
let(:host) { '1.2.3.4' }
|
2465
|
+
let(:key) { 'HKLM\\Registry\\Key' }
|
2466
|
+
let(:value_name) { 'Value' }
|
2467
|
+
let(:named_pipe) { double('Named pipe') }
|
2468
|
+
let(:result) { double('Result') }
|
2469
|
+
before :example do
|
2470
|
+
allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
|
2471
|
+
allow(named_pipe).to receive(:read_registry_key_value).and_return(result)
|
2472
|
+
end
|
2473
|
+
|
2474
|
+
it 'calls #connect_to_winreg to wrap the main logic around' do
|
2475
|
+
client.read_registry_key_value(host, key, value_name)
|
2476
|
+
expect(client).to have_received(:connect_to_winreg).with(host)
|
2477
|
+
end
|
2478
|
+
|
2479
|
+
it 'calls Pipe #read_registry_key_value' do
|
2480
|
+
client.read_registry_key_value(host, key, value_name)
|
2481
|
+
expect(named_pipe).to have_received(:read_registry_key_value).with(key, value_name)
|
2482
|
+
end
|
2483
|
+
end
|
2484
|
+
|
2485
|
+
describe '#enum_registry_key' do
|
2486
|
+
let(:host) { '1.2.3.4' }
|
2487
|
+
let(:key) { 'HKLM\\Registry\\Key' }
|
2488
|
+
let(:named_pipe) { double('Named pipe') }
|
2489
|
+
let(:result) { double('Result') }
|
2490
|
+
before :example do
|
2491
|
+
allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
|
2492
|
+
allow(named_pipe).to receive(:enum_registry_key).and_return(result)
|
2493
|
+
end
|
2494
|
+
|
2495
|
+
it 'calls #connect_to_winreg to wrap the main logic around' do
|
2496
|
+
client.enum_registry_key(host, key)
|
2497
|
+
expect(client).to have_received(:connect_to_winreg).with(host)
|
2498
|
+
end
|
2499
|
+
|
2500
|
+
it 'calls Pipe #enum_registry_key' do
|
2501
|
+
client.enum_registry_key(host, key)
|
2502
|
+
expect(named_pipe).to have_received(:enum_registry_key).with(key)
|
2503
|
+
end
|
2504
|
+
end
|
2505
|
+
|
2506
|
+
describe '#enum_registry_values' do
|
2507
|
+
let(:host) { '1.2.3.4' }
|
2508
|
+
let(:key) { 'HKLM\\Registry\\Key' }
|
2509
|
+
let(:named_pipe) { double('Named pipe') }
|
2510
|
+
let(:result) { double('Result') }
|
2511
|
+
before :example do
|
2512
|
+
allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
|
2513
|
+
allow(named_pipe).to receive(:enum_registry_values).and_return(result)
|
2514
|
+
end
|
2515
|
+
|
2516
|
+
it 'calls #connect_to_winreg to wrap the main logic around' do
|
2517
|
+
client.enum_registry_values(host, key)
|
2518
|
+
expect(client).to have_received(:connect_to_winreg).with(host)
|
2519
|
+
end
|
2520
|
+
|
2521
|
+
it 'calls Pipe #enum_registry_values' do
|
2522
|
+
client.enum_registry_values(host, key)
|
2523
|
+
expect(named_pipe).to have_received(:enum_registry_values).with(key)
|
2524
|
+
end
|
1213
2525
|
end
|
1214
2526
|
end
|
1215
2527
|
|
2528
|
+
describe '#update_preauth_hash' do
|
2529
|
+
it 'raises an EncryptionError exception if the preauth integrity hash algorithm is not known' do
|
2530
|
+
expect { client.update_preauth_hash('Test') }.to raise_error(
|
2531
|
+
RubySMB::Error::EncryptionError,
|
2532
|
+
'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
|
2533
|
+
)
|
2534
|
+
end
|
2535
|
+
|
2536
|
+
it 'computes the hash value' do
|
2537
|
+
packet = RubySMB::SMB2::Packet::EchoRequest.new
|
2538
|
+
data = 'Previous hash'
|
2539
|
+
algo = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[
|
2540
|
+
RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
2541
|
+
]
|
2542
|
+
client.preauth_integrity_hash_algorithm = algo
|
2543
|
+
client.preauth_integrity_hash_value = data
|
2544
|
+
hash = OpenSSL::Digest.digest(algo, data + packet.to_binary_s)
|
2545
|
+
client.update_preauth_hash(packet)
|
2546
|
+
expect(client.preauth_integrity_hash_value).to eq(hash)
|
2547
|
+
end
|
2548
|
+
end
|
2549
|
+
|
2550
|
+
context 'Encryption' do
|
2551
|
+
describe '#smb3_encrypt' do
|
2552
|
+
let(:transform_packet) { double('TransformHeader packet') }
|
2553
|
+
let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
|
2554
|
+
let(:data) { RubySMB::SMB2::Packet::TreeConnectRequest.new.to_binary_s }
|
2555
|
+
|
2556
|
+
before :example do
|
2557
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:new).and_return(transform_packet)
|
2558
|
+
allow(transform_packet).to receive(:encrypt)
|
2559
|
+
client.session_key = session_key
|
2560
|
+
end
|
2561
|
+
|
2562
|
+
it 'does not generate a new client encryption key if it already exists' do
|
2563
|
+
client.client_encryption_key = 'key'
|
2564
|
+
expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
|
2565
|
+
expect(client.client_encryption_key).to eq('key')
|
2566
|
+
client.smb3_encrypt(data)
|
2567
|
+
end
|
2568
|
+
|
2569
|
+
['0x0300', '0x0302'].each do |dialect|
|
2570
|
+
context "with #{dialect} dialect" do
|
2571
|
+
before :example do
|
2572
|
+
client.dialect = dialect
|
2573
|
+
end
|
2574
|
+
|
2575
|
+
it 'generates the client encryption key with the expected parameters' do
|
2576
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2577
|
+
session_key,
|
2578
|
+
"SMB2AESCCM\x00",
|
2579
|
+
"ServerIn \x00"
|
2580
|
+
).and_call_original
|
2581
|
+
client.smb3_encrypt(data)
|
2582
|
+
end
|
2583
|
+
end
|
2584
|
+
end
|
2585
|
+
|
2586
|
+
context 'with 0x0311 dialect' do
|
2587
|
+
it 'generates the client encryption key with the expected parameters' do
|
2588
|
+
client.preauth_integrity_hash_value = ''
|
2589
|
+
client.dialect = '0x0311'
|
2590
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2591
|
+
session_key,
|
2592
|
+
"SMBC2SCipherKey\x00",
|
2593
|
+
''
|
2594
|
+
).and_call_original
|
2595
|
+
client.smb3_encrypt(data)
|
2596
|
+
end
|
2597
|
+
end
|
2598
|
+
|
2599
|
+
it 'raises the expected exception if the dialect is incompatible' do
|
2600
|
+
client.dialect = '0x0202'
|
2601
|
+
expect { client.smb3_encrypt(data) }.to raise_error(RubySMB::Error::EncryptionError)
|
2602
|
+
end
|
2603
|
+
|
2604
|
+
it 'creates a TransformHeader packet and encrypt the data' do
|
2605
|
+
client.dialect = '0x0300'
|
2606
|
+
client.encryption_algorithm = 'AES-128-CCM'
|
2607
|
+
client.session_id = 123
|
2608
|
+
client.smb3_encrypt(data)
|
2609
|
+
expect(RubySMB::SMB2::Packet::TransformHeader).to have_received(:new).with(flags: 1, session_id: 123)
|
2610
|
+
expect(transform_packet).to have_received(:encrypt).with(data, client.client_encryption_key, algorithm: 'AES-128-CCM')
|
2611
|
+
end
|
2612
|
+
|
2613
|
+
it 'generates the expected client encryption key with 0x0302 dialect' do
|
2614
|
+
client.dialect = '0x0302'
|
2615
|
+
expected_enc_key =
|
2616
|
+
"\xa4\xfa\x23\xc1\xb0\x65\x84\xce\x47\x08\x5b\xe0\x64\x98\xd7\x87".b
|
2617
|
+
client.smb3_encrypt(data)
|
2618
|
+
expect(client.client_encryption_key).to eq expected_enc_key
|
2619
|
+
end
|
2620
|
+
|
2621
|
+
it 'generates the expected client encryption key with 0x0311 dialect' do
|
2622
|
+
client.dialect = '0x0311'
|
2623
|
+
client.session_key =
|
2624
|
+
"\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
|
2625
|
+
client.preauth_integrity_hash_value =
|
2626
|
+
"\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
|
2627
|
+
"\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
|
2628
|
+
"\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
|
2629
|
+
"\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
|
2630
|
+
expected_enc_key =
|
2631
|
+
"\xc7\x4e\xfe\x4d\x15\x48\x5b\x0b\x71\x45\x49\x26\x8a\xd9\x6c\xaa".b
|
2632
|
+
client.smb3_encrypt(data)
|
2633
|
+
expect(client.client_encryption_key).to eq expected_enc_key
|
2634
|
+
end
|
2635
|
+
end
|
2636
|
+
|
2637
|
+
describe '#smb3_decrypt' do
|
2638
|
+
let(:transform_packet) { double('TransformHeader packet') }
|
2639
|
+
let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
|
2640
|
+
|
2641
|
+
before :example do
|
2642
|
+
allow(transform_packet).to receive(:decrypt)
|
2643
|
+
client.session_key = session_key
|
2644
|
+
end
|
2645
|
+
|
2646
|
+
it 'does not generate a new server encryption key if it already exists' do
|
2647
|
+
client.server_encryption_key = 'key'
|
2648
|
+
expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
|
2649
|
+
expect(client.server_encryption_key).to eq('key')
|
2650
|
+
client.smb3_decrypt(transform_packet)
|
2651
|
+
end
|
2652
|
+
|
2653
|
+
['0x0300', '0x0302'].each do |dialect|
|
2654
|
+
context "with #{dialect} dialect" do
|
2655
|
+
before :example do
|
2656
|
+
client.dialect = dialect
|
2657
|
+
end
|
2658
|
+
|
2659
|
+
it 'generates the client encryption key with the expected parameters' do
|
2660
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2661
|
+
session_key,
|
2662
|
+
"SMB2AESCCM\x00",
|
2663
|
+
"ServerOut\x00"
|
2664
|
+
).and_call_original
|
2665
|
+
client.smb3_decrypt(transform_packet)
|
2666
|
+
end
|
2667
|
+
end
|
2668
|
+
end
|
2669
|
+
|
2670
|
+
context 'with 0x0311 dialect' do
|
2671
|
+
it 'generates the client encryption key with the expected parameters' do
|
2672
|
+
client.preauth_integrity_hash_value = ''
|
2673
|
+
client.dialect = '0x0311'
|
2674
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2675
|
+
session_key,
|
2676
|
+
"SMBS2CCipherKey\x00",
|
2677
|
+
''
|
2678
|
+
).and_call_original
|
2679
|
+
client.smb3_decrypt(transform_packet)
|
2680
|
+
end
|
2681
|
+
end
|
2682
|
+
|
2683
|
+
it 'raises the expected exception if the dialect is incompatible' do
|
2684
|
+
client.dialect = '0x0202'
|
2685
|
+
expect { client.smb3_decrypt(transform_packet) }.to raise_error(RubySMB::Error::EncryptionError)
|
2686
|
+
end
|
2687
|
+
|
2688
|
+
it 'creates a TransformHeader packet and encrypt the data' do
|
2689
|
+
client.dialect = '0x0300'
|
2690
|
+
client.encryption_algorithm = 'AES-128-CCM'
|
2691
|
+
client.session_id = 123
|
2692
|
+
client.smb3_decrypt(transform_packet)
|
2693
|
+
expect(transform_packet).to have_received(:decrypt).with(client.server_encryption_key, algorithm: 'AES-128-CCM')
|
2694
|
+
end
|
2695
|
+
|
2696
|
+
it 'generates the expected server encryption key with 0x0302 dialect' do
|
2697
|
+
client.dialect = '0x0302'
|
2698
|
+
expected_enc_key =
|
2699
|
+
"\x65\x21\xd3\x6d\xe9\xe3\x5a\x66\x09\x61\xae\x3e\xc6\x49\x6b\xdf".b
|
2700
|
+
client.smb3_decrypt(transform_packet)
|
2701
|
+
expect(client.server_encryption_key).to eq expected_enc_key
|
2702
|
+
end
|
2703
|
+
|
2704
|
+
it 'generates the expected server encryption key with 0x0311 dialect' do
|
2705
|
+
client.dialect = '0x0311'
|
2706
|
+
client.session_key =
|
2707
|
+
"\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
|
2708
|
+
client.preauth_integrity_hash_value =
|
2709
|
+
"\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
|
2710
|
+
"\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
|
2711
|
+
"\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
|
2712
|
+
"\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
|
2713
|
+
expected_enc_key =
|
2714
|
+
"\x8c\x2c\x31\x15\x66\xba\xa9\xab\xcf\xb2\x47\x8d\x72\xd5\xd7\x4a".b
|
2715
|
+
client.smb3_decrypt(transform_packet)
|
2716
|
+
expect(client.server_encryption_key).to eq expected_enc_key
|
2717
|
+
end
|
2718
|
+
end
|
2719
|
+
end
|
1216
2720
|
end
|
1217
2721
|
|