Class: RubySMB::Dcerpc::Client
- Inherits:
-
Object
- Object
- RubySMB::Dcerpc::Client
- Includes:
- RubySMB::Dcerpc, Epm, PeerInfo, Utils
- 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 Utils
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.
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 138 139 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 105 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 = RubySMB::Utils.safe_encode(username, 'utf-8') @password = RubySMB::Utils.safe_encode(password, 'utf-8') @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
59 60 61 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 59 def default_domain @default_domain end |
#default_name ⇒ String
54 55 56 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 54 def default_name @default_name end |
#dns_domain_name ⇒ String
69 70 71 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 69 def dns_domain_name @dns_domain_name end |
#dns_host_name ⇒ String
64 65 66 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 64 def dns_host_name @dns_host_name end |
#dns_tree_name ⇒ String
74 75 76 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 74 def dns_tree_name @dns_tree_name end |
#domain ⇒ String
29 30 31 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 29 def domain @domain end |
#local_workstation ⇒ String
34 35 36 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 34 def local_workstation @local_workstation end |
#max_buffer_size ⇒ Integer
85 86 87 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 85 def max_buffer_size @max_buffer_size end |
#ntlm_client ⇒ String
39 40 41 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 39 def ntlm_client @ntlm_client end |
#os_version ⇒ String
79 80 81 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 79 def os_version @os_version end |
#password ⇒ String
49 50 51 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 49 def password @password end |
#tcp_socket ⇒ TcpSocket
90 91 92 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 90 def tcp_socket @tcp_socket end |
#username ⇒ String
44 45 46 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 44 def username @username end |
Instance Method Details
#close ⇒ Object
Close the TCP Socket
176 177 178 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 176 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
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 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 149 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.
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 243 244 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 194 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
180 181 182 183 184 185 186 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 180 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
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 270 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
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/ruby_smb/dcerpc/client.rb', line 250 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 |