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.
- checksums.yaml +4 -4
- data/examples/anonymous_auth.rb +5 -0
- data/examples/append_file.rb +57 -14
- data/examples/authenticate.rb +64 -16
- data/examples/delete_file.rb +53 -11
- data/examples/dump_secrets_from_sid.rb +43 -8
- data/examples/enum_domain_users.rb +51 -8
- data/examples/enum_registry_key.rb +51 -7
- data/examples/enum_registry_values.rb +51 -9
- data/examples/get_computer_info.rb +48 -8
- data/examples/list_directory.rb +54 -12
- data/examples/negotiate.rb +54 -42
- data/examples/negotiate_with_netbios_service.rb +55 -16
- data/examples/net_share_enum_all.rb +47 -8
- data/examples/pipes.rb +51 -7
- data/examples/query_service_status.rb +51 -8
- data/examples/read_file_encryption.rb +71 -26
- data/examples/read_registry_key_value.rb +54 -9
- data/examples/rename_file.rb +58 -15
- data/examples/write_file.rb +58 -15
- data/lib/ruby_smb/client/authentication.rb +53 -0
- data/lib/ruby_smb/client/negotiation.rb +10 -2
- data/lib/ruby_smb/client/tree_connect.rb +8 -1
- data/lib/ruby_smb/client.rb +16 -5
- data/lib/ruby_smb/rap/net_share_enum.rb +166 -0
- data/lib/ruby_smb/rap.rb +10 -0
- data/lib/ruby_smb/smb1/commands.rb +1 -0
- data/lib/ruby_smb/smb1/packet/negotiate_response.rb +11 -0
- data/lib/ruby_smb/smb1/packet/open_andx_request.rb +39 -0
- data/lib/ruby_smb/smb1/packet/open_andx_response.rb +40 -0
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +2 -2
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +11 -0
- data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +53 -13
- data/lib/ruby_smb/smb1/packet/trans2/find_information_level/find_info_standard.rb +39 -0
- data/lib/ruby_smb/smb1/packet/trans2/find_information_level.rb +1 -0
- data/lib/ruby_smb/smb1/packet/trans2/win9x_framing.rb +68 -0
- data/lib/ruby_smb/smb1/packet/trans2.rb +1 -0
- data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/tree_connect_response.rb +10 -1
- data/lib/ruby_smb/smb1/packet.rb +2 -0
- data/lib/ruby_smb/smb1/pipe.rb +2 -0
- data/lib/ruby_smb/smb1/tree.rb +113 -9
- data/lib/ruby_smb/version.rb +1 -1
- data/lib/ruby_smb.rb +1 -0
- data/spec/lib/ruby_smb/client_spec.rb +2 -1
- data/spec/lib/ruby_smb/rap/net_share_enum_spec.rb +185 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/win9x_framing_spec.rb +113 -0
- data/spec/lib/ruby_smb/smb1/tree_spec.rb +188 -2
- 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
|
data/lib/ruby_smb/rap.rb
ADDED
|
@@ -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
|
|
@@ -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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/ruby_smb/smb1/packet.rb
CHANGED
|
@@ -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'
|
data/lib/ruby_smb/smb1/pipe.rb
CHANGED
|
@@ -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
|