ruby_smb 1.1.0 → 2.0.4
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 -5
- data/Gemfile +6 -2
- data/examples/anonymous_auth.rb +3 -3
- data/examples/append_file.rb +10 -8
- data/examples/authenticate.rb +9 -5
- data/examples/delete_file.rb +8 -6
- data/examples/enum_registry_key.rb +5 -4
- data/examples/enum_registry_values.rb +5 -4
- data/examples/list_directory.rb +8 -6
- data/examples/negotiate.rb +51 -8
- data/examples/negotiate_with_netbios_service.rb +9 -5
- data/examples/net_share_enum_all.rb +6 -4
- data/examples/pipes.rb +11 -12
- data/examples/query_service_status.rb +64 -0
- data/examples/read_file.rb +8 -6
- data/examples/read_file_encryption.rb +56 -0
- data/examples/read_registry_key_value.rb +6 -5
- data/examples/rename_file.rb +9 -7
- data/examples/tree_connect.rb +7 -5
- data/examples/write_file.rb +9 -7
- data/lib/ruby_smb.rb +4 -0
- data/lib/ruby_smb/client.rb +246 -26
- data/lib/ruby_smb/client/authentication.rb +32 -18
- data/lib/ruby_smb/client/echo.rb +2 -4
- data/lib/ruby_smb/client/encryption.rb +62 -0
- data/lib/ruby_smb/client/negotiation.rb +156 -16
- data/lib/ruby_smb/client/signing.rb +19 -0
- data/lib/ruby_smb/client/tree_connect.rb +6 -8
- data/lib/ruby_smb/client/utils.rb +24 -17
- data/lib/ruby_smb/client/winreg.rb +1 -1
- data/lib/ruby_smb/crypto.rb +30 -0
- data/lib/ruby_smb/dcerpc.rb +2 -0
- data/lib/ruby_smb/dcerpc/error.rb +3 -0
- data/lib/ruby_smb/dcerpc/ndr.rb +209 -44
- data/lib/ruby_smb/dcerpc/request.rb +13 -0
- data/lib/ruby_smb/dcerpc/rpc_security_attributes.rb +34 -0
- data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +9 -6
- data/lib/ruby_smb/dcerpc/svcctl.rb +479 -0
- data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request.rb +48 -0
- data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response.rb +26 -0
- data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request.rb +25 -0
- data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response.rb +26 -0
- data/lib/ruby_smb/dcerpc/svcctl/control_service_request.rb +26 -0
- data/lib/ruby_smb/dcerpc/svcctl/control_service_response.rb +26 -0
- data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request.rb +35 -0
- data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response.rb +23 -0
- data/lib/ruby_smb/dcerpc/svcctl/open_service_w_request.rb +31 -0
- data/lib/ruby_smb/dcerpc/svcctl/open_service_w_response.rb +23 -0
- data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request.rb +25 -0
- data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response.rb +44 -0
- data/lib/ruby_smb/dcerpc/svcctl/query_service_status_request.rb +23 -0
- data/lib/ruby_smb/dcerpc/svcctl/query_service_status_response.rb +27 -0
- data/lib/ruby_smb/dcerpc/svcctl/service_status.rb +25 -0
- data/lib/ruby_smb/dcerpc/svcctl/start_service_w_request.rb +27 -0
- data/lib/ruby_smb/dcerpc/svcctl/start_service_w_response.rb +25 -0
- data/lib/ruby_smb/dcerpc/winreg.rb +98 -17
- data/lib/ruby_smb/dcerpc/winreg/create_key_request.rb +73 -0
- data/lib/ruby_smb/dcerpc/winreg/create_key_response.rb +36 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +1 -1
- data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +1 -1
- data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +1 -1
- data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +4 -4
- data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +1 -1
- data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +7 -6
- data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +10 -10
- data/lib/ruby_smb/dcerpc/winreg/save_key_request.rb +37 -0
- data/lib/ruby_smb/dcerpc/winreg/save_key_response.rb +23 -0
- data/lib/ruby_smb/dispatcher/base.rb +1 -1
- data/lib/ruby_smb/dispatcher/socket.rb +5 -4
- data/lib/ruby_smb/error.rb +49 -6
- data/lib/ruby_smb/field/stringz16.rb +17 -1
- data/lib/ruby_smb/generic_packet.rb +11 -1
- data/lib/ruby_smb/nbss/session_header.rb +4 -4
- data/lib/ruby_smb/smb1/commands.rb +1 -1
- data/lib/ruby_smb/smb1/file.rb +13 -28
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
- data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
- data/lib/ruby_smb/smb1/pipe.rb +8 -8
- data/lib/ruby_smb/smb1/tree.rb +25 -12
- 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 +59 -77
- 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/compression_transform_header.rb +41 -0
- data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
- data/lib/ruby_smb/smb2/packet/negotiate_response.rb +50 -4
- data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
- data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
- data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
- data/lib/ruby_smb/smb2/pipe.rb +8 -20
- data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
- data/lib/ruby_smb/smb2/tree.rb +44 -28
- data/lib/ruby_smb/version.rb +1 -1
- data/ruby_smb.gemspec +3 -1
- data/spec/lib/ruby_smb/client_spec.rb +1408 -70
- data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
- data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +1396 -77
- data/spec/lib/ruby_smb/dcerpc/rpc_security_attributes_spec.rb +161 -0
- data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +49 -12
- data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request_spec.rb +191 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request_spec.rb +30 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_request_spec.rb +39 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request_spec.rb +78 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_request_spec.rb +59 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response_spec.rb +152 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_request_spec.rb +30 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/service_status_spec.rb +72 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_request_spec.rb +46 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_response_spec.rb +30 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl_spec.rb +512 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/create_key_request_spec.rb +110 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/create_key_response_spec.rb +44 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +0 -4
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +9 -4
- data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +0 -4
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +17 -17
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +11 -23
- data/spec/lib/ruby_smb/dcerpc/winreg/save_key_request_spec.rb +57 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/save_key_response_spec.rb +22 -0
- data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +227 -41
- data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +12 -12
- data/spec/lib/ruby_smb/error_spec.rb +88 -0
- data/spec/lib/ruby_smb/field/stringz16_spec.rb +12 -0
- data/spec/lib/ruby_smb/generic_packet_spec.rb +7 -0
- data/spec/lib/ruby_smb/nbss/session_header_spec.rb +4 -11
- data/spec/lib/ruby_smb/smb1/file_spec.rb +1 -3
- 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/pipe_spec.rb +30 -5
- data/spec/lib/ruby_smb/smb1/tree_spec.rb +22 -0
- 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 +147 -71
- 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/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/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 -30
- data/spec/lib/ruby_smb/smb2/pipe_spec.rb +9 -45
- data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +111 -9
- metadata +194 -75
- metadata.gz.sig +2 -1
data/examples/read_file.rb
CHANGED
@@ -9,17 +9,19 @@
|
|
9
9
|
require 'bundler/setup'
|
10
10
|
require 'ruby_smb'
|
11
11
|
|
12
|
-
address
|
13
|
-
username
|
14
|
-
password
|
15
|
-
share
|
16
|
-
file
|
12
|
+
address = ARGV[0]
|
13
|
+
username = ARGV[1]
|
14
|
+
password = ARGV[2]
|
15
|
+
share = ARGV[3]
|
16
|
+
file = ARGV[4]
|
17
|
+
smb_versions = ARGV[5]&.split(',') || ['1','2','3']
|
18
|
+
|
17
19
|
path = "\\\\#{address}\\#{share}"
|
18
20
|
|
19
21
|
sock = TCPSocket.new address, 445
|
20
22
|
dispatcher = RubySMB::Dispatcher::Socket.new(sock)
|
21
23
|
|
22
|
-
client = RubySMB::Client.new(dispatcher, smb1:
|
24
|
+
client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password)
|
23
25
|
protocol = client.negotiate
|
24
26
|
status = client.authenticate
|
25
27
|
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
# This example script is used for testing the reading of a file.
|
4
|
+
# It will attempt to connect to a specific share and then read a specified file.
|
5
|
+
# Example usage: ruby read_file.rb 192.168.172.138 msfadmin msfadmin TEST_SHARE short.txt
|
6
|
+
# This will try to connect to \\192.168.172.138\TEST_SHARE with the msfadmin:msfadmin credentials
|
7
|
+
# and read the file short.txt
|
8
|
+
|
9
|
+
require 'bundler/setup'
|
10
|
+
require 'ruby_smb'
|
11
|
+
|
12
|
+
address = ARGV[0]
|
13
|
+
username = ARGV[1]
|
14
|
+
password = ARGV[2]
|
15
|
+
share = ARGV[3]
|
16
|
+
filename = ARGV[4]
|
17
|
+
path = "\\\\#{address}\\#{share}"
|
18
|
+
|
19
|
+
sock = TCPSocket.new address, 445
|
20
|
+
dispatcher = RubySMB::Dispatcher::Socket.new(sock)
|
21
|
+
|
22
|
+
# To require encryption on the server, run this in an elevated Powershell:
|
23
|
+
# C:\> Set-SmbServerConfiguration -EncryptData $true
|
24
|
+
|
25
|
+
# To enable per-share encryption on the server, run this in an elevated Powershell:
|
26
|
+
# C:\ Set-SmbServerConfiguration -EncryptData $false
|
27
|
+
# C:\ Set-SmbShare -Name <share name> -EncryptData 1
|
28
|
+
|
29
|
+
# For this encryption to work, it has to be SMBv3. By only setting smb3 to true,
|
30
|
+
# we make sure the server will negotiate this version, if it supports it
|
31
|
+
opts = {
|
32
|
+
smb1: false,
|
33
|
+
smb2: false,
|
34
|
+
smb3: true,
|
35
|
+
username: username,
|
36
|
+
password: password,
|
37
|
+
}
|
38
|
+
|
39
|
+
# By default, the client uses encryption even if it is not required by the server. Disable this by setting always_encrypt to false
|
40
|
+
#opts[:always_encrypt] = false
|
41
|
+
|
42
|
+
client = RubySMB::Client.new(dispatcher, opts)
|
43
|
+
protocol = client.negotiate
|
44
|
+
status = client.authenticate
|
45
|
+
|
46
|
+
begin
|
47
|
+
tree = client.tree_connect(path)
|
48
|
+
rescue StandardError => e
|
49
|
+
puts "Failed to connect to #{path}: #{e.message}"
|
50
|
+
end
|
51
|
+
|
52
|
+
file = tree.open_file(filename: filename)
|
53
|
+
|
54
|
+
data = file.read
|
55
|
+
puts data
|
56
|
+
file.close
|
@@ -8,16 +8,17 @@
|
|
8
8
|
require 'bundler/setup'
|
9
9
|
require 'ruby_smb'
|
10
10
|
|
11
|
-
address
|
12
|
-
username
|
13
|
-
password
|
11
|
+
address = ARGV[0]
|
12
|
+
username = ARGV[1]
|
13
|
+
password = ARGV[2]
|
14
14
|
registry_key = ARGV[3]
|
15
|
-
value_name
|
15
|
+
value_name = ARGV[4]
|
16
|
+
smb_versions = ARGV[5]&.split(',') || ['1','2','3']
|
16
17
|
|
17
18
|
sock = TCPSocket.new address, 445
|
18
19
|
dispatcher = RubySMB::Dispatcher::Socket.new(sock, read_timeout: 60)
|
19
20
|
|
20
|
-
client = RubySMB::Client.new(dispatcher, smb1:
|
21
|
+
client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password)
|
21
22
|
protocol = client.negotiate
|
22
23
|
status = client.authenticate
|
23
24
|
|
data/examples/rename_file.rb
CHANGED
@@ -9,18 +9,20 @@
|
|
9
9
|
require 'bundler/setup'
|
10
10
|
require 'ruby_smb'
|
11
11
|
|
12
|
-
address
|
13
|
-
username
|
14
|
-
password
|
15
|
-
share
|
16
|
-
file
|
17
|
-
new_name
|
12
|
+
address = ARGV[0]
|
13
|
+
username = ARGV[1]
|
14
|
+
password = ARGV[2]
|
15
|
+
share = ARGV[3]
|
16
|
+
file = ARGV[4]
|
17
|
+
new_name = ARGV[5]
|
18
|
+
smb_versions = ARGV[6]&.split(',') || ['1','2','3']
|
19
|
+
|
18
20
|
path = "\\\\#{address}\\#{share}"
|
19
21
|
|
20
22
|
sock = TCPSocket.new address, 445
|
21
23
|
dispatcher = RubySMB::Dispatcher::Socket.new(sock)
|
22
24
|
|
23
|
-
client = RubySMB::Client.new(dispatcher, smb1:
|
25
|
+
client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password)
|
24
26
|
|
25
27
|
protocol = client.negotiate
|
26
28
|
status = client.authenticate
|
data/examples/tree_connect.rb
CHANGED
@@ -8,16 +8,18 @@
|
|
8
8
|
require 'bundler/setup'
|
9
9
|
require 'ruby_smb'
|
10
10
|
|
11
|
-
address
|
12
|
-
username
|
13
|
-
password
|
14
|
-
share
|
11
|
+
address = ARGV[0]
|
12
|
+
username = ARGV[1]
|
13
|
+
password = ARGV[2]
|
14
|
+
share = ARGV[3]
|
15
|
+
smb_versions = ARGV[4]&.split(',') || ['1','2','3']
|
16
|
+
|
15
17
|
path = "\\\\#{address}\\#{share}"
|
16
18
|
|
17
19
|
sock = TCPSocket.new address, 445
|
18
20
|
dispatcher = RubySMB::Dispatcher::Socket.new(sock)
|
19
21
|
|
20
|
-
client = RubySMB::Client.new(dispatcher, smb1:
|
22
|
+
client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password)
|
21
23
|
protocol = client.negotiate
|
22
24
|
status = client.authenticate
|
23
25
|
|
data/examples/write_file.rb
CHANGED
@@ -9,18 +9,20 @@
|
|
9
9
|
require 'bundler/setup'
|
10
10
|
require 'ruby_smb'
|
11
11
|
|
12
|
-
address
|
13
|
-
username
|
14
|
-
password
|
15
|
-
share
|
16
|
-
file
|
17
|
-
data
|
12
|
+
address = ARGV[0]
|
13
|
+
username = ARGV[1]
|
14
|
+
password = ARGV[2]
|
15
|
+
share = ARGV[3]
|
16
|
+
file = ARGV[4]
|
17
|
+
data = ARGV[5]
|
18
|
+
smb_versions = ARGV[6]&.split(',') || ['1','2','3']
|
19
|
+
|
18
20
|
path = "\\\\#{address}\\#{share}"
|
19
21
|
|
20
22
|
sock = TCPSocket.new address, 445
|
21
23
|
dispatcher = RubySMB::Dispatcher::Socket.new(sock)
|
22
24
|
|
23
|
-
client = RubySMB::Client.new(dispatcher, smb1:
|
25
|
+
client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password)
|
24
26
|
protocol = client.negotiate
|
25
27
|
status = client.authenticate
|
26
28
|
|
data/lib/ruby_smb.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require 'bindata'
|
2
2
|
require 'net/ntlm'
|
3
3
|
require 'net/ntlm/client'
|
4
|
+
require 'openssl'
|
5
|
+
require 'openssl/ccm'
|
6
|
+
require 'openssl/cmac'
|
4
7
|
require 'windows_error'
|
5
8
|
require 'windows_error/nt_status'
|
6
9
|
# A packet parsing and manipulation library for the SMB1 and SMB2 protocols
|
@@ -22,4 +25,5 @@ module RubySMB
|
|
22
25
|
require 'ruby_smb/smb2'
|
23
26
|
require 'ruby_smb/smb1'
|
24
27
|
require 'ruby_smb/client'
|
28
|
+
require 'ruby_smb/crypto'
|
25
29
|
end
|
data/lib/ruby_smb/client.rb
CHANGED
@@ -9,6 +9,7 @@ module RubySMB
|
|
9
9
|
require 'ruby_smb/client/echo'
|
10
10
|
require 'ruby_smb/client/utils'
|
11
11
|
require 'ruby_smb/client/winreg'
|
12
|
+
require 'ruby_smb/client/encryption'
|
12
13
|
|
13
14
|
include RubySMB::Client::Negotiation
|
14
15
|
include RubySMB::Client::Authentication
|
@@ -17,13 +18,20 @@ module RubySMB
|
|
17
18
|
include RubySMB::Client::Echo
|
18
19
|
include RubySMB::Client::Utils
|
19
20
|
include RubySMB::Client::Winreg
|
21
|
+
include RubySMB::Client::Encryption
|
20
22
|
|
21
23
|
# The Default SMB1 Dialect string used in an SMB1 Negotiate Request
|
22
24
|
SMB1_DIALECT_SMB1_DEFAULT = 'NT LM 0.12'.freeze
|
23
25
|
# The Default SMB2 Dialect string used in an SMB1 Negotiate Request
|
24
26
|
SMB1_DIALECT_SMB2_DEFAULT = 'SMB 2.002'.freeze
|
25
|
-
# Dialect
|
26
|
-
|
27
|
+
# The SMB2 wildcard revision number Dialect string used in an SMB1 Negotiate Request
|
28
|
+
# It indicates that the server implements SMB 2.1 or future dialect revisions
|
29
|
+
# Note that this must be used for SMB3
|
30
|
+
SMB1_DIALECT_SMB2_WILDCARD = 'SMB 2.???'.freeze
|
31
|
+
# Dialect values for SMB2
|
32
|
+
SMB2_DIALECT_DEFAULT = ['0x0202', '0x0210']
|
33
|
+
# Dialect values for SMB3
|
34
|
+
SMB3_DIALECT_DEFAULT = ['0x0300', '0x0302', '0x0311']
|
27
35
|
# The default maximum size of a SMB message that the Client accepts (in bytes)
|
28
36
|
MAX_BUFFER_SIZE = 64512
|
29
37
|
# The default maximum size of a SMB message that the Server accepts (in bytes)
|
@@ -135,6 +143,11 @@ module RubySMB
|
|
135
143
|
# @return [Boolean]
|
136
144
|
attr_accessor :smb2
|
137
145
|
|
146
|
+
# Whether or not the Client should support SMB3
|
147
|
+
# @!attribute [rw] smb3
|
148
|
+
# @return [Boolean]
|
149
|
+
attr_accessor :smb3
|
150
|
+
|
138
151
|
# Tracks the current SMB2 Message ID that keeps communication in sync
|
139
152
|
# @!attribute [rw] smb2_message_id
|
140
153
|
# @return [Integer]
|
@@ -147,9 +160,16 @@ module RubySMB
|
|
147
160
|
|
148
161
|
# The UID set in SMB1
|
149
162
|
# @!attribute [rw] user_id
|
150
|
-
# @return [
|
163
|
+
# @return [Integer]
|
151
164
|
attr_accessor :user_id
|
152
165
|
|
166
|
+
# The Process ID set in SMB1
|
167
|
+
# It is randomly generated during the client initialization, but can
|
168
|
+
# be modified using the accessor.
|
169
|
+
# @!attribute [rw] pid
|
170
|
+
# @return [Integer]
|
171
|
+
attr_accessor :pid
|
172
|
+
|
153
173
|
# The maximum size SMB message that the Client accepts (in bytes)
|
154
174
|
# The default value is equal to {MAX_BUFFER_SIZE}.
|
155
175
|
# @!attribute [rw] max_buffer_size
|
@@ -178,15 +198,92 @@ module RubySMB
|
|
178
198
|
# @return [Integer]
|
179
199
|
attr_accessor :server_max_transact_size
|
180
200
|
|
201
|
+
# The algorithm to compute the preauthentication integrity hash (SMB 3.1.1).
|
202
|
+
# @!attribute [rw] preauth_integrity_hash_algorithm
|
203
|
+
# @return [String]
|
204
|
+
attr_accessor :preauth_integrity_hash_algorithm
|
205
|
+
|
206
|
+
# The preauthentication integrity hash value (SMB 3.1.1).
|
207
|
+
# @!attribute [rw] preauth_integrity_hash_value
|
208
|
+
# @return [String]
|
209
|
+
attr_accessor :preauth_integrity_hash_value
|
210
|
+
|
211
|
+
# The algorithm for encryption (SMB 3.x).
|
212
|
+
# @!attribute [rw] encryption_algorithm
|
213
|
+
# @return [String]
|
214
|
+
attr_accessor :encryption_algorithm
|
215
|
+
|
216
|
+
# The client encryption key (SMB 3.x).
|
217
|
+
# @!attribute [rw] client_encryption_key
|
218
|
+
# @return [String]
|
219
|
+
attr_accessor :client_encryption_key
|
220
|
+
|
221
|
+
# The server encryption key (SMB 3.x).
|
222
|
+
# @!attribute [rw] server_encryption_key
|
223
|
+
# @return [String]
|
224
|
+
attr_accessor :server_encryption_key
|
225
|
+
|
226
|
+
# Whether or not the whole session needs to be encrypted (SMB 3.x)
|
227
|
+
# @!attribute [rw] session_encrypt_data
|
228
|
+
# @return [Boolean]
|
229
|
+
attr_accessor :session_encrypt_data
|
230
|
+
|
231
|
+
# The encryption algorithms supported by the server (SMB 3.x).
|
232
|
+
# @!attribute [rw] server_encryption_algorithms
|
233
|
+
# @return [Array<Integer>] list of supported encryption algorithms
|
234
|
+
# (constants defined in RubySMB::SMB2::EncryptionCapabilities)
|
235
|
+
attr_accessor :server_encryption_algorithms
|
236
|
+
|
237
|
+
# The compression algorithms supported by the server (SMB 3.x).
|
238
|
+
# @!attribute [rw] server_compression_algorithms
|
239
|
+
# @return [Array<Integer>] list of supported compression algorithms
|
240
|
+
# (constants defined in RubySMB::SMB2::CompressionCapabilities)
|
241
|
+
attr_accessor :server_compression_algorithms
|
242
|
+
|
243
|
+
# The GUID of the server (SMB 2.x and 3.x).
|
244
|
+
# @!attribute [rw] server_guid
|
245
|
+
# @return [String]
|
246
|
+
attr_accessor :server_guid
|
247
|
+
|
248
|
+
# The server's start time if it is reported as part of the negotiation
|
249
|
+
# process (SMB 2.x and 3.x). This value is nil if the server does not report
|
250
|
+
# it (reports a value of 0).
|
251
|
+
# @!attribute [rw] server_start_time
|
252
|
+
# @return [Time] the time that the server reports that it was started at
|
253
|
+
attr_accessor :server_start_time
|
254
|
+
|
255
|
+
# The server's current time if it is reported as part of the negotiation
|
256
|
+
# process (SMB 2.x and 3.x). This value is nil if the server does not report
|
257
|
+
# it (reports a value of 0).
|
258
|
+
# @!attribute [rw] server_system_time
|
259
|
+
# @return [Time] the time that the server reports as current
|
260
|
+
attr_accessor :server_system_time
|
261
|
+
|
262
|
+
# The SMB version that has been successfully negotiated. This value is only
|
263
|
+
# set after the NEGOTIATE handshake has been performed.
|
264
|
+
# @!attribute [rw] negotiated_smb_version
|
265
|
+
# @return [Integer] the negotiated SMB version
|
266
|
+
attr_accessor :negotiated_smb_version
|
267
|
+
|
268
|
+
# Whether or not the server supports multi-credit operations. It is
|
269
|
+
# reported by the LARGE_MTU capabiliy as part of the negotiation process
|
270
|
+
# (SMB 2.x and 3.x).
|
271
|
+
# @!attribute [rw] server_supports_multi_credit
|
272
|
+
# @return [Boolean] true if the server supports multi-credit operations,
|
273
|
+
# false otherwise
|
274
|
+
attr_accessor :server_supports_multi_credit
|
275
|
+
|
181
276
|
# @param dispatcher [RubySMB::Dispatcher::Socket] the packet dispatcher to use
|
182
277
|
# @param smb1 [Boolean] whether or not to enable SMB1 support
|
183
278
|
# @param smb2 [Boolean] whether or not to enable SMB2 support
|
184
|
-
|
279
|
+
# @param smb3 [Boolean] whether or not to enable SMB3 support
|
280
|
+
def initialize(dispatcher, smb1: true, smb2: true, smb3: true, username:, password:, domain: '.', local_workstation: 'WORKSTATION', always_encrypt: true)
|
185
281
|
raise ArgumentError, 'No Dispatcher provided' unless dispatcher.is_a? RubySMB::Dispatcher::Base
|
186
|
-
if smb1 == false && smb2 == false
|
282
|
+
if smb1 == false && smb2 == false && smb3 == false
|
187
283
|
raise ArgumentError, 'You must enable at least one Protocol'
|
188
284
|
end
|
189
285
|
@dispatcher = dispatcher
|
286
|
+
@pid = rand(0xFFFF)
|
190
287
|
@domain = domain
|
191
288
|
@local_workstation = local_workstation
|
192
289
|
@password = password.encode('utf-8') || ''.encode('utf-8')
|
@@ -196,6 +293,7 @@ module RubySMB
|
|
196
293
|
@signing_required = false
|
197
294
|
@smb1 = smb1
|
198
295
|
@smb2 = smb2
|
296
|
+
@smb3 = smb3
|
199
297
|
@username = username.encode('utf-8') || ''.encode('utf-8')
|
200
298
|
@max_buffer_size = MAX_BUFFER_SIZE
|
201
299
|
# These sizes will be modifed during negotiation
|
@@ -203,11 +301,16 @@ module RubySMB
|
|
203
301
|
@server_max_read_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
|
204
302
|
@server_max_write_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
|
205
303
|
@server_max_transact_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
|
304
|
+
@server_supports_multi_credit = false
|
305
|
+
|
306
|
+
# SMB 3.x options
|
307
|
+
@session_encrypt_data = always_encrypt
|
206
308
|
|
207
309
|
negotiate_version_flag = 0x02000000
|
208
310
|
flags = Net::NTLM::Client::DEFAULT_FLAGS |
|
209
311
|
Net::NTLM::FLAGS[:TARGET_INFO] |
|
210
|
-
negotiate_version_flag
|
312
|
+
negotiate_version_flag ^
|
313
|
+
Net::NTLM::FLAGS[:OEM]
|
211
314
|
|
212
315
|
@ntlm_client = Net::NTLM::Client.new(
|
213
316
|
@username,
|
@@ -243,7 +346,7 @@ module RubySMB
|
|
243
346
|
# @param data [String] the data the server should echo back (ignored in SMB2)
|
244
347
|
# @return [WindowsError::ErrorCode] the NTStatus of the last response received
|
245
348
|
def echo(count: 1, data: '')
|
246
|
-
response = if smb2
|
349
|
+
response = if smb2 || smb3
|
247
350
|
smb2_echo
|
248
351
|
else
|
249
352
|
smb1_echo(count: count, data: data)
|
@@ -258,10 +361,8 @@ module RubySMB
|
|
258
361
|
# @param packet [RubySMB::GenericPacket] the packet to set the message id for
|
259
362
|
# @return [RubySMB::GenericPacket] the modified packet
|
260
363
|
def increment_smb_message_id(packet)
|
261
|
-
|
262
|
-
|
263
|
-
self.smb2_message_id += 1
|
264
|
-
end
|
364
|
+
packet.smb2_header.message_id = self.smb2_message_id
|
365
|
+
self.smb2_message_id += 1
|
265
366
|
packet
|
266
367
|
end
|
267
368
|
|
@@ -283,7 +384,8 @@ module RubySMB
|
|
283
384
|
negotiate_version_flag = 0x02000000
|
284
385
|
flags = Net::NTLM::Client::DEFAULT_FLAGS |
|
285
386
|
Net::NTLM::FLAGS[:TARGET_INFO] |
|
286
|
-
negotiate_version_flag
|
387
|
+
negotiate_version_flag ^
|
388
|
+
Net::NTLM::FLAGS[:OEM]
|
287
389
|
|
288
390
|
@ntlm_client = Net::NTLM::Client.new(
|
289
391
|
@username,
|
@@ -301,7 +403,7 @@ module RubySMB
|
|
301
403
|
# @return [WindowsError::ErrorCode] the NTStatus of the response
|
302
404
|
# @raise [RubySMB::Error::InvalidPacket] if the response packet is not a LogoffResponse packet
|
303
405
|
def logoff!
|
304
|
-
if smb2
|
406
|
+
if smb2 || smb3
|
305
407
|
request = RubySMB::SMB2::Packet::LogoffRequest.new
|
306
408
|
raw_response = send_recv(request)
|
307
409
|
response = RubySMB::SMB2::Packet::LogoffResponse.read(raw_response)
|
@@ -309,8 +411,7 @@ module RubySMB
|
|
309
411
|
raise RubySMB::Error::InvalidPacket.new(
|
310
412
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
311
413
|
expected_cmd: RubySMB::SMB2::Packet::LogoffResponse::COMMAND,
|
312
|
-
|
313
|
-
received_cmd: response.smb2_header.command
|
414
|
+
packet: response
|
314
415
|
)
|
315
416
|
end
|
316
417
|
else
|
@@ -321,8 +422,7 @@ module RubySMB
|
|
321
422
|
raise RubySMB::Error::InvalidPacket.new(
|
322
423
|
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
323
424
|
expected_cmd: RubySMB::SMB1::Packet::LogoffResponse::COMMAND,
|
324
|
-
|
325
|
-
received_cmd: response.smb_header.command
|
425
|
+
packet: response
|
326
426
|
)
|
327
427
|
end
|
328
428
|
end
|
@@ -334,25 +434,131 @@ module RubySMB
|
|
334
434
|
# It will also sign the packet if neccessary.
|
335
435
|
#
|
336
436
|
# @param packet [RubySMB::GenericPacket] the request to be sent
|
437
|
+
# @param encrypt [Boolean] true if encryption has to be enabled for this transaction
|
438
|
+
# (note that if @session_encrypt_data is set, encryption will be enabled
|
439
|
+
# regardless of this parameter value)
|
337
440
|
# @return [String] the raw response data received
|
338
|
-
def send_recv(packet)
|
339
|
-
|
441
|
+
def send_recv(packet, encrypt: false)
|
442
|
+
version = packet.packet_smb_version
|
443
|
+
case version
|
340
444
|
when 'SMB1'
|
341
|
-
packet.smb_header.uid = user_id if user_id
|
445
|
+
packet.smb_header.uid = self.user_id if self.user_id
|
446
|
+
packet.smb_header.pid_low = self.pid if self.pid
|
342
447
|
packet = smb1_sign(packet)
|
343
448
|
when 'SMB2'
|
344
449
|
packet = increment_smb_message_id(packet)
|
345
450
|
packet.smb2_header.session_id = session_id
|
346
451
|
unless packet.is_a?(RubySMB::SMB2::Packet::SessionSetupRequest)
|
347
|
-
|
452
|
+
if self.smb2
|
453
|
+
packet = smb2_sign(packet)
|
454
|
+
elsif self.smb3
|
455
|
+
packet = smb3_sign(packet)
|
456
|
+
end
|
348
457
|
end
|
349
458
|
else
|
350
459
|
packet = packet
|
351
460
|
end
|
352
|
-
|
353
|
-
|
461
|
+
|
462
|
+
encrypt_data = false
|
463
|
+
if can_be_encrypted?(packet) && encryption_supported? && (@session_encrypt_data || encrypt)
|
464
|
+
encrypt_data = true
|
465
|
+
end
|
466
|
+
send_packet(packet, encrypt: encrypt_data)
|
467
|
+
raw_response = recv_packet(encrypt: encrypt_data)
|
468
|
+
smb2_header = nil
|
469
|
+
loop do
|
470
|
+
smb2_header = RubySMB::SMB2::SMB2Header.read(raw_response)
|
471
|
+
break unless is_status_pending?(smb2_header)
|
472
|
+
sleep 1
|
473
|
+
raw_response = recv_packet(encrypt: encrypt_data)
|
474
|
+
rescue IOError
|
475
|
+
# We're expecting an SMB2 packet, but the server sent an SMB1 packet
|
476
|
+
# instead. This behavior has been observed with older versions of Samba
|
477
|
+
# when something goes wrong on the server side. So, we just ignore it
|
478
|
+
# and expect the caller to handle this wrong response packet.
|
479
|
+
break
|
480
|
+
end unless version == 'SMB1'
|
354
481
|
|
355
482
|
self.sequence_counter += 1 if signing_required && !session_key.empty?
|
483
|
+
# update the SMB2 message ID according to the received Credit Charged
|
484
|
+
self.smb2_message_id += smb2_header.credit_charge - 1 if smb2_header && self.server_supports_multi_credit
|
485
|
+
raw_response
|
486
|
+
end
|
487
|
+
|
488
|
+
# Check if the response is an asynchronous operation with STATUS_PENDING
|
489
|
+
# status code.
|
490
|
+
#
|
491
|
+
# @param smb2_header [String] the response packet SMB2 header
|
492
|
+
# @return [Boolean] true if it is a status pending operation, false otherwise
|
493
|
+
def is_status_pending?(smb2_header)
|
494
|
+
value = smb2_header.nt_status.value
|
495
|
+
status_code = WindowsError::NTStatus.find_by_retval(value).first
|
496
|
+
status_code == WindowsError::NTStatus::STATUS_PENDING &&
|
497
|
+
smb2_header.flags.async_command == 1
|
498
|
+
end
|
499
|
+
|
500
|
+
# Check if the request packet can be encrypted. Per the SMB2 spec,
|
501
|
+
# SessionSetupRequest and NegotiateRequest must not be encrypted.
|
502
|
+
#
|
503
|
+
# @param packet [RubySMB::GenericPacket] the request packet
|
504
|
+
# @return [Boolean] true if the packet can be encrypted
|
505
|
+
def can_be_encrypted?(packet)
|
506
|
+
return false if packet.packet_smb_version == 'SMB1'
|
507
|
+
[RubySMB::SMB2::Packet::SessionSetupRequest, RubySMB::SMB2::Packet::NegotiateRequest].none? do |klass|
|
508
|
+
packet.is_a?(klass)
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
# Check if the current dialect supports encryption.
|
513
|
+
#
|
514
|
+
# @return [Boolean] true if encryption is supported
|
515
|
+
def encryption_supported?
|
516
|
+
['0x0300', '0x0302', '0x0311'].include?(@dialect)
|
517
|
+
end
|
518
|
+
|
519
|
+
# Encrypt (if required) and send a packet.
|
520
|
+
#
|
521
|
+
# @param encrypt [Boolean] true if the packet should be encrypted, false
|
522
|
+
# otherwise
|
523
|
+
def send_packet(packet, encrypt: false)
|
524
|
+
if encrypt
|
525
|
+
begin
|
526
|
+
packet = smb3_encrypt(packet.to_binary_s)
|
527
|
+
rescue RubySMB::Error::RubySMBError => e
|
528
|
+
raise RubySMB::Error::EncryptionError, "Error while encrypting #{packet.class.name} packet (SMB #{@dialect}): #{e}"
|
529
|
+
end
|
530
|
+
end
|
531
|
+
dispatcher.send_packet(packet)
|
532
|
+
end
|
533
|
+
|
534
|
+
# Receives the raw response through the Dispatcher and decrypt the packet (if required).
|
535
|
+
#
|
536
|
+
# @param encrypt [Boolean] true if the packet is encrypted, false otherwise
|
537
|
+
# @return [String] the raw unencrypted packet
|
538
|
+
def recv_packet(encrypt: false)
|
539
|
+
begin
|
540
|
+
raw_response = dispatcher.recv_packet
|
541
|
+
rescue RubySMB::Error::CommunicationError => e
|
542
|
+
if encrypt
|
543
|
+
raise RubySMB::Error::EncryptionError, "Communication error with the "\
|
544
|
+
"remote host: #{e.message}. The server supports encryption but was "\
|
545
|
+
"not able to handle the encrypted request."
|
546
|
+
else
|
547
|
+
raise e
|
548
|
+
end
|
549
|
+
end
|
550
|
+
if encrypt
|
551
|
+
begin
|
552
|
+
transform_response = RubySMB::SMB2::Packet::TransformHeader.read(raw_response)
|
553
|
+
rescue IOError
|
554
|
+
raise RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet'
|
555
|
+
end
|
556
|
+
begin
|
557
|
+
raw_response = smb3_decrypt(transform_response)
|
558
|
+
rescue RubySMB::Error::RubySMBError => e
|
559
|
+
raise RubySMB::Error::EncryptionError, "Error while decrypting #{transform_response.class.name} packet (SMB #@dialect}): #{e}"
|
560
|
+
end
|
561
|
+
end
|
356
562
|
raw_response
|
357
563
|
end
|
358
564
|
|
@@ -362,7 +568,7 @@ module RubySMB
|
|
362
568
|
# @return [RubySMB::SMB1::Tree] if talking over SMB1
|
363
569
|
# @return [RubySMB::SMB2::Tree] if talking over SMB2
|
364
570
|
def tree_connect(share)
|
365
|
-
connected_tree = if smb2
|
571
|
+
connected_tree = if smb2 || smb3
|
366
572
|
smb2_tree_connect(share)
|
367
573
|
else
|
368
574
|
smb1_tree_connect(share)
|
@@ -377,7 +583,7 @@ module RubySMB
|
|
377
583
|
# @param [String] host
|
378
584
|
def net_share_enum_all(host)
|
379
585
|
tree = tree_connect("\\\\#{host}\\IPC$")
|
380
|
-
named_pipe = tree.
|
586
|
+
named_pipe = tree.open_pipe(filename: "srvsvc", write: true, read: true)
|
381
587
|
named_pipe.net_share_enum_all(host)
|
382
588
|
end
|
383
589
|
|
@@ -392,6 +598,9 @@ module RubySMB
|
|
392
598
|
self.session_key = ''
|
393
599
|
self.sequence_counter = 0
|
394
600
|
self.smb2_message_id = 0
|
601
|
+
self.client_encryption_key = nil
|
602
|
+
self.server_encryption_key = nil
|
603
|
+
self.server_supports_multi_credit = false
|
395
604
|
end
|
396
605
|
|
397
606
|
# Requests a NetBIOS Session Service using the provided name.
|
@@ -429,10 +638,21 @@ module RubySMB
|
|
429
638
|
session_request.session_header.session_packet_type = RubySMB::Nbss::SESSION_REQUEST
|
430
639
|
session_request.called_name = called_name
|
431
640
|
session_request.calling_name = calling_name
|
432
|
-
session_request.session_header.
|
641
|
+
session_request.session_header.stream_protocol_length =
|
433
642
|
session_request.num_bytes - session_request.session_header.num_bytes
|
434
643
|
session_request
|
435
644
|
end
|
436
645
|
|
646
|
+
def update_preauth_hash(data)
|
647
|
+
unless @preauth_integrity_hash_algorithm
|
648
|
+
raise RubySMB::Error::EncryptionError.new(
|
649
|
+
'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
|
650
|
+
)
|
651
|
+
end
|
652
|
+
@preauth_integrity_hash_value = OpenSSL::Digest.digest(
|
653
|
+
@preauth_integrity_hash_algorithm,
|
654
|
+
@preauth_integrity_hash_value + data.to_binary_s
|
655
|
+
)
|
656
|
+
end
|
437
657
|
end
|
438
658
|
end
|