Class: RubySMB::Dcerpc::Client
Overview
Represents DCERPC SMB client capable of talking to an RPC endpoint in stand-alone.
Constant Summary
collapse
- MAX_BUFFER_SIZE =
The default maximum size of a RPC message that the Client accepts (in bytes)
64512
- READ_TIMEOUT =
The read timeout when receiving packets.
30
- ENDPOINT_MAPPER_PORT =
The default Endpoint Mapper port
135
DCE_C_AUTHZ_DCE, DCE_C_AUTHZ_NAME, MAX_RECV_FRAG, MAX_XMIT_FRAG, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHN_GSS_KERBEROS, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_GSS_SCHANNEL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_AUTHN_LEVEL_NONE, RPC_C_AUTHN_LEVEL_PKT, RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_NETLOGON, RPC_C_AUTHN_NONE, RPC_C_AUTHN_WINNT
Instance Attribute Summary collapse
Instance Method Summary
collapse
-
#close ⇒ Object
-
#connect(port: nil) ⇒ TcpSocket
Connect to the RPC endpoint.
-
#dcerpc_request(stub_packet, auth_level: nil, auth_type: nil) ⇒ Object
Send a DCERPC request with the provided stub packet.
-
#initialize(host, endpoint, tcp_socket: nil, read_timeout: READ_TIMEOUT, username: '', password: '', domain: '.', local_workstation: 'WORKSTATION', ntlm_flags: NTLM::DEFAULT_CLIENT_FLAGS) ⇒ Client
constructor
A new instance of Client.
-
#process_ntlm_type2(type2_message) ⇒ Object
-
#recv_struct(struct) ⇒ Object
Receive a packet from the remote host and parse it according to struct.
-
#send_packet(packet) ⇒ Object
Send a packet to the remote host.
Methods included from PeerInfo
#extract_os_version, #store_target_info
#add_auth_verifier, #auth_provider_complete_handshake, #auth_provider_decrypt_and_verify, #auth_provider_encrypt_and_sign, #auth_provider_init, #bind, #force_set_auth_params, #get_auth_padding_length, #get_response_full_stub, #handle_integrity_privacy, #set_decrypted_packet, #set_encrypted_packet, #set_integrity_privacy, #set_signature_on_packet
Constructor Details
#initialize(host, endpoint, tcp_socket: nil, read_timeout: READ_TIMEOUT, username: '', password: '', domain: '.', local_workstation: 'WORKSTATION', ntlm_flags: NTLM::DEFAULT_CLIENT_FLAGS) ⇒ Client
Returns a new instance of Client.
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 102
def initialize(host,
endpoint,
tcp_socket: nil,
read_timeout: READ_TIMEOUT,
username: '',
password: '',
domain: '.',
local_workstation: 'WORKSTATION',
ntlm_flags: NTLM::DEFAULT_CLIENT_FLAGS)
@endpoint = endpoint
extend @endpoint
@host = host
@tcp_socket = tcp_socket
@read_timeout = read_timeout
@domain = domain
@local_workstation = local_workstation
@username = username
@password = password
@max_buffer_size = MAX_BUFFER_SIZE
@call_id = 1
@ctx_id = 0
@auth_ctx_id_base = rand(0xFFFFFFFF)
unless username.empty? && password.empty?
@ntlm_client = RubySMB::NTLM::Client.new(
@username,
@password,
workstation: @local_workstation,
domain: @domain,
flags: ntlm_flags
)
end
end
|
Instance Attribute Details
#default_domain ⇒ String
56
57
58
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 56
def default_domain
@default_domain
end
|
#default_name ⇒ String
51
52
53
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 51
def default_name
@default_name
end
|
#dns_domain_name ⇒ String
66
67
68
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 66
def dns_domain_name
@dns_domain_name
end
|
#dns_host_name ⇒ String
61
62
63
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 61
def dns_host_name
@dns_host_name
end
|
#dns_tree_name ⇒ String
71
72
73
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 71
def dns_tree_name
@dns_tree_name
end
|
#domain ⇒ String
26
27
28
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 26
def domain
@domain
end
|
#local_workstation ⇒ String
31
32
33
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 31
def local_workstation
@local_workstation
end
|
#max_buffer_size ⇒ Integer
82
83
84
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 82
def max_buffer_size
@max_buffer_size
end
|
#ntlm_client ⇒ String
36
37
38
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 36
def ntlm_client
@ntlm_client
end
|
#os_version ⇒ String
76
77
78
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 76
def os_version
@os_version
end
|
#password ⇒ String
46
47
48
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 46
def password
@password
end
|
#tcp_socket ⇒ TcpSocket
87
88
89
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 87
def tcp_socket
@tcp_socket
end
|
#username ⇒ String
41
42
43
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 41
def username
@username
end
|
Instance Method Details
#close ⇒ Object
177
178
179
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 177
def close
@tcp_socket.close if @tcp_socket && !@tcp_socket.closed?
end
|
#connect(port: nil) ⇒ TcpSocket
Connect to the RPC endpoint. If a TCP socket was not provided, it takes
care of asking the Endpoint Mapper Interface the port used by the given
endpoint provided in #initialize and connect a TCP socket
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 146
def connect(port: nil)
return if @tcp_socket
unless port
if @endpoint == Epm
port = ENDPOINT_MAPPER_PORT
else
epm_client = Client.new(@host, Epm, read_timeout: @read_timeout)
epm_client.connect
begin
epm_client.bind
towers = epm_client.ept_map_endpoint(@endpoint)
rescue RubySMB::Dcerpc::Error::DcerpcError => e
e.message.prepend(
"Cannot resolve the remote port number for endpoint #{@endpoint::UUID}. "\
"Set @tcp_socket parameter to specify the service port number and bypass "\
"EPM port resolution. Error: "
)
raise e
ensure
epm_client.close
end
port = towers.first[:port]
end
end
@tcp_socket = TCPSocket.new(@host, port)
end
|
#dcerpc_request(stub_packet, auth_level: nil, auth_type: nil) ⇒ Object
Send a DCERPC request with the provided stub packet.
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 195
def dcerpc_request(stub_packet, auth_level: nil, auth_type: nil)
stub_class = stub_packet.class.name.split('::')
values = {
opnum: stub_packet.opnum,
p_cont_id: @ctx_id
}
dcerpc_req = Request.new(values, { endpoint: stub_class[-2] })
dcerpc_req..call_id = @call_id
dcerpc_req.stub.read(stub_packet.to_binary_s)
if auth_level &&
[RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(auth_level)
set_integrity_privacy(dcerpc_req, auth_level: auth_level, auth_type: auth_type)
valid_offset = (((dcerpc_req.sec_trailer.abs_offset - dcerpc_req.stub.abs_offset) % 16))
valid_auth_pad = (dcerpc_req.sec_trailer.auth_pad_length == dcerpc_req.auth_pad.length)
raise Error::InvalidPacket unless valid_offset == 0 && valid_auth_pad
end
send_packet(dcerpc_req)
dcerpc_res = recv_struct(Response)
unless dcerpc_res..pfc_flags.first_frag == 1
raise Error::InvalidPacket, "Not the first fragment"
end
if auth_level &&
[RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(auth_level)
handle_integrity_privacy(dcerpc_res, auth_level: auth_level, auth_type: auth_type)
end
raw_stub = dcerpc_res.stub.to_binary_s
loop do
break if dcerpc_res..pfc_flags.last_frag == 1
dcerpc_res = recv_struct(Response)
if auth_level &&
[RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY].include?(auth_level)
handle_integrity_privacy(dcerpc_res, auth_level: auth_level, auth_type: auth_type)
end
raw_stub << dcerpc_res.stub.to_binary_s
end
raw_stub
end
|
#process_ntlm_type2(type2_message) ⇒ Object
181
182
183
184
185
186
187
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 181
def process_ntlm_type2(type2_message)
auth3 = super
challenge_message = @ntlm_client.session.challenge_message
store_target_info(challenge_message.target_info) if challenge_message.has_flag?(:TARGET_INFO)
@os_version = (challenge_message.os_version.to_s) unless challenge_message.os_version.empty?
auth3
end
|
#recv_struct(struct) ⇒ Object
Receive a packet from the remote host and parse it according to struct
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 271
def recv_struct(struct)
raise Error::CommunicationError, 'Connection has already been closed' if @tcp_socket.closed?
if IO.select([@tcp_socket], nil, nil, @read_timeout).nil?
raise Error::CommunicationError, "Read timeout expired when reading from the Socket (timeout=#{@read_timeout})"
end
begin
response = struct.read(@tcp_socket)
rescue IOError
raise Error::InvalidPacket, "Error reading the #{struct} response"
end
unless response..ptype == struct::PTYPE
raise Error::InvalidPacket, "Not a #{struct} packet"
end
response
rescue Errno::EINVAL, Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE => e
raise Error::CommunicationError, "An error occurred reading from the Socket: #{e.message}"
end
|
#send_packet(packet) ⇒ Object
Send a packet to the remote host
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
|
# File 'lib/ruby_smb/dcerpc/client.rb', line 251
def send_packet(packet)
data = packet.to_binary_s
bytes_written = 0
begin
loop do
break unless bytes_written < data.size
retval = @tcp_socket.write(data[bytes_written..-1])
bytes_written += retval
end
rescue IOError, Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE => e
raise Error::CommunicationError, "An error occurred writing to the Socket: #{e.message}"
end
nil
end
|