2019-08-07 07:25:42 -05:00
# -*- coding: binary -*-
module Msf
###
#
# This module exposes methods for interacting with a remote RDP service
#
###
module Exploit::Remote::RDP
2019-08-07 14:47:33 -05:00
require 'rc4'
include Msf :: Exploit :: Remote :: Tcp
2019-08-07 07:25:42 -05:00
#
# 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 )
2019-09-07 17:19:59 +02:00
register_advanced_options (
[
2019-09-07 18:39:59 +02:00
OptInt . new ( 'RDP_TLS_SECURITY_LEVEL' , [ true , 'Change default TLS security level. "0" (default) means everything is permitted. "1" rejects very weak parameters and "2" is even stricter.' , 0 ] )
2019-09-07 17:19:59 +02:00
] , Msf :: Exploit :: Remote :: RDP )
2019-08-07 07:25:42 -05:00
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 "
}
2019-08-09 14:49:42 +10:00
REDIRECTION_SUPPORTED = 0x1
REDIRECTION_VERSION3 = 0x2 << 2
REDIRECTION_VERSION4 = 0x3 << 2
REDIRECTION_VERSION5 = 0x4 << 2
2019-08-22 09:50:50 +10:00
ENCRYPTION_40BIT = 0x01
ENCRYPTION_128BIT = 0x02
ENCRYPTION_56BIT = 0x08
ENCRYPTION_FIPS = 0x10
2019-09-19 06:09:41 -05:00
LICENSE_REQUEST = 0x01
LICENSE_PLATFORM_CHALLENGE = 0x02
LICENSE_NEW_LICENSE = 0x03
LICENSE_UPGRADE_LICENSE = 0x04
LICENSE_LICENSE_INFO = 0x12
LICENSE_NEW_LICENSE_REQ = 0x13
LICENSE_PLATFORM_CHAL_RESP = 0x15
LICENSE_ERROR_ALERT = 0xff
LICENSE_TAGS = {
0x01 = > " LICENSE_REQUEST " ,
0x02 = > " LICENSE_PLATFORM_CHALLENGE " ,
0x03 = > " LICENSE_NEW_LICENSE " ,
0x04 = > " LICENSE_UPGRADE_LICENSE " ,
0x12 = > " LICENSE_LICENSE_INFO " ,
0x13 = > " LICENSE_NEW_LICENSE_REQ " ,
0x15 = > " LICENSE_PLATFORM_CHAL_RESP " ,
0xff = > " LICENSE_ERROR_ALERT "
}
2019-09-20 08:15:07 -05:00
LICENSE_ERR_INVALID_SCOPE = 0x04
LICENSE_ERR_NO_LICENSE_SERVER = 0x06
LICENSE_ERR_LICENSE_ISSUED = 0x07
LICENSE_ERR_INVALID_CLIENT = 0x08
LICENSE_ERR_INVALID_PRODUCTID = 0x0b
LICENSE_ERR_INVALID_MSG_LEN = 0x0c
LICENSE_ERRS = {
0x04 = > " INVALID_SCOPE " ,
0x06 = > " NO_LICENSE_SERVER " ,
0x07 = > " LICENSE_ISSUED " ,
0x08 = > " INVALID_CLIENT " ,
0x0b = > " INVALID_PRODUCTID " ,
0x0c = > " INVALID_MSG_LEN "
}
2019-08-22 09:50:50 +10:00
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
2019-08-09 14:49:42 +10:00
CHAN_REMOTE_CONTROL_PERSISTENT = 0x00100000
2019-08-07 07:25:42 -05:00
2019-08-22 09:50:50 +10:00
CHAN_FLAG_FIRST = 0x01
CHAN_FLAG_LAST = 0x02
CHAN_FLAG_SHOW_PROTOCOL = 0x10
2019-08-14 10:54:21 +10:00
RDPDR_CTYP_CORE = 0x4472
2019-08-22 09:50:50 +10:00
PAKID_CORE_SERVER_ANNOUNCE = 0x496e
PAKID_CORE_SERVER_CAPABILITY = 0x5350
PAKID_CORE_CLIENTID_CONFIRM = 0x4343
PAKID_CORE_CLIENT_NAME = 0x434e
2019-08-14 10:54:21 +10:00
PAKID_CORE_DEVICELIST_ANNOUNCE = 0x4441
2019-08-09 14:49:42 +10:00
end
2019-08-07 07:25:42 -05:00
2019-08-08 12:00:50 +10:00
def rdp_connect
self . rdp_sock = connect ( false )
self . rdp_sock . setsockopt ( :: Socket :: IPPROTO_TCP , :: Socket :: TCP_NODELAY , 1 )
end
2019-08-14 10:54:21 +10:00
2019-08-08 12:00:50 +10:00
def rdp_disconnect
disconnect ( self . rdp_sock )
self . rdp_sock = nil
end
2019-08-07 07:25:42 -05:00
def rdp_send ( data )
2019-08-08 12:00:50 +10:00
self . rdp_sock . put ( data )
2019-08-07 07:25:42 -05:00
end
2019-08-08 12:00:50 +10:00
def rdp_recv ( length = - 1 , timeout = 5 )
res = self . rdp_sock . get_once ( length , timeout )
2019-08-07 07:25:42 -05:00
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
2019-08-11 15:50:53 -07:00
# 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
2019-08-23 21:31:33 -05:00
def rdp_fingerprint
2019-08-11 15:50:53 -07:00
peer_info = { }
# warning: if rdp_check_protocol starts handling NLA, this will need to be updated
2019-08-11 01:24:55 -07:00
is_rdp , server_selected_proto = rdp_check_protocol ( RDPConstants :: PROTOCOL_SSL | RDPConstants :: PROTOCOL_HYBRID | RDPConstants :: PROTOCOL_HYBRID_EX )
return false , nil unless is_rdp
2019-08-11 19:23:29 -07:00
return true , peer_info unless [ RDPConstants :: PROTOCOL_HYBRID , RDPConstants :: PROTOCOL_HYBRID_EX ] . include? server_selected_proto
2019-08-11 01:24:55 -07:00
2019-08-23 21:31:33 -05:00
swap_sock_plain_to_ssl
2019-08-11 01:24:55 -07:00
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 )
2019-08-11 19:23:29 -07:00
ntlmssp_start = resp . index ( 'NTLMSSP' )
if ntlmssp_start
2021-09-22 12:09:19 +10:00
message = Net :: NTLM :: Message . parse ( resp [ ntlmssp_start .. - 1 ] )
version = message . os_version . bytes
ti = Net :: NTLM :: TargetInfo . new ( message . target_info )
peer_info [ :nb_name ] = ti . av_pairs [ Net :: NTLM :: TargetInfo :: MSV_AV_NB_COMPUTER_NAME ]
peer_info [ :nb_domain ] = ti . av_pairs [ Net :: NTLM :: TargetInfo :: MSV_AV_NB_DOMAIN_NAME ]
peer_info [ :dns_server ] = ti . av_pairs [ Net :: NTLM :: TargetInfo :: MSV_AV_DNS_COMPUTER_NAME ]
peer_info [ :dns_domain ] = ti . av_pairs [ Net :: NTLM :: TargetInfo :: MSV_AV_DNS_DOMAIN_NAME ]
2019-08-11 19:23:29 -07:00
peer_info [ :product_version ] = " #{ version [ 0 ] } . #{ version [ 1 ] } . #{ version [ 2 ] | ( version [ 3 ] << 8 ) } "
end
2019-08-11 01:24:55 -07:00
2019-08-11 15:50:53 -07:00
return is_rdp , peer_info
2019-08-11 01:24:55 -07:00
end
2019-08-14 10:54:21 +10:00
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 (
2023-07-14 12:46:26 +01:00
[ data_length ] . pack ( 'L<' ) ,
[ flags ] . pack ( 'L<' ) ,
2019-08-14 10:54:21 +10:00
data
)
] . join ( '' )
build_data_tpdu ( pdu )
end
def rdp_send_channel ( chan_user_id , chan_id , data , flags = 3 , data_length = nil )
tpkt = rdp_create_channel_msg ( chan_user_id , chan_id , data , flags , data_length )
rdp_send ( tpkt )
end
def rdp_terminate
body = " \x21 \x80 " # user requested disconnect provider ultimatum
rdp_send ( build_data_tpdu ( body ) )
end
2019-08-07 08:27:15 -05:00
# Connect and detect security protocol
2019-08-07 07:25:42 -05:00
#
# Note: NLA is detected but not supported yet
#
# @return [Boolean] Is service RDP
# @return [RDPConstants] Protocol supported
2019-08-11 01:24:55 -07:00
def rdp_check_protocol ( req_proto = RDPConstants :: PROTOCOL_SSL )
2019-08-07 07:25:42 -05:00
if datastore [ 'RDP_USER' ]
@user_name = datastore [ 'RDP_USER' ]
else
@user_name = Rex :: Text . rand_text_alpha ( 7 )
end
if datastore [ 'RDP_DOMAIN' ]
@domain = datastore [ 'RDP_DOMAIN' ]
else
@domain = Rex :: Text . rand_text_alpha ( 7 )
end
if datastore [ 'RDP_CLIENT_NAME' ]
@computer_name = datastore [ 'RDP_CLIENT_NAME' ]
else
@computer_name = Rex :: Text . rand_text_alpha ( 15 )
end
@ip_address = datastore [ 'RDP_CLIENT_IP' ]
# code to check if RDP is open or not
vprint_status ( " Verifying RDP protocol... " )
vprint_status ( " Attempting to connect using TLS security " )
2019-08-11 01:24:55 -07:00
res = rdp_send_recv ( pdu_negotiation_request ( @user_name , req_proto ) )
2019-08-07 07:25:42 -05:00
# return true if the response is a X.224 Connect Confirm
# We can't use a check for RDP Negotiation Response because WinXP excludes it
if res
result , err_msg = rdp_parse_negotiation_response ( res )
return true , result if result
2019-08-07 08:27:15 -05:00
# No current support for NLA, nothing to do here
2019-08-07 07:25:42 -05:00
return true , RDPConstants :: PROTOCOL_HYBRID if err_msg == 'HYBRID_REQUIRED_BY_SERVER'
if err_msg == " Negotiation Response packet too short. "
vprint_status ( " Attempt to connect with TLS failed but looks like the target is Windows XP " )
else
vprint_status ( " Attempt to connect with TLS failed with error: #{ err_msg } " )
end
if [ " SSL_NOT_ALLOWED_BY_SERVER " , " Negotiation Response packet too short. " ] . include? err_msg
# This happens if the server is configured to ONLY permit RDP Security
vprint_status ( " Attempting to connect using Standard RDP security " )
2019-08-30 18:00:57 -04:00
rdp_disconnect
rdp_connect
2019-08-07 07:25:42 -05:00
res = rdp_send_recv ( pdu_negotiation_request ( @user_name , RDPConstants :: PROTOCOL_RDP ) )
if res
result , err_msg = rdp_parse_negotiation_response ( res )
return true , result if result
# Windows XP doesn't return the standard Negotiation Response packet
# but we at least know this was RDP since the packet contained a
# Connect-Confirm response (0xd0).
if err_msg == " Negotiation Response packet too short. "
return true , RDPConstants :: PROTOCOL_RDP
end
vprint_status ( " Attempt to connect with Standard RDP failed with error #{ err_msg } " )
end
end
end
return false , 0
end
2019-08-07 08:50:01 -05:00
# Negotiate security protocol and begin session building
2019-08-07 08:27:15 -05:00
#
# @return [Boolean] success
2019-08-09 14:49:42 +10:00
def rdp_negotiate_security ( channels , req_proto = RDPConstants :: PROTOCOL_SSL )
2019-08-07 08:27:15 -05:00
if req_proto == RDPConstants :: PROTOCOL_SSL
2019-08-08 12:00:50 +10:00
swap_sock_plain_to_ssl
2019-08-09 14:49:42 +10:00
res = rdp_send_recv ( pdu_connect_initial ( channels , req_proto , @computer_name ) )
2019-08-07 08:27:15 -05:00
elsif req_proto == RDPConstants :: PROTOCOL_RDP
2019-08-09 14:49:42 +10:00
res = rdp_send_recv ( pdu_connect_initial ( channels , req_proto , @computer_name ) )
2019-08-07 08:27:15 -05:00
rsmod , rsexp , _rsran , server_rand , bitlen = rdp_parse_connect_response ( res )
2019-08-14 10:54:21 +10:00
elsif [ RDPConstants :: PROTOCOL_HYBRID , RDPConstants :: PROTOCOL_HYBRID_EX ] . include? ( req_proto )
2019-08-07 08:27:15 -05:00
vprint_status ( " NLA Security protocol unsupported at this time. " )
return false
else
vprint_error ( " Unknown protocol requested ( #{ req_proto } ). " )
return false
end
2019-08-07 08:50:01 -05:00
# erect domain and attach user
vprint_status ( " Sending erect domain request " )
rdp_send ( pdu_erect_domain_request )
res = rdp_send_recv ( pdu_attach_user_request )
2019-08-14 10:54:21 +10:00
self . rdp_user_id = res [ 9 , 2 ] . unpack ( " n " ) . first
2019-08-07 08:50:01 -05:00
# send channel requests
[ 1009 , 1003 , 1004 , 1005 , 1006 , 1007 , 1008 ] . each do | chan |
2019-08-14 10:54:21 +10:00
rdp_send_recv ( pdu_channel_join_request ( self . rdp_user_id , chan ) )
2019-08-07 08:50:01 -05:00
end
if req_proto == RDPConstants :: PROTOCOL_RDP
@rdp_sec = true
# 5.3.4 Client Random Value
client_rand = ''
32 . times { client_rand << rand ( 0 .. 255 ) }
rcran = bytes_to_bignum ( client_rand )
vprint_status ( " Sending security exchange PDU " )
rdp_send ( pdu_security_exchange ( rcran , rsexp , rsmod , bitlen ) )
# We aren't decrypting anything at this point. Leave the variables here
# to make it easier to understand in the future.
rc4encstart , _rc4decstart , @hmackey , _sessblob = rdp_calculate_rc4_keys ( client_rand , server_rand )
@rc4enckey = RC4 . new ( rc4encstart )
end
return true
end
2019-09-22 16:47:08 -05:00
def rdp_generate_license_keys ( data )
2019-09-20 08:15:07 -05:00
client_random = ''
32 . times { client_random << rand ( 0 .. 255 ) }
premaster_secret = ''
32 . times { premaster_secret << rand ( 0 .. 255 ) }
server_random = data [ 0 .. 31 ]
master_secret = rdp_salted_hash48 ( premaster_secret , " A " , client_random , server_random )
key_block = rdp_salted_hash48 ( master_secret , " A " , client_random , server_random )
license_sign_key = key_block [ 0 .. 15 ]
2019-09-22 16:47:08 -05:00
license_key = rdp_salted_hash16 ( key_block [ 16 .. 31 ] , client_random , server_random )
return client_random , license_key , license_sign_key
end
2019-09-23 04:50:54 -05:00
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpele/e17772e9-9642-4bb6-a2bc-82875dd6da7c
# Server License Request - 2.2.2.1
2019-09-22 16:47:08 -05:00
def rdp_handle_license_request ( data )
2019-09-23 04:50:54 -05:00
# Note: license_key is currently unused
2019-09-22 16:47:08 -05:00
client_random , license_key , license_sign_key = rdp_generate_license_keys ( data )
2019-09-23 04:50:54 -05:00
# We're not really decrypting the license from the server, but it should be good enough
2019-09-22 17:20:58 -05:00
vprint_status ( " Sending new license request PDU " )
2019-09-23 04:50:54 -05:00
new_license_request = pdu_new_license_request ( client_random , @user_name , @computer_name )
2019-09-22 16:47:08 -05:00
pkt = rdp_build_pkt ( new_license_request , license_info : true )
2019-09-23 04:50:54 -05:00
# Expect that we are issued a license here
2019-09-22 16:47:08 -05:00
res = rdp_send_recv ( pkt )
rdp_parse_license_pdu ( res )
2019-09-20 08:15:07 -05:00
end
def rdp_handle_license_error_alert ( data )
error_code , state_transition , error_info = data [ 0 .. 11 ] . unpack ( " VVV " )
vprint_status ( " License error/alert code 0x #{ error_code . to_s ( 16 ) } ( #{ RDPConstants :: LICENSE_ERRS [ error_code ] } ) " )
# Ensure that we were issued a license by the server
raise RdpCommunicationError if error_code != RDPConstants :: LICENSE_ERR_LICENSE_ISSUED
end
2019-09-19 06:09:41 -05:00
def rdp_parse_license_pdu ( data )
raise RdpCommunicationError if data . length < 20
rdp_version = data [ 0 ] . unpack ( " C " ) [ 0 ]
raise RdpCommunicationError if rdp_version != 3
length = data [ 2 .. 3 ] . unpack ( " n " ) [ 0 ]
2019-10-09 08:39:03 -05:00
if data . length < length
2019-10-09 11:36:39 -05:00
vprint_error ( " Got #{ data . length } bytes, expected #{ length } " )
2019-10-09 08:39:03 -05:00
raise RdpCommunicationError
end
2019-09-19 06:09:41 -05:00
data_len = data [ 13 ] . unpack ( " C " ) [ 0 ]
tag_offset = 18
tag_offset += 1 if ( data_len & 0x80 == 0x80 ) # 2 byte length
tag = data [ tag_offset ] . unpack ( " C " ) [ 0 ]
vprint_status ( " Got license packet type 0x #{ tag . to_s ( 16 ) } ( #{ RDPConstants :: LICENSE_TAGS [ tag ] } ) " )
2019-09-20 08:15:07 -05:00
case tag
when RDPConstants :: LICENSE_REQUEST
rdp_handle_license_request ( data [ tag_offset + 4 .. - 1 ] )
when RDPConstants :: LICENSE_PLATFORM_CHALLENGE
when RDPConstants :: LICENSE_NEW_LICENSE
when RDPConstants :: LICENSE_UPGRADE_LICENSE
when RDPConstants :: LICENSE_LICENSE_INFO
when RDPConstants :: LICENSE_NEW_LICENSE_REQ
when RDPConstants :: LICENSE_PLATFORM_CHAL_RESP
when RDPConstants :: LICENSE_ERROR_ALERT
rdp_handle_license_error_alert ( data [ tag_offset + 4 .. - 1 ] )
end
2019-09-19 06:09:41 -05:00
end
2019-08-07 08:50:01 -05:00
# Finish building session after all security is negotiated
def rdp_establish_session
vprint_status ( " Sending client info PDU " )
2019-09-22 16:47:08 -05:00
res = rdp_send_recv ( rdp_build_pkt ( pdu_client_info ( @user_name , @domain , @ip_address ) ,
" \x03 \xeb " , client_info : true ) )
2019-09-19 06:09:41 -05:00
vprint_status ( " Received License packet ( #{ res . length } bytes) " )
rdp_parse_license_pdu ( res )
2019-08-07 08:50:01 -05:00
# Windows XP sometimes sends a very large license packet. This is likely
# some form of license error. When it does this it doesn't send a Server
# Demand packet. If we wait on one we will time out here and error. We
# can still successfully check for vulnerability anyway.
if res . length < = 34
vprint_status ( " Waiting for Server Demand packet " )
_res = rdp_recv
vprint_status ( " Received Server Demand packet " )
end
vprint_status ( " Sending client confirm active PDU " )
rdp_send ( rdp_build_pkt ( pdu_client_confirm_active ) )
vprint_status ( " Sending client synchronize PDU " )
vprint_status ( " Sending client control cooperate PDU " )
# Unsure why we're using 1009 here but it works.
synch = rdp_build_pkt ( pdu_client_synchronize ( 1009 ) )
coop = rdp_build_pkt ( pdu_client_control_cooperate )
rdp_send ( synch + coop )
vprint_status ( " Sending client control request control PDU " )
rdp_send ( rdp_build_pkt ( pdu_client_control_request ) )
2023-09-24 17:42:00 -04:00
vprint_status ( " Sending client input synchronize PDU " )
rdp_send ( rdp_build_pkt ( pdu_client_input_event_synchronize ) )
2019-08-07 08:50:01 -05:00
vprint_status ( " Sending client font list PDU " )
rdp_send ( rdp_build_pkt ( pdu_client_font_list ) )
2019-08-07 08:27:15 -05:00
end
2020-01-12 08:19:44 -06:00
def rdp_move_mouse ( x = 1 , y = 1 )
mouse_move_blob = " "
mouse_move_blob << " \x04 \x80 \x0a " # copypasta FAST PATH stuff from xfreerdp
mouse_move_blob << " \x20 " # TS_FP_INPUT_EVENT::eventHeader = 0x20 (FASTPATH_INPUT_EVENT_MOUSE)
mouse_move_blob << " \x00 \x08 " # TS_FP_POINTER_EVENT::pointerFlags = 0x0800 (PTRFLAGS_MOVE)
mouse_move_blob << [ x , y ] . pack ( 'vv' ) # TS_FP_POINTER_EVENT::xPos, TS_FP_POINTER_EVENT::yPos
rdp_send ( mouse_move_blob )
end
2019-08-07 07:25:42 -05:00
#
# Protocol parsers
#
# Parse RDP Negotiation Data - 2.2.1.2
# Reference: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/13757f8f-66db-4273-9d2c-385c33b1e483
# @return [String, nil] String representation of the Selected Protocol or nil on failure
# @return [String] Error message
def rdp_parse_negotiation_response ( data )
return false , " Response is not an RDP Negotiation Response packet. " unless data . match ( " \x03 \x00 \x00 .. \xd0 " )
return false , " Negotiation Response packet too short. " if data . length < 19
response_code = data [ 11 ] . unpack ( " C " ) [ 0 ]
if response_code == 2 # TYPE_RDP_NEG_RSP
# RDP Negotiation Response - 2.2.1.2.1
server_selected_proto = data [ 15 .. 18 ] . unpack ( " L< " ) [ 0 ]
proto_label = RDPConstants :: RDP_NEG_PROTOCOL [ server_selected_proto ]
return server_selected_proto , nil if proto_label
2019-08-07 10:00:18 -05:00
return nil , " Unknown protocol in Negotiation Response: #{ server_selected_proto } "
2019-08-07 07:25:42 -05:00
elsif response_code == 3 # TYPE_RDP_NEG_FAILURE
# RDP Negotiation Failure - 2.2.1.2.2
failure_code = data [ 15 .. 18 ] . unpack ( " L< " ) [ 0 ]
return nil , RDPConstants :: RDP_NEG_FAILURE [ failure_code ]
else
2019-08-07 10:00:18 -05:00
return nil , " Unknown Negotiation Response code: #{ response_code } "
2019-08-07 07:25:42 -05:00
end
end
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/927de44c-7fe8-4206-a14f-e5517dc24b1c
# Parse Server MCS Connect Response PUD - 2.2.1.4
def rdp_parse_connect_response ( pkt )
ptr = 0
rdp_pkt = pkt [ 0x49 .. pkt . length ]
while ptr < rdp_pkt . length
header_type = rdp_pkt [ ptr .. ptr + 1 ]
header_length = rdp_pkt [ ptr + 2 .. ptr + 3 ] . unpack ( " S< " ) [ 0 ]
if header_type == " \x02 \x0c "
server_random = rdp_pkt [ ptr + 20 .. ptr + 51 ]
public_exponent = rdp_pkt [ ptr + 84 .. ptr + 87 ]
rsa_magic = rdp_pkt [ ptr + 68 .. ptr + 71 ]
if rsa_magic != " RSA1 "
print_error ( " Server cert isn't RSA, this scenario isn't supported (yet). " )
raise RdpCommunicationError
end
bitlen = rdp_pkt [ ptr + 72 .. ptr + 75 ] . unpack ( " L< " ) [ 0 ] - 8
modulus = rdp_pkt [ ptr + 88 .. ptr + 87 + bitlen ]
end
ptr += header_length
end
# vprint_status("SERVER_MODULUS: #{bin_to_hex(modulus)}")
# vprint_status("SERVER_EXPONENT: #{bin_to_hex(public_exponent)}")
# vprint_status("SERVER_RANDOM: #{bin_to_hex(server_random)}")
rsmod = bytes_to_bignum ( modulus )
rsexp = bytes_to_bignum ( public_exponent )
rsran = bytes_to_bignum ( server_random )
# vprint_status("MODULUS = #{bin_to_hex(modulus)} - #{rsmod.to_s}")
# vprint_status("EXPONENT = #{bin_to_hex(public_exponent)} - #{rsexp.to_s}")
# vprint_status("SVRANDOM = #{bin_to_hex(server_random)} - #{rsran.to_s}")
return rsmod , rsexp , rsran , server_random , bitlen
end
#
# Encryption: Standard RDP Security
#
2019-08-07 10:00:18 -05:00
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/7c61b54e-f6cd-4819-a59a-daf200f6bf94
2019-08-07 07:25:42 -05:00
# mac_salt_key = "W\x13\xc58\x7f\xeb\xa9\x10*\x1e\xddV\x96\x8b[d"
# data_content = "\x12\x00\x17\x00\xef\x03\xea\x03\x02\x00\x00\x01\x04\x00$\x00\x00\x00"
# hmac = rdp_hmac(mac_salt_key, data_content) # == hexlified: "22d5aeb486994a0c785dc929a2855923"
def rdp_hmac ( mac_salt_key , data_content )
sha1 = Digest :: SHA1 . new
md5 = Digest :: MD5 . new
pad1 = " \x36 " * 40
pad2 = " \x5c " * 48
sha1 << mac_salt_key
sha1 << pad1
2023-07-14 12:46:26 +01:00
sha1 << [ data_content . length ] . pack ( 'L<' )
2019-08-07 07:25:42 -05:00
sha1 << data_content
md5 << mac_salt_key
md5 << pad2
md5 << [ sha1 . hexdigest ] . pack ( " H* " )
[ md5 . hexdigest ] . pack ( " H* " )
end
2019-09-22 16:47:08 -05:00
def rdp_salted_hash16 ( s_bytes , salt1 , salt2 )
md5 = Digest :: MD5 . new
md5 << s_bytes [ 0 .. 15 ]
md5 << salt1 [ 0 .. 31 ]
md5 << salt2 [ 0 .. 31 ]
[ md5 . hexdigest ] . pack ( " H* " )
end
2019-08-07 07:25:42 -05:00
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/705f9542-b0e3-48be-b9a5-cf2ee582607f
# SaltedHash(S, I) = MD5(S + SHA(I + S + ClientRandom + ServerRandom))
def rdp_salted_hash ( s_bytes , i_bytes , client_random_bytes , server_random_bytes )
sha1 = Digest :: SHA1 . new
md5 = Digest :: MD5 . new
sha1 << i_bytes
sha1 << s_bytes
sha1 << client_random_bytes
sha1 << server_random_bytes
md5 << s_bytes
md5 << [ sha1 . hexdigest ] . pack ( " H* " )
[ md5 . hexdigest ] . pack ( " H* " )
end
2019-09-20 08:15:07 -05:00
def rdp_salted_hash48 ( s_bytes , i_byte , client_random , server_random )
rdp_salted_hash ( s_bytes , i_byte , client_random , server_random ) + \
rdp_salted_hash ( s_bytes , ( i_byte . ord + 1 ) . chr * 2 , client_random , server_random ) + \
rdp_salted_hash ( s_bytes , ( i_byte . ord + 2 ) . chr * 3 , client_random , server_random )
end
2019-08-07 07:25:42 -05:00
# FinalHash(K) = MD5(K + ClientRandom + ServerRandom)
def rdp_final_hash ( k , client_random_bytes , server_random_bytes )
md5 = Digest :: MD5 . new
md5 << k
md5 << client_random_bytes
md5 << server_random_bytes
[ md5 . hexdigest ] . pack ( " H* " )
end
def rdp_calculate_rc4_keys ( client_random , server_random )
2019-09-20 08:15:07 -05:00
# g = First192Bits(ClientRandom) + First192Bits(ServerRandom)
g = client_random [ 0 .. 23 ] + server_random [ 0 .. 23 ]
2019-08-07 07:25:42 -05:00
2019-09-20 08:15:07 -05:00
# PreMasterHash(I) = SaltedHash(g, I)
2019-08-07 07:25:42 -05:00
# MasterSecret = PreMasterHash(0x41) + PreMasterHash(0x4242) + PreMasterHash(0x434343)
2019-09-20 08:15:07 -05:00
master_secret = rdp_salted_hash48 ( g , " A " , client_random , server_random )
2019-08-07 07:25:42 -05:00
# MasterHash(I) = SaltedHash(MasterSecret, I)
# SessionKeyBlob = MasterHash(0x58) + MasterHash(0x5959) + MasterHash(0x5A5A5A)
2019-09-20 08:15:07 -05:00
sessionKeyBlob = rdp_salted_hash48 ( master_secret , " X " , client_random , server_random )
2019-08-07 07:25:42 -05:00
# InitialClientDecryptKey128 = FinalHash(Second128Bits(SessionKeyBlob))
initialClientDecryptKey128 = rdp_final_hash ( sessionKeyBlob [ 16 .. 31 ] , client_random , server_random )
# InitialClientEncryptKey128 = FinalHash(Third128Bits(SessionKeyBlob))
initialClientEncryptKey128 = rdp_final_hash ( sessionKeyBlob [ 32 .. 47 ] , client_random , server_random )
2019-08-07 10:28:24 -05:00
mac_key = sessionKeyBlob [ 0 .. 15 ]
2019-08-07 07:25:42 -05:00
2019-08-07 10:28:24 -05:00
return initialClientEncryptKey128 , initialClientDecryptKey128 , mac_key , sessionKeyBlob
2019-08-07 07:25:42 -05:00
end
def rsa_encrypt ( bignum , rsexp , rsmod )
( bignum ** rsexp ) % rsmod
end
def rdp_rc4_crypt ( rc4obj , data )
rc4obj . encrypt ( data )
end
2019-08-07 10:28:24 -05:00
def bytes_to_bignum ( bytes_val , order = " little " )
bytes = bin_to_hex ( bytes_val )
2019-08-07 07:25:42 -05:00
if order == " little "
bytes = bytes . scan ( / .. / ) . reverse . join ( '' )
end
2019-08-07 10:28:24 -05:00
s = " 0x " + bytes
2019-08-07 07:25:42 -05:00
s . to_i ( 16 )
end
# https://www.ruby-forum.com/t/integer-to-byte-string-speed-improvements/67110
2019-08-07 10:28:24 -05:00
def int_to_bytestring ( int_val , num_chars = nil )
2019-08-07 07:25:42 -05:00
unless num_chars
2019-08-07 10:28:24 -05:00
bits_needed = Math . log ( int_val ) / Math . log ( 2 )
2019-08-07 07:25:42 -05:00
num_chars = ( bits_needed / 8 . 0 ) . ceil
end
2019-08-07 10:28:24 -05:00
if pack_code = { 1 = > 'C' , 2 = > 'S' , 4 = > 'L' } [ num_chars ]
[ int_val ] . pack ( pack_code )
2019-08-07 07:25:42 -05:00
else
a = ( 0 .. ( num_chars ) ) . map { | i |
2019-08-07 10:28:24 -05:00
( ( int_val >> i * 8 ) & 0xFF ) . chr
2019-08-07 07:25:42 -05:00
} . join
a [ 0 .. - 2 ] # seems legit lol
end
end
2019-08-07 10:28:24 -05:00
def bin_to_hex ( str_val )
str_val . each_byte . map { | b | b . to_s ( 16 ) . rjust ( 2 , '0' ) } . join
2019-08-07 07:25:42 -05:00
end
#
# Protocol Data Unit definitions and helpers
#
# Build the X.224 packet, encrypt with Standard RDP Security as needed
# default channel_id = 0x03eb = 1003
2019-09-22 16:47:08 -05:00
def rdp_build_pkt ( data , channel_id = " \x03 \xeb " , client_info : false , license_info : false )
2019-08-07 07:25:42 -05:00
flags = 0
2019-09-22 16:47:08 -05:00
flags |= 0x08 if @rdp_sec # Set SEC_ENCRYPT
flags |= 0x40 if client_info # Set SEC_INFO_PKT
2019-09-23 04:50:54 -05:00
flags |= 0x80 if license_info # Set SEC_LICENSE_PKT
2019-08-07 07:25:42 -05:00
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
2019-08-07 10:28:24 -05:00
2019-08-07 07:25:42 -05:00
[ data_len ] . pack ( " L< " ) + # length
2019-08-07 10:28:24 -05:00
[ flags ] . pack ( " L< " ) + # flags
data
2019-08-07 07:25:42 -05:00
end
# Builds x.224 Data (DT) TPDU - Section 13.7
def build_data_tpdu ( data )
tpkt_length = data . length + 7
2019-08-07 10:28:24 -05:00
" \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
2019-08-07 07:25:42 -05:00
end
2019-09-22 16:47:08 -05:00
2019-09-23 04:50:54 -05:00
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpele/c57e4890-9049-421e-9fe8-9a6f9519675a
# Client New License Request PDU - 2.2.2.2
def pdu_new_license_request ( client_random , user , host )
length = 24 + client_random . length + 64 + user . length + 1 + host . length + 1
2019-09-22 16:47:08 -05:00
[ RDPConstants :: LICENSE_NEW_LICENSE_REQ ] . pack ( " C " ) +
2019-09-23 04:50:54 -05:00
" \x03 " + # Version
[ length ] . pack ( " S< " ) + # Length
2019-09-22 16:47:08 -05:00
" \x01 \x01 \x00 \x00 \x00 \x01 \xff " + # KEY_EXCHANGE_ALG_RSA
client_random [ 0 .. 31 ] +
2019-09-23 04:50:54 -05:00
" \x02 \x00 " + # Encrypted Premaster Secret RANDOM_BLOB
[ 64 ] . pack ( " S< " ) +
" \x00 " * 64 + # The client license premaster secret, we don't care about the license contents
" \x0f \x00 " + # USER_NAME_BLOB
2019-09-22 16:47:08 -05:00
[ user . length + 1 ] . pack ( " S< " ) +
user + " \x00 " +
2019-09-23 04:50:54 -05:00
" \x10 \x00 " + # CLIENT_MACHINE_NAME_BLOB
2019-09-22 16:47:08 -05:00
[ host . length + 1 ] . pack ( " S< " ) +
host + " \x00 "
end
2019-08-07 07:25:42 -05:00
# 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
2019-08-07 10:28:24 -05:00
" \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
2019-08-07 07:25:42 -05:00
end
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/db6713ee-1c0e-4064-a3b3-0fac30b4037b
2019-08-09 14:49:42 +10:00
def pdu_connect_initial ( channels , selected_proto = 0 , host_name = " rdesktop " )
2019-08-07 07:25:42 -05:00
# After negotiating TLS or NLA the connectInitial packet needs to include the
# protocol selection that the server indicated in its Negotiation Response
2019-08-09 14:49:42 +10:00
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 ( '' )
2019-08-07 07:25:42 -05:00
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
2019-08-07 10:28:24 -05:00
pdu =
" \x04 " + # T.125 ErectDomainRequest
" \x01 \x00 " + # subHeight - length 1, value 0
" \x01 \x00 " # subInterval - length 1, value 0
2019-08-07 07:25:42 -05:00
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
2019-08-14 10:54:21 +10:00
def pdu_channel_join_request ( user1 , channel_id )
2019-08-07 10:28:24 -05:00
pdu =
" \x38 " + # T.125 ChannelJoinRequest
[ user1 , channel_id ] . pack ( " nn " )
2019-08-07 07:25:42 -05:00
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
2019-08-07 10:00:18 -05:00
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)
2019-08-07 07:25:42 -05:00
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?
2019-08-07 10:00:18 -05:00
user_unicode = Rex :: Text . to_unicode ( user_name [ 0 .. 20 ] , 'utf-16le' )
2019-08-07 07:25:42 -05:00
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.
2019-08-07 10:00:18 -05:00
domain_unicode = Rex :: Text . to_unicode ( domain_name [ 0 .. 24 ] , 'utf-16le' )
2019-08-07 07:25:42 -05:00
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
2019-08-07 10:00:18 -05:00
ip_unicode = Rex :: Text . to_unicode ( ip_address , 'utf-16le' ) + " \x00 \x00 "
2019-08-07 07:25:42 -05:00
ip_len = ip_unicode . length
2019-08-07 10:28:24 -05:00
" \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
2019-08-07 07:25:42 -05:00
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
2019-08-07 10:28:24 -05:00
[ type ] . pack ( " S< " ) + # pduType - flags 16 bit, unsigned
" \xf1 \x03 " + # PDUSource: 0x03f1 = 1009
data
2019-08-07 07:25:42 -05:00
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
2019-08-07 10:28:24 -05:00
" \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
2019-08-07 07:25:42 -05:00
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
2019-08-07 10:00:18 -05:00
pdu =
" \x04 \x00 " + # action: 4 - CTRLACTION_COOPERATE
" \x00 \x00 " + # grantId: 0
" \x00 \x00 \x00 \x00 " # controlId: 0
2019-08-07 07:25:42 -05:00
# 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
2019-08-07 10:00:18 -05:00
pdu =
" \x01 \x00 " + # action: 1 - CTRLACTION_REQUEST_CONTROL
" \x00 \x00 " + # grantId: 0
" \x00 \x00 \x00 \x00 " # controlId: 0
2019-08-07 07:25:42 -05:00
# 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
2019-08-07 10:00:18 -05:00
pdu =
" \x00 \x00 " + # numberFonts: 0
" \x00 \x00 " + # totalNumberFonts: 0
" \x03 \x00 " + # listFlags: 3 (FONTLIST_FIRST | FONTLIST_LAST)
" \x32 \x00 " # entrySize: 50
2019-08-07 07:25:42 -05:00
# 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 )
2019-08-07 10:00:18 -05:00
pdu =
" \x01 \x00 " + # messageType: 1 SYNCMSGTYPE_SYNC
[ target_user ] . pack ( " S< " ) # targetUser, 16 bit, unsigned.
2019-08-07 07:25:42 -05:00
# 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
2019-08-07 10:00:18 -05:00
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
2019-08-07 10:28:24 -05:00
" \x01 \x00 \x03 \x00 \x00 \x02 \x00 \x00 \x00 \x00 \x0d \x04 \x00 \x00 \x00 \x00 " + #
" \x00 \x00 \x00 \x00 " + #
2019-08-07 10:00:18 -05:00
" \x02 \x00 " + # capabilitySetType: 2 - TS_BITMAP_CAPABILITYSET
" \x1c \x00 " + # lengthCapability: 28
2019-08-07 10:28:24 -05:00
" \x10 \x00 \x01 \x00 \x01 \x00 \x01 \x00 \x20 \x03 \x58 \x02 \x00 \x00 \x01 \x00 " + #
" \x01 \x00 \x00 \x00 \x01 \x00 \x00 \x00 " + #
2019-08-07 10:00:18 -05:00
" \x03 \x00 " + # capabilitySetType: 3 - TS_ORDER_CAPABILITYSET
" \x58 \x00 " + # lengthCapability: 88
2019-08-07 10:28:24 -05:00
" \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 " + #
2019-08-07 10:00:18 -05:00
" \x08 \x00 " + # capabilitySetType: 8 - TS_POINTER_CAPABILITYSET
" \x0a \x00 " + # lengthCapability: 10
2019-08-07 10:28:24 -05:00
" \x01 \x00 \x14 \x00 \x14 \x00 " + #
2019-08-07 10:00:18 -05:00
" \x0a \x00 " + # capabilitySetType: 10 - TS_COLORTABLE_CAPABILITYSET
" \x08 \x00 " + # lengthCapability: 8
2019-08-07 10:28:24 -05:00
" \x06 \x00 \x00 \x00 " + #
2019-08-07 10:00:18 -05:00
" \x07 \x00 " + # capabilitySetType: 7 - TSWINDOWACTIVATION_CAPABILITYSET
" \x0c \x00 " + # lengthCapability: 12
2019-08-07 10:28:24 -05:00
" \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 " + #
2019-08-07 10:00:18 -05:00
" \x05 \x00 " + # capabilitySetType: 5 - TS_CONTROL_CAPABILITYSET
" \x0c \x00 " + # lengthCapability: 12
2019-08-07 10:28:24 -05:00
" \x00 \x00 \x00 \x00 \x02 \x00 \x02 \x00 " + #
2019-08-07 10:00:18 -05:00
" \x09 \x00 " + # capabilitySetType: 9 - TS_SHARE_CAPABILITYSET
" \x08 \x00 " + # lengthCapability: 8
2019-08-07 10:28:24 -05:00
" \x00 \x00 \x00 \x00 " + #
2019-08-07 10:00:18 -05:00
" \x0f \x00 " + # capabilitySetType: 15 - TS_BRUSH_CAPABILITYSET
" \x08 \x00 " + # lengthCapability: 8
2019-08-07 10:28:24 -05:00
" \x01 \x00 \x00 \x00 " + #
2019-08-07 10:00:18 -05:00
" \x0d \x00 " + # capabilitySetType: 13 - TS_INPUT_CAPABILITYSET
" \x58 \x00 " + # lengthCapability: 88
2019-08-07 10:28:24 -05:00
" \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 " + #
2019-08-07 10:00:18 -05:00
" \x0c \x00 " + # capabilitySetType: 12 - TS_SOUND_CAPABILITYSET
" \x08 \x00 " + # lengthCapability: 8
2019-08-07 10:28:24 -05:00
" \x01 \x00 \x00 \x00 " + #
2019-08-07 10:00:18 -05:00
" \x0e \x00 " + # capabilitySetType: 14 - TS_FONT_CAPABILITYSET
" \x08 \x00 " + # lengthCapability: 8
2019-08-07 10:28:24 -05:00
" \x01 \x00 \x00 \x00 " + #
2019-08-07 10:00:18 -05:00
" \x10 \x00 " + # capabilitySetType: 16 - TS_GLYPHCAChE_CAPABILITYSET
" \x34 \x00 " + # lengthCapability: 52
2019-08-07 10:28:24 -05:00
" \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 " + #
2019-08-07 10:00:18 -05:00
" \xfe \x00 \x00 \x01 \x40 \x00 \x00 \x08 \x00 \x01 \x00 \x01 \x02 \x00 \x00 \x00 "
2019-08-07 07:25:42 -05:00
# 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
2023-09-24 17:42:00 -04:00
def pdu_client_input_event_synchronize
2019-08-07 10:00:18 -05:00
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
2019-08-07 07:25:42 -05:00
# 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
2019-08-08 12:00:50 +10:00
def swap_sock_plain_to_ssl
2019-08-07 07:25:42 -05:00
ctx = OpenSSL :: SSL :: SSLContext . new
2019-08-19 19:48:25 +02:00
ctx . min_version = OpenSSL :: SSL :: TLS1_VERSION
2019-09-07 18:39:59 +02:00
ctx . security_level = datastore [ 'RDP_TLS_SECURITY_LEVEL' ]
2019-08-08 12:00:50 +10:00
ssl = OpenSSL :: SSL :: SSLSocket . new ( self . rdp_sock , ctx )
2019-08-07 07:25:42 -05:00
2019-09-07 17:19:59 +02:00
begin
ssl . connect
rescue Errno :: ECONNRESET
vprint_error ( " Retry with advanced option RDP_TLS_SECURITY_LEVEL=0 " )
raise
end
2019-08-07 07:25:42 -05:00
2019-08-08 12:00:50 +10:00
self . rdp_sock . extend ( Rex :: Socket :: SslTcp )
self . rdp_sock . sslsock = ssl
self . rdp_sock . sslctx = ctx
2019-08-07 07:25:42 -05:00
end
2019-08-08 12:00:50 +10:00
protected
2019-08-09 14:49:42 +10:00
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
)
2023-07-14 12:46:26 +01:00
body = [ flags , session_id ] . pack ( 'L<L<' )
2019-08-09 14:49:42 +10:00
result = [
2023-07-14 12:46:26 +01:00
[ 0xc004 , body . length + 4 ] . pack ( 'S<S<' ) ,
2019-08-09 14:49:42 +10:00
body
] . join ( '' )
result
end
def cs_security_data (
encryption_methods : RDPConstants :: ENCRYPTION_40BIT | RDPConstants :: ENCRYPTION_128BIT ,
ext_encryption_methods : 0
)
2023-07-14 12:46:26 +01:00
body = [ encryption_methods , ext_encryption_methods ] . pack ( 'L<L<' )
2019-08-09 14:49:42 +10:00
result = [
2023-07-14 12:46:26 +01:00
[ 0xc002 , body . length + 4 ] . pack ( 'S<S<' ) ,
2019-08-09 14:49:42 +10:00
body
] . join ( '' )
result
end
def cs_network_data ( channels )
chan_data = channels . map { | c |
2023-07-14 12:46:26 +01:00
[ c [ 0 ] . encode ( 'ASCII' ) ] . pack ( 'a8' ) + [ c [ 1 ] ] . pack ( 'L' )
2019-08-09 14:49:42 +10:00
} . join ( '' )
body = [
[ channels . length ] . pack ( 'L' ) ,
chan_data
] . join ( '' )
result = [
2023-07-14 12:46:26 +01:00
[ 0xc003 , body . length + 4 ] . pack ( 'S<S<' ) ,
2019-08-09 14:49:42 +10:00
body
] . join ( '' )
result
end
2023-07-14 12:46:26 +01:00
# https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/00f1da4a-ee9c-421a-852f-c19f92343d73
2019-08-09 14:49:42 +10:00
def cs_core_data (
version : 0x80004 ,
width : 800 ,
height : 600 ,
keyboard : 1033 , # English
client_build : 2600 ,
client_name : " rdesktop " ,
keyboard_type : 4 , # IBMEhanced 101/102
keyboard_subtype : 0 ,
keyboard_func_key : 12 ,
serial_num : 0 ,
client_product_id : 1 ,
client_dig_product_id : " " ,
selected_proto : 0
2020-01-12 08:19:44 -06:00
)
2019-08-09 14:49:42 +10:00
client_name = Rex :: Text . to_unicode ( client_name [ 0 .. 16 ] , 'utf-16le' )
client_dig_product_id = Rex :: Text . to_unicode ( client_dig_product_id [ 0 .. 32 ] , 'utf-16le' )
body = [
2023-07-14 12:46:26 +01:00
[ version , width , height ] . pack ( 'L<S<S<' ) ,
2019-08-09 14:49:42 +10:00
" \x01 \xca " , # colour depth (8BPP)
" \x03 \xaa " , # SASSequence
2023-07-14 12:46:26 +01:00
[ keyboard , client_build , client_name ] . pack ( 'L<L<a32' ) ,
[ keyboard_type , keyboard_subtype , keyboard_func_key ] . pack ( 'L<L<L<' ) ,
2019-08-09 14:49:42 +10:00
" \x00 " * 64 , # imeFileName
" \x01 \xca " , # postBeta2ColorDepth (8BPP)
2023-07-14 12:46:26 +01:00
[ client_product_id , serial_num ] . pack ( 'S<L<' ) ,
2019-08-09 14:49:42 +10:00
" \x18 \x00 " , # highColorDepth: 24 bpp
" \x07 \x00 " , # supportedColorDepths: flag (24 bpp | 16 bpp | 15 bpp )
" \x01 \x00 " , # earlyCapabilityFlags: 1 (RNS_UD_CS_SUPPORT_ERRINFO_PDU)
2023-07-14 12:46:26 +01:00
[ client_dig_product_id ] . pack ( 'a64' ) ,
2019-08-09 14:49:42 +10:00
" \x00 " , # connectionType: 0
" \x00 " , # pad1octet
# serverSelectedProtocol - After negotiating TLS or CredSSP this value must
# match the selectedProtocol value from the server's Negotiate Connection
# confirm PDU that was sent before encryption was started.
[ selected_proto ] . pack ( 'L<' )
] . join ( '' )
result = [
2023-07-14 12:46:26 +01:00
[ 0xc001 , body . length + 4 ] . pack ( 'S<S<' ) ,
2019-08-09 14:49:42 +10:00
body
] . join ( '' )
result
end
def conf_create_req ( user_data_sets : 1 , h221_key : " Duca " )
b2 = 0
b2 |= 0x08 if user_data_sets > 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
2019-08-14 10:54:21 +10:00
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 ]
2023-07-14 12:46:26 +01:00
flags = pkt [ 18 .. 21 ] . unpack ( 'L<' ) [ 0 ]
2019-08-14 10:54:21 +10:00
data = pkt [ 22 .. pkt . length ]
rdp_on_channel_receive ( pkt , chan_user_id , chan_id , flags , data )
end
end
end
end
def rdp_on_channel_receive ( pkt , chan_user_id , chan_id , flags , data )
ctype = data [ 0 .. 1 ] . unpack ( 'S' ) [ 0 ]
if ctype == RDPConstants :: RDPDR_CTYP_CORE
opcode = data [ 2 .. 3 ] . unpack ( 'S' ) [ 0 ]
if opcode == RDPConstants :: PAKID_CORE_SERVER_ANNOUNCE
rdp_on_core_server_announce ( pkt , chan_user_id , chan_id , flags , data )
elsif opcode == RDPConstants :: PAKID_CORE_SERVER_CAPABILITY
rdp_on_core_server_capability ( pkt , chan_user_id , chan_id , flags , data )
elsif opcode == RDPConstants :: PAKID_CORE_CLIENTID_CONFIRM
rdp_on_core_client_id_confirm ( pkt , chan_user_id , chan_id , flags , data )
end
end
end
def rdp_on_core_server_announce ( pkt , chan_user_id , chan_id , flags , data )
vprint_status ( " Handling SERVER ANNOUNCE ... " )
rdpdr_client_announce_reply ( pkt , chan_user_id , chan_id , flags , data )
rdpdr_client_name_request ( pkt , chan_user_id , chan_id , flags , data )
end
def rdp_on_core_server_capability ( pkt , chan_user_id , chan_id , flags , data )
vprint_status ( " Handling SERVER CAPABILITY ... " )
# change opcode 1 byte to match server capabilities
reply = [ data [ 0 .. 2 ] , " \x43 " , data [ 4 .. data . length ] ] . join ( '' )
rdp_send_channel ( chan_user_id , chan_id , reply )
end
def rdp_on_core_client_id_confirm ( pkt , chan_user_id , chan_id , flags , data )
vprint_status ( " Handling CLIENT ID CONFIRM ... " )
rdpdr_client_device_list_announce_request ( pkt , chan_user_id , chan_id , flags , data )
end
def rdpdr_client_device_list_announce_request ( pkt , chan_user_id , chan_id , flags , data )
reply = [
RDPConstants :: RDPDR_CTYP_CORE ,
RDPConstants :: PAKID_CORE_DEVICELIST_ANNOUNCE ,
0x0 , # Device count
] . pack ( 'SSL' )
rdp_send_channel ( chan_user_id , chan_id , reply )
end
def rdpdr_client_announce_reply ( pkt , chan_user_id , chan_id , flags , data )
reply = [
RDPConstants :: RDPDR_CTYP_CORE ,
RDPConstants :: PAKID_CORE_CLIENTID_CONFIRM ,
0x1 , # Version Major
0xc , # Version Minor
0x2 , # client ID (TODO: configure this? read it from the packet?
] . pack ( 'SSSSL' )
rdp_send_channel ( chan_user_id , chan_id , reply )
end
def rdpdr_client_name_request ( pkt , chan_user_id , chan_id , flags , data )
2019-09-11 09:09:58 -05:00
computer_name = Rex :: Text . to_unicode ( " #{ @computer_name } \x00 " , 'utf-16le' )
2019-08-14 10:54:21 +10:00
reply = [
RDPConstants :: RDPDR_CTYP_CORE ,
RDPConstants :: PAKID_CORE_CLIENT_NAME ,
0x1 , # Unicode flag
0x0 , # Code Page
computer_name . length ,
computer_name ,
] . pack ( 'SSLLLa*' )
rdp_send_channel ( chan_user_id , chan_id , reply )
end
2019-08-08 12:00:50 +10:00
attr_accessor :rdp_sock
2019-08-14 10:54:21 +10:00
attr_accessor :rdp_user_id
=begin
# debug stuff
def rdp_to_file(b, del = false)
p = "/tmp/ruby-full.bin"
::File.delete(p) if del && ::File.exist?(p)
f = ::File.new(p, "ab")
f.write(b)
f.close
end
=end
2019-08-07 07:25:42 -05:00
end
2019-08-07 10:00:18 -05:00
end