# -*- coding: binary -*- module Msf ### # # This module exposes methods for interacting with a remote RDP service # ### module Exploit::Remote::RDP require 'rc4' include Msf::Exploit::Remote::Tcp # # Creates an instance of a RDP exploit module. # def initialize(info = {}) super register_options( [ OptString.new('RDP_USER', [ false, 'The username to report during connect, UNSET = random']), OptString.new('RDP_CLIENT_NAME', [ false, 'The client computer name to report during connect, UNSET = random', 'rdesktop']), OptString.new('RDP_DOMAIN', [ false, 'The client domain name to report during connect']), OptAddress.new('RDP_CLIENT_IP', [ true, 'The client IPv4 address to report during connect', '192.168.0.100']), Opt::RPORT(3389) ], Msf::Exploit::Remote::RDP) end # used to abruptly abort scanner for a given host class RdpCommunicationError < StandardError end # # Constants # class RDPConstants SSL_REQUIRED_BY_SERVER = 1 SSL_NOT_ALLOWED_BY_SERVER = 2 SSL_CERT_NOT_ON_SERVER = 3 INCONSISTENT_FLAGS = 4 HYBRID_REQUIRED_BY_SERVER = 5 SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 6 PROTOCOL_RDP = 0 PROTOCOL_SSL = 1 PROTOCOL_HYBRID = 2 PROTOCOL_RDSTLS = 4 PROTOCOL_HYBRID_EX = 8 RDP_NEG_PROTOCOL = { 0 => "PROTOCOL_RDP", 1 => "PROTOCOL_SSL", 2 => "PROTOCOL_HYBRID", 4 => "PROTOCOL_RDSTLS", 8 => "PROTOCOL_HYBRID_EX" } RDP_NEG_FAILURE = { 1 => "SSL_REQUIRED_BY_SERVER", 2 => "SSL_NOT_ALLOWED_BY_SERVER", 3 => "SSL_CERT_NOT_ON_SERVER", 4 => "INCONSISTENT_FLAGS", 5 => "HYBRID_REQUIRED_BY_SERVER", 6 => "SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER" } REDIRECTION_SUPPORTED = 0x1 REDIRECTION_VERSION3 = 0x2 << 2 REDIRECTION_VERSION4 = 0x3 << 2 REDIRECTION_VERSION5 = 0x4 << 2 ENCRYPTION_40BIT = 0x01 ENCRYPTION_128BIT = 0x02 ENCRYPTION_56BIT = 0x08 ENCRYPTION_FIPS = 0x10 CHAN_INITIALIZED = 0x80000000 CHAN_ENCRYPT_RDP = 0x40000000 CHAN_ENCRYPT_SC = 0x20000000 CHAN_ENCRYPT_CS = 0x10000000 CHAN_PRI_HIGH = 0x08000000 CHAN_PRI_MED = 0x04000000 CHAN_PRI_LOW = 0x02000000 CHAN_COMPRESS_RDP = 0x00800000 CHAN_COMPRESS = 0x00400000 CHAN_SHOW_PROTOCOL = 0x00200000 CHAN_REMOTE_CONTROL_PERSISTENT = 0x00100000 CHAN_FLAG_FIRST = 0x01 CHAN_FLAG_LAST = 0x02 CHAN_FLAG_SHOW_PROTOCOL = 0x10 RDPDR_CTYP_CORE = 0x4472 PAKID_CORE_SERVER_ANNOUNCE = 0x496e PAKID_CORE_SERVER_CAPABILITY = 0x5350 PAKID_CORE_CLIENTID_CONFIRM = 0x4343 PAKID_CORE_CLIENT_NAME = 0x434e PAKID_CORE_DEVICELIST_ANNOUNCE = 0x4441 end def rdp_connect self.rdp_sock = connect(false) self.rdp_sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1) end def rdp_disconnect disconnect(self.rdp_sock) self.rdp_sock = nil end def rdp_send(data) self.rdp_sock.put(data) end def rdp_recv(length = -1, timeout = 5) res = self.rdp_sock.get_once(length, timeout) raise RdpCommunicationError unless res # nil due to a timeout res rescue EOFError raise RdpCommunicationError end def rdp_send_recv(data) rdp_send(data) rdp_recv end # Connect and perform fingerprinting of the RDP service # # Note: NLA is required to detect the product_version # # @return [Boolean] Is service RDP # @return [Hash] Version information def rdp_fingerprint peer_info = {} # warning: if rdp_check_protocol starts handling NLA, this will need to be updated is_rdp, server_selected_proto = rdp_check_protocol(RDPConstants::PROTOCOL_SSL | RDPConstants::PROTOCOL_HYBRID | RDPConstants::PROTOCOL_HYBRID_EX) return false, nil unless is_rdp return true, peer_info unless [RDPConstants::PROTOCOL_HYBRID, RDPConstants::PROTOCOL_HYBRID_EX].include? server_selected_proto swap_sock_plain_to_ssl ntlm_negotiate_blob = '' # see: https://fadedlab.wordpress.com/2019/06/13/using-nmap-to-extract-windows-info-from-rdp/ ntlm_negotiate_blob << "\x30\x37\xa0\x03\x02\x01\x60\xa1\x30\x30\x2e\x30\x2c\xa0\x2a\x04\x28" ntlm_negotiate_blob << "\x4e\x54\x4c\x4d\x53\x53\x50\x00" # Identifier - NTLMSSP ntlm_negotiate_blob << "\x01\x00\x00\x00" # Type: NTLMSSP Negotiate - 01 ntlm_negotiate_blob << "\xb7\x82\x08\xe2" # Flags (NEGOTIATE_SIGN_ALWAYS | NEGOTIATE_NTLM | NEGOTIATE_SIGN | REQUEST_TARGET | NEGOTIATE_UNICODE) ntlm_negotiate_blob << "\x00\x00" # DomainNameLen ntlm_negotiate_blob << "\x00\x00" # DomainNameMaxLen ntlm_negotiate_blob << "\x00\x00\x00\x00" # DomainNameBufferOffset ntlm_negotiate_blob << "\x00\x00" # WorkstationLen ntlm_negotiate_blob << "\x00\x00" # WorkstationMaxLen ntlm_negotiate_blob << "\x00\x00\x00\x00" # WorkstationBufferOffset ntlm_negotiate_blob << "\x0a" # ProductMajorVersion = 10 ntlm_negotiate_blob << "\x00" # ProductMinorVersion = 0 ntlm_negotiate_blob << "\x63\x45" # ProductBuild = 0x4563 = 17763 ntlm_negotiate_blob << "\x00\x00\x00" # Reserved ntlm_negotiate_blob << "\x0f" # NTLMRevision = 5 = NTLMSSP_REVISION_W2K3 resp = rdp_send_recv(ntlm_negotiate_blob) ntlmssp_start = resp.index('NTLMSSP') if ntlmssp_start ntlmssp = NTLM_MESSAGE::parse(resp[ntlmssp_start..-1]) version = ntlmssp.padding.bytes peer_info[:product_version] = "#{version[0]}.#{version[1]}.#{version[2] | (version[3] << 8)}" end return is_rdp, peer_info end def rdp_dispatch_loop while rdp_sock do rdp_handle_packet(rdp_recv) end end def rdp_create_channel_msg(chan_user_id, chan_id, data, flags = 3, data_length = nil) data_length ||= data.length pdu = [ [25 << 2].pack('C'), # MCS send data request structure, choice 25 [self.rdp_user_id, chan_id].pack('S>S>'), # MCS send data request structure, choice 25 "\x70", # Wut (security header) per_data( [data_length].pack(' 'C', 2 => 'S', 4 => 'L' }[num_chars] [int_val].pack(pack_code) else a = (0..(num_chars)).map{ |i| (( int_val >> i*8 ) & 0xFF ).chr }.join a[0..-2] # seems legit lol end end def bin_to_hex(str_val) str_val.each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join end # # Protocol Data Unit definitions and helpers # # Build the X.224 packet, encrypt with Standard RDP Security as needed # default channel_id = 0x03eb = 1003 def rdp_build_pkt(data, channel_id = "\x03\xeb", client_info = false) flags = 0 flags |= 0b1000 if @rdp_sec # Set SEC_ENCRYPT flags |= 0b1000000 if client_info # Set SEC_INFO_PKT pdu = "" # TS_SECURITY_HEADER - 2.2.8.1.1.2.1 # Send when the packet is encrypted w/ Standard RDP Security and in all Client Info PDUs if client_info || @rdp_sec pdu << [flags].pack("S<") # flags "\x48\x00" = SEC_INFO_PKT | SEC_ENCRYPT pdu << "\x00\x00" # flagsHi end if @rdp_sec # Encrypt the payload with RDP Standard Encryption pdu << rdp_hmac(@hmackey, data)[0..7] pdu << rdp_rc4_crypt(@rc4enckey, data) else pdu << data end user_data_len = pdu.length udl_with_flag = 0x8000 | user_data_len pkt = "\x64" # sendDataRequest pkt << "\x00\x08" # intiator userId .. TODO: for a functional client this isn't static pkt << channel_id # channelId pkt << "\x70" # dataPriority pkt << [udl_with_flag].pack("S>") pkt << pdu build_data_tpdu(pkt) end # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/6c074267-1b32-4ceb-9496-2eb941a23e6b # Virtual Channel PDU 2.2.6.1 def build_virtual_channel_pdu(flags, data) data_len = data.length [data_len].pack("L<") + # length [flags].pack("L<") + # flags data end # Builds x.224 Data (DT) TPDU - Section 13.7 def build_data_tpdu(data) tpkt_length = data.length + 7 "\x03\x00" + # TPKT Header version 03, reserved 0 [tpkt_length].pack("S>") + # TPKT length "\x02\xf0\x80" + # X.224 Data TPDU (2 bytes: 0xf0 = Data TPDU, 0x80 = EOT, end of transmission) data end # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/18a27ef9-6f9a-4501-b000-94b1fe3c2c10 # Client X.224 Connect Request PDU - 2.2.1.1 def pdu_negotiation_request(user_name = "", requested_protocols = 0) # Blank username is ok, nil = random user_name = Rex::Text.rand_text_alpha(12) if user_name.nil? tpkt_len = user_name.length + 38 x224_len = user_name.length + 33 "\x03\x00" + # TPKT Header version 03, reserved 0 [tpkt_len].pack("S>") + # TPKT length: 43 [x224_len].pack("C") + # X.224 LengthIndicator "\xe0" + # X.224 Type: Connect Request "\x00\x00" + # dst reference "\x00\x00" + # src reference "\x00" + # class and options # cookie - literal 'Cookie: mstshash=' "\x43\x6f\x6f\x6b\x69\x65\x3a\x20\x6d\x73\x74\x73\x68\x61\x73\x68\x3d" + user_name + # Identifier "username" "\x0d\x0a" + # cookie terminator "\x01\x00" + # Type: RDP Negotiation Request ( 0x01 ) "\x08\x00" + # Length [requested_protocols].pack('L<') # requestedProtocols end # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/db6713ee-1c0e-4064-a3b3-0fac30b4037b def pdu_connect_initial(channels, selected_proto = 0, host_name = "rdesktop") # After negotiating TLS or NLA the connectInitial packet needs to include the # protocol selection that the server indicated in its Negotiation Response pdu = [ "\x7f\x65", # T.125 Connect-Initial (BER: Application 101) ber_data( "\x04\x01\x01", # CallingDomainSelector: 1 (BER: OctetString) "\x04\x01\x01", # CalledDomainSelector: 1 (BER: OctetString) "\x01\x01\xff", # UpwaredFlag: True (BER: boolean) # TargetParamenters encode_domain_selector( max_chan_ids: 0x22, max_user_ids: 0x2 ), # MinimumParameters encode_domain_selector( max_chan_ids: 0x1, max_user_ids: 0x1, max_token_ids: 0x1, max_mcspdu_size: 0x0420 ), # MaximumParameters encode_domain_selector( max_chan_ids: 0xffff, max_user_ids: 0xfc17, max_token_ids: 0xffff ), # UserData ber_octet_string( # T.124 GCC Connection Data (ConnectData)- PER Encoding used per_object(oid(0, 0, 20, 124, 0, 1)), per_data( conf_create_req(), per_data( cs_core_data(client_name: host_name, selected_proto: selected_proto), cs_cluster_data(), cs_security_data(), cs_network_data(channels) ) ) ) ) ].join('') build_data_tpdu(pdu) end # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/04c60697-0d9a-4afd-a0cd-2cc133151a9c # Client MCS Erect Domain Request PDU - 2.2.1.5 def pdu_erect_domain_request pdu = "\x04" + # T.125 ErectDomainRequest "\x01\x00" + # subHeight - length 1, value 0 "\x01\x00" # subInterval - length 1, value 0 build_data_tpdu(pdu) end # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/f5d6a541-9b36-4100-b78f-18710f39f247\ # Client MCS Attach User Request PDU - 2.2.1.6 def pdu_attach_user_request pdu = "\x28" # T.125 AttachUserRequest build_data_tpdu(pdu) end # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/64564639-3b2d-4d2c-ae77-1105b4cc011b # Client MCS Channel Join Request PDU -2.2.1.8 def pdu_channel_join_request(user1, channel_id) pdu = "\x38" + # T.125 ChannelJoinRequest [user1, channel_id].pack("nn") build_data_tpdu(pdu) end # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/9cde84cd-5055-475a-ac8b-704db419b66f # Client Security Exchange PDU - 2.2.1.10 def pdu_security_exchange(rcran, rsexp, rsmod, bitlen) encrypted_rcran_bignum = rsa_encrypt(rcran, rsexp, rsmod) encrypted_rcran = int_to_bytestring(encrypted_rcran_bignum) bitlen += 8 # Pad with size of TS_SECURITY_PACKET header userdata_length = 8 + bitlen userdata_length_low = userdata_length & 0xFF userdata_length_high = userdata_length / 256 flags = 0x80 | userdata_length_high pdu = "\x64" + # T.125 sendDataRequest "\x00\x08" + # intiator userId "\x03\xeb" + # channelId = 1003 "\x70" + # dataPriority = high, segmentation = begin | end [flags].pack("C") + [userdata_length_low].pack("C") + # UserData length # TS_SECURITY_PACKET - 2.2.1.10.1 "\x01\x00" + # securityHeader flags "\x00\x00" + # securityHeader flagsHi [bitlen].pack("L<") + # TS_ length encrypted_rcran + # encryptedClientRandom - 64 bytes "\x00\x00\x00\x00\x00\x00\x00\x00" # 8 bytes rear padding (always present) build_data_tpdu(pdu) end # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/772d618e-b7d6-4cd0-b735-fa08af558f9d # TS_INFO_PACKET - 2.2.1.11.1.1 def pdu_client_info(user_name, domain_name = "", ip_address = "") # Max len for 4.0/6.0 servers is 44 bytes including terminator # Max len for all other versions is 512 including terminator # We're going to limit to 44 (21 chars + null -> unicode) here. # Blank username is ok, nil = random user_name = Rex::Text.rand_text_alpha(10) if user_name.nil? user_unicode = Rex::Text.to_unicode(user_name[0..20], 'utf-16le') uname_len = user_unicode.length # Domain can can be, and for rdesktop typically is, empty. # Max len for 4.0/5.0 servers is 52 including terminator # Max len for all other versions is 512 including terminator # We're going to limit to 52 (25 chars + null -> unicode) here. domain_unicode = Rex::Text.to_unicode(domain_name[0..24], 'utf-16le') domain_len = domain_unicode.length # This address value is primarily used to reduce the fields by which this # module can be fingerprinted. It doesn't show up in Windows logs. # clientAddress + null terminator ip_unicode = Rex::Text.to_unicode(ip_address, 'utf-16le') + "\x00\x00" ip_len = ip_unicode.length "\x00\x00\x00\x00" + # CodePage "\x33\x01\x00\x00" + # flags - INFO_MOUSE, INFO_DISABLECTRLALTDEL, INFO_UNICODE, INFO_MAXIMIZESHELL, INFO_ENABLEWINDOWSKEY [domain_len].pack("S<") + # cbDomain (length value) - EXCLUDES null terminator [uname_len].pack("S<") + # cbUserName (length value) - EXCLUDES null terminator "\x00\x00" + # cbPassword (length value) "\x00\x00" + # cbAlternateShell (length value) "\x00\x00" + # cbWorkingDir (length value) [domain_unicode].pack("a*") + # Domain "\x00\x00" + # Domain null terminator, EXCLUDED from value of cbDomain [user_unicode].pack("a*") + # UserName "\x00\x00" + # UserName null terminator, EXCLUDED FROM value of cbUserName "\x00\x00" + # Password - empty "\x00\x00" + # AlternateShell - empty "\x00\x00" + # WorkingDir - empty # TS_EXTENDED_INFO_PACKET - 2.2.1.11.1.1.1 "\x02\x00" + # clientAddressFamily - AF_INET - FIXFIX - detect and set dynamically [ip_len].pack("S<") + # cbClientAddress (length value) - INCLUDES terminator ... for reasons. [ip_unicode].pack("a*") + # clientAddress (unicode + null terminator (unicode) "\x3c\x00" + # cbClientDir (length value): 60 # clientDir - 'C:\WINNT\System32\mstscax.dll' + null terminator "\x3c\x00\x43\x00\x3a\x00\x5c\x00\x57\x00\x49\x00\x4e\x00\x4e\x00" + # "\x54\x00\x5c\x00\x53\x00\x79\x00\x73\x00\x74\x00\x65\x00\x6d\x00" + # "\x33\x00\x32\x00\x5c\x00\x6d\x00\x73\x00\x74\x00\x73\x00\x63\x00" + # "\x61\x00\x78\x00\x2e\x00\x64\x00\x6c\x00\x6c\x00\x00\x00" + # # clientTimeZone - TS_TIME_ZONE struct - 172 bytes # These are the default values for rdesktop "\xa4\x01\x00\x00" + # Bias # StandardName - 'GTB,normaltid' "\x47\x00\x54\x00\x42\x00\x2c\x00\x20\x00\x6e\x00\x6f\x00\x72\x00" + # "\x6d\x00\x61\x00\x6c\x00\x74\x00\x69\x00\x64\x00\x00\x00\x00\x00" + # "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # "\x00\x00\x0a\x00\x00\x00\x05\x00\x03\x00\x00\x00\x00\x00\x00\x00" + # StandardDate - Oct 5 "\x00\x00\x00\x00" + # StandardBias # DaylightName - 'GTB,sommartid' "\x47\x00\x54\x00\x42\x00\x2c\x00\x20\x00\x73\x00\x6f\x00\x6d\x00" + # "\x6d\x00\x61\x00\x72\x00\x74\x00\x69\x00\x64\x00\x00\x00\x00\x00" + # "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # "\x00\x00\x03\x00\x00\x00\x05\x00\x02\x00\x00\x00\x00\x00\x00\x00" + # DaylightDate - Mar 3 "\xc4\xff\xff\xff" + # DaylightBias "\x00\x00\x00\x00" + # clientSessionId "\x27\x00\x00\x00" + # performanceFlags "\x00\x00" # cbAutoReconnectCookie end # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/73d01865-2eae-407f-9b2c-87e31daac471 # Share Control Header - TS_SHARECONTROLHEADER - 2.2.8.1.1.1.1 def build_share_control_header(type, data) total_len = data.length + 6 [total_len].pack("S<") + # totalLength - includes all headers [type].pack("S<") + # pduType - flags 16 bit, unsigned "\xf1\x03" + # PDUSource: 0x03f1 = 1009 data end # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/4b5d4c0d-a657-41e9-9c69-d58632f46d31 # Share Data Header - TS_SHAREDATAHEADER - 2.2.8.1.1.1.2 def build_share_data_header(type, data) uncompressed_len = data.length + 4 "\xea\x03\x01\x00" + # shareId: 66538 "\x00" + # pad1 "\x01" + # streamID: 1 [uncompressed_len].pack("S<") + # uncompressedLength - 16 bit, unsigned int [type].pack("C") + # pduType2 - 8 bit, unsigned int - 2.2.8.1.1.2 "\x00" + # compressedType: 0 "\x00\x00" + # compressedLength: 0 data end # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/9d1e1e21-d8b4-4bfd-9caf-4b72ee91a7135 # Control Cooperate - TC_CONTROL_PDU 2.2.1.15 def pdu_client_control_cooperate pdu = "\x04\x00" + # action: 4 - CTRLACTION_COOPERATE "\x00\x00" + # grantId: 0 "\x00\x00\x00\x00" # controlId: 0 # pduType2 = 0x14 = 20 - PDUTYPE2_CONTROL data_header = build_share_data_header(0x14, pdu) # type = 0x17 = TS_PROTOCOL_VERSION | PDUTYPE_DATAPDU build_share_control_header(0x17, data_header) end # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/4f94e123-970b-4242-8cf6-39820d8e3d35 # Control Request - TC_CONTROL_PDU 2.2.1.16 def pdu_client_control_request pdu = "\x01\x00" + # action: 1 - CTRLACTION_REQUEST_CONTROL "\x00\x00" + # grantId: 0 "\x00\x00\x00\x00" # controlId: 0 # pduType2 = 0x14 = 20 - PDUTYPE2_CONTROL data_header = build_share_data_header(0x14, pdu) # type = 0x17 = TS_PROTOCOL_VERSION | PDUTYPE_DATAPDU build_share_control_header(0x17, data_header) end # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/7067da0d-e318-4464-88e8-b11509cf0bd9 # Client Font List - TS_FONT_LIST_PDU - 2.2.1.18 def pdu_client_font_list pdu = "\x00\x00" + # numberFonts: 0 "\x00\x00" + # totalNumberFonts: 0 "\x03\x00" + # listFlags: 3 (FONTLIST_FIRST | FONTLIST_LAST) "\x32\x00" # entrySize: 50 # pduType2 = 0x27 = 29 - PDUTYPE2_FONTLIST data_header = build_share_data_header(0x27, pdu) # type = 0x17 = TS_PROTOCOL_VERSION | PDUTYPE_DATAPDU build_share_control_header(0x17, data_header) end # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/5186005a-36f5-4f5d-8c06-968f28e2d992 # Client Synchronize - TS_SYNCHRONIZE_PDU - 2.2.1.19 / 2.2.14.1 def pdu_client_synchronize(target_user = 0) pdu = "\x01\x00" + # messageType: 1 SYNCMSGTYPE_SYNC [target_user].pack("S<") # targetUser, 16 bit, unsigned. # pduType2 = 0x1f = 31 - PDUTYPE2_SCYNCHRONIZE data_header = build_share_data_header(0x1f, pdu) # type = 0x17 = TS_PROTOCOL_VERSION | PDUTYPE_DATAPDU build_share_control_header(0x17, data_header) end # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/4e9722c3-ad83-43f5-af5a-529f73d88b48 # Confirm Active PDU Data - TS_CONFIRM_ACTIVE_PDU - 2.2.1.13.2.1 def pdu_client_confirm_active pdu = "\xea\x03\x01\x00" + # shareId: 66538 "\xea\x03" + # originatorId "\x06\x00" + # lengthSourceDescriptor: 6 "\x8e\x01" + # lengthCombinedCapabilities: 398 "\x4d\x53\x54\x53\x43\x00" + # SourceDescriptor: 'MSTSC' "\x0e\x00" + # numberCapabilities: 14 "\x00\x00" + # pad2Octets "\x01\x00" + # capabilitySetType: 1 - TS_GENERAL_CAPABILITYSET "\x18\x00" + # lengthCapability: 24 "\x01\x00\x03\x00\x00\x02\x00\x00\x00\x00\x0d\x04\x00\x00\x00\x00" + # "\x00\x00\x00\x00" + # "\x02\x00" + # capabilitySetType: 2 - TS_BITMAP_CAPABILITYSET "\x1c\x00" + # lengthCapability: 28 "\x10\x00\x01\x00\x01\x00\x01\x00\x20\x03\x58\x02\x00\x00\x01\x00" + # "\x01\x00\x00\x00\x01\x00\x00\x00" + # "\x03\x00" + # capabilitySetType: 3 - TS_ORDER_CAPABILITYSET "\x58\x00" + # lengthCapability: 88 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # "\x00\x00\x00\x00\x01\x00\x14\x00\x00\x00\x01\x00\x47\x01\x2a\x00" + # "\x01\x01\x01\x01\x00\x00\x00\x00\x01\x01\x01\x01\x00\x01\x01\x00" + # "\x00\x00\x00\x00\x01\x01\x01\x00\x00\x01\x01\x01\x00\x00\x00\x00" + # "\xa1\x06\x00\x00\x00\x00\x00\x00\x00\x84\x03\x00\x00\x00\x00\x00" + # "\xe4\x04\x00\x00\x13\x00\x28\x00\x00\x00\x00\x03\x78\x00\x00\x00" + # "\x78\x00\x00\x00\x50\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # "\x08\x00" + # capabilitySetType: 8 - TS_POINTER_CAPABILITYSET "\x0a\x00" + # lengthCapability: 10 "\x01\x00\x14\x00\x14\x00" + # "\x0a\x00" + # capabilitySetType: 10 - TS_COLORTABLE_CAPABILITYSET "\x08\x00" + # lengthCapability: 8 "\x06\x00\x00\x00" + # "\x07\x00" + # capabilitySetType: 7 - TSWINDOWACTIVATION_CAPABILITYSET "\x0c\x00" + # lengthCapability: 12 "\x00\x00\x00\x00\x00\x00\x00\x00" + # "\x05\x00" + # capabilitySetType: 5 - TS_CONTROL_CAPABILITYSET "\x0c\x00" + # lengthCapability: 12 "\x00\x00\x00\x00\x02\x00\x02\x00" + # "\x09\x00" + # capabilitySetType: 9 - TS_SHARE_CAPABILITYSET "\x08\x00" + # lengthCapability: 8 "\x00\x00\x00\x00" + # "\x0f\x00" + # capabilitySetType: 15 - TS_BRUSH_CAPABILITYSET "\x08\x00" + # lengthCapability: 8 "\x01\x00\x00\x00" + # "\x0d\x00" + # capabilitySetType: 13 - TS_INPUT_CAPABILITYSET "\x58\x00" + # lengthCapability: 88 "\x01\x00\x00\x00\x09\x04\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00" + # "\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + # "\x00\x00\x00\x00" + # "\x0c\x00" + # capabilitySetType: 12 - TS_SOUND_CAPABILITYSET "\x08\x00" + # lengthCapability: 8 "\x01\x00\x00\x00" + # "\x0e\x00" + # capabilitySetType: 14 - TS_FONT_CAPABILITYSET "\x08\x00" + # lengthCapability: 8 "\x01\x00\x00\x00" + # "\x10\x00" + # capabilitySetType: 16 - TS_GLYPHCAChE_CAPABILITYSET "\x34\x00" + # lengthCapability: 52 "\xfe\x00\x04\x00\xfe\x00\x04\x00\xfe\x00\x08\x00\xfe\x00\x08\x00" + # "\xfe\x00\x10\x00\xfe\x00\x20\x00\xfe\x00\x40\x00\xfe\x00\x80\x00" + # "\xfe\x00\x00\x01\x40\x00\x00\x08\x00\x01\x00\x01\x02\x00\x00\x00" # type = 0x13 = TS_PROTOCOL_VERSION | PDUTYPE_CONFIRMACTIVEPDU build_share_control_header(0x13, pdu) end # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/ff7f06f8-0dcf-4c8d-be1f-596ae60c4396 # Client Input Event Data - TS_INPUT_PDU_DATA - 2.2.8.1.1.3.1 def pdu_client_input_event_sychronize pdu = "\x01\x00" + # numEvents: 1 "\x00\x00" + # pad2Octets "\x00\x00\x00\x00" + # eventTime "\x00\x00" + # messageType: 0 - INPUT_EVENT_SYNC # TS_SYNC_EVENT 202.8.1.1.3.1.1.5 "\x00\x00" + # pad2Octets "\x00\x00\x00\x00" # toggleFlags # pduType2 = 0x1c = 28 - PDUTYPE2_INPUT data_header = build_share_data_header(0x1c, pdu) # type = 0x17 = TS_PROTOCOL_VERSION | PDUTYPE_DATAPDU build_share_control_header(0x17, data_header) end # # Non-RDP protocol helper methods # # Create a new SSL session on the existing socket. # Stolen from exploit/smtp_deliver.rb def swap_sock_plain_to_ssl ctx = OpenSSL::SSL::SSLContext.new ctx.min_version = OpenSSL::SSL::TLS1_VERSION ssl = OpenSSL::SSL::SSLSocket.new(self.rdp_sock, ctx) ssl.connect self.rdp_sock.extend(Rex::Socket::SslTcp) self.rdp_sock.sslsock = ssl self.rdp_sock.sslctx = ctx end protected def encode_domain_selector( max_chan_ids: 0, max_user_ids: 0, max_token_ids: 0, num_priorities: 1, min_throughput: 0, max_height: 1, max_mcspdu_size: 65535, protocol_ver: 2 ) body = [ ber_int(max_chan_ids), ber_int(max_user_ids), ber_int(max_token_ids), ber_int(num_priorities), ber_int(min_throughput), ber_int(max_height), ber_int(max_mcspdu_size), ber_int(protocol_ver) ].join('') result = [ "\x30", [body.length].pack('C'), body ].join('') result end def per_object(*ds) body = ds.join('') result = [ "\x00", [body.length].pack('C'), body ].join('') result end def per_data(*ds) data = ds.join('') result = '' if data.length < 0x4000 result = [data.length | 0x8000].pack('S>') + data else result = "\xA2" + [data.length].pack('S>') + data end result end def cs_cluster_data( flags: RDPConstants::REDIRECTION_SUPPORTED | RDPConstants::REDIRECTION_VERSION3, session_id: 0 ) body = [flags, session_id].pack(' 0 b5 = 0x40 b5 |= 0x80 if user_data_sets > 0 # TODO: add more flags here [ "\x00", [b2].pack('C'), "\x00\x10\x00", [user_data_sets].pack('C'), [b5].pack('C'), "\x00", [h221_key.encode('ASCII')].pack('a*') ].join('') end def oid(itut, rec, t, t124, ver, desc) [(itut << 8) | rec, t, t124, ver, desc].pack('C*') end def ber_octet_string(*ds) result = [ "\x04", ber_data(ds) ].join('') result end def ber_data(*ds) data = ds.join('') result = [ "\x82", [data.length].pack('S>'), data ].join('') result end def ber_int(i) d = '' if i < (2 ** 8) d = [i].pack('C') elsif i < (2 ** 16) d = [i].pack('S>') else d = [i].pack('L>') end "\x02" + [d.length].pack('C') + d end def rdp_handle_packet(pkt) if pkt && pkt[0] == "\x03" if pkt[4..6] == "\x02\xf0\x80" if pkt[7] == "\x68" chan_user_id = pkt[8..9].unpack('S>')[0] chan_id = pkt[10..11].unpack('S>')[0] flags = pkt[18..21].unpack('