ruby_smb 1.0.4 → 2.0.2
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 +207 -18
- data/lib/ruby_smb/client/authentication.rb +27 -8
- data/lib/ruby_smb/client/encryption.rb +62 -0
- data/lib/ruby_smb/client/negotiation.rb +153 -12
- data/lib/ruby_smb/client/signing.rb +19 -0
- data/lib/ruby_smb/client/tree_connect.rb +4 -4
- 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 +28 -1
- data/lib/ruby_smb/smb1/commands.rb +1 -1
- data/lib/ruby_smb/smb1/file.rb +6 -4
- data/lib/ruby_smb/smb1/packet/empty_packet.rb +4 -2
- 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 +79 -3
- data/lib/ruby_smb/smb1/tree.rb +12 -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 +25 -43
- 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/error_packet.rb +9 -4
- 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 +77 -3
- data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
- data/lib/ruby_smb/smb2/tree.rb +23 -17
- data/lib/ruby_smb/version.rb +1 -1
- data/ruby_smb.gemspec +5 -3
- data/spec/lib/ruby_smb/client_spec.rb +1441 -61
- 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/smb1/file_spec.rb +9 -1
- data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +10 -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/pipe_spec.rb +210 -148
- 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 +86 -62
- 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 +29 -2
- 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 +220 -149
- data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +53 -8
- metadata +187 -81
- metadata.gz.sig +0 -0
- data/lib/ruby_smb/smb1/dcerpc.rb +0 -72
- data/lib/ruby_smb/smb2/dcerpc.rb +0 -75
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'ruby_smb/smb2/negotiate_context'
|
2
|
+
|
1
3
|
module RubySMB
|
2
4
|
module SMB2
|
3
5
|
module Packet
|
@@ -8,11 +10,12 @@ module RubySMB
|
|
8
10
|
|
9
11
|
endian :little
|
10
12
|
smb2_header :smb2_header
|
11
|
-
uint16 :structure_size,
|
13
|
+
uint16 :structure_size, label: 'Structure Size', initial_value: 65
|
12
14
|
smb2_security_mode :security_mode
|
13
15
|
uint16 :dialect_revision, label: 'Dialect Revision'
|
14
|
-
uint16 :negotiate_context_count, label: 'Negotiate Context Count',
|
15
|
-
|
16
|
+
uint16 :negotiate_context_count, label: 'Negotiate Context Count', initial_value: -> { negotiate_context_list.size }, onlyif: -> { has_negotiate_context? }
|
17
|
+
uint16 :reserved1, label: 'Reserved', initial_value: 0, onlyif: -> { !has_negotiate_context? }
|
18
|
+
string :server_guid, label: 'Server GUID', length: 16
|
16
19
|
smb2_capabilities :capabilities
|
17
20
|
uint32 :max_transact_size, label: 'Max Transaction Size'
|
18
21
|
uint32 :max_read_size, label: 'Max Read Size'
|
@@ -21,13 +24,56 @@ module RubySMB
|
|
21
24
|
file_time :server_start_time, label: 'Server Start Time'
|
22
25
|
uint16 :security_buffer_offset, label: 'Offset to Security Buffer'
|
23
26
|
uint16 :security_buffer_length, label: 'Security Buffer Length', initial_value: -> { security_buffer.length }
|
24
|
-
uint32 :negotiate_context_offset, label: 'Offset to Negotiate Context'
|
27
|
+
uint32 :negotiate_context_offset, label: 'Offset to Negotiate Context', onlyif: -> { has_negotiate_context? }
|
28
|
+
uint32 :reserved2, label: 'Reserved', initial_value: 0, onlyif: -> { !has_negotiate_context? }
|
25
29
|
string :security_buffer, label: 'Security Buffer', read_length: :security_buffer_length
|
30
|
+
string :pad, label: 'Padding', length: -> { pad_length(self.security_buffer) }, onlyif: -> { has_negotiate_context? }
|
31
|
+
array :negotiate_context_list, label: 'Negotiate Context List', initial_length: -> { negotiate_context_count }, type: :negotiate_context, onlyif: -> { has_negotiate_context? }
|
26
32
|
|
27
33
|
def initialize_instance
|
28
34
|
super
|
29
35
|
smb2_header.flags.reply = 1
|
30
36
|
end
|
37
|
+
|
38
|
+
# Find the first Negotiate Context structure that matches the given
|
39
|
+
# context type
|
40
|
+
#
|
41
|
+
# @param [Integer] the Negotiate Context structure you wish to add
|
42
|
+
# @return [NegotiateContext] the Negotiate Context structure or nil if
|
43
|
+
# not found
|
44
|
+
def find_negotiate_context(type)
|
45
|
+
negotiate_context_list.find { |nc| nc.context_type == type }
|
46
|
+
end
|
47
|
+
|
48
|
+
# Adds a Negotiate Context to the #negotiate_context_list
|
49
|
+
#
|
50
|
+
# @param [NegotiateContext] the Negotiate Context structure you wish to add
|
51
|
+
# @return [Array<Fixnum>] the array of all currently added Negotiate Contexts
|
52
|
+
# @raise [ArgumentError] if the dialect is not a NegotiateContext structure
|
53
|
+
def add_negotiate_context(nc)
|
54
|
+
raise ArgumentError, 'Must be a NegotiateContext' unless nc.is_a? NegotiateContext
|
55
|
+
previous_element = negotiate_context_list.last || negotiate_context_list
|
56
|
+
pad_length = pad_length(previous_element)
|
57
|
+
self.negotiate_context_list << nc
|
58
|
+
self.negotiate_context_list.last.pad = "\x00" * pad_length
|
59
|
+
self.negotiate_context_list
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Determines the correct length for the padding, so that the next
|
66
|
+
# field is 8-byte aligned.
|
67
|
+
def pad_length(prev_element)
|
68
|
+
offset = (prev_element.abs_offset + prev_element.to_binary_s.length) % 8
|
69
|
+
(8 - offset) % 8
|
70
|
+
end
|
71
|
+
|
72
|
+
# Return true if the dialect version requires Negotiate Contexts
|
73
|
+
def has_negotiate_context?
|
74
|
+
dialect_revision == 0x0311
|
75
|
+
end
|
76
|
+
|
31
77
|
end
|
32
78
|
end
|
33
79
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module RubySMB
|
2
|
+
module SMB2
|
3
|
+
module Packet
|
4
|
+
# An SMB2 TRANSFORM_HEADER Packet as defined in
|
5
|
+
# [2.2.41 SMB2 TRANSFORM_HEADER](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/d6ce2327-a4c9-4793-be66-7b5bad2175fa)
|
6
|
+
class TransformHeader < BinData::Record
|
7
|
+
endian :little
|
8
|
+
hide :reserved0
|
9
|
+
|
10
|
+
endian :little
|
11
|
+
bit32 :protocol, label: 'Protocol ID Field', initial_value: 0xFD534D42
|
12
|
+
string :signature, label: 'Signature', length: 16
|
13
|
+
string :nonce, label: 'Nonce', length: 16
|
14
|
+
uint32 :original_message_size, label: 'Original Message Size'
|
15
|
+
uint16 :reserved0
|
16
|
+
uint16 :flags, label: 'Flags / Encryption Algorithm'
|
17
|
+
uint64 :session_id, label: 'Session ID'
|
18
|
+
array :encrypted_data, label: 'Encrypted Data', type: :uint8, read_until: :eof
|
19
|
+
|
20
|
+
def decrypt(key, algorithm: 'AES-128-GCM')
|
21
|
+
auth_data = self.to_binary_s[20...52]
|
22
|
+
encrypted_data = self.encrypted_data.to_ary.pack('C*')
|
23
|
+
|
24
|
+
case algorithm
|
25
|
+
when 'AES-128-CCM'
|
26
|
+
cipher = OpenSSL::CCM.new('AES', key, 16)
|
27
|
+
unencrypted_data = cipher.decrypt(encrypted_data + self.signature, self.nonce[0...11], auth_data)
|
28
|
+
unless unencrypted_data.length > 0
|
29
|
+
raise OpenSSL::Cipher::CipherError # raised for consistency with GCM mode
|
30
|
+
end
|
31
|
+
when 'AES-128-GCM'
|
32
|
+
cipher = OpenSSL::Cipher.new(algorithm).decrypt
|
33
|
+
cipher.key = key
|
34
|
+
cipher.iv = self.nonce[0...12]
|
35
|
+
cipher.auth_data = auth_data
|
36
|
+
cipher.auth_tag = self.signature
|
37
|
+
unencrypted_data = cipher.update(encrypted_data)
|
38
|
+
cipher.final # raises OpenSSL::Cipher::CipherError on signature failure
|
39
|
+
else
|
40
|
+
raise ArgumentError.new('Invalid algorithm, must be either AES-128-CCM or AES-128-GCM')
|
41
|
+
end
|
42
|
+
|
43
|
+
unencrypted_data[0...self.original_message_size]
|
44
|
+
rescue Exception => e
|
45
|
+
raise RubySMB::Error::EncryptionError, "Error while decrypting with '#{algorithm}' (#{e.class}: #{e})"
|
46
|
+
end
|
47
|
+
|
48
|
+
def encrypt(unencrypted_data, key, algorithm: 'AES-128-GCM')
|
49
|
+
if unencrypted_data.is_a? BinData::Record
|
50
|
+
unencrypted_data = unencrypted_data.to_binary_s
|
51
|
+
end
|
52
|
+
|
53
|
+
self.original_message_size.assign(unencrypted_data.length)
|
54
|
+
|
55
|
+
case algorithm
|
56
|
+
when 'AES-128-CCM'
|
57
|
+
cipher = OpenSSL::CCM.new('AES', key, 16)
|
58
|
+
random_iv = OpenSSL::Random.random_bytes(11)
|
59
|
+
self.nonce.assign(random_iv)
|
60
|
+
result = cipher.encrypt(unencrypted_data, random_iv, self.to_binary_s[20...52])
|
61
|
+
encrypted_data = result[0...-16]
|
62
|
+
auth_tag = result[-16..-1]
|
63
|
+
when 'AES-128-GCM'
|
64
|
+
cipher = OpenSSL::Cipher.new(algorithm).encrypt
|
65
|
+
cipher.iv_len = 12
|
66
|
+
cipher.key = key
|
67
|
+
self.nonce.assign(cipher.random_iv)
|
68
|
+
cipher.auth_data = self.to_binary_s[20...52]
|
69
|
+
encrypted_data = cipher.update(unencrypted_data) + cipher.final
|
70
|
+
auth_tag = cipher.auth_tag
|
71
|
+
else
|
72
|
+
raise ArgumentError.new('Invalid algorithm, must be either AES-128-CCM or AES-128-GCM')
|
73
|
+
end
|
74
|
+
|
75
|
+
self.encrypted_data.assign(encrypted_data.bytes)
|
76
|
+
self.signature.assign(auth_tag)
|
77
|
+
nil
|
78
|
+
rescue Exception => e
|
79
|
+
raise RubySMB::Error::EncryptionError, "Error while encrypting with '#{algorithm}' (#{e.class}: #{e})"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -1,22 +1,108 @@
|
|
1
1
|
module RubySMB
|
2
2
|
module SMB2
|
3
3
|
module Packet
|
4
|
+
|
5
|
+
|
6
|
+
# An SMB2 RemotedIdentityTreeConnectContext Packet as defined in
|
7
|
+
# [2.2.9.2.1 SMB2_REMOTED_IDENTITY_TREE_CONNECT Context](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/ee7ff411-93e0-484f-9f73-31916fee4cb8)
|
8
|
+
# TODO: implement helper methods to add each Remote Identity element
|
9
|
+
class RemotedIdentityTreeConnectContext < BinData::Record
|
10
|
+
endian :little
|
11
|
+
uint16 :ticket_type, label: 'Ticket Type', initial_value: 0x0001
|
12
|
+
uint16 :ticket_size, label: 'Ticket Size', initial_value: -> { num_bytes }
|
13
|
+
uint16 :user, label: 'User'
|
14
|
+
uint16 :user_name, label: 'User Name'
|
15
|
+
uint16 :domain, label: 'Domain'
|
16
|
+
uint16 :groups, label: 'Groups'
|
17
|
+
uint16 :restricted_groups, label: 'Restricted Groups'
|
18
|
+
uint16 :privileges, label: 'Privileges'
|
19
|
+
uint16 :primary_group, label: 'Primary Group'
|
20
|
+
uint16 :owner, label: 'Owner'
|
21
|
+
uint16 :default_dacl, label: 'Default DACL'
|
22
|
+
uint16 :device_groups, label: 'Device Groups'
|
23
|
+
uint16 :user_claims, label: 'User Claims'
|
24
|
+
uint16 :device_claims, label: 'Device Claims'
|
25
|
+
string :ticket_info, label: 'Ticket Info', read_length: -> { ticket_size - ticket_info.rel_offset}
|
26
|
+
end
|
27
|
+
|
28
|
+
# An SMB2 TreeConnectContext Packet as defined in
|
29
|
+
# [2.2.9.2 SMB2 TREE_CONNECT_CONTEXT Request Values](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/06eaaabc-caca-4776-9daf-82439e90dacd)
|
30
|
+
class TreeConnectContext < BinData::Record
|
31
|
+
|
32
|
+
# Context Types
|
33
|
+
|
34
|
+
# This value is reserved.
|
35
|
+
SMB2_RESERVED_TREE_CONNECT_CONTEXT_ID = 0x0000
|
36
|
+
# The Data field contains remoted identity tree connect context data as
|
37
|
+
# specified in section [2.2.9.2.1](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/ee7ff411-93e0-484f-9f73-31916fee4cb8)
|
38
|
+
SMB2_REMOTED_IDENTITY_TREE_CONNECT_CONTEXT_ID = 0x0001
|
39
|
+
|
40
|
+
endian :little
|
41
|
+
uint16 :context_type, label: 'Context Type'
|
42
|
+
uint16 :data_length, label: 'Data Length', initial_value: -> { data.to_binary_s.size }
|
43
|
+
uint32 :reserved, label: 'Reserved'
|
44
|
+
choice :data, label: 'Data', selection: -> { context_type } do
|
45
|
+
remoted_identity_tree_connect_context SMB2_REMOTED_IDENTITY_TREE_CONNECT_CONTEXT_ID, label: 'Remoted Identity Tree Connect Context'
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
# An SMB2 TreeConnectRequestExtension Packet as defined in
|
51
|
+
# [2.2.9.1 SMB2 TREE_CONNECT Request Extension](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/9ca7328b-b6ca-41a7-9773-0fa237261b76)
|
52
|
+
class TreeConnectRequestExtension < BinData::Record
|
53
|
+
endian :little
|
54
|
+
uint32 :tree_connect_context_offset, label: 'Tree Connect Context Offset', initial_value: -> { tree_connect_contexts.rel_offset }
|
55
|
+
uint16 :tree_connect_context_count, label: 'Tree Connect Context Count', initial_value: -> { tree_connect_contexts.size }
|
56
|
+
string :reserved, label: 'Reserved', length: 10
|
57
|
+
string16 :path, label: 'Path Buffer'
|
58
|
+
array :tree_connect_contexts, label: 'Tree Connect Contexts', type: :tree_connect_context, initial_length: -> { tree_connect_context_count }
|
59
|
+
end
|
60
|
+
|
4
61
|
# An SMB2 TreeConnectRequest Packet as defined in
|
5
62
|
# [2.2.9 SMB2 TREE_CONNECT Request](https://msdn.microsoft.com/en-us/library/cc246567.aspx)
|
6
63
|
class TreeConnectRequest < RubySMB::GenericPacket
|
7
64
|
COMMAND = RubySMB::SMB2::Commands::TREE_CONNECT
|
8
65
|
|
66
|
+
# Flags (SMB 3.1.1 only)
|
67
|
+
|
68
|
+
# The client has previously connected to the specified cluster share
|
69
|
+
# using the SMB dialect of the connection on which the request is received.
|
70
|
+
SMB2_TREE_CONNECT_FLAG_CLUSTER_RECONNECT = 0x0001
|
71
|
+
# The client can handle synchronous share redirects via a Share Redirect
|
72
|
+
# error context response as specified in section [2.2.2.2.2](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/f3073a8b-9f0f-47c0-91e5-ec3be9a49f37).
|
73
|
+
SMB2_TREE_CONNECT_FLAG_REDIRECT_TO_OWNER = 0x0002
|
74
|
+
# A tree connect request extension, as specified in section
|
75
|
+
# [2.2.9.1](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/9ca7328b-b6ca-41a7-9773-0fa237261b76),
|
76
|
+
# is present, starting at the Buffer field of this tree connect request.
|
77
|
+
SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT = 0x0003
|
78
|
+
|
9
79
|
endian :little
|
10
80
|
smb2_header :smb2_header
|
11
81
|
uint16 :structure_size, label: 'Structure Size', initial_value: 9
|
82
|
+
# The flags field is only used by SMB 3.1.1, it must be 0 for other versions
|
12
83
|
uint16 :flags, label: 'Flags', initial_value: 0x00
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
84
|
+
# if SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT flag is set, #path_offset
|
85
|
+
# will have to be updated with the correct offset of the path name,
|
86
|
+
# which is located in the TreeConnect Context.
|
87
|
+
uint16 :path_offset, label: 'Path Offset', initial_value: -> do
|
88
|
+
if flags == SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT
|
89
|
+
tree_connect_request_extension.path.abs_offset
|
90
|
+
else
|
91
|
+
path.abs_offset
|
92
|
+
end
|
93
|
+
end
|
94
|
+
# if SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT flag is set, #path_length
|
95
|
+
# will have to be updated with the correct full share path name,
|
96
|
+
# which is located in the TreeConnect Context.
|
97
|
+
uint16 :path_length, label: 'Path Length', initial_value: -> do
|
98
|
+
if flags == SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT
|
99
|
+
tree_connect_request_extension.path.to_binary_s.length
|
100
|
+
else
|
101
|
+
path.to_binary_s.length
|
102
|
+
end
|
19
103
|
end
|
104
|
+
string16 :path, label: 'Path Buffer', onlyif: -> { flags != SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT }
|
105
|
+
tree_connect_request_extension :tree_connect_request_extension, label: 'Tree Connect Request Extension', onlyif: -> { flags == SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT }
|
20
106
|
end
|
21
107
|
end
|
22
108
|
end
|
@@ -6,6 +6,14 @@ module RubySMB
|
|
6
6
|
class TreeConnectResponse < RubySMB::GenericPacket
|
7
7
|
COMMAND = RubySMB::SMB2::Commands::TREE_CONNECT
|
8
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
|
+
|
9
17
|
endian :little
|
10
18
|
smb2_header :smb2_header
|
11
19
|
uint16 :structure_size, label: 'Structure Size', initial_value: 16
|
@@ -20,32 +28,6 @@ module RubySMB
|
|
20
28
|
smb2_header.flags.reply = 1
|
21
29
|
end
|
22
30
|
|
23
|
-
# Returns the ACCESS_MASK for the Maximal Share Access Rights. The packet
|
24
|
-
# defaults this to a {RubySMB::SMB2::BitField::DirectoryAccessMask}. If it is anything other than
|
25
|
-
# a directory that has been connected to, it will re-cast it as a {RubySMB::SMB2::BitField::FileAccessMask}
|
26
|
-
#
|
27
|
-
# @return [RubySMB::SMB2::BitField::DirectoryAccessMask] if a directory was connected to
|
28
|
-
# @return [RubySMB::SMB2::BitField::FileAccessMask] if anything else was connected to
|
29
|
-
# @raise [RubySMB::Error::InvalidBitField] if ACCESS_MASK bit field is not valid
|
30
|
-
def access_rights
|
31
|
-
if is_directory?
|
32
|
-
maximal_access
|
33
|
-
else
|
34
|
-
mask = maximal_access.to_binary_s
|
35
|
-
begin
|
36
|
-
RubySMB::SMB2::BitField::FileAccessMask.read(mask)
|
37
|
-
rescue IOError
|
38
|
-
raise RubySMB::Error::InvalidBitField, 'Invalid ACCESS_MASK for the Maximal Share Access Rights'
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# Checks if the remote Tree is a directory
|
44
|
-
#
|
45
|
-
# @return [Boolean]
|
46
|
-
def is_directory?
|
47
|
-
share_type == 0x01
|
48
|
-
end
|
49
31
|
end
|
50
32
|
end
|
51
33
|
end
|
data/lib/ruby_smb/smb2/pipe.rb
CHANGED
@@ -3,13 +3,24 @@ 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
|
@@ -35,7 +46,7 @@ module RubySMB
|
|
35
46
|
end
|
36
47
|
|
37
48
|
unless response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW or response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
38
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
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,12 +23,18 @@ module RubySMB
|
|
23
23
|
# @return [Integer]
|
24
24
|
attr_accessor :id
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
# Whether or not the share associated with this tree connect needs to be encrypted (SMB 3.x)
|
27
|
+
# @!attribute [rw] tree_connect_encrypt_data
|
28
|
+
# @return [Boolean]
|
29
|
+
attr_accessor :tree_connect_encrypt_data
|
30
|
+
|
31
|
+
def initialize(client:, share:, response:, encrypt: false)
|
32
|
+
@client = client
|
33
|
+
@share = share
|
34
|
+
@id = response.smb2_header.tree_id
|
35
|
+
@permissions = response.maximal_access
|
36
|
+
@share_type = response.share_type
|
37
|
+
@tree_connect_encrypt_data = encrypt
|
32
38
|
end
|
33
39
|
|
34
40
|
# Disconnects this Tree from the current session
|
@@ -38,7 +44,7 @@ module RubySMB
|
|
38
44
|
def disconnect!
|
39
45
|
request = RubySMB::SMB2::Packet::TreeDisconnectRequest.new
|
40
46
|
request = set_header_fields(request)
|
41
|
-
raw_response = client.send_recv(request)
|
47
|
+
raw_response = client.send_recv(request, encrypt: @tree_connect_encrypt_data)
|
42
48
|
response = RubySMB::SMB2::Packet::TreeDisconnectResponse.read(raw_response)
|
43
49
|
unless response.valid?
|
44
50
|
raise RubySMB::Error::InvalidPacket.new(
|
@@ -96,7 +102,7 @@ module RubySMB
|
|
96
102
|
create_request.create_disposition = disposition
|
97
103
|
create_request.name = filename
|
98
104
|
|
99
|
-
raw_response = client.send_recv(create_request)
|
105
|
+
raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
|
100
106
|
response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
|
101
107
|
unless response.valid?
|
102
108
|
raise RubySMB::Error::InvalidPacket.new(
|
@@ -107,15 +113,15 @@ module RubySMB
|
|
107
113
|
)
|
108
114
|
end
|
109
115
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
110
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
116
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
111
117
|
end
|
112
118
|
|
113
119
|
case @share_type
|
114
|
-
when
|
115
|
-
RubySMB::SMB2::File.new(name: filename, tree: self, response: response)
|
116
|
-
when
|
120
|
+
when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_DISK
|
121
|
+
RubySMB::SMB2::File.new(name: filename, tree: self, response: response, encrypt: @tree_connect_encrypt_data)
|
122
|
+
when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_PIPE
|
117
123
|
RubySMB::SMB2::Pipe.new(name: filename, tree: self, response: response)
|
118
|
-
# when
|
124
|
+
# when RubySMB::SMB2::TreeConnectResponse::SMB2_SHARE_TYPE_PRINT
|
119
125
|
# it's a printer!
|
120
126
|
else
|
121
127
|
raise RubySMB::Error::RubySMBError, 'Unsupported share type'
|
@@ -148,7 +154,7 @@ module RubySMB
|
|
148
154
|
files = []
|
149
155
|
|
150
156
|
loop do
|
151
|
-
response = client.send_recv(directory_request)
|
157
|
+
response = client.send_recv(directory_request, encrypt: @tree_connect_encrypt_data)
|
152
158
|
directory_response = RubySMB::SMB2::Packet::QueryDirectoryResponse.read(response)
|
153
159
|
unless directory_response.valid?
|
154
160
|
raise RubySMB::Error::InvalidPacket.new(
|
@@ -164,7 +170,7 @@ module RubySMB
|
|
164
170
|
break if status_code == WindowsError::NTStatus::STATUS_NO_MORE_FILES
|
165
171
|
|
166
172
|
unless status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
167
|
-
raise RubySMB::Error::UnexpectedStatusCode, status_code
|
173
|
+
raise RubySMB::Error::UnexpectedStatusCode, status_code
|
168
174
|
end
|
169
175
|
|
170
176
|
files += directory_response.results(type)
|
@@ -193,7 +199,7 @@ module RubySMB
|
|
193
199
|
|
194
200
|
create_request = open_directory_packet(directory: directory, disposition: disposition,
|
195
201
|
impersonation: impersonation, read: read, write: write, delete: delete)
|
196
|
-
raw_response = client.send_recv(create_request)
|
202
|
+
raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
|
197
203
|
response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
|
198
204
|
unless response.valid?
|
199
205
|
raise RubySMB::Error::InvalidPacket.new(
|
@@ -204,7 +210,7 @@ module RubySMB
|
|
204
210
|
)
|
205
211
|
end
|
206
212
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
207
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
213
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
208
214
|
end
|
209
215
|
|
210
216
|
response
|