ruby_smb 1.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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 +200 -20
- 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 +160 -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 +5 -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 +41 -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 +51 -4
- 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 +1563 -104
- 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 +3 -1
- 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,16 @@ module RubySMB
|
|
4
4
|
# An SMB2 TreeConnectResponse Packet as defined in
|
5
5
|
# [2.2.10 SMB2 TREE_CONNECT Response](https://msdn.microsoft.com/en-us/library/cc246499.aspx)
|
6
6
|
class TreeConnectResponse < RubySMB::GenericPacket
|
7
|
+
COMMAND = RubySMB::SMB2::Commands::TREE_CONNECT
|
8
|
+
|
9
|
+
# Share Types
|
10
|
+
# Physical disk share
|
11
|
+
SMB2_SHARE_TYPE_DISK = 0x01
|
12
|
+
# Named pipe share
|
13
|
+
SMB2_SHARE_TYPE_PIPE = 0x02
|
14
|
+
# Printer share
|
15
|
+
SMB2_SHARE_TYPE_PRINT = 0x03
|
16
|
+
|
7
17
|
endian :little
|
8
18
|
smb2_header :smb2_header
|
9
19
|
uint16 :structure_size, label: 'Structure Size', initial_value: 16
|
@@ -15,31 +25,9 @@ module RubySMB
|
|
15
25
|
|
16
26
|
def initialize_instance
|
17
27
|
super
|
18
|
-
smb2_header.command = RubySMB::SMB2::Commands::TREE_CONNECT
|
19
28
|
smb2_header.flags.reply = 1
|
20
29
|
end
|
21
30
|
|
22
|
-
# Returns the ACCESS_MASK for the Maximal Share Access Rights. The packet
|
23
|
-
# defaults this to a {RubySMB::SMB2::BitField::DirectoryAccessMask}. If it is anything other than
|
24
|
-
# a directory that has been connected to, it will re-cast it as a {RubySMB::SMB2::BitField::FileAccessMask}
|
25
|
-
#
|
26
|
-
# @return [RubySMB::SMB2::BitField::DirectoryAccessMask] if a directory was connected to
|
27
|
-
# @return [RubySMB::SMB2::BitField::FileAccessMask] if anything else was connected to
|
28
|
-
def access_rights
|
29
|
-
if is_directory?
|
30
|
-
maximal_access
|
31
|
-
else
|
32
|
-
mask = maximal_access.to_binary_s
|
33
|
-
RubySMB::SMB2::BitField::FileAccessMask.read(mask)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
# Checks if the remote Tree is a directory
|
38
|
-
#
|
39
|
-
# @return [Boolean]
|
40
|
-
def is_directory?
|
41
|
-
share_type == 0x01
|
42
|
-
end
|
43
31
|
end
|
44
32
|
end
|
45
33
|
end
|
@@ -4,15 +4,13 @@ module RubySMB
|
|
4
4
|
# An SMB2 TreeDisconnectRequest Packet as defined in
|
5
5
|
# [2.2.11 SMB2 TREE_DISCONNECT Request](https://msdn.microsoft.com/en-us/library/cc246500.aspx)
|
6
6
|
class TreeDisconnectRequest < RubySMB::GenericPacket
|
7
|
+
COMMAND = RubySMB::SMB2::Commands::TREE_DISCONNECT
|
8
|
+
|
7
9
|
endian :little
|
8
10
|
smb2_header :smb2_header
|
9
11
|
uint16 :structure_size, label: 'Structure Size', initial_value: 4
|
10
12
|
uint16 :reserved, label: 'Reserved', initial_value: 0
|
11
13
|
|
12
|
-
def initialize_instance
|
13
|
-
super
|
14
|
-
smb2_header.command = RubySMB::SMB2::Commands::TREE_DISCONNECT
|
15
|
-
end
|
16
14
|
end
|
17
15
|
end
|
18
16
|
end
|
@@ -4,13 +4,14 @@ module RubySMB
|
|
4
4
|
# An SMB2 TreeDisconnectResponse Packet as defined in
|
5
5
|
# [2.2.12 SMB2 TREE_DISCONNECT Response](https://msdn.microsoft.com/en-us/library/cc246501.aspx)
|
6
6
|
class TreeDisconnectResponse < RubySMB::GenericPacket
|
7
|
+
COMMAND = RubySMB::SMB2::Commands::TREE_DISCONNECT
|
8
|
+
|
7
9
|
endian :little
|
8
10
|
smb2_header :smb2_header
|
9
11
|
uint16 :structure_size, label: 'Structure Size', initial_value: 4
|
10
12
|
|
11
13
|
def initialize_instance
|
12
14
|
super
|
13
|
-
smb2_header.command = RubySMB::SMB2::Commands::TREE_DISCONNECT
|
14
15
|
smb2_header.flags.reply = 1
|
15
16
|
end
|
16
17
|
end
|
@@ -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 encryption is required (SMB 3.x)
|
27
|
+
# @!attribute [rw] encryption_required
|
28
|
+
# @return [Boolean]
|
29
|
+
attr_accessor :encryption_required
|
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
|
+
@encryption_required = 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: @encryption_required)
|
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: @encryption_required)
|
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: @encryption_required)
|
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: @encryption_required)
|
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: @encryption_required)
|
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 :encryption_required }
|
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 encryption_required attribute' do
|
110
|
+
client = described_class.new(dispatcher, username: username, password: password, always_encrypt: true)
|
111
|
+
expect(client.encryption_required).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
|
@@ -75,13 +143,52 @@ 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
194
|
it 'checks the packet version' do
|
@@ -89,14 +196,218 @@ RSpec.describe RubySMB::Client do
|
|
89
196
|
client.send_recv(smb1_request)
|
90
197
|
end
|
91
198
|
|
92
|
-
|
93
|
-
|
199
|
+
context 'when signing' do
|
200
|
+
it 'calls #smb1_sign if it is an SMB1 packet' do
|
201
|
+
expect(client).to receive(:smb1_sign).with(smb1_request).and_call_original
|
202
|
+
client.send_recv(smb1_request)
|
203
|
+
end
|
204
|
+
|
205
|
+
context 'with an SMB2 packet' do
|
206
|
+
it 'does not sign a SessionSetupRequest packet' do
|
207
|
+
expect(smb2_client).to_not receive(:smb2_sign)
|
208
|
+
expect(smb2_client).to_not receive(:smb3_sign)
|
209
|
+
client.send_recv(RubySMB::SMB2::Packet::SessionSetupRequest.new)
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'calls #smb2_sign if it is an SMB2 client' do
|
213
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(false)
|
214
|
+
expect(smb2_client).to receive(:smb2_sign).with(smb2_request).and_call_original
|
215
|
+
smb2_client.send_recv(smb2_request)
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'calls #smb3_sign if it is an SMB3 client' do
|
219
|
+
allow(smb3_client).to receive(:is_status_pending?).and_return(false)
|
220
|
+
expect(smb3_client).to receive(:smb3_sign).with(smb2_request).and_call_original
|
221
|
+
smb3_client.send_recv(smb2_request)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'sends the expected packet and gets the response' do
|
227
|
+
expect(dispatcher).to receive(:send_packet).with(smb1_request)
|
228
|
+
expect(dispatcher).to receive(:recv_packet)
|
94
229
|
client.send_recv(smb1_request)
|
95
230
|
end
|
96
231
|
|
97
|
-
|
98
|
-
|
99
|
-
|
232
|
+
context 'with SMB1' do
|
233
|
+
it 'does not check if it is a STATUS_PENDING response' do
|
234
|
+
expect(smb1_client).to_not receive(:is_status_pending?)
|
235
|
+
smb1_client.send_recv(smb1_request)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
context 'with SMB2' do
|
240
|
+
context 'when receiving a STATUS_PENDING response' do
|
241
|
+
it 'waits 1 second and reads/decrypts again' do
|
242
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(true, false)
|
243
|
+
expect(smb2_client).to receive(:sleep).with(1)
|
244
|
+
expect(dispatcher).to receive(:recv_packet).twice
|
245
|
+
smb2_client.send_recv(smb2_request)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context 'with SMB3 and encryption' do
|
251
|
+
before :example do
|
252
|
+
smb3_client.dialect = '0x0300'
|
253
|
+
allow(smb3_client).to receive(:is_status_pending?).and_return(false)
|
254
|
+
end
|
255
|
+
|
256
|
+
context 'with a SessionSetupRequest' do
|
257
|
+
it 'does not encrypt/decrypt' do
|
258
|
+
request = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
259
|
+
expect(smb3_client).to_not receive(:send_encrypt).with(request)
|
260
|
+
expect(smb3_client).to_not receive(:recv_encrypt)
|
261
|
+
expect(dispatcher).to receive(:send_packet).with(request)
|
262
|
+
expect(dispatcher).to receive(:recv_packet)
|
263
|
+
smb3_client.send_recv(request)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
context 'with a NegotiateRequest' do
|
268
|
+
it 'does not encrypt/decrypt' do
|
269
|
+
request = RubySMB::SMB2::Packet::NegotiateRequest.new
|
270
|
+
expect(smb3_client).to_not receive(:send_encrypt).with(request)
|
271
|
+
expect(smb3_client).to_not receive(:recv_encrypt)
|
272
|
+
expect(dispatcher).to receive(:send_packet).with(request)
|
273
|
+
expect(dispatcher).to receive(:recv_packet)
|
274
|
+
smb3_client.send_recv(request)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'encrypts and decrypts' do
|
279
|
+
expect(smb3_client).to receive(:send_encrypt).with(smb2_request)
|
280
|
+
expect(smb3_client).to receive(:recv_encrypt)
|
281
|
+
smb3_client.send_recv(smb2_request)
|
282
|
+
end
|
283
|
+
|
284
|
+
context 'when receiving a STATUS_PENDING response' do
|
285
|
+
it 'waits 1 second and reads/decrypts again' do
|
286
|
+
allow(smb3_client).to receive(:is_status_pending?).and_return(true, false)
|
287
|
+
expect(smb3_client).to receive(:sleep).with(1)
|
288
|
+
expect(smb3_client).to receive(:send_encrypt).with(smb2_request)
|
289
|
+
expect(smb3_client).to receive(:recv_encrypt).twice
|
290
|
+
smb3_client.send_recv(smb2_request)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
describe '#is_status_pending?' do
|
297
|
+
let(:response) {
|
298
|
+
res = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
299
|
+
res.smb2_header.nt_status= WindowsError::NTStatus::STATUS_PENDING.value
|
300
|
+
res.smb2_header.flags.async_command = 1
|
301
|
+
res
|
302
|
+
}
|
303
|
+
|
304
|
+
it 'returns true when the response has a STATUS_PENDING status code and the async_command flag set' do
|
305
|
+
expect(client.is_status_pending?(response.to_binary_s)).to be true
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'returns false when the response has a STATUS_PENDING status code and the async_command flag not set' do
|
309
|
+
response.smb2_header.flags.async_command = 0
|
310
|
+
expect(client.is_status_pending?(response.to_binary_s)).to be false
|
311
|
+
end
|
312
|
+
|
313
|
+
it 'returns false when the response has no STATUS_PENDING status code but the async_command flag set' do
|
314
|
+
response.smb2_header.nt_status= WindowsError::NTStatus::STATUS_SUCCESS.value
|
315
|
+
expect(client.is_status_pending?(response.to_binary_s)).to be false
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
describe '#can_be_encrypted?' do
|
320
|
+
it 'returns true if the packet can be encrypted' do
|
321
|
+
packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
|
322
|
+
expect(client.can_be_encrypted?(packet)).to be true
|
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 'parses the response as a Transform response packet' do
|
389
|
+
expect(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).with(packet.to_binary_s)
|
390
|
+
client.recv_encrypt
|
391
|
+
end
|
392
|
+
|
393
|
+
it 'raises an InvalidPacket exception if an error occurs while parsing the response' do
|
394
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_raise(IOError)
|
395
|
+
expect { client.recv_encrypt}.to raise_error(RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet')
|
396
|
+
end
|
397
|
+
|
398
|
+
it 'decrypts the Transform response packet' do
|
399
|
+
transform = double('Transform header packet')
|
400
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_return(transform)
|
401
|
+
client.recv_encrypt
|
402
|
+
expect(client).to have_received(:smb3_decrypt).with(transform)
|
403
|
+
end
|
404
|
+
|
405
|
+
it 'raises an EncryptionError exception if an error occurs while decrypting' do
|
406
|
+
allow(client).to receive(:smb3_decrypt).and_raise(RubySMB::Error::RubySMBError )
|
407
|
+
expect { client.recv_encrypt}.to raise_error(
|
408
|
+
RubySMB::Error::EncryptionError,
|
409
|
+
"Error while decrypting RubySMB::SMB2::Packet::TransformHeader packet (SMB 0x0300}): RubySMB::Error::RubySMBError"
|
410
|
+
)
|
100
411
|
end
|
101
412
|
end
|
102
413
|
|
@@ -153,6 +464,132 @@ RSpec.describe RubySMB::Client do
|
|
153
464
|
end
|
154
465
|
end
|
155
466
|
|
467
|
+
describe '#logoff!' do
|
468
|
+
context 'with SMB1' do
|
469
|
+
let(:raw_response) { double('Raw response') }
|
470
|
+
let(:logoff_response) {
|
471
|
+
RubySMB::SMB1::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB1::Commands::SMB_COM_LOGOFF} )
|
472
|
+
}
|
473
|
+
before :example do
|
474
|
+
allow(smb1_client).to receive(:send_recv).and_return(raw_response)
|
475
|
+
allow(RubySMB::SMB1::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
476
|
+
allow(smb1_client).to receive(:wipe_state!)
|
477
|
+
end
|
478
|
+
|
479
|
+
it 'creates a LogoffRequest packet' do
|
480
|
+
expect(RubySMB::SMB1::Packet::LogoffRequest).to receive(:new).and_call_original
|
481
|
+
smb1_client.logoff!
|
482
|
+
end
|
483
|
+
|
484
|
+
it 'calls #send_recv' do
|
485
|
+
expect(smb1_client).to receive(:send_recv)
|
486
|
+
smb1_client.logoff!
|
487
|
+
end
|
488
|
+
|
489
|
+
it 'reads the raw response as a LogoffResponse packet' do
|
490
|
+
expect(RubySMB::SMB1::Packet::LogoffResponse).to receive(:read).with(raw_response)
|
491
|
+
smb1_client.logoff!
|
492
|
+
end
|
493
|
+
|
494
|
+
it 'raise an InvalidPacket exception when the response is an empty packet' do
|
495
|
+
allow(RubySMB::SMB1::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB1::Packet::EmptyPacket.new)
|
496
|
+
expect {smb1_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
497
|
+
end
|
498
|
+
|
499
|
+
it 'raise an InvalidPacket exception when the response is not valid' do
|
500
|
+
allow(logoff_response).to receive(:valid?).and_return(false)
|
501
|
+
expect {smb1_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
502
|
+
end
|
503
|
+
|
504
|
+
it 'calls #wipe_state!' do
|
505
|
+
expect(smb1_client).to receive(:wipe_state!)
|
506
|
+
smb1_client.logoff!
|
507
|
+
end
|
508
|
+
|
509
|
+
it 'returns the expected status code' do
|
510
|
+
logoff_response.smb_header.nt_status = WindowsError::NTStatus::STATUS_PENDING.value
|
511
|
+
allow(RubySMB::SMB1::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
512
|
+
expect(smb1_client.logoff!).to eq(WindowsError::NTStatus::STATUS_PENDING)
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
context 'with SMB2' do
|
517
|
+
let(:raw_response) { double('Raw response') }
|
518
|
+
let(:logoff_response) {
|
519
|
+
RubySMB::SMB2::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB2::Commands::LOGOFF} )
|
520
|
+
}
|
521
|
+
before :example do
|
522
|
+
allow(smb2_client).to receive(:send_recv).and_return(raw_response)
|
523
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
524
|
+
allow(smb2_client).to receive(:wipe_state!)
|
525
|
+
end
|
526
|
+
|
527
|
+
it 'creates a LogoffRequest packet' do
|
528
|
+
expect(RubySMB::SMB2::Packet::LogoffRequest).to receive(:new).and_call_original
|
529
|
+
smb2_client.logoff!
|
530
|
+
end
|
531
|
+
|
532
|
+
it 'calls #send_recv' do
|
533
|
+
expect(smb2_client).to receive(:send_recv)
|
534
|
+
smb2_client.logoff!
|
535
|
+
end
|
536
|
+
|
537
|
+
it 'reads the raw response as a LogoffResponse packet' do
|
538
|
+
expect(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).with(raw_response)
|
539
|
+
smb2_client.logoff!
|
540
|
+
end
|
541
|
+
|
542
|
+
it 'raise an InvalidPacket exception when the response is an error packet' do
|
543
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB2::Packet::ErrorPacket.new)
|
544
|
+
expect {smb2_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
545
|
+
end
|
546
|
+
|
547
|
+
it 'raise an InvalidPacket exception when the response is not a LOGOFF command' do
|
548
|
+
logoff_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
|
549
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
550
|
+
expect {smb2_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
context 'with SMB3' do
|
555
|
+
let(:raw_response) { double('Raw response') }
|
556
|
+
let(:logoff_response) {
|
557
|
+
RubySMB::SMB2::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB2::Commands::LOGOFF} )
|
558
|
+
}
|
559
|
+
before :example do
|
560
|
+
allow(smb3_client).to receive(:send_recv).and_return(raw_response)
|
561
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
562
|
+
allow(smb3_client).to receive(:wipe_state!)
|
563
|
+
end
|
564
|
+
|
565
|
+
it 'creates a LogoffRequest packet' do
|
566
|
+
expect(RubySMB::SMB2::Packet::LogoffRequest).to receive(:new).and_call_original
|
567
|
+
smb3_client.logoff!
|
568
|
+
end
|
569
|
+
|
570
|
+
it 'calls #send_recv' do
|
571
|
+
expect(smb3_client).to receive(:send_recv)
|
572
|
+
smb3_client.logoff!
|
573
|
+
end
|
574
|
+
|
575
|
+
it 'reads the raw response as a LogoffResponse packet' do
|
576
|
+
expect(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).with(raw_response)
|
577
|
+
smb3_client.logoff!
|
578
|
+
end
|
579
|
+
|
580
|
+
it 'raise an InvalidPacket exception when the response is an error packet' do
|
581
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB2::Packet::ErrorPacket.new)
|
582
|
+
expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
583
|
+
end
|
584
|
+
|
585
|
+
it 'raise an InvalidPacket exception when the response is not a LOGOFF command' do
|
586
|
+
logoff_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
|
587
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
588
|
+
expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
589
|
+
end
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
156
593
|
context 'NetBIOS Session Service' do
|
157
594
|
describe '#session_request' do
|
158
595
|
let(:session_header) { RubySMB::Nbss::SessionHeader.new }
|
@@ -197,6 +634,11 @@ RSpec.describe RubySMB::Client do
|
|
197
634
|
allow(dispatcher).to receive(:recv_packet).and_return(negative_session_response.to_binary_s)
|
198
635
|
expect { client.session_request }.to raise_error(RubySMB::Error::NetBiosSessionService)
|
199
636
|
end
|
637
|
+
|
638
|
+
it 'raises an InvalidPacket exception when an error occurs while reading' do
|
639
|
+
allow(RubySMB::Nbss::SessionHeader).to receive(:read).and_raise(IOError)
|
640
|
+
expect { client.session_request }.to raise_error(RubySMB::Error::InvalidPacket)
|
641
|
+
end
|
200
642
|
end
|
201
643
|
|
202
644
|
describe '#session_request_packet' do
|
@@ -271,7 +713,8 @@ RSpec.describe RubySMB::Client do
|
|
271
713
|
smb1_extended_response.to_binary_s
|
272
714
|
}
|
273
715
|
|
274
|
-
let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new }
|
716
|
+
let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x200) }
|
717
|
+
let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x300) }
|
275
718
|
|
276
719
|
describe '#smb1_negotiate_request' do
|
277
720
|
it 'returns an SMB1 Negotiate Request packet' do
|
@@ -279,33 +722,158 @@ RSpec.describe RubySMB::Client do
|
|
279
722
|
end
|
280
723
|
|
281
724
|
it 'sets the default SMB1 Dialect' do
|
282
|
-
expect(client.smb1_negotiate_request.dialects).to include(
|
725
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
726
|
+
buffer_format: 2,
|
727
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
|
728
|
+
)
|
283
729
|
end
|
284
730
|
|
285
731
|
it 'sets the SMB2.02 dialect if SMB2 support is enabled' do
|
286
|
-
expect(client.smb1_negotiate_request.dialects).to include(
|
732
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
733
|
+
buffer_format: 2,
|
734
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
|
735
|
+
)
|
287
736
|
end
|
288
737
|
|
289
738
|
it 'excludes the SMB2.02 Dialect if SMB2 support is disabled' do
|
290
|
-
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
739
|
+
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
740
|
+
buffer_format: 2,
|
741
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
|
742
|
+
)
|
291
743
|
end
|
292
744
|
|
293
745
|
it 'excludes the default SMB1 Dialect if SMB1 support is disabled' do
|
294
|
-
expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
|
746
|
+
expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
|
747
|
+
buffer_format: 2,
|
748
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
|
749
|
+
)
|
750
|
+
end
|
751
|
+
|
752
|
+
it 'sets the SMB wildcard dialect if SMB2 support is enabled' do
|
753
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
754
|
+
buffer_format: 2,
|
755
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
756
|
+
)
|
757
|
+
end
|
758
|
+
|
759
|
+
it 'sets the SMB wildcard dialect if SMB3 support is enabled' do
|
760
|
+
expect(smb3_client.smb1_negotiate_request.dialects).to include(
|
761
|
+
buffer_format: 2,
|
762
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
763
|
+
)
|
764
|
+
end
|
765
|
+
|
766
|
+
it 'excludes the SMB wildcard dialect if both SMB2 and SMB3 supports are disabled' do
|
767
|
+
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
768
|
+
buffer_format: 2,
|
769
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
770
|
+
)
|
295
771
|
end
|
296
772
|
end
|
297
773
|
|
298
|
-
describe '#
|
774
|
+
describe '#smb2_3_negotiate_request' do
|
299
775
|
it 'return an SMB2 Negotiate Request packet' do
|
300
|
-
expect(client.
|
776
|
+
expect(client.smb2_3_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
|
301
777
|
end
|
302
778
|
|
303
|
-
it 'sets the default SMB2 Dialect' do
|
304
|
-
expect(client.
|
779
|
+
it 'sets the default SMB2 Dialect if SMB2 support is enabled' do
|
780
|
+
expect(client.smb2_3_negotiate_request.dialects).to include(
|
781
|
+
*(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
782
|
+
)
|
783
|
+
end
|
784
|
+
|
785
|
+
it 'does not set the default SMB2 Dialect if SMB2 support is disabled' do
|
786
|
+
expect(smb3_client.smb2_3_negotiate_request.dialects).to_not include(
|
787
|
+
*(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
788
|
+
)
|
305
789
|
end
|
306
790
|
|
307
791
|
it 'sets the Message ID to 0' do
|
308
|
-
expect(client.
|
792
|
+
expect(client.smb2_3_negotiate_request.smb2_header.message_id).to eq 0
|
793
|
+
end
|
794
|
+
|
795
|
+
it 'adds SMB3 dialects if if SMB3 support is enabled' do
|
796
|
+
expect(client.smb2_3_negotiate_request.dialects).to include(
|
797
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
798
|
+
)
|
799
|
+
end
|
800
|
+
|
801
|
+
it 'does not set the default SMB3 Dialect if SMB3 support is disabled' do
|
802
|
+
expect(smb2_client.smb2_3_negotiate_request.dialects).to_not include(
|
803
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
804
|
+
)
|
805
|
+
end
|
806
|
+
end
|
807
|
+
|
808
|
+
describe '#add_smb3_to_negotiate_request' do
|
809
|
+
let(:negotiate_request) { RubySMB::SMB2::Packet::NegotiateRequest.new }
|
810
|
+
|
811
|
+
it 'adds the default SMB3 dialects' do
|
812
|
+
expect(client.add_smb3_to_negotiate_request(negotiate_request).dialects).to include(
|
813
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
814
|
+
)
|
815
|
+
end
|
816
|
+
|
817
|
+
it 'raises the expected exception when the dialects is not an array of strings' do
|
818
|
+
dialects = ['0x0300', 0x0302, '0x0311']
|
819
|
+
expect { client.add_smb3_to_negotiate_request(negotiate_request, dialects) }.to raise_error(ArgumentError)
|
820
|
+
end
|
821
|
+
|
822
|
+
it 'sets encryption capability flag' do
|
823
|
+
expect(client.add_smb3_to_negotiate_request(negotiate_request).capabilities.encryption).to eq(1)
|
824
|
+
end
|
825
|
+
|
826
|
+
context 'when the negotiate packet includes the 0x0311 dialect' do
|
827
|
+
before :example do
|
828
|
+
client.add_smb3_to_negotiate_request(negotiate_request, ['0x0311'])
|
829
|
+
end
|
830
|
+
|
831
|
+
it 'adds 3 Negotiate Contexts' do
|
832
|
+
expect(negotiate_request.negotiate_context_info.negotiate_context_count).to eq(3)
|
833
|
+
end
|
834
|
+
|
835
|
+
it 'adds a Preauth Integrity Negotiate Context with the expected hash algorithms' do
|
836
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
837
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
838
|
+
end
|
839
|
+
expect(nc.length).to eq(1)
|
840
|
+
expect(nc.first.data.hash_algorithms).to eq([RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512])
|
841
|
+
end
|
842
|
+
|
843
|
+
it 'adds Encryption Negotiate Contexts with the expected encryption algorithms' do
|
844
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
845
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
846
|
+
end
|
847
|
+
expect(nc.length).to eq(1)
|
848
|
+
expect(nc.first.data.ciphers).to eq(
|
849
|
+
[
|
850
|
+
RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM,
|
851
|
+
RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
852
|
+
]
|
853
|
+
)
|
854
|
+
end
|
855
|
+
|
856
|
+
it 'adds Compression Negotiate Contexts with the expected compression algorithms' do
|
857
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
858
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
859
|
+
end
|
860
|
+
expect(nc.length).to eq(1)
|
861
|
+
expect(nc.first.data.compression_algorithms).to eq(
|
862
|
+
[
|
863
|
+
RubySMB::SMB2::CompressionCapabilities::LZNT1,
|
864
|
+
RubySMB::SMB2::CompressionCapabilities::LZ77,
|
865
|
+
RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman,
|
866
|
+
RubySMB::SMB2::CompressionCapabilities::Pattern_V1
|
867
|
+
]
|
868
|
+
)
|
869
|
+
end
|
870
|
+
end
|
871
|
+
|
872
|
+
context 'when the negotiate packet does not include the 0x0311 dialect' do
|
873
|
+
it 'does not add any Negotiate Context' do
|
874
|
+
client.add_smb3_to_negotiate_request(negotiate_request, ['0x0300', '0x0302'])
|
875
|
+
expect(negotiate_request.negotiate_context_list?). to be false
|
876
|
+
end
|
309
877
|
end
|
310
878
|
end
|
311
879
|
|
@@ -320,10 +888,15 @@ RSpec.describe RubySMB::Client do
|
|
320
888
|
client.negotiate_request
|
321
889
|
end
|
322
890
|
|
323
|
-
it 'calls #
|
324
|
-
expect(smb2_client).to receive(:
|
891
|
+
it 'calls #smb2_3_negotiate_request if SMB2 is enabled' do
|
892
|
+
expect(smb2_client).to receive(:smb2_3_negotiate_request)
|
325
893
|
smb2_client.negotiate_request
|
326
894
|
end
|
895
|
+
|
896
|
+
it 'calls #smb2_3_negotiate_request if SMB3 is enabled' do
|
897
|
+
expect(smb3_client).to receive(:smb2_3_negotiate_request)
|
898
|
+
smb3_client.negotiate_request
|
899
|
+
end
|
327
900
|
end
|
328
901
|
|
329
902
|
describe '#negotiate_response' do
|
@@ -332,10 +905,15 @@ RSpec.describe RubySMB::Client do
|
|
332
905
|
expect(smb1_client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
|
333
906
|
end
|
334
907
|
|
335
|
-
it 'raises an exception if the
|
908
|
+
it 'raises an exception if the response is not a SMB packet' do
|
336
909
|
expect { smb1_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
|
337
910
|
end
|
338
911
|
|
912
|
+
it 'raises an InvalidPacket error if the response is not a valid response' do
|
913
|
+
empty_packet.smb_header.command = RubySMB::SMB2::Commands::NEGOTIATE
|
914
|
+
expect { smb1_client.negotiate_response(empty_packet.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
915
|
+
end
|
916
|
+
|
339
917
|
it 'considers the response invalid if it is not an actual Negotiate Response' do
|
340
918
|
bogus_response = smb1_extended_response
|
341
919
|
bogus_response.smb_header.command = 0xff
|
@@ -357,14 +935,36 @@ RSpec.describe RubySMB::Client do
|
|
357
935
|
it 'raises an exception if the Response is invalid' do
|
358
936
|
expect { smb2_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
|
359
937
|
end
|
938
|
+
|
939
|
+
it 'considers the response invalid if it is not an actual Negotiate Response' do
|
940
|
+
bogus_response = smb2_response
|
941
|
+
bogus_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
|
942
|
+
expect { smb2_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
943
|
+
end
|
360
944
|
end
|
361
945
|
|
362
|
-
context 'with
|
946
|
+
context 'with only SMB3' do
|
947
|
+
it 'returns a properly formed packet' do
|
948
|
+
expect(smb3_client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
|
949
|
+
end
|
950
|
+
|
951
|
+
it 'raises an exception if the Response is invalid' do
|
952
|
+
expect { smb3_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
|
953
|
+
end
|
954
|
+
|
955
|
+
it 'considers the response invalid if it is not an actual Negotiate Response' do
|
956
|
+
bogus_response = smb2_response
|
957
|
+
bogus_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
|
958
|
+
expect { smb3_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
959
|
+
end
|
960
|
+
end
|
961
|
+
|
962
|
+
context 'with SMB1, SMB2 and SMB3 enabled' do
|
363
963
|
it 'returns an SMB1 NegotiateResponse if it looks like SMB1' do
|
364
964
|
expect(client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
|
365
965
|
end
|
366
966
|
|
367
|
-
it 'returns an SMB2 NegotiateResponse if it looks like SMB2' do
|
967
|
+
it 'returns an SMB2 NegotiateResponse if it looks like SMB2 or SMB3' do
|
368
968
|
expect(client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
|
369
969
|
end
|
370
970
|
end
|
@@ -372,9 +972,10 @@ RSpec.describe RubySMB::Client do
|
|
372
972
|
|
373
973
|
describe '#parse_negotiate_response' do
|
374
974
|
context 'when SMB1 was Negotiated' do
|
375
|
-
it 'turns off SMB2 support' do
|
975
|
+
it 'turns off SMB2 and SMB3 support' do
|
376
976
|
client.parse_negotiate_response(smb1_extended_response)
|
377
977
|
expect(client.smb2).to be false
|
978
|
+
expect(client.smb3).to be false
|
378
979
|
end
|
379
980
|
|
380
981
|
it 'sets whether or not signing is required' do
|
@@ -383,67 +984,391 @@ RSpec.describe RubySMB::Client do
|
|
383
984
|
expect(client.signing_required).to be true
|
384
985
|
end
|
385
986
|
|
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
|
987
|
+
it 'sets #dialect to the negotiated dialect' do
|
988
|
+
smb1_extended_response.dialects = [
|
989
|
+
RubySMB::SMB1::Dialect.new(dialect_string: 'A'),
|
990
|
+
RubySMB::SMB1::Dialect.new(dialect_string: 'B'),
|
991
|
+
RubySMB::SMB1::Dialect.new(dialect_string: 'C'),
|
992
|
+
]
|
993
|
+
smb1_extended_response.parameter_block.dialect_index = 1
|
994
|
+
client.parse_negotiate_response(smb1_extended_response)
|
995
|
+
expect(client.dialect).to eq 'B'
|
996
|
+
end
|
997
|
+
|
998
|
+
it 'returns the string \'SMB1\'' do
|
999
|
+
expect(client.parse_negotiate_response(smb1_extended_response)).to eq ('SMB1')
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
it 'sets #negotiated_smb_version to 1' do
|
1003
|
+
client.parse_negotiate_response(smb1_extended_response)
|
1004
|
+
expect(client.negotiated_smb_version).to eq(1)
|
1005
|
+
end
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
context 'when SMB2 was negotiated' do
|
1009
|
+
it 'turns off SMB1 and SMB3 support' do
|
1010
|
+
client.parse_negotiate_response(smb2_response)
|
1011
|
+
expect(client.smb1).to be false
|
1012
|
+
expect(client.smb3).to be false
|
1013
|
+
end
|
1014
|
+
|
1015
|
+
it 'sets whether or not signing is required' do
|
1016
|
+
smb2_response.security_mode.signing_required = 1
|
1017
|
+
client.parse_negotiate_response(smb2_response)
|
1018
|
+
expect(client.signing_required).to be true
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
it 'sets #dialect to the negotiated dialect' do
|
1022
|
+
smb2_response.dialect_revision = 2
|
1023
|
+
client.parse_negotiate_response(smb2_response)
|
1024
|
+
expect(client.dialect).to eq '0x0002'
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
it 'returns the string \'SMB2\'' do
|
1028
|
+
expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
|
1029
|
+
end
|
1030
|
+
end
|
1031
|
+
|
1032
|
+
context 'when SMB3 was negotiated' do
|
1033
|
+
it 'turns off SMB1 and SMB2 support' do
|
1034
|
+
client.parse_negotiate_response(smb3_response)
|
1035
|
+
expect(client.smb1).to be false
|
1036
|
+
expect(client.smb2).to be false
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
it 'sets whether or not signing is required' do
|
1040
|
+
smb3_response.security_mode.signing_required = 1
|
1041
|
+
client.parse_negotiate_response(smb3_response)
|
1042
|
+
expect(client.signing_required).to be true
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
it 'sets #dialect to the negotiated dialect' do
|
1046
|
+
client.parse_negotiate_response(smb3_response)
|
1047
|
+
expect(client.dialect).to eq '0x0300'
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
it 'returns the string \'SMB2\'' do
|
1051
|
+
expect(client.parse_negotiate_response(smb3_response)).to eq ('SMB3')
|
1052
|
+
end
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
context 'when the response contains the SMB2 wildcard revision number dialect' do
|
1056
|
+
it 'only turns off SMB1 support' do
|
1057
|
+
smb2_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x02ff)
|
1058
|
+
client.parse_negotiate_response(smb2_response)
|
1059
|
+
expect(client.smb1).to be false
|
1060
|
+
expect(client.smb2).to be true
|
1061
|
+
expect(client.smb3).to be true
|
1062
|
+
end
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
context 'when the negotiation failed' do
|
1066
|
+
context 'with a STATUS_NOT_SUPPORTED status code' do
|
1067
|
+
before :example do
|
1068
|
+
error_packet.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED.value
|
1069
|
+
end
|
1070
|
+
|
1071
|
+
it 'raises the expected exception with SMB2' do
|
1072
|
+
expect { smb2_client.parse_negotiate_response(error_packet) }.to raise_error(
|
1073
|
+
RubySMB::Error::NegotiationFailure,
|
1074
|
+
'Unable to negotiate with remote host, SMB2 not supported'
|
1075
|
+
)
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
it 'raises the expected exception with SMB3' do
|
1079
|
+
expect { smb3_client.parse_negotiate_response(error_packet) }.to raise_error(
|
1080
|
+
RubySMB::Error::NegotiationFailure,
|
1081
|
+
'Unable to negotiate with remote host, SMB3 not supported'
|
1082
|
+
)
|
1083
|
+
end
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
context 'with an unknown status code' do
|
1087
|
+
it 'raises the expected exception' do
|
1088
|
+
expect { client.parse_negotiate_response(empty_packet) }.to raise_error(
|
1089
|
+
RubySMB::Error::NegotiationFailure,
|
1090
|
+
'Unable to negotiate with remote host'
|
1091
|
+
)
|
1092
|
+
end
|
1093
|
+
end
|
1094
|
+
end
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
describe '#negotiate' do
|
1098
|
+
let(:request_packet) { client.smb1_negotiate_request }
|
1099
|
+
before :example do
|
1100
|
+
allow(client).to receive(:negotiate_request)
|
1101
|
+
allow(client).to receive(:send_recv)
|
1102
|
+
allow(client).to receive(:negotiate_response)
|
1103
|
+
allow(client).to receive(:parse_negotiate_response)
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
it 'calls the backing methods' do
|
1107
|
+
expect(client).to receive(:negotiate_request)
|
1108
|
+
expect(client).to receive(:send_recv)
|
1109
|
+
expect(client).to receive(:negotiate_response)
|
1110
|
+
expect(client).to receive(:parse_negotiate_response)
|
1111
|
+
client.negotiate
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
context 'with SMB1' do
|
1115
|
+
it 'sets the response-packet #dialects array with the dialects sent in the request' do
|
1116
|
+
request_packet = client.smb1_negotiate_request
|
1117
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1118
|
+
allow(client).to receive(:negotiate_response).and_return(smb1_extended_response)
|
1119
|
+
expect(smb1_extended_response).to receive(:dialects=).with(request_packet.dialects)
|
1120
|
+
client.negotiate
|
1121
|
+
end
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
['0x0300', '0x0302'].each do |dialect|
|
1125
|
+
context "with #{dialect} dialect" do
|
1126
|
+
before :example do
|
1127
|
+
client.dialect = dialect
|
1128
|
+
end
|
1129
|
+
|
1130
|
+
it 'sets the expected encryption algorithm' do
|
1131
|
+
client.negotiate
|
1132
|
+
expect(client.encryption_algorithm).to eq(RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM])
|
1133
|
+
end
|
1134
|
+
end
|
1135
|
+
end
|
1136
|
+
|
1137
|
+
context "with 0x0311 dialect" do
|
1138
|
+
it 'calls #parse_smb3_encryption_data' do
|
1139
|
+
client.dialect = '0x0311'
|
1140
|
+
request_packet = client.smb2_3_negotiate_request
|
1141
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1142
|
+
allow(client).to receive(:negotiate_response).and_return(smb3_response)
|
1143
|
+
expect(client).to receive(:parse_smb3_encryption_data).with(request_packet, smb3_response)
|
1144
|
+
client.negotiate
|
1145
|
+
end
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
context 'with a wildcard revision number response' do
|
1149
|
+
before :example do
|
1150
|
+
client.dialect = '0x02ff'
|
1151
|
+
allow(client).to receive(:smb2_message_id=) do
|
1152
|
+
client.dialect = '0x0202'
|
1153
|
+
end
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
it 'increments the message ID' do
|
1157
|
+
expect(client).to receive(:smb2_message_id=).with(1)
|
1158
|
+
client.negotiate
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
it 're-negotiates' do
|
1162
|
+
expect(client).to receive(:negotiate_request).twice
|
1163
|
+
expect(client).to receive(:send_recv).twice
|
1164
|
+
expect(client).to receive(:negotiate_response).twice
|
1165
|
+
expect(client).to receive(:parse_negotiate_response).twice
|
1166
|
+
client.negotiate
|
1167
|
+
end
|
1168
|
+
end
|
1169
|
+
|
1170
|
+
context 'when an error occurs' do
|
1171
|
+
before :example do
|
1172
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1173
|
+
allow(client).to receive(:send_recv).and_raise(RubySMB::Error::InvalidPacket)
|
1174
|
+
client.smb1 = false
|
1175
|
+
client.smb2 = false
|
1176
|
+
client.smb3 = false
|
1177
|
+
end
|
1178
|
+
|
1179
|
+
context 'with SMB1' do
|
1180
|
+
let(:request_packet) { client.smb1_negotiate_request }
|
1181
|
+
|
1182
|
+
it 'raise the expected exception' do
|
1183
|
+
client.smb1 = true
|
1184
|
+
expect { client.negotiate }.to raise_error(
|
1185
|
+
RubySMB::Error::NegotiationFailure,
|
1186
|
+
"Unable to negotiate SMB1 with the remote host: RubySMB::Error::InvalidPacket"
|
1187
|
+
)
|
1188
|
+
end
|
1189
|
+
end
|
1190
|
+
|
1191
|
+
context 'with SMB2' do
|
1192
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
1193
|
+
|
1194
|
+
it 'raise the expected exception' do
|
1195
|
+
client.smb2 = true
|
1196
|
+
expect { client.negotiate }.to raise_error(
|
1197
|
+
RubySMB::Error::NegotiationFailure,
|
1198
|
+
"Unable to negotiate SMB2 with the remote host: RubySMB::Error::InvalidPacket"
|
1199
|
+
)
|
1200
|
+
end
|
1201
|
+
end
|
1202
|
+
|
1203
|
+
context 'with SMB3' do
|
1204
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
396
1205
|
|
397
|
-
|
398
|
-
|
1206
|
+
it 'raise the expected exception' do
|
1207
|
+
client.smb3 = true
|
1208
|
+
expect { client.negotiate }.to raise_error(
|
1209
|
+
RubySMB::Error::NegotiationFailure,
|
1210
|
+
"Unable to negotiate SMB3 with the remote host: RubySMB::Error::InvalidPacket"
|
1211
|
+
)
|
1212
|
+
end
|
399
1213
|
end
|
400
1214
|
end
|
401
1215
|
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
1216
|
+
describe '#parse_smb3_encryption_data' do
|
1217
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
1218
|
+
let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311) }
|
1219
|
+
let(:nc_encryption) do
|
1220
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1221
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1222
|
+
)
|
1223
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
|
1224
|
+
nc
|
406
1225
|
end
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
1226
|
+
let(:nc_integrity) do
|
1227
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1228
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1229
|
+
)
|
1230
|
+
nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
1231
|
+
nc
|
412
1232
|
end
|
413
1233
|
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
1234
|
+
before :example do
|
1235
|
+
allow(smb3_client).to receive(:update_preauth_hash)
|
1236
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1237
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
418
1238
|
end
|
419
1239
|
|
420
|
-
|
421
|
-
|
1240
|
+
context 'when selecting the integrity hash algorithm' do
|
1241
|
+
context 'with one algorithm' do
|
1242
|
+
it 'selects the expected algorithm' do
|
1243
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1244
|
+
expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
|
1245
|
+
end
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
context 'with multiple algorithms' do
|
1249
|
+
it 'selects the first algorithm' do
|
1250
|
+
nc = smb3_response.find_negotiate_context(
|
1251
|
+
RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1252
|
+
)
|
1253
|
+
nc.data.hash_algorithms << 3
|
1254
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1255
|
+
expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
|
1256
|
+
end
|
1257
|
+
end
|
1258
|
+
|
1259
|
+
context 'without integrity negotiate context' do
|
1260
|
+
it 'raises the expected exception' do
|
1261
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1262
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1263
|
+
expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
|
1264
|
+
RubySMB::Error::EncryptionError,
|
1265
|
+
'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
|
1266
|
+
)
|
1267
|
+
end
|
1268
|
+
end
|
1269
|
+
|
1270
|
+
context 'with an unknown integrity hash algorithm' do
|
1271
|
+
it 'raises the expected exception' do
|
1272
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1273
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1274
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1275
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1276
|
+
)
|
1277
|
+
nc.data.hash_algorithms << 5
|
1278
|
+
smb3_response.add_negotiate_context(nc)
|
1279
|
+
expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
|
1280
|
+
RubySMB::Error::EncryptionError,
|
1281
|
+
'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
|
1282
|
+
)
|
1283
|
+
end
|
1284
|
+
end
|
422
1285
|
end
|
423
|
-
end
|
424
|
-
end
|
425
1286
|
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
end
|
1287
|
+
context 'when selecting the encryption algorithm' do
|
1288
|
+
context 'with one algorithm' do
|
1289
|
+
it 'selects the expected algorithm' do
|
1290
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1291
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
|
1292
|
+
end
|
1293
|
+
end
|
434
1294
|
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
1295
|
+
context 'with multiple algorithms' do
|
1296
|
+
it 'selects the AES-128-GCM algorithm if included' do
|
1297
|
+
nc = smb3_response.find_negotiate_context(
|
1298
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1299
|
+
)
|
1300
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
1301
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1302
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-GCM')
|
1303
|
+
end
|
1304
|
+
|
1305
|
+
it 'selects the first algorithm if AES-128-GCM is not included' do
|
1306
|
+
nc = smb3_response.find_negotiate_context(
|
1307
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1308
|
+
)
|
1309
|
+
nc.data.ciphers << 3
|
1310
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1311
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
|
1312
|
+
end
|
1313
|
+
|
1314
|
+
it 'keep tracks of the server supported algorithms' do
|
1315
|
+
nc = smb3_response.find_negotiate_context(
|
1316
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1317
|
+
)
|
1318
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
1319
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1320
|
+
expect(smb3_client.server_encryption_algorithms).to eq([1, 2])
|
1321
|
+
end
|
1322
|
+
end
|
1323
|
+
|
1324
|
+
context 'without encryption context' do
|
1325
|
+
it 'raises the expected exception' do
|
1326
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1327
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
1328
|
+
expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
|
1329
|
+
RubySMB::Error::EncryptionError,
|
1330
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
1331
|
+
)
|
1332
|
+
end
|
1333
|
+
end
|
1334
|
+
|
1335
|
+
context 'with an unknown encryption algorithm' do
|
1336
|
+
it 'raises the expected exception' do
|
1337
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1338
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
1339
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1340
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1341
|
+
)
|
1342
|
+
nc.data.ciphers << 14
|
1343
|
+
smb3_response.add_negotiate_context(nc)
|
1344
|
+
expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
|
1345
|
+
RubySMB::Error::EncryptionError,
|
1346
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
1347
|
+
)
|
1348
|
+
end
|
1349
|
+
end
|
1350
|
+
end
|
1351
|
+
|
1352
|
+
context 'when selecting the compression algorithm' do
|
1353
|
+
it 'keep tracks of the server supported algorithms' do
|
1354
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1355
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
1356
|
+
)
|
1357
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
|
1358
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
|
1359
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
|
1360
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
|
1361
|
+
smb3_response.add_negotiate_context(nc)
|
1362
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1363
|
+
expect(smb3_client.server_compression_algorithms).to eq([1, 2, 3, 4])
|
1364
|
+
end
|
1365
|
+
end
|
443
1366
|
|
444
|
-
|
445
|
-
|
446
|
-
|
1367
|
+
it 'updates the preauth hash' do
|
1368
|
+
expect(smb3_client).to receive(:update_preauth_hash).with(request_packet)
|
1369
|
+
expect(smb3_client).to receive(:update_preauth_hash).with(smb3_response)
|
1370
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1371
|
+
end
|
447
1372
|
end
|
448
1373
|
end
|
449
1374
|
end
|
@@ -624,7 +1549,7 @@ RSpec.describe RubySMB::Client do
|
|
624
1549
|
expect { smb1_client.smb1_ntlmssp_challenge_packet(response.to_binary_s) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
|
625
1550
|
end
|
626
1551
|
|
627
|
-
it '
|
1552
|
+
it 'raise an InvalidPacket exception when the response is not valid' do
|
628
1553
|
expect { smb1_client.smb1_ntlmssp_challenge_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
629
1554
|
end
|
630
1555
|
end
|
@@ -645,7 +1570,7 @@ RSpec.describe RubySMB::Client do
|
|
645
1570
|
expect(smb1_client.smb1_ntlmssp_final_packet(response.to_binary_s)).to eq response
|
646
1571
|
end
|
647
1572
|
|
648
|
-
it '
|
1573
|
+
it 'raise an InvalidPacket exception when the response is not valid' do
|
649
1574
|
expect { smb1_client.smb1_ntlmssp_final_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
650
1575
|
end
|
651
1576
|
end
|
@@ -717,12 +1642,7 @@ RSpec.describe RubySMB::Client do
|
|
717
1642
|
expect(smb1_client.smb1_anonymous_auth_response(anonymous_response.to_binary_s)).to eq anonymous_response
|
718
1643
|
end
|
719
1644
|
|
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
|
1645
|
+
it 'raise an InvalidPacket exception when the response is not valid' do
|
726
1646
|
anonymous_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_NEGOTIATE
|
727
1647
|
expect { smb1_client.smb1_anonymous_auth_response(anonymous_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
728
1648
|
end
|
@@ -775,6 +1695,39 @@ RSpec.describe RubySMB::Client do
|
|
775
1695
|
smb2_client.smb2_authenticate
|
776
1696
|
expect(smb2_client.os_version).to eq '6.1.7601'
|
777
1697
|
end
|
1698
|
+
|
1699
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1700
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1701
|
+
smb2_client.dialect = dialect
|
1702
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1703
|
+
smb2_client.smb2_authenticate
|
1704
|
+
end
|
1705
|
+
end
|
1706
|
+
|
1707
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1708
|
+
smb2_client.dialect = '0x0311'
|
1709
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(response_packet)
|
1710
|
+
smb2_client.smb2_authenticate
|
1711
|
+
end
|
1712
|
+
|
1713
|
+
context 'when setting the encryption_required parameter' do
|
1714
|
+
before :example do
|
1715
|
+
smb2_client.smb3 = true
|
1716
|
+
smb2_client.encryption_required = false
|
1717
|
+
end
|
1718
|
+
|
1719
|
+
it 'sets the encryption_required parameter to true if the server requires encryption' do
|
1720
|
+
final_response_packet.session_flags.encrypt_data = 1
|
1721
|
+
smb2_client.smb2_authenticate
|
1722
|
+
expect(smb2_client.encryption_required).to be true
|
1723
|
+
end
|
1724
|
+
|
1725
|
+
it 'does not set the encryption_required parameter if the server does not require encryption' do
|
1726
|
+
final_response_packet.session_flags.encrypt_data = 0
|
1727
|
+
smb2_client.smb2_authenticate
|
1728
|
+
expect(smb2_client.encryption_required).to be false
|
1729
|
+
end
|
1730
|
+
end
|
778
1731
|
end
|
779
1732
|
|
780
1733
|
describe '#smb2_ntlmssp_negotiate_packet' do
|
@@ -790,20 +1743,34 @@ RSpec.describe RubySMB::Client do
|
|
790
1743
|
smb2_client.smb2_ntlmssp_negotiate_packet
|
791
1744
|
end
|
792
1745
|
|
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)
|
1746
|
+
it 'enables signing' do
|
1747
|
+
expect(smb2_client.smb2_ntlmssp_negotiate_packet.security_mode.signing_enabled).to eq 1
|
799
1748
|
end
|
800
1749
|
end
|
801
1750
|
|
802
1751
|
describe '#smb2_ntlmssp_negotiate' do
|
1752
|
+
before :example do
|
1753
|
+
allow(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
|
1754
|
+
allow(smb2_client).to receive(:send_recv)
|
1755
|
+
end
|
1756
|
+
|
803
1757
|
it 'sends the request packet and receives a response' do
|
804
|
-
expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
|
805
|
-
expect(
|
806
|
-
|
1758
|
+
expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
|
1759
|
+
expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
|
1760
|
+
smb2_client.smb2_ntlmssp_negotiate
|
1761
|
+
end
|
1762
|
+
|
1763
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1764
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1765
|
+
smb2_client.dialect = dialect
|
1766
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1767
|
+
smb2_client.smb2_ntlmssp_negotiate
|
1768
|
+
end
|
1769
|
+
end
|
1770
|
+
|
1771
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1772
|
+
smb2_client.dialect = '0x0311'
|
1773
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
|
807
1774
|
smb2_client.smb2_ntlmssp_negotiate
|
808
1775
|
end
|
809
1776
|
end
|
@@ -829,7 +1796,7 @@ RSpec.describe RubySMB::Client do
|
|
829
1796
|
expect { smb2_client.smb2_ntlmssp_challenge_packet(response.to_binary_s) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
|
830
1797
|
end
|
831
1798
|
|
832
|
-
it '
|
1799
|
+
it 'raise an InvalidPacket exception when the response is not valid' do
|
833
1800
|
expect { smb2_client.smb2_ntlmssp_challenge_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
834
1801
|
end
|
835
1802
|
end
|
@@ -861,13 +1828,35 @@ RSpec.describe RubySMB::Client do
|
|
861
1828
|
it 'sets the session ID on the request packet' do
|
862
1829
|
expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).smb2_header.session_id).to eq session_id
|
863
1830
|
end
|
1831
|
+
|
1832
|
+
it 'enables signing' do
|
1833
|
+
expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).security_mode.signing_enabled).to eq 1
|
1834
|
+
end
|
864
1835
|
end
|
865
1836
|
|
866
1837
|
describe '#smb2_ntlmssp_authenticate' do
|
1838
|
+
before :example do
|
1839
|
+
allow(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
|
1840
|
+
allow(smb2_client).to receive(:send_recv)
|
1841
|
+
end
|
1842
|
+
|
867
1843
|
it 'sends the request packet and receives a response' do
|
868
|
-
expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
|
869
|
-
expect(
|
870
|
-
|
1844
|
+
expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
|
1845
|
+
expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
|
1846
|
+
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
1847
|
+
end
|
1848
|
+
|
1849
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1850
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1851
|
+
smb2_client.dialect = dialect
|
1852
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1853
|
+
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
1854
|
+
end
|
1855
|
+
end
|
1856
|
+
|
1857
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1858
|
+
smb2_client.dialect = '0x0311'
|
1859
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
|
871
1860
|
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
872
1861
|
end
|
873
1862
|
end
|
@@ -888,7 +1877,7 @@ RSpec.describe RubySMB::Client do
|
|
888
1877
|
expect(smb2_client.smb2_ntlmssp_final_packet(response.to_binary_s)).to eq response
|
889
1878
|
end
|
890
1879
|
|
891
|
-
it '
|
1880
|
+
it 'raise an InvalidPacket exception when the response is not valid' do
|
892
1881
|
expect { smb2_client.smb2_ntlmssp_final_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
893
1882
|
end
|
894
1883
|
end
|
@@ -1003,6 +1992,108 @@ RSpec.describe RubySMB::Client do
|
|
1003
1992
|
end
|
1004
1993
|
end
|
1005
1994
|
end
|
1995
|
+
|
1996
|
+
describe '#smb3_sign' do
|
1997
|
+
context 'if signing is required and we have a session key' do
|
1998
|
+
let(:request) {
|
1999
|
+
packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
2000
|
+
packet.smb2_header.flags.signed = 1
|
2001
|
+
packet.smb2_header.signature = "\x00" * 16
|
2002
|
+
packet
|
2003
|
+
}
|
2004
|
+
let(:session_key) { 'Session Key' }
|
2005
|
+
before :example do
|
2006
|
+
smb3_client.session_key = session_key
|
2007
|
+
smb3_client.signing_required = true
|
2008
|
+
end
|
2009
|
+
|
2010
|
+
['0x0300', '0x0302'].each do |dialect|
|
2011
|
+
context "with #{dialect} dialect" do
|
2012
|
+
it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
|
2013
|
+
smb3_client.dialect = dialect
|
2014
|
+
fake_hash = "\x34\xc0\x40\xfe\x87\xcf\x49\x3d\x37\x87\x52\xd0\xd5\xf5\xfb\x86".b
|
2015
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
|
2016
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
|
2017
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2018
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2019
|
+
end
|
2020
|
+
end
|
2021
|
+
end
|
2022
|
+
|
2023
|
+
context "with 0x0311 dialect" do
|
2024
|
+
it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
|
2025
|
+
smb3_client.dialect = '0x0311'
|
2026
|
+
preauth_integrity_hash_value = 'Preauth Integrity Hash'
|
2027
|
+
fake_hash = "\x0e\x49\x6f\x8e\x74\x7c\xf2\xa0\x88\x5e\x9d\x54\xff\x0d\x0d\xfa".b
|
2028
|
+
smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
|
2029
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
|
2030
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
|
2031
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2032
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2033
|
+
end
|
2034
|
+
end
|
2035
|
+
|
2036
|
+
context 'with an incompatible dialect' do
|
2037
|
+
it 'raises the expected exception' do
|
2038
|
+
smb3_client.dialect = '0x0202'
|
2039
|
+
expect { smb3_client.smb3_sign(request) }.to raise_error(
|
2040
|
+
RubySMB::Error::SigningError,
|
2041
|
+
'Dialect is incompatible with SMBv3 signing'
|
2042
|
+
)
|
2043
|
+
end
|
2044
|
+
end
|
2045
|
+
end
|
2046
|
+
|
2047
|
+
context 'if signing is not required but it is a TreeConnectRequest and we have a session key' do
|
2048
|
+
let(:request) {
|
2049
|
+
packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
|
2050
|
+
packet.smb2_header.flags.signed = 1
|
2051
|
+
packet.smb2_header.signature = "\x00" * 16
|
2052
|
+
packet
|
2053
|
+
}
|
2054
|
+
let(:session_key) { 'Session Key' }
|
2055
|
+
before :example do
|
2056
|
+
smb3_client.session_key = session_key
|
2057
|
+
smb3_client.signing_required = false
|
2058
|
+
end
|
2059
|
+
|
2060
|
+
['0x0300', '0x0302'].each do |dialect|
|
2061
|
+
context "with #{dialect} dialect" do
|
2062
|
+
it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
|
2063
|
+
smb3_client.dialect = dialect
|
2064
|
+
fake_hash = "\x34\x9e\x28\xb9\x50\x08\x34\x31\xc0\x83\x9d\xba\x56\xa5\x70\xa4".b
|
2065
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
|
2066
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
|
2067
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2068
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2069
|
+
end
|
2070
|
+
end
|
2071
|
+
end
|
2072
|
+
|
2073
|
+
context "with 0x0311 dialect" do
|
2074
|
+
it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
|
2075
|
+
smb3_client.dialect = '0x0311'
|
2076
|
+
preauth_integrity_hash_value = 'Preauth Integrity Hash'
|
2077
|
+
fake_hash = "\x83\xd9\x31\x39\x60\x46\xbe\x1e\x29\x34\xc8\xcf\x8c\x8e\xb4\x73".b
|
2078
|
+
smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
|
2079
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
|
2080
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
|
2081
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2082
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2083
|
+
end
|
2084
|
+
end
|
2085
|
+
|
2086
|
+
context 'with an incompatible dialect' do
|
2087
|
+
it 'raises the expected exception' do
|
2088
|
+
smb3_client.dialect = '0x0202'
|
2089
|
+
expect { smb3_client.smb3_sign(request) }.to raise_error(
|
2090
|
+
RubySMB::Error::SigningError,
|
2091
|
+
'Dialect is incompatible with SMBv3 signing'
|
2092
|
+
)
|
2093
|
+
end
|
2094
|
+
end
|
2095
|
+
end
|
2096
|
+
end
|
1006
2097
|
end
|
1007
2098
|
|
1008
2099
|
context '#increment_smb_message_id' do
|
@@ -1056,7 +2147,10 @@ RSpec.describe RubySMB::Client do
|
|
1056
2147
|
|
1057
2148
|
it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
|
1058
2149
|
response.smb_header.nt_status = 0xc0000015
|
1059
|
-
expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
|
2150
|
+
expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
|
2151
|
+
RubySMB::Error::UnexpectedStatusCode,
|
2152
|
+
'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
|
2153
|
+
)
|
1060
2154
|
end
|
1061
2155
|
|
1062
2156
|
it 'creates a new Tree from itself, the share path, and the response packet' do
|
@@ -1077,11 +2171,14 @@ RSpec.describe RubySMB::Client do
|
|
1077
2171
|
}
|
1078
2172
|
|
1079
2173
|
describe '#smb2_tree_connect' do
|
1080
|
-
it 'builds and sends
|
2174
|
+
it 'builds and sends the expected TreeconnectRequest for the supplied share' do
|
1081
2175
|
allow(RubySMB::SMB2::Packet::TreeConnectRequest).to receive(:new).and_return(request)
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
2176
|
+
expect(smb2_client).to receive(:send_recv) do |req|
|
2177
|
+
expect(req).to eq(request)
|
2178
|
+
expect(req.smb2_header.tree_id).to eq(65_535)
|
2179
|
+
expect(req.path).to eq(path.encode('UTF-16LE'))
|
2180
|
+
response.to_binary_s
|
2181
|
+
end
|
1085
2182
|
smb2_client.smb2_tree_connect(path)
|
1086
2183
|
end
|
1087
2184
|
|
@@ -1100,11 +2197,20 @@ RSpec.describe RubySMB::Client do
|
|
1100
2197
|
|
1101
2198
|
it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
|
1102
2199
|
response.smb2_header.nt_status = 0xc0000015
|
1103
|
-
expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
|
2200
|
+
expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
|
2201
|
+
RubySMB::Error::UnexpectedStatusCode,
|
2202
|
+
'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
|
2203
|
+
)
|
1104
2204
|
end
|
1105
2205
|
|
1106
2206
|
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)
|
2207
|
+
expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: false)
|
2208
|
+
smb2_client.smb2_tree_from_response(path, response)
|
2209
|
+
end
|
2210
|
+
|
2211
|
+
it 'creates a new with encryption set if the response requires it' do
|
2212
|
+
response.share_flags.encrypt = 1
|
2213
|
+
expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: true)
|
1108
2214
|
smb2_client.smb2_tree_from_response(path, response)
|
1109
2215
|
end
|
1110
2216
|
end
|
@@ -1199,6 +2305,12 @@ RSpec.describe RubySMB::Client do
|
|
1199
2305
|
expect(smb1_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
|
1200
2306
|
expect(smb1_client.echo).to eq WindowsError::NTStatus::STATUS_ABANDONED
|
1201
2307
|
end
|
2308
|
+
|
2309
|
+
it 'raise an InvalidPacket exception when the response is not valid' do
|
2310
|
+
echo_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX
|
2311
|
+
allow(smb1_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
|
2312
|
+
expect { smb1_client.echo }.to raise_error(RubySMB::Error::InvalidPacket)
|
2313
|
+
end
|
1202
2314
|
end
|
1203
2315
|
|
1204
2316
|
context 'with SMB2' do
|
@@ -1210,8 +2322,355 @@ RSpec.describe RubySMB::Client do
|
|
1210
2322
|
expect(smb2_client).to receive(:send_recv).with(echo_request).and_return(echo_response.to_binary_s)
|
1211
2323
|
expect(smb2_client.smb2_echo).to eq echo_response
|
1212
2324
|
end
|
2325
|
+
|
2326
|
+
it 'raise an InvalidPacket exception when the response is not valid' do
|
2327
|
+
echo_response.smb2_header.command = RubySMB::SMB2::Commands::SESSION_SETUP
|
2328
|
+
allow(smb2_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
|
2329
|
+
expect { smb2_client.smb2_echo }.to raise_error(RubySMB::Error::InvalidPacket)
|
2330
|
+
end
|
2331
|
+
end
|
2332
|
+
end
|
2333
|
+
|
2334
|
+
context 'Winreg' do
|
2335
|
+
describe '#connect_to_winreg' do
|
2336
|
+
let(:host) { '1.2.3.4' }
|
2337
|
+
let(:share) { "\\\\#{host}\\IPC$" }
|
2338
|
+
let(:ipc_tree) { double('IPC$ tree') }
|
2339
|
+
let(:named_pipe) { double('Named pipe') }
|
2340
|
+
before :example do
|
2341
|
+
allow(ipc_tree).to receive_messages(
|
2342
|
+
:share => share,
|
2343
|
+
:open_file => named_pipe
|
2344
|
+
)
|
2345
|
+
allow(client).to receive(:tree_connect).and_return(ipc_tree)
|
2346
|
+
end
|
2347
|
+
|
2348
|
+
context 'when the client is already connected to the IPC$ share' do
|
2349
|
+
before :example do
|
2350
|
+
client.tree_connects << ipc_tree
|
2351
|
+
allow(ipc_tree).to receive(:share).and_return(share)
|
2352
|
+
end
|
2353
|
+
|
2354
|
+
it 'does not connect to the already connected tree' do
|
2355
|
+
client.connect_to_winreg(host)
|
2356
|
+
expect(client).to_not have_received(:tree_connect)
|
2357
|
+
end
|
2358
|
+
end
|
2359
|
+
|
2360
|
+
it 'calls #tree_connect' do
|
2361
|
+
client.connect_to_winreg(host)
|
2362
|
+
expect(client).to have_received(:tree_connect).with(share)
|
2363
|
+
end
|
2364
|
+
|
2365
|
+
it 'open \'winreg\' file on the IPC$ Tree' do
|
2366
|
+
client.connect_to_winreg(host)
|
2367
|
+
expect(ipc_tree).to have_received(:open_file).with(filename: "winreg", write: true, read: true)
|
2368
|
+
end
|
2369
|
+
|
2370
|
+
it 'returns the expected opened named pipe' do
|
2371
|
+
expect(client.connect_to_winreg(host)).to eq(named_pipe)
|
2372
|
+
end
|
2373
|
+
|
2374
|
+
context 'when a block is given' do
|
2375
|
+
before :example do
|
2376
|
+
allow(named_pipe).to receive(:close)
|
2377
|
+
end
|
2378
|
+
|
2379
|
+
it 'yields the expected named_pipe' do
|
2380
|
+
client.connect_to_winreg(host) do |np|
|
2381
|
+
expect(np).to eq(named_pipe)
|
2382
|
+
end
|
2383
|
+
end
|
2384
|
+
|
2385
|
+
it 'closes the named pipe' do
|
2386
|
+
client.connect_to_winreg(host) { |np| }
|
2387
|
+
expect(named_pipe).to have_received(:close)
|
2388
|
+
end
|
2389
|
+
|
2390
|
+
it 'returns the block return value' do
|
2391
|
+
result = double('Result')
|
2392
|
+
expect(client.connect_to_winreg(host) { |np| result }).to eq(result)
|
2393
|
+
end
|
2394
|
+
end
|
2395
|
+
end
|
2396
|
+
|
2397
|
+
describe '#has_registry_key?' do
|
2398
|
+
let(:host) { '1.2.3.4' }
|
2399
|
+
let(:key) { 'HKLM\\Registry\\Key' }
|
2400
|
+
let(:named_pipe) { double('Named pipe') }
|
2401
|
+
let(:result) { double('Result') }
|
2402
|
+
before :example do
|
2403
|
+
allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
|
2404
|
+
allow(named_pipe).to receive(:has_registry_key?).and_return(result)
|
2405
|
+
end
|
2406
|
+
|
2407
|
+
it 'calls #connect_to_winreg to wrap the main logic around' do
|
2408
|
+
client.has_registry_key?(host, key)
|
2409
|
+
expect(client).to have_received(:connect_to_winreg).with(host)
|
2410
|
+
end
|
2411
|
+
|
2412
|
+
it 'calls Pipe #has_registry_key?' do
|
2413
|
+
client.has_registry_key?(host, key)
|
2414
|
+
expect(named_pipe).to have_received(:has_registry_key?).with(key)
|
2415
|
+
end
|
2416
|
+
end
|
2417
|
+
|
2418
|
+
describe '#read_registry_key_value' do
|
2419
|
+
let(:host) { '1.2.3.4' }
|
2420
|
+
let(:key) { 'HKLM\\Registry\\Key' }
|
2421
|
+
let(:value_name) { 'Value' }
|
2422
|
+
let(:named_pipe) { double('Named pipe') }
|
2423
|
+
let(:result) { double('Result') }
|
2424
|
+
before :example do
|
2425
|
+
allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
|
2426
|
+
allow(named_pipe).to receive(:read_registry_key_value).and_return(result)
|
2427
|
+
end
|
2428
|
+
|
2429
|
+
it 'calls #connect_to_winreg to wrap the main logic around' do
|
2430
|
+
client.read_registry_key_value(host, key, value_name)
|
2431
|
+
expect(client).to have_received(:connect_to_winreg).with(host)
|
2432
|
+
end
|
2433
|
+
|
2434
|
+
it 'calls Pipe #read_registry_key_value' do
|
2435
|
+
client.read_registry_key_value(host, key, value_name)
|
2436
|
+
expect(named_pipe).to have_received(:read_registry_key_value).with(key, value_name)
|
2437
|
+
end
|
2438
|
+
end
|
2439
|
+
|
2440
|
+
describe '#enum_registry_key' do
|
2441
|
+
let(:host) { '1.2.3.4' }
|
2442
|
+
let(:key) { 'HKLM\\Registry\\Key' }
|
2443
|
+
let(:named_pipe) { double('Named pipe') }
|
2444
|
+
let(:result) { double('Result') }
|
2445
|
+
before :example do
|
2446
|
+
allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
|
2447
|
+
allow(named_pipe).to receive(:enum_registry_key).and_return(result)
|
2448
|
+
end
|
2449
|
+
|
2450
|
+
it 'calls #connect_to_winreg to wrap the main logic around' do
|
2451
|
+
client.enum_registry_key(host, key)
|
2452
|
+
expect(client).to have_received(:connect_to_winreg).with(host)
|
2453
|
+
end
|
2454
|
+
|
2455
|
+
it 'calls Pipe #enum_registry_key' do
|
2456
|
+
client.enum_registry_key(host, key)
|
2457
|
+
expect(named_pipe).to have_received(:enum_registry_key).with(key)
|
2458
|
+
end
|
2459
|
+
end
|
2460
|
+
|
2461
|
+
describe '#enum_registry_values' do
|
2462
|
+
let(:host) { '1.2.3.4' }
|
2463
|
+
let(:key) { 'HKLM\\Registry\\Key' }
|
2464
|
+
let(:named_pipe) { double('Named pipe') }
|
2465
|
+
let(:result) { double('Result') }
|
2466
|
+
before :example do
|
2467
|
+
allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
|
2468
|
+
allow(named_pipe).to receive(:enum_registry_values).and_return(result)
|
2469
|
+
end
|
2470
|
+
|
2471
|
+
it 'calls #connect_to_winreg to wrap the main logic around' do
|
2472
|
+
client.enum_registry_values(host, key)
|
2473
|
+
expect(client).to have_received(:connect_to_winreg).with(host)
|
2474
|
+
end
|
2475
|
+
|
2476
|
+
it 'calls Pipe #enum_registry_values' do
|
2477
|
+
client.enum_registry_values(host, key)
|
2478
|
+
expect(named_pipe).to have_received(:enum_registry_values).with(key)
|
2479
|
+
end
|
1213
2480
|
end
|
1214
2481
|
end
|
1215
2482
|
|
2483
|
+
describe '#update_preauth_hash' do
|
2484
|
+
it 'raises an EncryptionError exception if the preauth integrity hash algorithm is not known' do
|
2485
|
+
expect { client.update_preauth_hash('Test') }.to raise_error(
|
2486
|
+
RubySMB::Error::EncryptionError,
|
2487
|
+
'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
|
2488
|
+
)
|
2489
|
+
end
|
2490
|
+
|
2491
|
+
it 'computes the hash value' do
|
2492
|
+
packet = RubySMB::SMB2::Packet::EchoRequest.new
|
2493
|
+
data = 'Previous hash'
|
2494
|
+
algo = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[
|
2495
|
+
RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
2496
|
+
]
|
2497
|
+
client.preauth_integrity_hash_algorithm = algo
|
2498
|
+
client.preauth_integrity_hash_value = data
|
2499
|
+
hash = OpenSSL::Digest.digest(algo, data + packet.to_binary_s)
|
2500
|
+
client.update_preauth_hash(packet)
|
2501
|
+
expect(client.preauth_integrity_hash_value).to eq(hash)
|
2502
|
+
end
|
2503
|
+
end
|
2504
|
+
|
2505
|
+
context 'Encryption' do
|
2506
|
+
describe '#smb3_encrypt' do
|
2507
|
+
let(:transform_packet) { double('TransformHeader packet') }
|
2508
|
+
let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
|
2509
|
+
let(:data) { RubySMB::SMB2::Packet::TreeConnectRequest.new.to_binary_s }
|
2510
|
+
|
2511
|
+
before :example do
|
2512
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:new).and_return(transform_packet)
|
2513
|
+
allow(transform_packet).to receive(:encrypt)
|
2514
|
+
client.session_key = session_key
|
2515
|
+
end
|
2516
|
+
|
2517
|
+
it 'does not generate a new client encryption key if it already exists' do
|
2518
|
+
client.client_encryption_key = 'key'
|
2519
|
+
expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
|
2520
|
+
expect(client.client_encryption_key).to eq('key')
|
2521
|
+
client.smb3_encrypt(data)
|
2522
|
+
end
|
2523
|
+
|
2524
|
+
['0x0300', '0x0302'].each do |dialect|
|
2525
|
+
context "with #{dialect} dialect" do
|
2526
|
+
before :example do
|
2527
|
+
client.dialect = dialect
|
2528
|
+
end
|
2529
|
+
|
2530
|
+
it 'generates the client encryption key with the expected parameters' do
|
2531
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2532
|
+
session_key,
|
2533
|
+
"SMB2AESCCM\x00",
|
2534
|
+
"ServerIn \x00"
|
2535
|
+
).and_call_original
|
2536
|
+
client.smb3_encrypt(data)
|
2537
|
+
end
|
2538
|
+
end
|
2539
|
+
end
|
2540
|
+
|
2541
|
+
context 'with 0x0311 dialect' do
|
2542
|
+
it 'generates the client encryption key with the expected parameters' do
|
2543
|
+
client.preauth_integrity_hash_value = ''
|
2544
|
+
client.dialect = '0x0311'
|
2545
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2546
|
+
session_key,
|
2547
|
+
"SMBC2SCipherKey\x00",
|
2548
|
+
''
|
2549
|
+
).and_call_original
|
2550
|
+
client.smb3_encrypt(data)
|
2551
|
+
end
|
2552
|
+
end
|
2553
|
+
|
2554
|
+
it 'raises the expected exception if the dialect is incompatible' do
|
2555
|
+
client.dialect = '0x0202'
|
2556
|
+
expect { client.smb3_encrypt(data) }.to raise_error(RubySMB::Error::EncryptionError)
|
2557
|
+
end
|
2558
|
+
|
2559
|
+
it 'creates a TransformHeader packet and encrypt the data' do
|
2560
|
+
client.dialect = '0x0300'
|
2561
|
+
client.encryption_algorithm = 'AES-128-CCM'
|
2562
|
+
client.session_id = 123
|
2563
|
+
client.smb3_encrypt(data)
|
2564
|
+
expect(RubySMB::SMB2::Packet::TransformHeader).to have_received(:new).with(flags: 1, session_id: 123)
|
2565
|
+
expect(transform_packet).to have_received(:encrypt).with(data, client.client_encryption_key, algorithm: 'AES-128-CCM')
|
2566
|
+
end
|
2567
|
+
|
2568
|
+
it 'generates the expected client encryption key with 0x0302 dialect' do
|
2569
|
+
client.dialect = '0x0302'
|
2570
|
+
expected_enc_key =
|
2571
|
+
"\xa4\xfa\x23\xc1\xb0\x65\x84\xce\x47\x08\x5b\xe0\x64\x98\xd7\x87".b
|
2572
|
+
client.smb3_encrypt(data)
|
2573
|
+
expect(client.client_encryption_key).to eq expected_enc_key
|
2574
|
+
end
|
2575
|
+
|
2576
|
+
it 'generates the expected client encryption key with 0x0311 dialect' do
|
2577
|
+
client.dialect = '0x0311'
|
2578
|
+
client.session_key =
|
2579
|
+
"\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
|
2580
|
+
client.preauth_integrity_hash_value =
|
2581
|
+
"\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
|
2582
|
+
"\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
|
2583
|
+
"\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
|
2584
|
+
"\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
|
2585
|
+
expected_enc_key =
|
2586
|
+
"\xc7\x4e\xfe\x4d\x15\x48\x5b\x0b\x71\x45\x49\x26\x8a\xd9\x6c\xaa".b
|
2587
|
+
client.smb3_encrypt(data)
|
2588
|
+
expect(client.client_encryption_key).to eq expected_enc_key
|
2589
|
+
end
|
2590
|
+
end
|
2591
|
+
|
2592
|
+
describe '#smb3_decrypt' do
|
2593
|
+
let(:transform_packet) { double('TransformHeader packet') }
|
2594
|
+
let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
|
2595
|
+
|
2596
|
+
before :example do
|
2597
|
+
allow(transform_packet).to receive(:decrypt)
|
2598
|
+
client.session_key = session_key
|
2599
|
+
end
|
2600
|
+
|
2601
|
+
it 'does not generate a new server encryption key if it already exists' do
|
2602
|
+
client.server_encryption_key = 'key'
|
2603
|
+
expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
|
2604
|
+
expect(client.server_encryption_key).to eq('key')
|
2605
|
+
client.smb3_decrypt(transform_packet)
|
2606
|
+
end
|
2607
|
+
|
2608
|
+
['0x0300', '0x0302'].each do |dialect|
|
2609
|
+
context "with #{dialect} dialect" do
|
2610
|
+
before :example do
|
2611
|
+
client.dialect = dialect
|
2612
|
+
end
|
2613
|
+
|
2614
|
+
it 'generates the client encryption key with the expected parameters' do
|
2615
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2616
|
+
session_key,
|
2617
|
+
"SMB2AESCCM\x00",
|
2618
|
+
"ServerOut\x00"
|
2619
|
+
).and_call_original
|
2620
|
+
client.smb3_decrypt(transform_packet)
|
2621
|
+
end
|
2622
|
+
end
|
2623
|
+
end
|
2624
|
+
|
2625
|
+
context 'with 0x0311 dialect' do
|
2626
|
+
it 'generates the client encryption key with the expected parameters' do
|
2627
|
+
client.preauth_integrity_hash_value = ''
|
2628
|
+
client.dialect = '0x0311'
|
2629
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2630
|
+
session_key,
|
2631
|
+
"SMBS2CCipherKey\x00",
|
2632
|
+
''
|
2633
|
+
).and_call_original
|
2634
|
+
client.smb3_decrypt(transform_packet)
|
2635
|
+
end
|
2636
|
+
end
|
2637
|
+
|
2638
|
+
it 'raises the expected exception if the dialect is incompatible' do
|
2639
|
+
client.dialect = '0x0202'
|
2640
|
+
expect { client.smb3_decrypt(transform_packet) }.to raise_error(RubySMB::Error::EncryptionError)
|
2641
|
+
end
|
2642
|
+
|
2643
|
+
it 'creates a TransformHeader packet and encrypt the data' do
|
2644
|
+
client.dialect = '0x0300'
|
2645
|
+
client.encryption_algorithm = 'AES-128-CCM'
|
2646
|
+
client.session_id = 123
|
2647
|
+
client.smb3_decrypt(transform_packet)
|
2648
|
+
expect(transform_packet).to have_received(:decrypt).with(client.server_encryption_key, algorithm: 'AES-128-CCM')
|
2649
|
+
end
|
2650
|
+
|
2651
|
+
it 'generates the expected server encryption key with 0x0302 dialect' do
|
2652
|
+
client.dialect = '0x0302'
|
2653
|
+
expected_enc_key =
|
2654
|
+
"\x65\x21\xd3\x6d\xe9\xe3\x5a\x66\x09\x61\xae\x3e\xc6\x49\x6b\xdf".b
|
2655
|
+
client.smb3_decrypt(transform_packet)
|
2656
|
+
expect(client.server_encryption_key).to eq expected_enc_key
|
2657
|
+
end
|
2658
|
+
|
2659
|
+
it 'generates the expected server encryption key with 0x0311 dialect' do
|
2660
|
+
client.dialect = '0x0311'
|
2661
|
+
client.session_key =
|
2662
|
+
"\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
|
2663
|
+
client.preauth_integrity_hash_value =
|
2664
|
+
"\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
|
2665
|
+
"\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
|
2666
|
+
"\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
|
2667
|
+
"\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
|
2668
|
+
expected_enc_key =
|
2669
|
+
"\x8c\x2c\x31\x15\x66\xba\xa9\xab\xcf\xb2\x47\x8d\x72\xd5\xd7\x4a".b
|
2670
|
+
client.smb3_decrypt(transform_packet)
|
2671
|
+
expect(client.server_encryption_key).to eq expected_enc_key
|
2672
|
+
end
|
2673
|
+
end
|
2674
|
+
end
|
1216
2675
|
end
|
1217
2676
|
|