ruby_smb 3.3.18 → 3.3.20

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/examples/anonymous_auth.rb +5 -0
  3. data/examples/append_file.rb +57 -14
  4. data/examples/authenticate.rb +64 -16
  5. data/examples/delete_file.rb +53 -11
  6. data/examples/dump_secrets_from_sid.rb +43 -8
  7. data/examples/enum_domain_users.rb +51 -8
  8. data/examples/enum_registry_key.rb +51 -7
  9. data/examples/enum_registry_values.rb +51 -9
  10. data/examples/get_computer_info.rb +48 -8
  11. data/examples/list_directory.rb +54 -12
  12. data/examples/negotiate.rb +54 -42
  13. data/examples/negotiate_with_netbios_service.rb +55 -16
  14. data/examples/net_share_enum_all.rb +47 -8
  15. data/examples/pipes.rb +51 -7
  16. data/examples/query_service_status.rb +51 -8
  17. data/examples/read_file_encryption.rb +71 -26
  18. data/examples/read_registry_key_value.rb +54 -9
  19. data/examples/rename_file.rb +58 -15
  20. data/examples/write_file.rb +58 -15
  21. data/lib/ruby_smb/client/authentication.rb +53 -0
  22. data/lib/ruby_smb/client/negotiation.rb +10 -2
  23. data/lib/ruby_smb/client/tree_connect.rb +8 -1
  24. data/lib/ruby_smb/client.rb +16 -5
  25. data/lib/ruby_smb/rap/net_share_enum.rb +166 -0
  26. data/lib/ruby_smb/rap.rb +10 -0
  27. data/lib/ruby_smb/smb1/commands.rb +1 -0
  28. data/lib/ruby_smb/smb1/packet/negotiate_response.rb +11 -0
  29. data/lib/ruby_smb/smb1/packet/open_andx_request.rb +39 -0
  30. data/lib/ruby_smb/smb1/packet/open_andx_response.rb +40 -0
  31. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +2 -2
  32. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +11 -0
  33. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +53 -13
  34. data/lib/ruby_smb/smb1/packet/trans2/find_information_level/find_info_standard.rb +39 -0
  35. data/lib/ruby_smb/smb1/packet/trans2/find_information_level.rb +1 -0
  36. data/lib/ruby_smb/smb1/packet/trans2/win9x_framing.rb +68 -0
  37. data/lib/ruby_smb/smb1/packet/trans2.rb +1 -0
  38. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +1 -1
  39. data/lib/ruby_smb/smb1/packet/tree_connect_response.rb +10 -1
  40. data/lib/ruby_smb/smb1/packet.rb +2 -0
  41. data/lib/ruby_smb/smb1/pipe.rb +2 -0
  42. data/lib/ruby_smb/smb1/tree.rb +113 -9
  43. data/lib/ruby_smb/version.rb +1 -1
  44. data/lib/ruby_smb.rb +1 -0
  45. data/spec/lib/ruby_smb/client_spec.rb +2 -1
  46. data/spec/lib/ruby_smb/rap/net_share_enum_spec.rb +185 -0
  47. data/spec/lib/ruby_smb/smb1/packet/trans2/win9x_framing_spec.rb +113 -0
  48. data/spec/lib/ruby_smb/smb1/tree_spec.rb +188 -2
  49. metadata +12 -2
@@ -0,0 +1,166 @@
1
+ module RubySMB
2
+ module Rap
3
+ # NetShareEnum (RAP opcode 0), as defined in [MS-RAP 3.3.4.1](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rap/48dd86d2-4092-49a4-9024-308f0ed77520).
4
+ # Carried over `\PIPE\LANMAN` using SMB_COM_TRANSACTION. Request parameters
5
+ # describe the shape of the data the server returns; response parameters
6
+ # carry the RAP status, entry count, and buffer sizing hint, and the
7
+ # response data block is an array of `share_info_1` records.
8
+ module NetShareEnum
9
+ OPCODE = 0
10
+
11
+ # Parameter descriptor for the RAP call itself: (W)ord info level,
12
+ # (r)eturn buffer pointer, (L)ength hint, (e)ntry count, (h)andle.
13
+ PARAM_DESCRIPTOR = 'WrLeh'.freeze
14
+
15
+ # Data descriptor for `share_info_1`: (B)13 name, (B)yte pad, (W)ord type,
16
+ # (z) pointer to remark. See MS-RAP 3.2.4 for descriptor syntax.
17
+ DATA_DESCRIPTOR_LEVEL_1 = 'B13BWz'.freeze
18
+
19
+ # Default server receive-buffer size.
20
+ DEFAULT_RECEIVE_BUFFER_SIZE = 0x1000
21
+
22
+ # Share type codes carried in the low bits of `share_info_1.shi1_type`
23
+ # per MS-RAP 2.5.14. The RAP field is 16 bits wide, unlike the 32-bit
24
+ # SRVSVC variant in {RubySMB::Dcerpc::Srvsvc::SHARE_TYPES}.
25
+ SHARE_TYPES = {
26
+ 0x0000 => 'DISK',
27
+ 0x0001 => 'PRINTER',
28
+ 0x0002 => 'DEVICE',
29
+ 0x0003 => 'IPC'
30
+ }.freeze
31
+ STYPE_SPECIAL = 0x8000
32
+ STYPE_TEMPORARY = 0x4000
33
+
34
+ # Single share entry (`share_info_1`) as it appears on the wire.
35
+ # MS-RAP 2.5.21. Fixed 20-byte layout.
36
+ class ShareInfo1 < BinData::Record
37
+ endian :little
38
+
39
+ string :netname, length: 13, trim_padding: true
40
+ uint8 :pad1
41
+ uint16 :share_type
42
+ uint32 :remark_offset
43
+ end
44
+
45
+ # Parameters block of the RAP request (sent in SMB trans_parameters).
46
+ # Variable-length because of the null-terminated descriptor strings.
47
+ class Request < BinData::Record
48
+ endian :little
49
+
50
+ uint16 :opcode, asserted_value: OPCODE
51
+ stringz :param_descriptor, initial_value: PARAM_DESCRIPTOR
52
+ stringz :data_descriptor, initial_value: DATA_DESCRIPTOR_LEVEL_1
53
+ uint16 :info_level, initial_value: 1
54
+ uint16 :receive_buffer_size, initial_value: DEFAULT_RECEIVE_BUFFER_SIZE
55
+ end
56
+
57
+ # Parameters block of the RAP response.
58
+ # MS-RAP 3.3.5.1 NetShareEnum Response.
59
+ class Response < BinData::Record
60
+ endian :little
61
+
62
+ uint16 :status
63
+ uint16 :converter
64
+ uint16 :entry_count
65
+ uint16 :available
66
+ end
67
+
68
+ # Sends a RAP NetShareEnum over `\PIPE\LANMAN` using the tree's
69
+ # existing SMB1 connection. Does not rely on having an opened pipe FID
70
+ # because Win9x does not permit OPEN_ANDX on `\PIPE\LANMAN`; RAP trans
71
+ # is accepted directly against the IPC$ tree.
72
+ #
73
+ # @return [Array<Hash>] each entry has :name (String) and :type (Integer).
74
+ # @raise [RubySMB::Error::InvalidPacket] on a malformed SMB response.
75
+ # @raise [RubySMB::Error::UnexpectedStatusCode] on a non-success SMB status.
76
+ # @raise [RubySMB::Error::RubySMBError] on a non-zero RAP status.
77
+ def net_share_enum
78
+ request = build_net_share_enum_request
79
+ raw_response = rap_client.send_recv(request)
80
+ response = RubySMB::SMB1::Packet::Trans::Response.read(raw_response)
81
+ validate_trans_response!(response)
82
+ parse_net_share_enum_response(response, raw_response)
83
+ end
84
+
85
+ private
86
+
87
+ # The SMB1 tree used to carry RAP traffic. `self` when this module is
88
+ # mixed into {RubySMB::SMB1::Tree}; the pipe's `tree` when mixed into
89
+ # {RubySMB::SMB1::Pipe}.
90
+ def rap_tree
91
+ is_a?(RubySMB::SMB1::Tree) ? self : tree
92
+ end
93
+
94
+ def rap_client
95
+ rap_tree.client
96
+ end
97
+
98
+ def build_net_share_enum_request
99
+ request = RubySMB::SMB1::Packet::Trans::Request.new
100
+ request.smb_header.tid = rap_tree.id
101
+ request.smb_header.flags2.unicode = 0
102
+ request.data_block.name = "\\PIPE\\LANMAN\x00".b
103
+ request.data_block.trans_parameters = Request.new.to_binary_s
104
+ request.parameter_block.max_parameter_count = 8
105
+ request.parameter_block.max_data_count = DEFAULT_RECEIVE_BUFFER_SIZE
106
+ request
107
+ end
108
+
109
+ def validate_trans_response!(response)
110
+ unless response.valid?
111
+ raise RubySMB::Error::InvalidPacket.new(
112
+ expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
113
+ expected_cmd: RubySMB::SMB1::Packet::Trans::Response::COMMAND,
114
+ packet: response
115
+ )
116
+ end
117
+ unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
118
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
119
+ end
120
+ end
121
+
122
+ def parse_net_share_enum_response(response, raw_response)
123
+ # Slice the parameter and data sections using the offsets the server
124
+ # reported, not the ones BinData computed. Win9x packs trans_parameters
125
+ # right after byte_count with no 4-byte-alignment padding, which
126
+ # confuses Trans::Response::DataBlock#pad1_length and shifts the
127
+ # trans_parameters window by 1 byte.
128
+ params_bytes = raw_response[response.parameter_block.parameter_offset,
129
+ response.parameter_block.parameter_count].to_s
130
+ data_bytes = raw_response[response.parameter_block.data_offset,
131
+ response.parameter_block.data_count].to_s
132
+
133
+ if params_bytes.bytesize < Response.new.num_bytes
134
+ raise RubySMB::Error::InvalidPacket,
135
+ "Truncated RAP NetShareEnum response parameters: #{params_bytes.unpack1('H*')}"
136
+ end
137
+ params = Response.read(params_bytes)
138
+ unless params.status.zero?
139
+ raise RubySMB::Error::RubySMBError,
140
+ "RAP NetShareEnum failed with status 0x#{params.status.to_i.to_s(16)}"
141
+ end
142
+
143
+ params.entry_count.times.map do |i|
144
+ offset = i * ShareInfo1.new.num_bytes
145
+ break [] if offset + ShareInfo1.new.num_bytes > data_bytes.bytesize
146
+ entry = ShareInfo1.read(data_bytes[offset, ShareInfo1.new.num_bytes])
147
+ {
148
+ name: entry.netname.to_s.delete("\x00"),
149
+ type: format_share_type(entry.share_type.to_i)
150
+ }
151
+ end.compact
152
+ end
153
+
154
+ # Format a RAP `share_info_1.shi1_type` value as a pipe-joined string in
155
+ # the same style as {RubySMB::Dcerpc::Srvsvc#net_share_enum_all}, so
156
+ # callers can consume both APIs uniformly.
157
+ def format_share_type(share_type)
158
+ base_bits = share_type & ~(STYPE_SPECIAL | STYPE_TEMPORARY)
159
+ parts = [SHARE_TYPES[base_bits] || format('UNKNOWN(0x%04x)', base_bits)]
160
+ parts << 'SPECIAL' unless (share_type & STYPE_SPECIAL).zero?
161
+ parts << 'TEMPORARY' unless (share_type & STYPE_TEMPORARY).zero?
162
+ parts.join('|')
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,10 @@
1
+ module RubySMB
2
+ # Remote Administration Protocol (RAP), as defined in [MS-RAP]
3
+ # (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rap/).
4
+ # RAP is the LAN Manager remote-administration API, carried over the
5
+ # `\PIPE\LANMAN` named pipe using SMB1 SMB_COM_TRANSACTION. It is the only
6
+ # share-enumeration path supported by pre-NT servers (e.g. Windows 95/98/ME).
7
+ module Rap
8
+ require 'ruby_smb/rap/net_share_enum'
9
+ end
10
+ end
@@ -4,6 +4,7 @@ module RubySMB
4
4
  SMB_COM_CLOSE = 0x04
5
5
  SMB_COM_TRANSACTION = 0x25
6
6
  SMB_COM_ECHO = 0x2B
7
+ SMB_COM_OPEN_ANDX = 0x2D
7
8
  SMB_COM_READ_ANDX = 0x2E
8
9
  SMB_COM_WRITE_ANDX = 0x2F
9
10
  SMB_COM_TRANSACTION2 = 0x32
@@ -22,10 +22,21 @@ module RubySMB
22
22
  end
23
23
 
24
24
  # An SMB_Data Block as defined by the {NegotiateResponse}
25
+ # Windows 95/98/ME may only return the challenge with no domain/server names.
25
26
  class DataBlock < RubySMB::SMB1::DataBlock
26
27
  string :challenge, label: 'Auth Challenge', length: 8
27
28
  stringz16 :domain_name, label: 'Primary Domain'
28
29
  stringz16 :server_name, label: 'Server Name'
30
+
31
+ # Override to handle Win95 responses that only contain the challenge
32
+ # (byte_count=8) without domain_name or server_name fields.
33
+ def do_read(io)
34
+ byte_count.do_read(io)
35
+ challenge.do_read(io)
36
+ return unless byte_count > 8
37
+ domain_name.do_read(io)
38
+ server_name.do_read(io)
39
+ end
29
40
  end
30
41
 
31
42
  smb_header :smb_header
@@ -0,0 +1,39 @@
1
+ module RubySMB
2
+ module SMB1
3
+ module Packet
4
+ # A SMB1 SMB_COM_OPEN_ANDX Request Packet as defined in
5
+ # [MS-CIFS 2.2.4.41.1 Request](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/3a760987-f60d-4012-930b-fe90328775cc)
6
+ #
7
+ # This is the LANMAN 1.0 file-open command, supported by all SMB1 servers
8
+ # including Windows 95/98/ME which lack NT_CREATE_ANDX (0xA2).
9
+ class OpenAndxRequest < RubySMB::GenericPacket
10
+ COMMAND = RubySMB::SMB1::Commands::SMB_COM_OPEN_ANDX
11
+
12
+ # A SMB1 Parameter Block as defined by the {OpenAndxRequest}
13
+ class ParameterBlock < RubySMB::SMB1::ParameterBlock
14
+ endian :little
15
+
16
+ and_x_block :andx_block
17
+ uint16 :flags, label: 'Flags'
18
+ uint16 :access_mode, label: 'Access Mode'
19
+ smb_file_attributes :search_attributes, label: 'Search Attributes'
20
+ smb_file_attributes :file_attributes, label: 'File Attributes'
21
+ uint32 :creation_time, label: 'Creation Time'
22
+ uint16 :open_mode, label: 'Open Mode'
23
+ uint32 :allocation_size, label: 'Allocation Size'
24
+ uint32 :timeout, label: 'Timeout'
25
+ uint32 :reserved, label: 'Reserved'
26
+ end
27
+
28
+ # Represents the specific layout of the DataBlock for an {OpenAndxRequest} Packet.
29
+ class DataBlock < RubySMB::SMB1::DataBlock
30
+ stringz :file_name, label: 'File Name'
31
+ end
32
+
33
+ smb_header :smb_header
34
+ parameter_block :parameter_block
35
+ data_block :data_block
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,40 @@
1
+ module RubySMB
2
+ module SMB1
3
+ module Packet
4
+ # A SMB1 SMB_COM_OPEN_ANDX Response Packet as defined in
5
+ # [MS-CIFS 2.2.4.41.2 Response](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/dbce00e7-68a1-41c6-982d-9483c902ad9b)
6
+ class OpenAndxResponse < RubySMB::GenericPacket
7
+ COMMAND = RubySMB::SMB1::Commands::SMB_COM_OPEN_ANDX
8
+
9
+ # A SMB1 Parameter Block as defined by the {OpenAndxResponse}.
10
+ # Field names and layout follow MS-CIFS 2.2.4.41.2.
11
+ class ParameterBlock < RubySMB::SMB1::ParameterBlock
12
+ endian :little
13
+
14
+ and_x_block :andx_block
15
+ uint16 :fid, label: 'FID'
16
+ smb_file_attributes :file_attributes, label: 'File Attributes'
17
+ utime :last_write_time, label: 'Last Write Time'
18
+ uint32 :file_data_size, label: 'File Data Size'
19
+ uint16 :access_rights, label: 'Access Rights'
20
+ uint16 :resource_type, label: 'Resource Type'
21
+ smb_nmpipe_status :nmpipe_status, label: 'Named Pipe Status'
22
+ uint16 :open_results, label: 'Open Results'
23
+ array :reserved, type: :uint16, initial_length: 3
24
+ end
25
+
26
+ class DataBlock < RubySMB::SMB1::DataBlock
27
+ end
28
+
29
+ smb_header :smb_header
30
+ parameter_block :parameter_block
31
+ data_block :data_block
32
+
33
+ def initialize_instance
34
+ super
35
+ smb_header.flags.reply = 1
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -27,8 +27,8 @@ module RubySMB
27
27
  string :oem_password, label: 'OEM Password'
28
28
  string :unicode_password, label: 'Unicode password'
29
29
  string :padding, label: 'Padding'
30
- string :account_name, label: 'Account Name(username)', length: 2
31
- string :primary_domain, label: 'Primary Domain', length: 2
30
+ stringz :account_name, label: 'Account Name(username)'
31
+ stringz :primary_domain, label: 'Primary Domain'
32
32
  stringz :native_os, label: 'Native OS', initial_value: 'Windows 7 Ultimate N 7601 Service Pack 1'
33
33
  stringz :native_lan_man, label: 'Native LAN Manager', initial_value: 'Windows 7 Ultimate N 6.1'
34
34
  end
@@ -13,11 +13,22 @@ module RubySMB
13
13
  end
14
14
 
15
15
  # Represents the specific layout of the DataBlock for a {SessionSetupResponse} Packet.
16
+ # Windows 95/98/ME may return byte_count=0 with no string fields.
16
17
  class DataBlock < RubySMB::SMB1::DataBlock
17
18
  string :pad, label: 'Padding', length: 0
18
19
  stringz :native_os, label: 'Native OS'
19
20
  stringz :native_lan_man, label: 'Native LAN Manager'
20
21
  stringz :primary_domain, label: 'Primary Domain'
22
+
23
+ # Override to handle Win95 responses with byte_count=0.
24
+ def do_read(io)
25
+ byte_count.do_read(io)
26
+ return unless byte_count > 0
27
+ pad.do_read(io)
28
+ native_os.do_read(io)
29
+ native_lan_man.do_read(io)
30
+ primary_domain.do_read(io)
31
+ end
21
32
  end
22
33
 
23
34
  smb_header :smb_header
@@ -41,6 +41,8 @@ module RubySMB
41
41
  # This class represents an SMB1 Trans2 FIND_FIRST2 Response Packet as defined in
42
42
  # [2.2.6.2.2 Response](https://msdn.microsoft.com/en-us/library/ee441704.aspx)
43
43
  class FindFirst2Response < RubySMB::GenericPacket
44
+ include RubySMB::SMB1::Packet::Trans2::Win9xFraming
45
+
44
46
  COMMAND = RubySMB::SMB1::Commands::SMB_COM_TRANSACTION2
45
47
 
46
48
  class ParameterBlock < RubySMB::SMB1::Packet::Trans2::Response::ParameterBlock
@@ -60,30 +62,68 @@ module RubySMB
60
62
  # structs for the given FileInformationClass. Pulled out of
61
63
  # the string buffer.
62
64
  #
65
+ # Info levels that carry a leading NextEntryOffset (e.g.
66
+ # FindFileFullDirectoryInfo) are framed by that field. Info levels
67
+ # without one (e.g. SMB_INFO_STANDARD, used by Win95/98/ME) are
68
+ # packed sequentially; each entry's length is derived from the
69
+ # record itself, and servers may insert an optional single NULL
70
+ # pad byte between entries (see MS-CIFS Appendix A, note <153>).
71
+ #
63
72
  # @param klass [Class] the FileInformationClass class to read the data as
73
+ # @param buffer [String, nil] raw trans2_data bytes to parse instead of
74
+ # the BinData-parsed buffer. Used by callers that detect a padding
75
+ # mismatch between BinData's expected layout and what a Win9x-era
76
+ # server actually sent (no 4-byte alignment pad before the data),
77
+ # and want to re-feed the bytes from the server-reported data_offset.
64
78
  # @return [array<BinData::Record>] An array of structs holding the requested information
65
79
  # @raise [RubySMB::Error::InvalidPacket] if the string buffer is not a valid File Information packet
66
- def results(klass, unicode:)
67
- information_classes = []
68
- blob = data_block.trans2_data.buffer.to_binary_s.dup
69
- until blob.empty?
70
- length = blob[0, 4].unpack('V').first
80
+ def results(klass, unicode:, buffer: nil)
81
+ blob = (buffer || data_block.trans2_data.buffer.to_binary_s).dup
82
+ if klass.new.respond_to?(:next_offset)
83
+ read_next_offset_entries(klass, blob, unicode: unicode)
84
+ else
85
+ read_sequential_entries(klass, blob, unicode: unicode)
86
+ end
87
+ end
71
88
 
72
- data = if length.zero?
73
- blob.slice!(0, blob.length)
74
- else
75
- blob.slice!(0, length)
76
- end
89
+ private
77
90
 
91
+ def read_next_offset_entries(klass, blob, unicode:)
92
+ entries = []
93
+ until blob.empty?
94
+ length = blob[0, 4].unpack1('V')
95
+ data = length.zero? ? blob.slice!(0, blob.length) : blob.slice!(0, length)
96
+ file_info = klass.new
97
+ file_info.unicode = unicode if file_info.respond_to?(:unicode=)
98
+ begin
99
+ entries << file_info.read(data)
100
+ rescue IOError
101
+ raise RubySMB::Error::InvalidPacket, "Invalid #{klass} File Information packet in the string buffer"
102
+ end
103
+ end
104
+ entries
105
+ end
106
+
107
+ def read_sequential_entries(klass, blob, unicode:)
108
+ entries = []
109
+ until blob.empty?
78
110
  file_info = klass.new
79
- file_info.unicode = unicode
111
+ file_info.unicode = unicode if file_info.respond_to?(:unicode=)
80
112
  begin
81
- information_classes << file_info.read(data)
113
+ file_info.read(blob)
82
114
  rescue IOError
83
115
  raise RubySMB::Error::InvalidPacket, "Invalid #{klass} File Information packet in the string buffer"
84
116
  end
117
+ consumed = file_info.num_bytes
118
+ break if consumed.zero?
119
+ blob.slice!(0, consumed)
120
+ # An entry with an empty file_name is a buffer-padding artifact, not a real entry; stop here.
121
+ break if file_info.respond_to?(:file_name_length) && file_info.file_name_length.zero?
122
+ entries << file_info
123
+ # Skip optional single NULL pad byte inserted by some servers between entries.
124
+ blob.slice!(0, 1) if blob.bytesize > 0 && blob.getbyte(0) == 0
85
125
  end
86
- information_classes
126
+ entries
87
127
  end
88
128
  end
89
129
  end
@@ -0,0 +1,39 @@
1
+ module RubySMB
2
+ module SMB1
3
+ module Packet
4
+ module Trans2
5
+ module FindInformationLevel
6
+ # SMB_INFO_STANDARD find result entry, as defined in
7
+ # [MS-CIFS 2.2.8.1.1 SMB_INFO_STANDARD](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/b7cc0966-f87d-41a6-aa1a-48526a9cc729).
8
+ # Used by TRANS2_FIND_FIRST2/FIND_NEXT2 on legacy servers
9
+ # (e.g. Windows 95/98/ME) that don't support NT LANMAN info levels.
10
+ #
11
+ # Unlike NT info levels, these entries have no next_offset field;
12
+ # they are packed sequentially with a variable-length filename.
13
+ # The optional leading ResumeKey (4 bytes) is only present when the
14
+ # SMB_FIND_RETURN_RESUME_KEYS flag is set in the request; this
15
+ # implementation does not set that flag and so omits the field.
16
+ class FindInfoStandard < BinData::Record
17
+ CLASS_LEVEL = FindInformationLevel::SMB_INFO_STANDARD
18
+
19
+ endian :little
20
+
21
+ uint16 :creation_date, label: 'Creation Date (SMB_DATE)'
22
+ uint16 :creation_time, label: 'Creation Time (SMB_TIME)'
23
+ uint16 :last_access_date, label: 'Last Access Date (SMB_DATE)'
24
+ uint16 :last_access_time, label: 'Last Access Time (SMB_TIME)'
25
+ uint16 :last_write_date, label: 'Last Write Date (SMB_DATE)'
26
+ uint16 :last_write_time, label: 'Last Write Time (SMB_TIME)'
27
+ uint32 :data_size, label: 'File Data Size'
28
+ uint32 :allocation_size, label: 'Allocation Size'
29
+ uint16 :file_attributes, label: 'File Attributes'
30
+ uint8 :file_name_length, label: 'File Name Length',
31
+ initial_value: -> { file_name.to_s.bytesize }
32
+ string :file_name, label: 'File Name',
33
+ read_length: -> { file_name_length }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -38,6 +38,7 @@ module RubySMB
38
38
 
39
39
  require 'ruby_smb/smb1/packet/trans2/find_information_level/find_file_both_directory_info'
40
40
  require 'ruby_smb/smb1/packet/trans2/find_information_level/find_file_full_directory_info'
41
+ require 'ruby_smb/smb1/packet/trans2/find_information_level/find_info_standard'
41
42
  end
42
43
  end
43
44
  end
@@ -0,0 +1,68 @@
1
+ module RubySMB
2
+ module SMB1
3
+ module Packet
4
+ module Trans2
5
+ # Shared workaround for pre-NT / LAN Manager-era servers (observed on
6
+ # Windows 9x / ME) that pack `trans2_parameters` directly after
7
+ # `byte_count` with no 4-byte-alignment pad, and `trans2_data` with
8
+ # whatever padding they feel like — always smaller than the NT-style
9
+ # alignment BinData unconditionally assumes via {DataBlock#pad1_length}
10
+ # and {DataBlock#pad2_length}. When that happens both sections land in
11
+ # the wrong place and `eos`, `sid`, `last_name_offset`, and every
12
+ # entry in the data buffer come back garbled.
13
+ #
14
+ # Fixing this in BinData itself (by having pad1/pad2 consult
15
+ # `parameter_block.parameter_offset` / `data_offset`) is the natural
16
+ # design, but cross-field lookups during field-read callbacks corrupt
17
+ # BinData's registered-class resolution cache, causing unrelated
18
+ # Trans2 responses to round-trip their `parameter_block` / `data_block`
19
+ # through the base classes instead of the concrete subclasses. So
20
+ # instead we surface the raw response bytes at the call site and let
21
+ # the response slice both sections from the offsets the server
22
+ # reported in its `parameter_block`.
23
+ #
24
+ # Mix into any {RubySMB::SMB1::Packet::Trans2} response whose caller
25
+ # holds on to the raw response bytes. The response itself must have
26
+ # the standard {Trans2::Response::ParameterBlock} shape
27
+ # (`parameter_offset` / `parameter_count` / `data_offset` /
28
+ # `data_count`) and a `data_block` with `trans2_parameters` and
29
+ # `trans2_data.buffer` fields — every concrete Trans2 response does.
30
+ #
31
+ # Same slicing pattern as {RubySMB::Rap::NetShareEnum#parse_net_share_enum_response}
32
+ # uses for the sibling Trans (not Trans2) response type.
33
+ module Win9xFraming
34
+ # Returns `[effective_trans2_parameters, effective_trans2_data_bytes]`
35
+ # when the server's layout differs from BinData's, or `[nil, nil]`
36
+ # when BinData already read the full buffer (standard NT-era servers).
37
+ #
38
+ # When a non-nil pair is returned, callers should prefer the override
39
+ # values over the BinData-parsed ones:
40
+ #
41
+ # params_ovr, data_ovr = response.win9x_trans2_overrides(raw)
42
+ # params = params_ovr || response.data_block.trans2_parameters
43
+ # data = data_ovr || response.data_block.trans2_data.buffer.to_binary_s
44
+ #
45
+ # @param raw_response [String] the raw bytes the response was read from.
46
+ # @return [Array(BinData::Record, String), Array(nil, nil)]
47
+ def win9x_trans2_overrides(raw_response)
48
+ declared_data = parameter_block.data_count.to_i
49
+ parsed_data = data_block.trans2_data.buffer.to_binary_s.bytesize
50
+ return [nil, nil] if declared_data.zero? || parsed_data == declared_data
51
+
52
+ param_offset = parameter_block.parameter_offset.to_i
53
+ param_count = parameter_block.parameter_count.to_i
54
+ data_offset = parameter_block.data_offset.to_i
55
+ return [nil, nil] if raw_response.bytesize < data_offset + declared_data
56
+ return [nil, nil] if raw_response.bytesize < param_offset + param_count
57
+
58
+ params_bytes = raw_response.byteslice(param_offset, param_count)
59
+ params_class = data_block.trans2_parameters.class
60
+ params = params_class.read(params_bytes)
61
+ data_bytes = raw_response.byteslice(data_offset, declared_data)
62
+ [params, data_bytes]
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -8,6 +8,7 @@ module RubySMB
8
8
  require 'ruby_smb/smb1/packet/trans2/query_information_level'
9
9
  require 'ruby_smb/smb1/packet/trans2/query_fs_information_level'
10
10
  require 'ruby_smb/smb1/packet/trans2/data_block'
11
+ require 'ruby_smb/smb1/packet/trans2/win9x_framing'
11
12
  require 'ruby_smb/smb1/packet/trans2/subcommands'
12
13
  require 'ruby_smb/smb1/packet/trans2/request'
13
14
  require 'ruby_smb/smb1/packet/trans2/request_secondary'
@@ -15,7 +15,7 @@ module RubySMB
15
15
 
16
16
  # The {RubySMB::SMB1::DataBlock} specific to this packet type.
17
17
  class DataBlock < RubySMB::SMB1::DataBlock
18
- stringz :password, label: 'Password Field', initial_value: '', length: -> { parent.parameter_block.password_length }
18
+ string :password, label: 'Password Field', initial_value: "\x00", length: -> { parent.parameter_block.password_length }
19
19
  choice :path, selection: -> { parent.smb_header.flags2.unicode } do
20
20
  stringz 0
21
21
  stringz16 1
@@ -9,15 +9,24 @@ module RubySMB
9
9
  # A SMB1 Parameter Block as defined by the {SessionSetupResponse}
10
10
  class ParameterBlock < RubySMB::SMB1::ParameterBlock
11
11
  and_x_block :andx_block
12
- optional_support :optional_support
12
+ optional_support :optional_support, onlyif: -> { word_count >= 3 }
13
13
  directory_access_mask :access_rights, label: 'Maximal Share Access Rights', onlyif: -> { word_count >= 5 }
14
14
  directory_access_mask :guest_access_rights, label: 'Guest Share Access Rights', onlyif: -> { word_count == 7 }
15
15
  end
16
16
 
17
17
  # Represents the specific layout of the DataBlock for a {SessionSetupResponse} Packet.
18
+ # Windows 95/98/ME may return a minimal DataBlock without the native file system.
18
19
  class DataBlock < RubySMB::SMB1::DataBlock
19
20
  stringz :service, label: 'Service Type'
20
21
  stringz :native_file_system, label: 'Native File System'
22
+
23
+ # Override to handle Win95 responses that may omit native_file_system.
24
+ def do_read(io)
25
+ byte_count.do_read(io)
26
+ return unless byte_count > 0
27
+ service.do_read(io)
28
+ native_file_system.do_read(io) if byte_count > service.num_bytes
29
+ end
21
30
  end
22
31
 
23
32
  smb_header :smb_header
@@ -22,6 +22,8 @@ module RubySMB
22
22
  require 'ruby_smb/smb1/packet/trans'
23
23
  require 'ruby_smb/smb1/packet/trans2'
24
24
  require 'ruby_smb/smb1/packet/nt_trans'
25
+ require 'ruby_smb/smb1/packet/open_andx_request'
26
+ require 'ruby_smb/smb1/packet/open_andx_response'
25
27
  require 'ruby_smb/smb1/packet/nt_create_andx_request'
26
28
  require 'ruby_smb/smb1/packet/nt_create_andx_response'
27
29
  require 'ruby_smb/smb1/packet/read_andx_request'
@@ -36,6 +36,8 @@ module RubySMB
36
36
  extend RubySMB::Dcerpc::Icpr
37
37
  when 'efsrpc', '\\efsrpc'
38
38
  extend RubySMB::Dcerpc::Efsrpc
39
+ when 'lanman', 'LANMAN', '\\PIPE\\LANMAN', '\\lanman'
40
+ extend RubySMB::Rap::NetShareEnum
39
41
  end
40
42
  super(tree: tree, response: response, name: name)
41
43
  end