From ed56f0291fd35a775341180815c85dd48e175bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milio=20Gonzalez?= Date: Sun, 14 Jun 2020 21:15:03 -0400 Subject: [PATCH] WIP log monitor data + other structures in clientdata --- pyrdp/enum/negotiation.py | 17 +++++- pyrdp/mitm/ClipboardMITM.py | 7 ++- pyrdp/mitm/X224MITM.py | 11 ++-- pyrdp/parser/rdp/connection.py | 108 +++++++++++++++++++++++++-------- pyrdp/pdu/rdp/connection.py | 45 ++++++++++++-- 5 files changed, 149 insertions(+), 39 deletions(-) diff --git a/pyrdp/enum/negotiation.py b/pyrdp/enum/negotiation.py index 416cfcf0e..d7bf63e17 100644 --- a/pyrdp/enum/negotiation.py +++ b/pyrdp/enum/negotiation.py @@ -4,7 +4,7 @@ # Licensed under the GPLv3 or later. # -from enum import IntEnum, Flag +from enum import IntEnum, IntFlag class NegotiationType(IntEnum): @@ -24,4 +24,17 @@ class NegotiationRequestFlags(IntEnum): NONE = 0x00 RESTRICTED_ADMIN_MODE_REQUIRED = 0x01 REDIRECTED_AUTHENTICATION_MODE_REQUIRED = 0x02 - CORRELATION_INFO_PRESENT = 0x08 \ No newline at end of file + 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 + diff --git a/pyrdp/mitm/ClipboardMITM.py b/pyrdp/mitm/ClipboardMITM.py index d90bb133d..23ce447b6 100644 --- a/pyrdp/mitm/ClipboardMITM.py +++ b/pyrdp/mitm/ClipboardMITM.py @@ -7,9 +7,10 @@ from logging import LoggerAdapter from pyrdp.core import decodeUTF16LE -from pyrdp.enum import ClipboardFormatNumber, ClipboardMessageFlags, ClipboardMessageType, PlayerPDUType +from pyrdp.enum import ClipboardFormatNumber, ClipboardMessageFlags, ClipboardMessageType, \ + PlayerPDUType from pyrdp.layer import ClipboardLayer -from pyrdp.logging.StatCounter import StatCounter, STAT +from pyrdp.logging.StatCounter import STAT, StatCounter from pyrdp.pdu import ClipboardPDU, FormatDataRequestPDU, FormatDataResponsePDU from pyrdp.recording import Recorder @@ -65,7 +66,7 @@ def handlePDU(self, pdu: ClipboardPDU, destination: ClipboardLayer): if pdu.msgFlags == ClipboardMessageFlags.CB_RESPONSE_OK: clipboardData = self.decodeClipboardData(pdu.requestedFormatData) - self.log.info("Clipboard data: %(clipboardData)r", {"clipboardData": clipboardData}) + self.log.info("Clipboard data: %(clipboardData)s", {"clipboardData": clipboardData}) self.recorder.record(pdu, PlayerPDUType.CLIPBOARD_DATA) if self.forwardNextDataResponse: diff --git a/pyrdp/mitm/X224MITM.py b/pyrdp/mitm/X224MITM.py index 9fd5f6131..1a2100536 100644 --- a/pyrdp/mitm/X224MITM.py +++ b/pyrdp/mitm/X224MITM.py @@ -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: @@ -111,7 +112,9 @@ 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 diff --git a/pyrdp/parser/rdp/connection.py b/pyrdp/parser/rdp/connection.py index 30e5f3f13..c50137561 100644 --- a/pyrdp/parser/rdp/connection.py +++ b/pyrdp/parser/rdp/connection.py @@ -12,12 +12,16 @@ from Crypto.Util.number import bytes_to_long, long_to_bytes from pyrdp.core import decodeUTF16LE, encodeUTF16LE, StrictStream, Uint16LE, Uint32LE, Uint8 -from pyrdp.enum import ColorDepth, ConnectionDataType, ConnectionType, DesktopOrientation, EncryptionLevel, \ - EncryptionMethod, HighColorDepth, RDPVersion, ServerCertificateType +from pyrdp.enum import ColorDepth, ConnectionDataType, ConnectionType, DesktopOrientation, \ + EncryptionLevel, \ + EncryptionMethod, RDPVersion, ServerCertificateType from pyrdp.exceptions import ParsingError, UnknownPDUTypeError 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): @@ -32,6 +36,7 @@ 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 = { @@ -39,7 +44,7 @@ def __init__(self): ConnectionDataType.CLIENT_SECURITY: self.writeClientSecurityData, ConnectionDataType.CLIENT_NETWORK: self.writeClientNetworkData, ConnectionDataType.CLIENT_CLUSTER: self.writeClientClusterData, - + ConnectionDataType.CLIENT_MONITOR: self.writeClientMonitorData, } def parse(self, data: bytes) -> ClientDataPDU: @@ -51,26 +56,33 @@ def parse(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) @@ -81,9 +93,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) @@ -157,6 +169,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. @@ -176,19 +208,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): @@ -243,6 +286,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): """ diff --git a/pyrdp/pdu/rdp/connection.py b/pyrdp/pdu/rdp/connection.py index da9b5b77b..cb793e1cc 100644 --- a/pyrdp/pdu/rdp/connection.py +++ b/pyrdp/pdu/rdp/connection.py @@ -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 @@ -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,