Class: RubySMB::Dcerpc::Client
- Inherits:
-
Object
- Object
- RubySMB::Dcerpc::Client
- Includes:
- RubySMB::Dcerpc, Epm, PeerInfo
- Defined in:
- lib/ruby_smb/dcerpc/client.rb
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
Constants included from Epm
Epm::EPT_MAP, Epm::UUID, Epm::VER_MAJOR, Epm::VER_MINOR
Constants included from RubySMB::Dcerpc
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
- #default_domain ⇒ String
- #default_name ⇒ String
- #dns_domain_name ⇒ String
- #dns_host_name ⇒ String
- #dns_tree_name ⇒ String
- #domain ⇒ String
- #local_workstation ⇒ String
- #max_buffer_size ⇒ Integer
- #ntlm_client ⇒ String
- #os_version ⇒ String
- #password ⇒ String
- #tcp_socket ⇒ TcpSocket
- #username ⇒ String
Instance Method Summary collapse
-
#close ⇒ Object
Close the TCP Socket.
-
#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
Methods included from Epm
#get_host_port_from_ept_mapper
Methods included from RubySMB::Dcerpc
#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.
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 137 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 103 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
57 58 59 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 57 def default_domain @default_domain end |
#default_name ⇒ String
52 53 54 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 52 def default_name @default_name end |
#dns_domain_name ⇒ String
67 68 69 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 67 def dns_domain_name @dns_domain_name end |
#dns_host_name ⇒ String
62 63 64 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 62 def dns_host_name @dns_host_name end |
#dns_tree_name ⇒ String
72 73 74 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 72 def dns_tree_name @dns_tree_name end |
#domain ⇒ String
27 28 29 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 27 def domain @domain end |
#local_workstation ⇒ String
32 33 34 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 32 def local_workstation @local_workstation end |
#max_buffer_size ⇒ Integer
83 84 85 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 83 def max_buffer_size @max_buffer_size end |
#ntlm_client ⇒ String
37 38 39 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 37 def ntlm_client @ntlm_client end |
#os_version ⇒ String
77 78 79 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 77 def os_version @os_version end |
#password ⇒ String
47 48 49 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 47 def password @password end |
#tcp_socket ⇒ TcpSocket
88 89 90 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 88 def tcp_socket @tcp_socket end |
#username ⇒ String
42 43 44 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 42 def username @username end |
Instance Method Details
#close ⇒ Object
Close the TCP Socket
174 175 176 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 174 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
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 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 147 def connect(port: nil) return if @tcp_socket unless port @tcp_socket = TCPSocket.new(@host, ENDPOINT_MAPPER_PORT) bind(endpoint: Epm) begin host_port = get_host_port_from_ept_mapper( uuid: @endpoint::UUID, maj_ver: @endpoint::VER_MAJOR, min_ver: @endpoint::VER_MINOR ) rescue RubySMB::Dcerpc::Error::DcerpcError => e e..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 end port = host_port[:port] @tcp_socket.close @tcp_socket = nil 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.
192 193 194 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 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 192 def dcerpc_request(stub_packet, auth_level: nil, auth_type: nil) stub_class = stub_packet.class.name.split('::') #opts.merge!(endpoint: stub_class[-2]) values = { opnum: stub_packet.opnum, p_cont_id: @ctx_id } dcerpc_req = Request.new(values, { endpoint: stub_class[-2] }) dcerpc_req.pdu_header.call_id = @call_id dcerpc_req.stub.read(stub_packet.to_binary_s) # TODO: handle fragmentation # We should fragment PDUs if: # 1) Payload exceeds max_xmit_frag (@max_buffer_size) received during BIND response # 2) We'e explicitly fragmenting packets with lower values 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) # Per the spec (MS_RPCE 2.2.2.11): start of the trailer should be a multiple of 16 bytes offset from the start of the stub 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.pdu_header.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.pdu_header.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
178 179 180 181 182 183 184 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 178 def process_ntlm_type2() auth3 = super = @ntlm_client.session. store_target_info(.target_info) if .has_flag?(:TARGET_INFO) @os_version = extract_os_version(.os_version.to_s) unless .os_version.empty? auth3 end |
#recv_struct(struct) ⇒ Object
Receive a packet from the remote host and parse it according to struct
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 268 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.pdu_header.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.}" end |
#send_packet(packet) ⇒ Object
Send a packet to the remote host
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 248 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.}" end nil end |