diff --git a/rdpy/core/type.py b/rdpy/core/type.py index 8090814..804ca5d 100644 --- a/rdpy/core/type.py +++ b/rdpy/core/type.py @@ -257,7 +257,7 @@ class SimpleType(Type, CallableValue): @raise InvalidSize: if there is not enough data in stream """ if s.dataLen() < self._typeSize: - raise InvalidSize("Stream is too small to read expected Simple") + raise InvalidSize("Stream is too small to read expected SimpleType") self.value = struct.unpack(self._structFormat, s.read(self._typeSize))[0] def mask(self): @@ -498,6 +498,9 @@ class CompositeType(Type): @summary: Call sizeof on each sub type @return: sum of sizeof of each Type attributes """ + if self._is_readed and not self._readLen is None: + return self._readLen.value + size = 0 for name in self._typeName: size += sizeof(self.__dict__[name]) @@ -808,7 +811,7 @@ class String(Type, CallableValue): self.value = s.getvalue()[s.pos:] else: self.value = "" - while self.value[-len(self._until):] != self._until or s.dataLen() == 0: + while self.value[-len(self._until):] != self._until and s.dataLen() != 0: self.value += s.read(1) else: self.value = s.read(self._readLen.value) diff --git a/rdpy/protocol/rdp/gcc.py b/rdpy/protocol/rdp/gcc.py index 6bbae3f..458a82a 100644 --- a/rdpy/protocol/rdp/gcc.py +++ b/rdpy/protocol/rdp/gcc.py @@ -256,17 +256,17 @@ class ClientCoreData(CompositeType): self.keyboardType = UInt32Le(KeyboardType.IBM_101_102_KEYS) self.keyboardSubType = UInt32Le(0) self.keyboardFnKeys = UInt32Le(12) - self.imeFileName = String("\x00"*64, readLen = UInt8(64)) - self.postBeta2ColorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP) - self.clientProductId = UInt16Le(1) - self.serialNumber = UInt32Le(0) - self.highColorDepth = UInt16Le(HighColor.HIGH_COLOR_24BPP) - self.supportedColorDepths = UInt16Le(Support.RNS_UD_15BPP_SUPPORT | Support.RNS_UD_16BPP_SUPPORT | Support.RNS_UD_24BPP_SUPPORT | Support.RNS_UD_32BPP_SUPPORT) - self.earlyCapabilityFlags = UInt16Le(CapabilityFlags.RNS_UD_CS_SUPPORT_ERRINFO_PDU) - self.clientDigProductId = String("\x00"*64, readLen = UInt8(64)) - self.connectionType = UInt8() - self.pad1octet = UInt8() - self.serverSelectedProtocol = UInt32Le() + self.imeFileName = String("\x00"*64, readLen = UInt8(64), optional = True) + self.postBeta2ColorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP, optional = True) + self.clientProductId = UInt16Le(1, optional = True) + self.serialNumber = UInt32Le(0, optional = True) + self.highColorDepth = UInt16Le(HighColor.HIGH_COLOR_24BPP, optional = True) + self.supportedColorDepths = UInt16Le(Support.RNS_UD_15BPP_SUPPORT | Support.RNS_UD_16BPP_SUPPORT | Support.RNS_UD_24BPP_SUPPORT | Support.RNS_UD_32BPP_SUPPORT, optional = True) + self.earlyCapabilityFlags = UInt16Le(CapabilityFlags.RNS_UD_CS_SUPPORT_ERRINFO_PDU, optional = True) + self.clientDigProductId = String("\x00"*64, readLen = UInt8(64), optional = True) + self.connectionType = UInt8(optional = True) + self.pad1octet = UInt8(optional = True) + self.serverSelectedProtocol = UInt32Le(optional = True) class ServerCoreData(CompositeType): """ @@ -278,7 +278,8 @@ class ServerCoreData(CompositeType): def __init__(self, readLen = None): CompositeType.__init__(self, readLen = readLen) self.rdpVersion = UInt32Le(Version.RDP_VERSION_5_PLUS) - self.clientRequestedProtocol = UInt32Le() + self.clientRequestedProtocol = UInt32Le(optional = True) + self.earlyCapabilityFlags = UInt32Le(optional = True) class ClientSecurityData(CompositeType): """ @@ -353,8 +354,8 @@ class ProprietaryServerCertificate(CompositeType): self.wPublicKeyBlobLen = UInt16Le(lambda:sizeof(self.PublicKeyBlob)) self.PublicKeyBlob = RSAPublicKey(readLen = self.wPublicKeyBlobLen) self.wSignatureBlobType = UInt16Le(0x0008, constant = True) - self.wSignatureBlobLen = UInt16Le(lambda:(sizeof(self.SignatureBlob) - 8)) - self.SignatureBlob = String(readLen = self.wSignatureBlobLen) + self.wSignatureBlobLen = UInt16Le(lambda:(sizeof(self.SignatureBlob) + sizeof(self.padding))) + self.SignatureBlob = String(readLen = UInt16Le(lambda:(self.wSignatureBlobLen.value - sizeof(self.padding)))) self.padding = String(b"\x00" * 8, readLen = UInt8(8)) def getPublicKey(self): @@ -380,7 +381,7 @@ class ProprietaryServerCertificate(CompositeType): md5Digest = md5.new() md5Digest.update(s.getvalue()) - return md5Digest.digest() + "\x00" + "\xff" * 46 + "\x01" + return md5Digest.digest() + "\x00" + "\xff" * 45 + "\x01" def sign(self): """ @@ -607,6 +608,6 @@ def writeConferenceCreateResponse(serverData): return (per.writeChoice(0), per.writeObjectIdentifier(t124_02_98_oid), per.writeLength(len(serverDataStream.getvalue()) + 14), per.writeChoice(0x14), - per.writeInteger16(0x79F3, 1001), per.writeInteger(1), per.writeEnumerates(16), + per.writeInteger16(0x79F3, 1001), per.writeInteger(1), per.writeEnumerates(0), per.writeNumberOfSet(1), per.writeChoice(0xc0), per.writeOctetStream(h221_sc_key, 4), per.writeOctetStream(serverDataStream.getvalue())) \ No newline at end of file diff --git a/rdpy/protocol/rdp/lic.py b/rdpy/protocol/rdp/lic.py index 44aac63..7d6e70f 100644 --- a/rdpy/protocol/rdp/lic.py +++ b/rdpy/protocol/rdp/lic.py @@ -97,8 +97,8 @@ class LicenseBinaryBlob(CompositeType): @summary: Blob use by license manager to exchange security data @see: http://msdn.microsoft.com/en-us/library/cc240481.aspx """ - def __init__(self, blobType = BinaryBlobType.BB_ANY_BLOB): - CompositeType.__init__(self) + def __init__(self, blobType = BinaryBlobType.BB_ANY_BLOB, optional = False): + CompositeType.__init__(self, optional = optional) self.wBlobType = UInt16Le(blobType, constant = True if blobType != BinaryBlobType.BB_ANY_BLOB else False) self.wBlobLen = UInt16Le(lambda:sizeof(self.blobData)) self.blobData = String(readLen = self.wBlobLen) @@ -110,11 +110,11 @@ class LicensingErrorMessage(CompositeType): """ _MESSAGE_TYPE_ = MessageType.ERROR_ALERT - def __init__(self): - CompositeType.__init__(self) + def __init__(self, readLen = None): + CompositeType.__init__(self, readLen = readLen) self.dwErrorCode = UInt32Le() self.dwStateTransition = UInt32Le() - self.blob = LicenseBinaryBlob(BinaryBlobType.BB_ERROR_BLOB) + self.blob = LicenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB) class ProductInformation(CompositeType): """ @@ -159,8 +159,8 @@ class ServerLicenseRequest(CompositeType): """ _MESSAGE_TYPE_ = MessageType.LICENSE_REQUEST - def __init__(self): - CompositeType.__init__(self) + def __init__(self, readLen = None): + CompositeType.__init__(self, readLen = readLen) self.serverRandom = String("\x00" * 32, readLen = UInt8(32)) self.productInfo = ProductInformation() self.keyExchangeList = LicenseBinaryBlob(BinaryBlobType.BB_KEY_EXCHG_ALG_BLOB) @@ -175,8 +175,8 @@ class ClientNewLicenseRequest(CompositeType): """ _MESSAGE_TYPE_ = MessageType.NEW_LICENSE_REQUEST - def __init__(self): - CompositeType.__init__(self) + def __init__(self, readLen = None): + CompositeType.__init__(self, readLen = readLen) #RSA and must be only RSA self.preferredKeyExchangeAlg = UInt32Le(0x00000001, constant = True) #pure microsoft client ;-) @@ -194,8 +194,8 @@ class ServerPlatformChallenge(CompositeType): """ _MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE - def __init__(self): - CompositeType.__init__(self) + def __init__(self, readLen = None): + CompositeType.__init__(self, readLen = readLen) self.connectFlags = UInt32Le() self.encryptedPlatformChallenge = LicenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB) self.MACData = String(readLen = UInt8(16)) @@ -207,8 +207,8 @@ class ClientPLatformChallengeResponse(CompositeType): """ _MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE_RESPONSE - def __init__(self): - CompositeType.__init__(self) + def __init__(self, readLen = None): + CompositeType.__init__(self, readLen = readLen) self.encryptedPlatformChallengeResponse = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB) self.encryptedHWID = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB) self.MACData = String(readLen = UInt8(16)) @@ -231,7 +231,7 @@ class LicPacket(CompositeType): """ for c in [LicensingErrorMessage, ServerLicenseRequest, ClientNewLicenseRequest, ServerPlatformChallenge, ClientPLatformChallengeResponse]: if self.bMsgtype.value == c._MESSAGE_TYPE_: - return c() + return c(readLen = self.wMsgSize - 4) log.debug("unknown license message : %s"%self.bMsgtype.value) return String() diff --git a/rdpy/protocol/rdp/mcs.py b/rdpy/protocol/rdp/mcs.py index a004dda..b108879 100644 --- a/rdpy/protocol/rdp/mcs.py +++ b/rdpy/protocol/rdp/mcs.py @@ -515,15 +515,16 @@ class Server(MCSLayer): self.readDomainParams(data) self.readDomainParams(data) self._clientSettings = gcc.readConferenceCreateRequest(Stream(ber.readOctetString(data))) - - i = 1 - for channelDef in self._clientSettings.getBlock(gcc.MessageType.CS_NET).channelDefArray._array: - self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelIdArray._array.append(UInt16Le(i + Channel.MCS_GLOBAL_CHANNEL)) - #if channel can be handle by serve add it - for serverChannelDef, layer in self._virtualChannels: - if channelDef.name == serverChannelDef.name: - self._channels[i + Channel.MCS_GLOBAL_CHANNEL] = layer - i += 1 + + if not self._clientSettings.CS_NET is None: + i = 1 + for channelDef in self._clientSettings.CS_NET.channelDefArray._array: + self._serverSettings.SC_NET.channelIdArray._array.append(UInt16Le(i + Channel.MCS_GLOBAL_CHANNEL)) + #if channel can be handle by serve add it + for serverChannelDef, layer in self._virtualChannels: + if channelDef.name == serverChannelDef.name: + self._channels[i + Channel.MCS_GLOBAL_CHANNEL] = layer + i += 1 self.sendConnectResponse() self.setNextState(self.recvErectDomainRequest) diff --git a/rdpy/protocol/rdp/pdu/data.py b/rdpy/protocol/rdp/pdu/data.py index 885941d..81765c9 100644 --- a/rdpy/protocol/rdp/pdu/data.py +++ b/rdpy/protocol/rdp/pdu/data.py @@ -429,7 +429,8 @@ class ShareControlHeader(CompositeType): #share control header self.totalLength = UInt16Le(totalLength) self.pduType = UInt16Le(pduType) - self.PDUSource = UInt16Le(userId) + #for xp sp3 and deactiveallpdu PDUSource may not be present + self.PDUSource = UInt16Le(userId, optional = True) class ShareDataHeader(CompositeType): """ @@ -519,7 +520,9 @@ class DeactiveAllPDU(CompositeType): _PDUTYPE_ = PDUType.PDUTYPE_DEACTIVATEALLPDU def __init__(self): - CompositeType.__init__(self) + #in old version this packet is empty i don't know + #and not specified + CompositeType.__init__(self, optional = True) self.shareId = UInt32Le() self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor)) self.sourceDescriptor = String("rdpy", readLen = self.lengthSourceDescriptor) diff --git a/rdpy/protocol/rdp/pdu/layer.py b/rdpy/protocol/rdp/pdu/layer.py index 377d53b..cbaf0ae 100644 --- a/rdpy/protocol/rdp/pdu/layer.py +++ b/rdpy/protocol/rdp/pdu/layer.py @@ -179,6 +179,9 @@ class Client(PDULayer): for cap in pdu.pduMessage.capabilitySets._array: self._serverCapabilities[cap.capabilitySetType] = cap + + #secure checksum cap here maybe protocol (another) design error + self._transport._enableSecureCheckSum = bool(self._serverCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags & caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM) self.sendConfirmActivePDU() #send synchronize @@ -312,7 +315,7 @@ class Client(PDULayer): generalCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability generalCapability.osMajorType.value = caps.MajorType.OSMAJORTYPE_WINDOWS generalCapability.osMinorType.value = caps.MinorType.OSMINORTYPE_WINDOWS_NT - generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR + generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR | caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM if not self._fastPathSender is None: generalCapability.extraFlags.value |= caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED @@ -413,6 +416,9 @@ class Server(PDULayer): #find use full flag self._clientFastPathSupported = bool(self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags.value & caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED) + #secure checksum cap here maybe protocol (another) design error + self._transport._enableSecureCheckSum = bool(self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags & caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM) + self.setNextState(self.recvClientSynchronizePDU) def recvClientSynchronizePDU(self, s): @@ -525,7 +531,7 @@ class Server(PDULayer): generalCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability generalCapability.osMajorType.value = caps.MajorType.OSMAJORTYPE_WINDOWS generalCapability.osMinorType.value = caps.MinorType.OSMINORTYPE_WINDOWS_NT - generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR | caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED + generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR | caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED | caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM inputCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_INPUT].capability inputCapability.inputFlags.value = caps.InputFlags.INPUT_FLAG_SCANCODES | caps.InputFlags.INPUT_FLAG_MOUSEX diff --git a/rdpy/protocol/rdp/sec.py b/rdpy/protocol/rdp/sec.py index 5eca307..78c5d81 100644 --- a/rdpy/protocol/rdp/sec.py +++ b/rdpy/protocol/rdp/sec.py @@ -164,12 +164,12 @@ def macData(macSaltKey, data): md5Digest = md5.new() #encode length - s = Stream() - s.writeType(UInt32Le(len(data))) + dataLength = Stream() + dataLength.writeType(UInt32Le(len(data))) sha1Digest.update(macSaltKey) sha1Digest.update("\x36" * 40) - sha1Digest.update(s.getvalue()) + sha1Digest.update(dataLength.getvalue()) sha1Digest.update(data) sha1Sig = sha1Digest.digest() @@ -180,6 +180,38 @@ def macData(macSaltKey, data): return md5Digest.digest() +def macSaltedData(macSaltKey, data, encryptionCount): + """ + @see: https://msdn.microsoft.com/en-us/library/cc240789.aspx + @param macSaltKey: {str} mac key + @param data: {str} data to sign + @param encryptionCount: nb encrypted packet + @return: {str} signature + """ + sha1Digest = sha.new() + md5Digest = md5.new() + + #encode length + dataLengthS = Stream() + dataLengthS.writeType(UInt32Le(len(data))) + + encryptionCountS = Stream() + encryptionCountS.writeType(UInt32Le(encryptionCount)) + + sha1Digest.update(macSaltKey) + sha1Digest.update("\x36" * 40) + sha1Digest.update(dataLengthS.getvalue()) + sha1Digest.update(data) + sha1Digest.update(encryptionCountS.getvalue()) + + sha1Sig = sha1Digest.digest() + + md5Digest.update(macSaltKey) + md5Digest.update("\x5c" * 48) + md5Digest.update(sha1Sig) + + return md5Digest.digest() + def tempKey(initialKey, currentKey): """ @see: http://msdn.microsoft.com/en-us/library/cc240792.aspx @@ -342,6 +374,9 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP #True if classic encryption is enable self._enableEncryption = False + #Enable Secure Mac generation + self._enableSecureCheckSum = False + #initialise decrypt and encrypt keys self._macKey = None self._initialDecrytKey = None @@ -358,10 +393,11 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP self._encryptRc4 = None - def readEncryptedPayload(self, s): + def readEncryptedPayload(self, s, saltedMacGeneration): """ @summary: decrypt basic RDP security payload @param s: {Stream} encrypted stream + @param saltedMacGeneration: {bool} use salted mac generation @return: {Stream} decrypted """ #if update is needed @@ -378,18 +414,22 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP decrypted = rc4.crypt(self._decryptRc4, encryptedPayload.value) #ckeck signature - if macData(self._macKey, decrypted)[:8] != signature.value: - raise InvalidExpectedDataException("Bad packet signature") + if not saltedMacGeneration and macData(self._macKey, decrypted)[:8] != signature.value: + raise InvalidExpectedDataException("bad signature") + + if saltedMacGeneration and macSaltedData(self._macKey, decrypted, self._nbDecryptedPacket)[:8] != signature.value: + raise InvalidExpectedDataException("bad signature") #count self._nbDecryptedPacket += 1 return Stream(decrypted) - def writeEncryptedPayload(self, data): + def writeEncryptedPayload(self, data, saltedMacGeneration): """ @summary: sign and crypt data - @param s: {Stream} raw stream + @param data: {Type} raw stream + @param saltedMacGeneration: {bool} use salted mac generation @return: {Tuple} (signature, encryptedData) """ if self._nbEncryptedPacket == 4096: @@ -400,9 +440,14 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP self._nbEncryptedPacket = 0 self._nbEncryptedPacket += 1 + s = Stream() s.writeType(data) - return (String(macData(self._macKey, s.getvalue())[:8]), String(rc4.crypt(self._encryptRc4, s.getvalue()))) + + if saltedMacGeneration: + return (String(macSaltedData(self._macKey, s.getvalue(), self._nbEncryptedPacket - 1)[:8]), String(rc4.crypt(self._encryptRc4, s.getvalue()))) + else: + return (String(macData(self._macKey, s.getvalue())[:8]), String(rc4.crypt(self._encryptRc4, s.getvalue()))) def recv(self, data): """ @@ -419,7 +464,7 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP data.readType((securityFlag, securityFlagHi)) if securityFlag.value & SecurityFlag.SEC_ENCRYPT: - data = self.readEncryptedPayload(data) + data = self.readEncryptedPayload(data, securityFlag.value & SecurityFlag.SEC_SECURE_CHECKSUM) self._presentation.recv(data) @@ -433,7 +478,12 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP self._transport.send(data) return - self.sendFlagged(SecurityFlag.SEC_ENCRYPT, data) + flag = SecurityFlag.SEC_ENCRYPT + + if self._enableSecureCheckSum: + flag |= SecurityFlag.SEC_SECURE_CHECKSUM + + self.sendFlagged(flag, data) def sendFlagged(self, flag, data): """ @@ -444,7 +494,7 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP @param data: {Type | Tuple} """ if flag & SecurityFlag.SEC_ENCRYPT: - data = self.writeEncryptedPayload(data) + data = self.writeEncryptedPayload(data, flag & SecurityFlag.SEC_SECURE_CHECKSUM) self._transport.send((UInt16Le(flag), UInt16Le(), data)) def recvFastPath(self, secFlag, fastPathS): @@ -454,7 +504,7 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP @param fastPathS: {Stream} """ if self._enableEncryption and secFlag & tpkt.SecFlags.FASTPATH_OUTPUT_ENCRYPTED: - fastPathS = self.readEncryptedPayload(fastPathS) + fastPathS = self.readEncryptedPayload(fastPathS, secFlag & tpkt.SecFlags.FASTPATH_OUTPUT_SECURE_CHECKSUM) self._fastPathPresentation.recvFastPath(secFlag, fastPathS) @@ -472,7 +522,11 @@ class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastP """ if self._enableEncryption: secFlag |= tpkt.SecFlags.FASTPATH_OUTPUT_ENCRYPTED - fastPathS = self.writeEncryptedPayload(fastPathS) + + if self._enableSecureCheckSum: + secFlag |= tpkt.SecFlags.FASTPATH_OUTPUT_SECURE_CHECKSUM + + fastPathS = self.writeEncryptedPayload(fastPathS, self._enableSecureCheckSum) self._fastPathTransport.sendFastPath(secFlag, fastPathS) @@ -661,7 +715,7 @@ class Server(SecLayer): raise InvalidExpectedDataException("Waiting info packet") if securityFlag.value & SecurityFlag.SEC_ENCRYPT: - s = self.readEncryptedPayload(s) + s = self.readEncryptedPayload(s, securityFlag.value & SecurityFlag.SEC_SECURE_CHECKSUM) s.readType(self._info) #next state send error license diff --git a/setup.py b/setup.py index 5256fbc..7295ea9 100644 --- a/setup.py +++ b/setup.py @@ -4,20 +4,20 @@ import setuptools from distutils.core import setup, Extension setup(name='rdpy', - version='1.2.1', + version='1.2.2', description='Remote Desktop Protocol in Python', long_description=""" RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (Client and Server side). RDPY is built over the event driven network engine Twisted. RDPY provide RDP and VNC binaries : - \t-RDP Man In The Middle proxy which record session - \t-RDP Honeypot - \t-RDP screenshoter - \t-RDP client - \t-VNC client - \t-VNC screenshoter - \t-RSS Player + - RDP Man In The Middle proxy which record session + - RDP Honeypot + - RDP screenshoter + - RDP client + - VNC client + - VNC screenshoter + - RSS Player """, author='Sylvain Peyrefitte', author_email='citronneur@gmail.com',