Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable forwarding of unparsed Client Data Blocks in the GCC negociation. #304

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions pyrdp/enum/negotiation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Licensed under the GPLv3 or later.
#

from enum import IntEnum, Flag
from enum import IntEnum, IntFlag


class NegotiationType(IntEnum):
Expand All @@ -24,4 +24,17 @@ class NegotiationRequestFlags(IntEnum):
NONE = 0x00
RESTRICTED_ADMIN_MODE_REQUIRED = 0x01
REDIRECTED_AUTHENTICATION_MODE_REQUIRED = 0x02
CORRELATION_INFO_PRESENT = 0x08
CORRELATION_INFO_PRESENT = 0x08


class NegotiationResponseFlags(IntFlag):
"""
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/b2975bdc-6d56-49ee-9c57-f2ff3a0b6817
"""
NONE = 0x00
EXTENDED_CLIENT_DATA_SUPPORTED = 0x01
DYNVC_GFX_PROTOCOL_SUPPORTED = 0x02
NEGRSP_FLAG_RESERVED = 0x04
RESTRICTED_ADMIN_MODE_SUPPORTED = 0x08
REDIRECTED_AUTHENTICATION_MODE_SUPPORTED = 0x10

1 change: 0 additions & 1 deletion pyrdp/mitm/ClipboardMITM.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from logging import LoggerAdapter
from io import BytesIO
from functools import partial

from pathlib import Path

from pyrdp.core import decodeUTF16LE, Uint64LE
Expand Down
12 changes: 8 additions & 4 deletions pyrdp/mitm/X224MITM.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
from logging import LoggerAdapter

from pyrdp.core import defer
from pyrdp.enum import NegotiationFailureCode, NegotiationProtocols, NegotiationType, NegotiationRequestFlags
from pyrdp.enum import NegotiationFailureCode, NegotiationRequestFlags, NegotiationType
from pyrdp.enum.negotiation import NegotiationResponseFlags
from pyrdp.layer import X224Layer
from pyrdp.mitm.state import RDPMITMState
from pyrdp.parser import NegotiationRequestParser, NegotiationResponseParser
from pyrdp.pdu import NegotiationRequestPDU, NegotiationResponsePDU, X224ConnectionConfirmPDU, X224ConnectionRequestPDU, \
X224DisconnectRequestPDU, X224ErrorPDU, NegotiationFailurePDU
from pyrdp.pdu import NegotiationFailurePDU, NegotiationRequestPDU, NegotiationResponsePDU, \
X224ConnectionConfirmPDU, X224ConnectionRequestPDU, X224DisconnectRequestPDU, X224ErrorPDU


class X224MITM:
Expand Down Expand Up @@ -111,7 +112,10 @@ def onConnectionConfirm(self, pdu: X224ConnectionConfirmPDU):
self.log.info("The server failed the negotiation. Error: %(error)s", {"error": NegotiationFailureCode.getMessage(response.failureCode)})
payload = pdu.payload
else:
payload = parser.write(NegotiationResponsePDU(NegotiationType.TYPE_RDP_NEG_RSP, 0x00, response.selectedProtocols))
# If the server supports extended client data blocks in the clientData PDU, we want the client to send them.
flagsToSend = response.flags & NegotiationResponseFlags.EXTENDED_CLIENT_DATA_SUPPORTED
payload = parser.write(NegotiationResponsePDU(NegotiationType.TYPE_RDP_NEG_RSP, flagsToSend, response.selectedProtocols))
self.client.sendConnectionConfirm(payload, source=0x1234)

# FIXME: This should be done based on what authentication method the server selected, not on what
# the client supports.
Expand Down
103 changes: 79 additions & 24 deletions pyrdp/parser/rdp/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@
EncryptionMethod, HighColorDepth, RDPVersion, ServerCertificateType
from pyrdp.exceptions import ParsingError, UnknownPDUTypeError, ExploitError
from pyrdp.parser.parser import Parser
from pyrdp.pdu import ClientChannelDefinition, ClientClusterData, ClientCoreData, ClientDataPDU, ClientNetworkData, \
ClientSecurityData, ProprietaryCertificate, ServerCoreData, ServerDataPDU, ServerNetworkData, ServerSecurityData
from pyrdp.pdu import ClientChannelDefinition, ClientClusterData, ClientCoreData, ClientDataPDU, \
ClientNetworkData, \
ClientSecurityData, ProprietaryCertificate, ServerCoreData, ServerDataPDU, ServerNetworkData, \
ServerSecurityData
from pyrdp.pdu.rdp.connection import ClientMonitorData, ClientUnparsedData, MonitorDef


class ClientConnectionParser(Parser):
Expand All @@ -32,14 +35,15 @@ def __init__(self):
ConnectionDataType.CLIENT_SECURITY: self.parseClientSecurityData,
ConnectionDataType.CLIENT_NETWORK: self.parseClientNetworkData,
ConnectionDataType.CLIENT_CLUSTER: self.parseClientClusterData,
ConnectionDataType.CLIENT_MONITOR: self.parseClientMonitorData,
}

self.writers = {
ConnectionDataType.CLIENT_CORE: self.writeClientCoreData,
ConnectionDataType.CLIENT_SECURITY: self.writeClientSecurityData,
ConnectionDataType.CLIENT_NETWORK: self.writeClientNetworkData,
ConnectionDataType.CLIENT_CLUSTER: self.writeClientClusterData,

ConnectionDataType.CLIENT_MONITOR: self.writeClientMonitorData,
}

def doParse(self, data: bytes) -> ClientDataPDU:
Expand All @@ -51,26 +55,33 @@ def doParse(self, data: bytes) -> ClientDataPDU:
security = None
network = None
cluster = None
monitor = None
rest = []

stream = BytesIO(data)
while stream.tell() != len(stream.getvalue()) and (core is None or security is None or network is None or cluster is None):
structure = self.parseStructure(stream)

if structure.header == ConnectionDataType.CLIENT_CORE:
core = structure
elif structure.header == ConnectionDataType.CLIENT_SECURITY:
security = structure
elif structure.header == ConnectionDataType.CLIENT_NETWORK:
network = structure
elif structure.header == ConnectionDataType.CLIENT_CLUSTER:
cluster = structure
while stream.tell() != len(stream.getvalue()):
parsed, structure = self.parseStructure(stream)

if parsed:
if structure.header == ConnectionDataType.CLIENT_CORE:
core = structure
elif structure.header == ConnectionDataType.CLIENT_SECURITY:
security = structure
elif structure.header == ConnectionDataType.CLIENT_NETWORK:
network = structure
elif structure.header == ConnectionDataType.CLIENT_CLUSTER:
cluster = structure
elif structure.header == ConnectionDataType.CLIENT_MONITOR:
monitor = structure
else:
rest.append(structure)

if len(stream.getvalue()) == 0:
break

return ClientDataPDU(core, security, network, cluster)
return ClientDataPDU(core, security, network, cluster, monitor, rest)

def parseStructure(self, stream: BytesIO) -> typing.Union[ClientCoreData, ClientNetworkData, ClientSecurityData, ClientClusterData]:
def parseStructure(self, stream: BytesIO) -> typing.Tuple[bool, typing.Union[ClientCoreData, ClientNetworkData, ClientSecurityData, ClientClusterData, ClientMonitorData, ClientUnparsedData]]:
header = Uint16LE.unpack(stream)
length = Uint16LE.unpack(stream) - 4
data = stream.read(length)
Expand All @@ -81,9 +92,9 @@ def parseStructure(self, stream: BytesIO) -> typing.Union[ClientCoreData, Client
substream = BytesIO(data)

if header not in self.parsers:
raise UnknownPDUTypeError("Trying to parse unknown client data structure %s" % header, header)
return False, ClientUnparsedData(header, substream.read(length))

return self.parsers[header](substream)
return True, self.parsers[header](substream)

def parseClientCoreData(self, stream: BytesIO) -> ClientCoreData:
stream = StrictStream(stream)
Expand Down Expand Up @@ -163,6 +174,26 @@ def parseClientClusterData(self, stream: BytesIO) -> ClientClusterData:
redirectedSessionID = Uint32LE.unpack(stream)
return ClientClusterData(flags, redirectedSessionID)

def parseClientMonitorData(self, stream: BytesIO) -> ClientMonitorData:
flags = Uint32LE.unpack(stream)
monitorCount = Uint32LE.unpack(stream)
monitorDefArray = self.parseMonitorDefArray(stream, monitorCount)
return ClientMonitorData(flags, monitorCount, monitorDefArray)

def parseMonitorDefArray(self, stream: BytesIO, count) -> typing.List[MonitorDef]:
"""
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/c3964b39-3d54-4ae1-a84a-ceaed311e0f6
"""
monitorDefArray = []
for i in range(count):
left = Uint32LE.unpack(stream)
top = Uint32LE.unpack(stream)
right = Uint32LE.unpack(stream)
bottom = Uint32LE.unpack(stream)
flags = Uint32LE.unpack(stream)
monitorDefArray.append(MonitorDef(left, top, right, bottom, flags))
return monitorDefArray

def write(self, pdu: ClientDataPDU) -> bytes:
"""
Encode a Client Data PDU to bytes.
Expand All @@ -182,19 +213,30 @@ def write(self, pdu: ClientDataPDU) -> bytes:
if pdu.clusterData:
self.writeStructure(stream, pdu.clusterData)

if pdu.monitorData:
self.writeStructure(stream, pdu.monitorData)

# TODO: If we let through unparsed data structures in the clientData PDU, it might cause trouble
# ex: UDP might get used by the client? https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/3801236b-b5ba-4b6e-bf0d-afbde1fe391c
# further testing/reading is required before this gets enabled.
for unparsedData in pdu.rest:
self.writeStructure(stream, unparsedData)


return stream.getvalue()

def writeStructure(self, stream: BytesIO, data: typing.Union[ClientCoreData, ClientNetworkData, ClientSecurityData, ClientClusterData]):
if data.header not in self.writers:
raise UnknownPDUTypeError("Trying to write unknown Client Data structure %s" % data.header, data.header)
def writeStructure(self, stream: BytesIO, data: typing.Union[ClientCoreData, ClientNetworkData, ClientSecurityData, ClientClusterData, ClientUnparsedData]):

substream = BytesIO()
self.writers[data.header](substream, data)
if data.header in self.writers:
substream = BytesIO()
self.writers[data.header](substream, data)
else:
substream = BytesIO(data.payload)

substream = substream.getvalue()

stream.write(Uint16LE.pack(data.header))
stream.write(Uint16LE.pack(len(substream) + 4))
stream.write(Uint16LE.pack(len(substream) + 4)) # Length
stream.write(substream)

def writeClientCoreData(self, stream: BytesIO, core: ClientCoreData):
Expand Down Expand Up @@ -249,6 +291,19 @@ def writeClientClusterData(self, stream: BytesIO, cluster: ClientClusterData):
stream.write(Uint32LE.pack(cluster.flags))
stream.write(Uint32LE.pack(cluster.redirectedSessionID))

def writeClientMonitorData(self, stream: BytesIO, pdu: ClientMonitorData):
stream.write(Uint32LE.pack(pdu.flags))
stream.write(Uint32LE.pack(pdu.monitorCount))
self.writeMonitorDefArray(stream, pdu.monitorDefArray)

def writeMonitorDefArray(self, stream: BytesIO, monitorDefs: typing.List[MonitorDef]):
for monitorDef in monitorDefs:
Uint32LE.pack(monitorDef.left, stream)
Uint32LE.pack(monitorDef.top, stream)
Uint32LE.pack(monitorDef.right, stream)
Uint32LE.pack(monitorDef.bottom, stream)
Uint32LE.pack(monitorDef.flags, stream)


class ServerConnectionParser(Parser):
"""
Expand Down
45 changes: 41 additions & 4 deletions pyrdp/pdu/rdp/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
#

import socket
from typing import Optional
from typing import List, Optional

from Crypto.PublicKey.RSA import RsaKey

from pyrdp.enum import ChannelOption, ConnectionDataType, RDPVersion, ServerCertificateType, EncryptionLevel
from pyrdp.enum.rdp import ClientCapabilityFlag, ColorDepth, ConnectionType, DesktopOrientation, EncryptionMethod, \
from pyrdp.enum import ChannelOption, ConnectionDataType, EncryptionLevel, RDPVersion, \
ServerCertificateType
from pyrdp.enum.rdp import ClientCapabilityFlag, ColorDepth, ConnectionType, DesktopOrientation, \
EncryptionMethod, \
HighColorDepth, KeyboardType, NegotiationProtocols, SupportedColorDepth
from pyrdp.pdu.pdu import PDU

Expand Down Expand Up @@ -140,13 +142,48 @@ def __init__(self, flags: int, redirectedSessionID: int):
self.redirectedSessionID = redirectedSessionID


class MonitorDef:
"""
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/c3964b39-3d54-4ae1-a84a-ceaed311e0f6
"""

def __init__(self, left: int, top: int, right: int, bottom: int, flags: int):
self.left = left
self.top = top
self.right = right
self.bottom = bottom
self.flags = flags


class ClientMonitorData:
"""
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/a8029f8e-01e1-4f19-af67-bcad5bdef624
"""
def __init__(self, flags: int, monitorCount: int, monitorDefArray: List[MonitorDef]):
self.header = ConnectionDataType.CLIENT_MONITOR
self.flags = flags
self.monitorCount = monitorCount
self.monitorDefArray = monitorDefArray


class ClientUnparsedData:

def __init__(self, header: int, data):
self.header = header
self.payload = data


class ClientDataPDU(PDU):
def __init__(self, coreData: ClientCoreData, securityData: ClientSecurityData, networkData: ClientNetworkData, clusterData: Optional[ClientClusterData]):
def __init__(self, coreData: ClientCoreData, securityData: ClientSecurityData, networkData: ClientNetworkData,
clusterData: Optional[ClientClusterData], monitorData: Optional[ClientMonitorData], rest: List[ClientUnparsedData]):
PDU.__init__(self)
self.coreData = coreData
self.securityData = securityData
self.networkData = networkData
self.clusterData = clusterData
self.monitorData = monitorData
self.rest = rest
""" The unparsed structures """

@staticmethod
def generate(serverSelectedProtocol: NegotiationProtocols,
Expand Down