ruby_smb 1.0.3 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.travis.yml +3 -2
- data/Gemfile +6 -2
- data/README.md +35 -47
- data/examples/enum_registry_key.rb +28 -0
- data/examples/enum_registry_values.rb +30 -0
- data/examples/negotiate.rb +51 -8
- data/examples/pipes.rb +2 -1
- data/examples/read_file_encryption.rb +56 -0
- data/examples/read_registry_key_value.rb +32 -0
- data/lib/ruby_smb.rb +4 -1
- data/lib/ruby_smb/client.rb +233 -22
- data/lib/ruby_smb/client/authentication.rb +70 -33
- data/lib/ruby_smb/client/echo.rb +20 -2
- data/lib/ruby_smb/client/encryption.rb +62 -0
- data/lib/ruby_smb/client/negotiation.rb +172 -24
- data/lib/ruby_smb/client/signing.rb +19 -0
- data/lib/ruby_smb/client/tree_connect.rb +24 -18
- data/lib/ruby_smb/client/utils.rb +8 -7
- data/lib/ruby_smb/client/winreg.rb +46 -0
- data/lib/ruby_smb/crypto.rb +30 -0
- data/lib/ruby_smb/dcerpc.rb +38 -0
- data/lib/ruby_smb/dcerpc/bind.rb +2 -2
- data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
- data/lib/ruby_smb/dcerpc/error.rb +3 -0
- data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
- data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
- data/lib/ruby_smb/dcerpc/request.rb +28 -9
- data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
- data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
- data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
- data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
- data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
- data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
- data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
- data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
- data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
- data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
- data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
- data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
- data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
- data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
- data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
- data/lib/ruby_smb/dispatcher/socket.rb +4 -3
- data/lib/ruby_smb/error.rb +68 -2
- data/lib/ruby_smb/generic_packet.rb +33 -4
- data/lib/ruby_smb/smb1/commands.rb +1 -1
- data/lib/ruby_smb/smb1/file.rb +66 -15
- data/lib/ruby_smb/smb1/packet/close_request.rb +2 -5
- data/lib/ruby_smb/smb1/packet/close_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/echo_request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/echo_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/empty_packet.rb +10 -1
- data/lib/ruby_smb/smb1/packet/logoff_request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/logoff_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/negotiate_request.rb +2 -5
- data/lib/ruby_smb/smb1/packet/negotiate_response.rb +3 -7
- data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +4 -4
- data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/nt_create_andx_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/nt_trans/create_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/nt_trans/request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/nt_trans/response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/read_andx_request.rb +2 -5
- data/lib/ruby_smb/smb1/packet/read_andx_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +3 -2
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +2 -5
- data/lib/ruby_smb/smb1/packet/session_setup_response.rb +3 -2
- data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_request.rb +0 -1
- data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_response.rb +3 -2
- data/lib/ruby_smb/smb1/packet/trans/request.rb +2 -5
- data/lib/ruby_smb/smb1/packet/trans/response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_response.rb +1 -1
- data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +8 -2
- data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +8 -2
- data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +2 -4
- data/lib/ruby_smb/smb1/packet/trans2/response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/tree_connect_response.rb +13 -3
- data/lib/ruby_smb/smb1/packet/tree_disconnect_request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/tree_disconnect_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/write_andx_request.rb +3 -6
- data/lib/ruby_smb/smb1/packet/write_andx_response.rb +2 -1
- data/lib/ruby_smb/smb1/pipe.rb +87 -6
- data/lib/ruby_smb/smb1/tree.rb +50 -3
- data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
- data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
- data/lib/ruby_smb/smb2/file.rb +103 -25
- data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
- data/lib/ruby_smb/smb2/packet.rb +2 -0
- data/lib/ruby_smb/smb2/packet/close_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/close_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
- data/lib/ruby_smb/smb2/packet/create_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/create_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/echo_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/echo_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/error_packet.rb +15 -3
- data/lib/ruby_smb/smb2/packet/ioctl_request.rb +2 -5
- data/lib/ruby_smb/smb2/packet/ioctl_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/logoff_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/logoff_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -17
- data/lib/ruby_smb/smb2/packet/negotiate_response.rb +52 -5
- data/lib/ruby_smb/smb2/packet/query_directory_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/query_directory_response.rb +8 -2
- data/lib/ruby_smb/smb2/packet/read_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/read_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/session_setup_request.rb +2 -5
- data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/set_info_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/set_info_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
- data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +93 -10
- data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +10 -22
- data/lib/ruby_smb/smb2/packet/tree_disconnect_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/tree_disconnect_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/write_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/write_response.rb +2 -1
- data/lib/ruby_smb/smb2/pipe.rb +86 -12
- data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
- data/lib/ruby_smb/smb2/tree.rb +65 -21
- data/lib/ruby_smb/version.rb +1 -1
- data/ruby_smb.gemspec +5 -3
- data/spec/lib/ruby_smb/client_spec.rb +1612 -108
- data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
- data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
- data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
- data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
- data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
- data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
- data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
- data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
- data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +2 -2
- data/spec/lib/ruby_smb/error_spec.rb +59 -0
- data/spec/lib/ruby_smb/generic_packet_spec.rb +52 -4
- data/spec/lib/ruby_smb/smb1/file_spec.rb +191 -2
- data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +68 -0
- data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +11 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +11 -2
- data/spec/lib/ruby_smb/smb1/packet/tree_connect_response_spec.rb +40 -0
- data/spec/lib/ruby_smb/smb1/pipe_spec.rb +272 -149
- data/spec/lib/ruby_smb/smb1/tree_spec.rb +44 -7
- data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
- data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
- data/spec/lib/ruby_smb/smb2/file_spec.rb +323 -6
- data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
- data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
- data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +78 -0
- data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
- data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
- data/spec/lib/ruby_smb/smb2/packet/query_directory_response_spec.rb +8 -0
- data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
- data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
- data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -22
- data/spec/lib/ruby_smb/smb2/pipe_spec.rb +286 -149
- data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +261 -2
- metadata +191 -83
- metadata.gz.sig +0 -0
- data/lib/ruby_smb/smb1/dcerpc.rb +0 -67
- data/lib/ruby_smb/smb2/dcerpc.rb +0 -70
- data/spec/lib/ruby_smb/smb1/packet/error_packet_spec.rb +0 -37
data/lib/ruby_smb/client/echo.rb
CHANGED
@@ -16,7 +16,16 @@ module RubySMB
|
|
16
16
|
(count - 1).times do
|
17
17
|
raw_response = dispatcher.recv_packet
|
18
18
|
end
|
19
|
-
RubySMB::SMB1::Packet::EchoResponse.read(raw_response)
|
19
|
+
response = RubySMB::SMB1::Packet::EchoResponse.read(raw_response)
|
20
|
+
unless response.valid?
|
21
|
+
raise RubySMB::Error::InvalidPacket.new(
|
22
|
+
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
23
|
+
expected_cmd: RubySMB::SMB1::Packet::EchoResponse::COMMAND,
|
24
|
+
received_proto: response.smb_header.protocol,
|
25
|
+
received_cmd: response.smb_header.command
|
26
|
+
)
|
27
|
+
end
|
28
|
+
response
|
20
29
|
end
|
21
30
|
|
22
31
|
# Sends an ECHO request packet and returns the
|
@@ -26,7 +35,16 @@ module RubySMB
|
|
26
35
|
def smb2_echo
|
27
36
|
request = RubySMB::SMB2::Packet::EchoRequest.new
|
28
37
|
raw_response = send_recv(request)
|
29
|
-
RubySMB::SMB2::Packet::EchoResponse.read(raw_response)
|
38
|
+
response = RubySMB::SMB2::Packet::EchoResponse.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::EchoResponse::COMMAND,
|
43
|
+
received_proto: response.smb2_header.protocol,
|
44
|
+
received_cmd: response.smb2_header.command
|
45
|
+
)
|
46
|
+
end
|
47
|
+
response
|
30
48
|
end
|
31
49
|
end
|
32
50
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module RubySMB
|
2
|
+
class Client
|
3
|
+
# Contains the methods for handling encryption / decryption
|
4
|
+
module Encryption
|
5
|
+
def smb3_encrypt(data)
|
6
|
+
unless @client_encryption_key
|
7
|
+
case @dialect
|
8
|
+
when '0x0300', '0x0302'
|
9
|
+
@client_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
10
|
+
@session_key,
|
11
|
+
"SMB2AESCCM\x00",
|
12
|
+
"ServerIn \x00"
|
13
|
+
)
|
14
|
+
when '0x0311'
|
15
|
+
@client_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
16
|
+
@session_key,
|
17
|
+
"SMBC2SCipherKey\x00",
|
18
|
+
@preauth_integrity_hash_value
|
19
|
+
)
|
20
|
+
else
|
21
|
+
raise RubySMB::Error::EncryptionError.new('Dialect is incompatible with SMBv3 encryption')
|
22
|
+
end
|
23
|
+
######
|
24
|
+
# DEBUG
|
25
|
+
#puts "Client encryption key = #{@client_encryption_key.each_byte.map {|e| '%02x' % e}.join}"
|
26
|
+
######
|
27
|
+
end
|
28
|
+
|
29
|
+
th = RubySMB::SMB2::Packet::TransformHeader.new(flags: 1, session_id: @session_id)
|
30
|
+
th.encrypt(data, @client_encryption_key, algorithm: @encryption_algorithm)
|
31
|
+
th
|
32
|
+
end
|
33
|
+
|
34
|
+
def smb3_decrypt(th)
|
35
|
+
unless @server_encryption_key
|
36
|
+
case @dialect
|
37
|
+
when '0x0300', '0x0302'
|
38
|
+
@server_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
39
|
+
@session_key,
|
40
|
+
"SMB2AESCCM\x00",
|
41
|
+
"ServerOut\x00"
|
42
|
+
)
|
43
|
+
when '0x0311'
|
44
|
+
@server_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
45
|
+
@session_key,
|
46
|
+
"SMBS2CCipherKey\x00",
|
47
|
+
@preauth_integrity_hash_value
|
48
|
+
)
|
49
|
+
else
|
50
|
+
raise RubySMB::Error::EncryptionError.new('Dialect is incompatible with SMBv3 decryption')
|
51
|
+
end
|
52
|
+
######
|
53
|
+
# DEBUG
|
54
|
+
#puts "Server encryption key = #{@server_encryption_key.each_byte.map {|e| '%02x' % e}.join}"
|
55
|
+
######
|
56
|
+
end
|
57
|
+
|
58
|
+
th.decrypt(@server_encryption_key, algorithm: @encryption_algorithm)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -17,10 +17,30 @@ module RubySMB
|
|
17
17
|
# internally to be able to retrieve the negotiated dialect later on.
|
18
18
|
# This is only valid for SMB1.
|
19
19
|
response_packet.dialects = request_packet.dialects if response_packet.respond_to? :dialects=
|
20
|
-
parse_negotiate_response(response_packet)
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
version = parse_negotiate_response(response_packet)
|
21
|
+
case @dialect
|
22
|
+
when '0x0300', '0x0302'
|
23
|
+
@encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM]
|
24
|
+
when '0x0311'
|
25
|
+
parse_smb3_encryption_data(request_packet, response_packet)
|
26
|
+
end
|
27
|
+
|
28
|
+
# If the response contains the SMB2 wildcard revision number dialect;
|
29
|
+
# it indicates that the server implements SMB 2.1 or future dialect
|
30
|
+
# revisions and expects the client to send a subsequent SMB2 Negotiate
|
31
|
+
# request to negotiate the actual SMB 2 Protocol revision to be used.
|
32
|
+
# The wildcard revision number is sent only in response to a
|
33
|
+
# multi-protocol negotiate request with the "SMB 2.???" dialect string.
|
34
|
+
if @dialect == '0x02ff'
|
35
|
+
self.smb2_message_id += 1
|
36
|
+
version = negotiate
|
37
|
+
end
|
38
|
+
version
|
39
|
+
rescue RubySMB::Error::InvalidPacket, Errno::ECONNRESET, RubySMB::Error::CommunicationError => e
|
40
|
+
version = request_packet.packet_smb_version
|
41
|
+
version = 'SMB3' if version == 'SMB2' && !@smb2 && @smb3
|
42
|
+
version = 'SMB2 or SMB3' if version == 'SMB2' && @smb2 && @smb3
|
43
|
+
error = "Unable to negotiate #{version} with the remote host: #{e.message}"
|
24
44
|
raise RubySMB::Error::NegotiationFailure, error
|
25
45
|
end
|
26
46
|
|
@@ -32,8 +52,8 @@ module RubySMB
|
|
32
52
|
def negotiate_request
|
33
53
|
if smb1
|
34
54
|
smb1_negotiate_request
|
35
|
-
|
36
|
-
|
55
|
+
else
|
56
|
+
smb2_3_negotiate_request
|
37
57
|
end
|
38
58
|
end
|
39
59
|
|
@@ -47,23 +67,38 @@ module RubySMB
|
|
47
67
|
def negotiate_response(raw_data)
|
48
68
|
response = nil
|
49
69
|
if smb1
|
50
|
-
|
51
|
-
packet = RubySMB::SMB1::Packet::NegotiateResponseExtended.read raw_data
|
52
|
-
rescue StandardError => e
|
53
|
-
raise RubySMB::Error::InvalidPacket, "Not a Valid SMB1 Negoitate Response #{e.message}"
|
54
|
-
end
|
70
|
+
packet = RubySMB::SMB1::Packet::NegotiateResponseExtended.read raw_data
|
55
71
|
response = packet if packet.valid?
|
56
72
|
end
|
57
|
-
if smb2 && response.nil?
|
58
|
-
|
59
|
-
|
60
|
-
rescue StandardError => e
|
61
|
-
raise RubySMB::Error::InvalidPacket, "Not a Valid SMB2 Negoitate Response #{e.message}"
|
62
|
-
end
|
63
|
-
response = packet
|
73
|
+
if (smb2 || smb3) && response.nil?
|
74
|
+
packet = RubySMB::SMB2::Packet::NegotiateResponse.read raw_data
|
75
|
+
response = packet if packet.valid?
|
64
76
|
end
|
65
77
|
if response.nil?
|
66
|
-
|
78
|
+
if packet.packet_smb_version == 'SMB1'
|
79
|
+
extended_security = if packet.is_a? RubySMB::SMB1::Packet::NegotiateResponseExtended
|
80
|
+
packet.parameter_block.capabilities.extended_security
|
81
|
+
else
|
82
|
+
"n/a"
|
83
|
+
end
|
84
|
+
raise RubySMB::Error::InvalidPacket.new(
|
85
|
+
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
86
|
+
expected_cmd: RubySMB::SMB1::Packet::NegotiateResponseExtended::COMMAND,
|
87
|
+
expected_custom: "extended_security=1",
|
88
|
+
received_proto: packet.smb_header.protocol,
|
89
|
+
received_cmd: packet.smb_header.command,
|
90
|
+
received_custom: "extended_security=#{extended_security}"
|
91
|
+
)
|
92
|
+
elsif packet.packet_smb_version == 'SMB2'
|
93
|
+
raise RubySMB::Error::InvalidPacket.new(
|
94
|
+
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
95
|
+
expected_cmd: RubySMB::SMB2::Packet::NegotiateResponse::COMMAND,
|
96
|
+
received_proto: packet.smb2_header.protocol,
|
97
|
+
received_cmd: packet.smb2_header.command
|
98
|
+
)
|
99
|
+
else
|
100
|
+
raise RubySMB::Error::InvalidPacket, 'Unknown SMB protocol version'
|
101
|
+
end
|
67
102
|
end
|
68
103
|
response
|
69
104
|
end
|
@@ -80,26 +115,83 @@ module RubySMB
|
|
80
115
|
when RubySMB::SMB1::Packet::NegotiateResponseExtended
|
81
116
|
self.smb1 = true
|
82
117
|
self.smb2 = false
|
118
|
+
self.smb3 = false
|
83
119
|
self.signing_required = packet.parameter_block.security_mode.security_signatures_required == 1
|
84
120
|
self.dialect = packet.negotiated_dialect.to_s
|
85
121
|
# MaxBufferSize is largest message server will receive, measured from start of the SMB header. Subtract 260
|
86
122
|
# for protocol overhead. Then this value can be used for max read/write size without having to factor in
|
87
123
|
# protocol overhead every time.
|
88
124
|
self.server_max_buffer_size = packet.parameter_block.max_buffer_size - 260
|
125
|
+
self.negotiated_smb_version = 1
|
89
126
|
'SMB1'
|
90
127
|
when RubySMB::SMB2::Packet::NegotiateResponse
|
91
128
|
self.smb1 = false
|
92
|
-
|
93
|
-
|
129
|
+
unless packet.dialect_revision.to_i == 0x02ff
|
130
|
+
self.smb2 = packet.dialect_revision.to_i >= 0x0200 && packet.dialect_revision.to_i < 0x0300
|
131
|
+
self.smb3 = packet.dialect_revision.to_i >= 0x0300 && packet.dialect_revision.to_i < 0x0400
|
132
|
+
# Only enable session encryption if the server supports it
|
133
|
+
@session_encrypt_data = self.smb3 && @session_encrypt_data && packet.capabilities.encryption == 1
|
134
|
+
end
|
135
|
+
self.signing_required = packet.security_mode.signing_required == 1 if self.smb2 || self.smb3
|
94
136
|
self.dialect = "0x%04x" % packet.dialect_revision
|
95
137
|
self.server_max_read_size = packet.max_read_size
|
96
138
|
self.server_max_write_size = packet.max_write_size
|
97
139
|
self.server_max_transact_size = packet.max_transact_size
|
98
140
|
# This value is used in SMB1 only but calculate a valid value anyway
|
99
141
|
self.server_max_buffer_size = [self.server_max_read_size, self.server_max_write_size, self.server_max_transact_size].min
|
100
|
-
|
142
|
+
self.negotiated_smb_version = self.smb2 ? 2 : 3
|
143
|
+
self.server_guid = packet.server_guid
|
144
|
+
self.server_start_time = packet.server_start_time.to_time if packet.server_start_time != 0
|
145
|
+
self.server_system_time = packet.system_time.to_time if packet.system_time != 0
|
146
|
+
return "SMB#{self.negotiated_smb_version}"
|
147
|
+
else
|
148
|
+
error = 'Unable to negotiate with remote host'
|
149
|
+
if packet.status_code == WindowsError::NTStatus::STATUS_NOT_SUPPORTED
|
150
|
+
error << ", SMB2" if @smb2
|
151
|
+
error << ", SMB3" if @smb3
|
152
|
+
error << ' not supported'
|
153
|
+
end
|
154
|
+
raise RubySMB::Error::NegotiationFailure, error
|
101
155
|
end
|
156
|
+
end
|
102
157
|
|
158
|
+
def parse_smb3_encryption_data(request_packet, response_packet)
|
159
|
+
nc = response_packet.find_negotiate_context(
|
160
|
+
RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
161
|
+
)
|
162
|
+
@preauth_integrity_hash_algorithm = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[nc&.data&.hash_algorithms&.first]
|
163
|
+
unless @preauth_integrity_hash_algorithm
|
164
|
+
raise RubySMB::Error::EncryptionError.new(
|
165
|
+
'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
|
166
|
+
)
|
167
|
+
end
|
168
|
+
# Set the encryption the client will use, prioritizing AES_128_GCM over AES_128_CCM
|
169
|
+
nc = response_packet.find_negotiate_context(
|
170
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
171
|
+
)
|
172
|
+
@server_encryption_algorithms = nc&.data&.ciphers&.to_ary
|
173
|
+
if @server_encryption_algorithms.nil? || @server_encryption_algorithms.empty?
|
174
|
+
raise RubySMB::Error::EncryptionError.new(
|
175
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
176
|
+
)
|
177
|
+
end
|
178
|
+
if @server_encryption_algorithms.include?(RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM)
|
179
|
+
@encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM]
|
180
|
+
else
|
181
|
+
@encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[@server_encryption_algorithms.first]
|
182
|
+
end
|
183
|
+
unless @encryption_algorithm
|
184
|
+
raise RubySMB::Error::EncryptionError.new(
|
185
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
186
|
+
)
|
187
|
+
end
|
188
|
+
update_preauth_hash(request_packet)
|
189
|
+
update_preauth_hash(response_packet)
|
190
|
+
|
191
|
+
nc = response_packet.find_negotiate_context(
|
192
|
+
RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
193
|
+
)
|
194
|
+
@server_compression_algorithms = nc&.data&.compression_algorithms&.to_ary || []
|
103
195
|
end
|
104
196
|
|
105
197
|
# Create a {RubySMB::SMB1::Packet::NegotiateRequest} packet with the
|
@@ -112,10 +204,17 @@ module RubySMB
|
|
112
204
|
# while being guaranteed to work with any modern Windows system. We can get more sophisticated
|
113
205
|
# with switching this on and off at a later date if the need arises.
|
114
206
|
packet.smb_header.flags2.extended_security = 1
|
207
|
+
# Recent Mac OS X requires the unicode flag to be set on the Negotiate
|
208
|
+
# SMB Header request, even if this packet does not contain string fields
|
209
|
+
# (see Flags2 SMB_FLAGS2_UNICODE definition in "2.2.3.1 The SMB Header"
|
210
|
+
# documentation:
|
211
|
+
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/69a29f73-de0c-45a6-a1aa-8ceeea42217f
|
212
|
+
packet.smb_header.flags2.unicode = 1
|
115
213
|
# There is no real good reason to ever send an SMB1 Negotiate packet
|
116
214
|
# to Negotiate strictly SMB2, but the protocol WILL support it
|
117
215
|
packet.add_dialect(SMB1_DIALECT_SMB1_DEFAULT) if smb1
|
118
216
|
packet.add_dialect(SMB1_DIALECT_SMB2_DEFAULT) if smb2
|
217
|
+
packet.add_dialect(SMB1_DIALECT_SMB2_WILDCARD) if smb2 || smb3
|
119
218
|
packet
|
120
219
|
end
|
121
220
|
|
@@ -124,11 +223,60 @@ module RubySMB
|
|
124
223
|
# may want to communicate over SMB1
|
125
224
|
#
|
126
225
|
# @ return [RubySMB::SMB2::Packet::NegotiateRequest] a completed SMB2 Negotiate Request packet
|
127
|
-
def
|
226
|
+
def smb2_3_negotiate_request
|
128
227
|
packet = RubySMB::SMB2::Packet::NegotiateRequest.new
|
129
228
|
packet.security_mode.signing_enabled = 1
|
130
|
-
packet.add_dialect(SMB2_DIALECT_DEFAULT)
|
131
229
|
packet.client_guid = SecureRandom.random_bytes(16)
|
230
|
+
packet.set_dialects(SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)}) if smb2
|
231
|
+
packet = add_smb3_to_negotiate_request(packet) if smb3
|
232
|
+
packet
|
233
|
+
end
|
234
|
+
|
235
|
+
# This adds SMBv3 specific information: SMBv3 supported dialects,
|
236
|
+
# encryption capability, Negotiate Contexts if the dialect requires them
|
237
|
+
#
|
238
|
+
# @param packet [RubySMB::SMB2::Packet::NegotiateRequest] the NegotiateRequest
|
239
|
+
# to add SMB3 specific info to
|
240
|
+
# @param dialects [Array<String>] the dialects to negotiate. This must be
|
241
|
+
# an array of strings. Default is SMB3_DIALECT_DEFAULT
|
242
|
+
# @return [RubySMB::SMB2::Packet::NegotiateRequest] a completed SMB3 Negotiate Request packet
|
243
|
+
# @raise [ArgumentError] if dialects is not an array of strings
|
244
|
+
def add_smb3_to_negotiate_request(packet, dialects = SMB3_DIALECT_DEFAULT)
|
245
|
+
dialects.each do |dialect|
|
246
|
+
raise ArgumentError, 'Must be an array of strings' unless dialect.is_a? String
|
247
|
+
packet.add_dialect(dialect.to_i(16))
|
248
|
+
end
|
249
|
+
packet.capabilities.encryption = 1
|
250
|
+
|
251
|
+
if packet.dialects.include?(0x0311)
|
252
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
253
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
254
|
+
)
|
255
|
+
nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
256
|
+
nc.data.salt = SecureRandom.random_bytes(32)
|
257
|
+
packet.add_negotiate_context(nc)
|
258
|
+
|
259
|
+
@preauth_integrity_hash_value = "\x00" * 64
|
260
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
261
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
262
|
+
)
|
263
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
|
264
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
265
|
+
packet.add_negotiate_context(nc)
|
266
|
+
|
267
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
268
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
269
|
+
)
|
270
|
+
# Adding all possible compression algorithm even if we don't support
|
271
|
+
# them yet. This will force the server to disclose the support
|
272
|
+
# algorithms in the repsonse.
|
273
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
|
274
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
|
275
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
|
276
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
|
277
|
+
packet.add_negotiate_context(nc)
|
278
|
+
end
|
279
|
+
|
132
280
|
packet
|
133
281
|
end
|
134
282
|
end
|
@@ -40,6 +40,25 @@ module RubySMB
|
|
40
40
|
end
|
41
41
|
packet
|
42
42
|
end
|
43
|
+
|
44
|
+
def smb3_sign(packet)
|
45
|
+
if !session_key.empty? && (signing_required || packet.is_a?(RubySMB::SMB2::Packet::TreeConnectRequest))
|
46
|
+
case @dialect
|
47
|
+
when '0x0300', '0x0302'
|
48
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(@session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
|
49
|
+
when '0x0311'
|
50
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(@session_key, "SMBSigningKey\x00", @preauth_integrity_hash_value)
|
51
|
+
else
|
52
|
+
raise RubySMB::Error::SigningError.new('Dialect is incompatible with SMBv3 signing')
|
53
|
+
end
|
54
|
+
|
55
|
+
packet.smb2_header.flags.signed = 1
|
56
|
+
packet.smb2_header.signature = "\x00" * 16
|
57
|
+
hmac = OpenSSL::CMAC.digest('AES', signing_key, packet.to_binary_s)
|
58
|
+
packet.smb2_header.signature = hmac[0, 16]
|
59
|
+
end
|
60
|
+
packet
|
61
|
+
end
|
43
62
|
end
|
44
63
|
end
|
45
64
|
end
|
@@ -17,11 +17,7 @@ module RubySMB
|
|
17
17
|
request.smb_header.tid = 65_535
|
18
18
|
request.data_block.path = share
|
19
19
|
raw_response = send_recv(request)
|
20
|
-
|
21
|
-
response = RubySMB::SMB1::Packet::TreeConnectResponse.read(raw_response)
|
22
|
-
rescue EOFError
|
23
|
-
response = RubySMB::SMB1::Packet::EmptyPacket.read(raw_response)
|
24
|
-
end
|
20
|
+
response = RubySMB::SMB1::Packet::TreeConnectResponse.read(raw_response)
|
25
21
|
smb1_tree_from_response(share, response)
|
26
22
|
end
|
27
23
|
|
@@ -30,12 +26,19 @@ module RubySMB
|
|
30
26
|
# @param share [String] the share path to connect to
|
31
27
|
# @param response [RubySMB::SMB1::Packet::TreeConnectResponse] the response packet to parse into our Tree
|
32
28
|
# @return [RubySMB::SMB1::Tree]
|
29
|
+
# @raise [RubySMB::Error::InvalidPacket] if the response command is not a TreeConnectResponse packet
|
30
|
+
# @raise [RubySMB::Error::UnexpectedStatusCode] if the response status code is not STATUS_SUCCESS
|
33
31
|
def smb1_tree_from_response(share, response)
|
34
|
-
unless response.
|
35
|
-
raise RubySMB::Error::InvalidPacket
|
32
|
+
unless response.valid?
|
33
|
+
raise RubySMB::Error::InvalidPacket.new(
|
34
|
+
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
35
|
+
expected_cmd: RubySMB::SMB1::Packet::TreeConnectResponse::COMMAND,
|
36
|
+
received_proto: response.smb_header.protocol,
|
37
|
+
received_cmd: response.smb_header.command
|
38
|
+
)
|
36
39
|
end
|
37
40
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
38
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
41
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
39
42
|
end
|
40
43
|
RubySMB::SMB1::Tree.new(client: self, share: share, response: response)
|
41
44
|
end
|
@@ -52,13 +55,9 @@ module RubySMB
|
|
52
55
|
def smb2_tree_connect(share)
|
53
56
|
request = RubySMB::SMB2::Packet::TreeConnectRequest.new
|
54
57
|
request.smb2_header.tree_id = 65_535
|
55
|
-
request.
|
58
|
+
request.path = share
|
56
59
|
raw_response = send_recv(request)
|
57
|
-
|
58
|
-
response = RubySMB::SMB2::Packet::TreeConnectResponse.read(raw_response)
|
59
|
-
rescue EOFError
|
60
|
-
response = RubySMB::SMB2::Packet::ErrorPacket.read(raw_response)
|
61
|
-
end
|
60
|
+
response = RubySMB::SMB2::Packet::TreeConnectResponse.read(raw_response)
|
62
61
|
smb2_tree_from_response(share, response)
|
63
62
|
end
|
64
63
|
|
@@ -67,14 +66,21 @@ module RubySMB
|
|
67
66
|
# @param share [String] the share path to connect to
|
68
67
|
# @param response [RubySMB::SMB2::Packet::TreeConnectResponse] the response packet to parse into our Tree
|
69
68
|
# @return [RubySMB::SMB2::Tree]
|
69
|
+
# @raise [RubySMB::Error::InvalidPacket] if the response command is not a TreeConnectResponse packet
|
70
|
+
# @raise [RubySMB::Error::UnexpectedStatusCode] if the response status code is not STATUS_SUCCESS
|
70
71
|
def smb2_tree_from_response(share, response)
|
71
|
-
unless response.
|
72
|
-
raise RubySMB::Error::InvalidPacket
|
72
|
+
unless response.valid?
|
73
|
+
raise RubySMB::Error::InvalidPacket.new(
|
74
|
+
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
75
|
+
expected_cmd: RubySMB::SMB2::Packet::TreeConnectResponse::COMMAND,
|
76
|
+
received_proto: response.smb2_header.protocol,
|
77
|
+
received_cmd: response.smb2_header.command
|
78
|
+
)
|
73
79
|
end
|
74
80
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
75
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
81
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
76
82
|
end
|
77
|
-
RubySMB::SMB2::Tree.new(client: self, share: share, response: response)
|
83
|
+
RubySMB::SMB2::Tree.new(client: self, share: share, response: response, encrypt: response.share_flags.encrypt == 1)
|
78
84
|
end
|
79
85
|
end
|
80
86
|
end
|