From ccf01561508cabfe812a076d1c3e7878078feb6f Mon Sep 17 00:00:00 2001 From: speyrefitte Date: Mon, 8 Dec 2014 18:15:28 +0100 Subject: [PATCH] add basic RDP secure layer... --- bin/rdpy-rdpclient.py | 11 ++ rdpy/base/error.py | 10 ++ rdpy/network/layer.py | 8 +- rdpy/protocol/rdp/gcc.py | 63 ++++++++- rdpy/protocol/rdp/{pdu => }/lic.py | 6 +- rdpy/protocol/rdp/mcs.py | 77 +++++----- rdpy/protocol/rdp/pdu/data.py | 22 --- rdpy/protocol/rdp/pdu/layer.py | 46 +++--- rdpy/protocol/rdp/pdu/sec.py | 113 --------------- rdpy/protocol/rdp/{pdu => }/rc4.py | 0 rdpy/protocol/rdp/rdp.py | 20 ++- rdpy/protocol/rdp/sec.py | 216 +++++++++++++++++++++++++++++ rdpy/protocol/rdp/x224.py | 11 +- rdpy/protocol/rfb/rfb.py | 3 +- 14 files changed, 391 insertions(+), 215 deletions(-) rename rdpy/protocol/rdp/{pdu => }/lic.py (98%) delete mode 100644 rdpy/protocol/rdp/pdu/sec.py rename rdpy/protocol/rdp/{pdu => }/rc4.py (100%) create mode 100644 rdpy/protocol/rdp/sec.py diff --git a/bin/rdpy-rdpclient.py b/bin/rdpy-rdpclient.py index 8d49361..e27061f 100755 --- a/bin/rdpy-rdpclient.py +++ b/bin/rdpy-rdpclient.py @@ -26,6 +26,7 @@ import sys, os, getopt, socket from PyQt4 import QtGui, QtCore from rdpy.ui.qt4 import RDPClientQt from rdpy.protocol.rdp import rdp +from rdpy.base.error import RDPSecurityNegoFail import rdpy.base.log as log log._LOG_LEVEL = log.Level.INFO @@ -54,6 +55,7 @@ class RDPClientQtFactory(rdp.ClientFactory): self._keyboardLayout = keyboardLayout self._optimized = optimized self._w = None + self._basicRDPSecurity = False def buildObserver(self, controller, addr): """ @@ -80,6 +82,9 @@ class RDPClientQtFactory(rdp.ClientFactory): controller.setHostname(socket.gethostname()) if self._optimized: controller.setPerformanceSession() + + if self._basicRDPSecurity: + controller.setRDPBasicSecurity() return client @@ -92,6 +97,12 @@ class RDPClientQtFactory(rdp.ClientFactory): @param connector: twisted connector use for rdp connection (use reconnect to restart connection) @param reason: str use to advertise reason of lost connection """ + #try reconnect with basic RDP security + if reason.type == RDPSecurityNegoFail and not self._basicRDPSecurity: + self._basicRDPSecurity = True + connector.connect() + return + QtGui.QMessageBox.warning(self._w, "Warning", "Lost connection : %s"%reason) reactor.stop() app.exit() diff --git a/rdpy/base/error.py b/rdpy/base/error.py index ce0d728..d5eb33f 100644 --- a/rdpy/base/error.py +++ b/rdpy/base/error.py @@ -90,3 +90,13 @@ class ErrorReportedFromPeer(Exception): @param message: message show when exception is raised """ Exception.__init__(self, message) + +class RDPSecurityNegoFail(Exception): + """ + @summary: Raise when security nego fail + """ + def __init__(self, message = ""): + """ + @param message: message show when exception is raised + """ + Exception.__init__(self, message) diff --git a/rdpy/network/layer.py b/rdpy/network/layer.py index 561be1d..b514483 100644 --- a/rdpy/network/layer.py +++ b/rdpy/network/layer.py @@ -129,10 +129,11 @@ class RawLayerClientFactory(protocol.ClientFactory): """ raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "buildRawLayer", "RawLayerClientFactory")) - def connectionLost(self, rawlayer): + def connectionLost(self, rawlayer, reason): """ @summary: Override this method to handle connection lost @param rawlayer: rawLayer that cause connectionLost event + @param reason: twisted reason """ raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "connectionLost", "RawLayerClientFactory")) @@ -156,10 +157,11 @@ class RawLayerServerFactory(protocol.ClientFactory): """ raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener")) - def connectionLost(self, rawlayer): + def connectionLost(self, rawlayer, reason): """ @summary: Override this method to handle connection lost @param rawlayer: rawLayer that cause connectionLost event + @param reason: twisted reason """ raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener")) @@ -218,7 +220,7 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender): @summary: Call from twisted engine when protocol is closed @param reason: str represent reason of close connection """ - self._factory.connectionLost(self) + self._factory.connectionLost(self, reason) def close(self): """ diff --git a/rdpy/protocol/rdp/gcc.py b/rdpy/protocol/rdp/gcc.py index 0e74d68..ed36575 100644 --- a/rdpy/protocol/rdp/gcc.py +++ b/rdpy/protocol/rdp/gcc.py @@ -188,6 +188,13 @@ class KeyboardLayout(object): DUTCH = 0x00000413 NORWEGIAN = 0x00000414 +class CertificateType(object): + """ + @see: http://msdn.microsoft.com/en-us/library/cc240521.aspx + """ + CERT_CHAIN_VERSION_1 = 0x00000001 + CERT_CHAIN_VERSION_2 = 0x00000002 + class DataBlock(CompositeType): """ Block settings @@ -268,7 +275,7 @@ class ClientSecurityData(CompositeType): def __init__(self, readLen = None): CompositeType.__init__(self, readLen = readLen) - self.encryptionMethods = UInt32Le(Encryption.ENCRYPTION_FLAG_128BIT) + self.encryptionMethods = UInt32Le(Encryption.ENCRYPTION_FLAG_40BIT | Encryption.ENCRYPTION_FLAG_56BIT | Encryption.ENCRYPTION_FLAG_128BIT) self.extEncryptionMethods = UInt32Le() class ServerSecurityData(CompositeType): @@ -285,7 +292,59 @@ class ServerSecurityData(CompositeType): self.serverRandomLen = UInt32Le(0x00000020, constant = True, conditional = lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel == 0)) self.serverCertLen = UInt32Le(lambda:sizeof(self.serverCertificate), conditional = lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel == 0)) self.serverRandom = String(readLen = self.serverRandomLen, conditional = lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel == 0)) - self.serverCertificate = String(readLen = self.serverCertLen, conditional = lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel == 0)) + self.serverCertificate = ServerCertificate(readLen = self.serverCertLen, conditional = lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel == 0)) + +class ServerCertificate(CompositeType): + """ + @summary: Server certificate structure + @see: http://msdn.microsoft.com/en-us/library/cc240521.aspx + """ + def __init__(self, readLen, conditional): + CompositeType.__init__(self, readLen = readLen, conditional = conditional) + self.dwVersion = UInt32Le() + + def CertificateFactory(): + """ + Closure for capability factory + """ + for c in [ProprietaryServerCertificate]: + if self.dwVersion.value & 0x7fffffff == c._TYPE_: + return c() + raise InvalidExpectedDataException("unknown certificate type : %s (RDPY doesn't support x.509 format please repport a bug)"%hex(self.dwVersion.value)) + + self.certData = FactoryType(CertificateFactory) + +class ProprietaryServerCertificate(CompositeType): + """ + @summary: microsoft proprietary certificate + @see: http://msdn.microsoft.com/en-us/library/cc240519.aspx + """ + _TYPE_ = CertificateType.CERT_CHAIN_VERSION_1 + + def __init__(self): + CompositeType.__init__(self) + self.dwSigAlgId = UInt32Le(0x00000001, constant = True) + self.dwKeyAlgId = UInt32Le(0x00000001, constant = True) + self.wPublicKeyBlobType = UInt16Le(0x0006, constant = True) + 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)) + self.SignatureBlob = String(readLen = self.wSignatureBlobLen) + +class RSAPublicKey(CompositeType): + """ + @see: http://msdn.microsoft.com/en-us/library/cc240520.aspx + """ + def __init__(self, readLen): + CompositeType.__init__(self, readLen = readLen) + self.magic = UInt32Le(0x31415352, constant = True) + self.keylen = UInt32Le(lambda:sizeof(self.modulus)) + self.bitlen = UInt32Le() + self.datalen = UInt32Le() + self.pubExp = UInt32Le() + self.modulus = String(readLen = lambda:(self.keylen - 8)) + self.padding = String("\x00" * 8, constant = True) class ChannelDef(CompositeType): """ diff --git a/rdpy/protocol/rdp/pdu/lic.py b/rdpy/protocol/rdp/lic.py similarity index 98% rename from rdpy/protocol/rdp/pdu/lic.py rename to rdpy/protocol/rdp/lic.py index 82715b0..e0d8561 100644 --- a/rdpy/protocol/rdp/pdu/lic.py +++ b/rdpy/protocol/rdp/lic.py @@ -25,7 +25,7 @@ from rdpy.network.type import CompositeType, UInt8, UInt16Le, UInt32Le, String, sizeof, FactoryType, ArrayType, Stream from rdpy.base.error import InvalidExpectedDataException import rdpy.base.log as log -import sec, rc4 +import rc4, sec class MessageType(object): """ @@ -271,8 +271,8 @@ class LicenseManager(object): """ @summary: generate key for license session """ - masterSecret = sec.generateMicrosoftKey(self._preMasterSecret, self._clientRandom, self._serverRandom) - sessionKeyBlob = sec.generateMicrosoftKey(masterSecret, self._serverRandom, self._clientRandom) + masterSecret = sec.generateMicrosoftKeyABBCCC(self._preMasterSecret, self._clientRandom, self._serverRandom) + sessionKeyBlob = sec.generateMicrosoftKeyABBCCC(masterSecret, self._serverRandom, self._clientRandom) self._macSalt = sessionKeyBlob[:16] self._licenseKey = sec.finalHash(sessionKeyBlob[16:32], self._clientRandom, self._serverRandom) diff --git a/rdpy/protocol/rdp/mcs.py b/rdpy/protocol/rdp/mcs.py index 18e7380..e93deb1 100644 --- a/rdpy/protocol/rdp/mcs.py +++ b/rdpy/protocol/rdp/mcs.py @@ -30,18 +30,18 @@ from rdpy.base.error import InvalidExpectedDataException, InvalidValue, InvalidS from rdpy.protocol.rdp.ber import writeLength import rdpy.base.log as log -import ber, gcc, per +import ber, gcc, per, sec class Message(object): """ - Message type + @summary: Message type """ MCS_TYPE_CONNECT_INITIAL = 0x65 MCS_TYPE_CONNECT_RESPONSE = 0x66 class DomainMCSPDU: """ - Domain MCS PDU header + @summary: Domain MCS PDU header """ ERECT_DOMAIN_REQUEST = 1 DISCONNECT_PROVIDER_ULTIMATUM = 8 @@ -54,40 +54,42 @@ class DomainMCSPDU: class Channel: """ - Channel id of main channels use in RDP + @summary: Channel id of main channels use in RDP """ MCS_GLOBAL_CHANNEL = 1003 MCS_USERCHANNEL_BASE = 1001 class MCSLayer(LayerAutomata): """ - Multiple Channel Service layer + @summary: Multiple Channel Service layer the main layer of RDP protocol is why he can do everything and more! """ class MCSProxySender(Layer, IStreamSender): """ - Proxy use to set as transport layer for upper channel + @summary: Proxy use to set as transport layer for upper channel use to abstract channel id for presentation layer """ - def __init__(self, mcs, channelId): + def __init__(self, presentation, mcs, channelId): """ + @param presentation: presentation layer @param mcs: MCS layer use as proxy @param channelId: channel id for presentation layer """ + Layer.__init__(self, presentation) self._mcs = mcs self._channelId = channelId def send(self, data): """ - A send proxy function, use channel id and specific + @summary: A send proxy function, use channel id and specific send function of MCS layer """ self._mcs.send(self._channelId, data) def close(self): """ - Close wrapped layer + @summary: Close wrapped layer """ self._mcs.close() @@ -139,7 +141,7 @@ class MCSLayer(LayerAutomata): def close(self): """ - Send disconnect provider ultimatum + @summary: Send disconnect provider ultimatum """ self._transport.send((UInt8(self.writeMCSPDUHeader(DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM, 1)), per.writeEnumerates(0x80), String("\x00" * 6))) @@ -147,7 +149,7 @@ class MCSLayer(LayerAutomata): def allChannelConnected(self): """ - All channels are connected to MCS layer + @summary: All channels are connected to MCS layer Send connect to upper channel And prepare MCS layer to receive data """ @@ -156,12 +158,11 @@ class MCSLayer(LayerAutomata): #try connection on all requested channel for (channelId, layer) in self._channels.iteritems(): #use proxy for each channel - layer._transport = MCSLayer.MCSProxySender(self, channelId) - layer.connect() + MCSLayer.MCSProxySender(layer, self, channelId).connect() def send(self, channelId, data): """ - Specific send function for channelId + @summary: Specific send function for channelId @param channelId: Channel use to send @param data: message to send """ @@ -173,7 +174,7 @@ class MCSLayer(LayerAutomata): def recvData(self, data): """ - Main receive method + @summary: Main receive method @param data: Stream """ opcode = UInt8() @@ -205,7 +206,7 @@ class MCSLayer(LayerAutomata): def writeDomainParams(self, maxChannels, maxUsers, maxTokens, maxPduSize): """ - Write a special domain parameter structure + @summary: Write a special domain parameter structure use in connection sequence @param maxChannels: number of MCS channel use @param maxUsers: number of MCS user used (1) @@ -220,7 +221,7 @@ class MCSLayer(LayerAutomata): def writeMCSPDUHeader(self, mcsPdu, options = 0): """ - Write MCS PDU header + @summary: Write MCS PDU header @param mcsPdu: PDU code @param options: option contains in header @return: UInt8 @@ -229,7 +230,7 @@ class MCSLayer(LayerAutomata): def readMCSPDUHeader(self, opcode, mcsPdu): """ - Read mcsPdu header and return options parameter + @summary: Read mcsPdu header and return options parameter @param opcode: opcode @param mcsPdu: mcsPdu will be checked @return: true if opcode is correct @@ -238,7 +239,7 @@ class MCSLayer(LayerAutomata): def readDomainParams(self, s): """ - Read domain parameters structure + @summary: Read domain parameters structure @return: (max_channels, max_users, max_tokens, max_pdu_size) """ if not ber.readUniversalTag(s, ber.Tag.BER_TAG_SEQUENCE, True): @@ -256,7 +257,7 @@ class MCSLayer(LayerAutomata): class Client(MCSLayer): """ - Client automata of multiple channel service layer + @summary: Client automata of multiple channel service layer """ def __init__(self, presentation, virtualChannels = []): """ @@ -272,7 +273,7 @@ class Client(MCSLayer): def connect(self): """ - Connect message in client automata case + @summary: Connect message in client automata case Send ConnectInitial Wait ConnectResponse """ @@ -286,7 +287,7 @@ class Client(MCSLayer): def connectNextChannel(self): """ - Send sendChannelJoinRequest message on next disconnect channel + @summary: Send sendChannelJoinRequest message on next disconnect channel Send channel request or connect upper layer if all channels are connected Wait channel confirm """ @@ -314,7 +315,7 @@ class Client(MCSLayer): def recvConnectResponse(self, data): """ - Receive MCS connect response from server + @summary: Receive MCS connect response from server Send Erect domain Request Send Attach User Request Wait Attach User Confirm @@ -340,7 +341,7 @@ class Client(MCSLayer): def recvAttachUserConfirm(self, data): """ - Receive an attach user confirm + @summary: Receive an attach user confirm Send Connect Channel @param data: Stream """ @@ -359,7 +360,7 @@ class Client(MCSLayer): def recvChannelJoinConfirm(self, data): """ - Receive a channel join confirm from server + @summary: Receive a channel join confirm from server client automata function @param data: Stream """ @@ -390,7 +391,7 @@ class Client(MCSLayer): def sendConnectInitial(self): """ - Send connect initial packet + @summary: Send connect initial packet client automata function """ ccReq = gcc.writeConferenceCreateRequest(self._clientSettings) @@ -406,7 +407,7 @@ class Client(MCSLayer): def sendErectDomainRequest(self): """ - Send a formated erect domain request for RDP connection + @summary: Send a formated erect domain request for RDP connection """ self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ERECT_DOMAIN_REQUEST)), per.writeInteger(0), @@ -414,13 +415,13 @@ class Client(MCSLayer): def sendAttachUserRequest(self): """ - Send a formated attach user request for RDP connection + @summary: Send a formated attach user request for RDP connection """ self._transport.send(self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ATTACH_USER_REQUEST))) def sendChannelJoinRequest(self, channelId): """ - Send a formated Channel join request from client to server + @summary: Send a formated Channel join request from client to server client automata function @param channelId: id of channel requested """ @@ -430,7 +431,7 @@ class Client(MCSLayer): class Server(MCSLayer): """ - Server automata of multiple channel service layer + @summary: Server automata of multiple channel service layer """ def __init__(self, presentation, virtualChannels = []): """ @@ -443,7 +444,7 @@ class Server(MCSLayer): def connect(self): """ - Connect message for server automata + @summary: Connect message for server automata Wait Connect Initial """ self._serverSettings.getBlock(gcc.MessageType.SC_CORE).clientRequestedProtocol.value = self._transport._requestedProtocol @@ -451,7 +452,7 @@ class Server(MCSLayer): def recvConnectInitial(self, data): """ - Receive MCS connect initial from client + @summary: Receive MCS connect initial from client Send Connect Response Wait Erect Domain Request @param data: Stream @@ -482,7 +483,7 @@ class Server(MCSLayer): def recvErectDomainRequest(self, data): """ - Receive erect domain request + @summary: Receive erect domain request Wait Attach User Request @param data: Stream """ @@ -499,7 +500,7 @@ class Server(MCSLayer): def recvAttachUserRequest(self, data): """ - Receive Attach user request + @summary: Receive Attach user request Send Attach User Confirm Wait Channel Join Request @param data: Stream @@ -515,7 +516,7 @@ class Server(MCSLayer): def recvChannelJoinRequest(self, data): """ - Receive for each client channel a request + @summary: Receive for each client channel a request Send Channel Join Confirm or Connect upper layer when all channel are joined @param data: Stream @@ -540,7 +541,7 @@ class Server(MCSLayer): def sendConnectResponse(self): """ - Send connect response + @summary: Send connect response """ ccReq = gcc.writeConferenceCreateResponse(self._serverSettings) ccReqStream = Stream() @@ -552,7 +553,7 @@ class Server(MCSLayer): def sendAttachUserConfirm(self): """ - Send attach user confirm + @summary: Send attach user confirm """ self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ATTACH_USER_CONFIRM), 2), per.writeEnumerates(0), @@ -560,7 +561,7 @@ class Server(MCSLayer): def sendChannelJoinConfirm(self, channelId, confirm): """ - Send a confirm channel (or not) to client + @summary: Send a confirm channel (or not) to client @param channelId: id of channel @param confirm: connection state """ diff --git a/rdpy/protocol/rdp/pdu/data.py b/rdpy/protocol/rdp/pdu/data.py index d88e28f..fab5bd6 100644 --- a/rdpy/protocol/rdp/pdu/data.py +++ b/rdpy/protocol/rdp/pdu/data.py @@ -27,28 +27,6 @@ from rdpy.base.error import InvalidExpectedDataException import rdpy.base.log as log import caps, order -class SecurityFlag(object): - """ - Microsoft security flags - @see: http://msdn.microsoft.com/en-us/library/cc240579.aspx - """ - SEC_EXCHANGE_PKT = 0x0001 - SEC_TRANSPORT_REQ = 0x0002 - RDP_SEC_TRANSPORT_RSP = 0x0004 - SEC_ENCRYPT = 0x0008 - SEC_RESET_SEQNO = 0x0010 - SEC_IGNORE_SEQNO = 0x0020 - SEC_INFO_PKT = 0x0040 - SEC_LICENSE_PKT = 0x0080 - SEC_LICENSE_ENCRYPT_CS = 0x0200 - SEC_LICENSE_ENCRYPT_SC = 0x0200 - SEC_REDIRECTION_PKT = 0x0400 - SEC_SECURE_CHECKSUM = 0x0800 - SEC_AUTODETECT_REQ = 0x1000 - SEC_AUTODETECT_RSP = 0x2000 - SEC_HEARTBEAT = 0x4000 - SEC_FLAGSHI_VALID = 0x8000 - class InfoFlag(object): """ Client capabilities informations diff --git a/rdpy/protocol/rdp/pdu/layer.py b/rdpy/protocol/rdp/pdu/layer.py index 7aa0db1..8ce6799 100644 --- a/rdpy/protocol/rdp/pdu/layer.py +++ b/rdpy/protocol/rdp/pdu/layer.py @@ -33,17 +33,17 @@ import lic, data, caps class PDUClientListener(object): """ - Interface for PDU client automata listener + @summary: Interface for PDU client automata listener """ def onReady(self): """ - Event call when PDU layer is ready to send events + @summary: Event call when PDU layer is ready to send events """ raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUClientListener")) def onUpdate(self, rectangles): """ - call when a bitmap data is received from update PDU + @summary: call when a bitmap data is received from update PDU @param rectangles: [pdu.BitmapData] struct """ raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onUpdate", "PDUClientListener")) @@ -56,24 +56,24 @@ class PDUClientListener(object): class PDUServerListener(object): """ - Interface for PDU server automata listener + @summary: Interface for PDU server automata listener """ def onReady(self): """ - Event call when PDU layer is ready to send update + @summary: Event call when PDU layer is ready to send update """ raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUServerListener")) def onSlowPathInput(self, slowPathInputEvents): """ - Event call when slow path input are available + @summary: Event call when slow path input are available @param slowPathInputEvents: [data.SlowPathInputEvent] """ raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSlowPathInput", "PDUServerListener")) class PDULayer(LayerAutomata): """ - Global channel for MCS that handle session + @summary: Global channel for MCS that handle session identification user, licensing management, and capabilities exchange """ def __init__(self): @@ -112,21 +112,21 @@ class PDULayer(LayerAutomata): def sendPDU(self, pduMessage): """ - Send a PDU data to transport layer + @summary: Send a PDU data to transport layer @param pduMessage: PDU message """ self._transport.send(data.PDU(self._transport.getUserId(), pduMessage)) def sendDataPDU(self, pduData): """ - Send an PDUData to transport layer + @summary: Send an PDUData to transport layer @param pduData: PDU data message """ self.sendPDU(data.DataPDU(pduData, self._shareId)) class Client(PDULayer, tpkt.IFastPathListener): """ - Client automata of PDU layer + @summary: Client automata of PDU layer """ def __init__(self, listener): """ @@ -140,7 +140,7 @@ class Client(PDULayer, tpkt.IFastPathListener): def connect(self): """ - Connect message in client automata + @summary: Connect message in client automata Send INfo packet (credentials) Wait License info """ @@ -152,7 +152,7 @@ class Client(PDULayer, tpkt.IFastPathListener): def close(self): """ - Send PDU close packet and call close method on transport method + @summary: Send PDU close packet and call close method on transport method """ self._transport.close() #self.sendDataPDU(data.ShutdownRequestPDU()) @@ -166,7 +166,7 @@ class Client(PDULayer, tpkt.IFastPathListener): def recvLicenceInfo(self, s): """ - Read license info packet and check if is a valid client info + @summary: Read license info packet and check if is a valid client info Wait Demand Active PDU @param s: Stream """ @@ -183,7 +183,7 @@ class Client(PDULayer, tpkt.IFastPathListener): def recvDemandActivePDU(self, s): """ - Receive demand active PDU which contains + @summary: Receive demand active PDU which contains Server capabilities. In this version of RDPY only Restricted group of capabilities are used. Send Confirm Active PDU @@ -212,7 +212,7 @@ class Client(PDULayer, tpkt.IFastPathListener): def recvServerSynchronizePDU(self, s): """ - Receive from server + @summary: Receive from server Wait Control Cooperate PDU @param s: Stream from transport layer """ @@ -228,7 +228,7 @@ class Client(PDULayer, tpkt.IFastPathListener): def recvServerControlCooperatePDU(self, s): """ - Receive control cooperate PDU from server + @summary: Receive control cooperate PDU from server Wait Control Granted PDU @param s: Stream from transport layer """ @@ -244,7 +244,7 @@ class Client(PDULayer, tpkt.IFastPathListener): def recvServerControlGrantedPDU(self, s): """ - Receive last control PDU the granted control PDU + @summary: Receive last control PDU the granted control PDU Wait Font map PDU @param s: Stream from transport layer """ @@ -260,7 +260,7 @@ class Client(PDULayer, tpkt.IFastPathListener): def recvServerFontMapPDU(self, s): """ - Last useless connection packet from server to client + @summary: Last useless connection packet from server to client Wait any PDU @param s: Stream from transport layer """ @@ -278,7 +278,7 @@ class Client(PDULayer, tpkt.IFastPathListener): def recvPDU(self, s): """ - Main receive function after connection sequence + @summary: Main receive function after connection sequence @param s: Stream from transport layer """ pdu = data.PDU() @@ -293,7 +293,7 @@ class Client(PDULayer, tpkt.IFastPathListener): def recvFastPath(self, fastPathS): """ - Implement IFastPathListener interface + @summary: Implement IFastPathListener interface Fast path is needed by RDP 8.0 @param fastPathS: Stream that contain fast path data """ @@ -304,7 +304,7 @@ class Client(PDULayer, tpkt.IFastPathListener): def readDataPDU(self, dataPDU): """ - read a data PDU object + @summary: read a data PDU object @param dataPDU: DataPDU object """ if dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU: @@ -321,7 +321,7 @@ class Client(PDULayer, tpkt.IFastPathListener): def readUpdateDataPDU(self, updateDataPDU): """ - Read an update data PDU data + @summary: Read an update data PDU data dispatch update data @param: UpdateDataPDU object """ @@ -330,7 +330,7 @@ class Client(PDULayer, tpkt.IFastPathListener): def sendInfoPkt(self): """ - Send a logon info packet + @summary: Send a logon info packet client automata data """ self._transport.send((UInt16Le(data.SecurityFlag.SEC_INFO_PKT), UInt16Le(), self._info)) diff --git a/rdpy/protocol/rdp/pdu/sec.py b/rdpy/protocol/rdp/pdu/sec.py deleted file mode 100644 index 2202350..0000000 --- a/rdpy/protocol/rdp/pdu/sec.py +++ /dev/null @@ -1,113 +0,0 @@ -# -# Copyright (c) 2014 Sylvain Peyrefitte -# -# This file is part of rdpy. -# -# rdpy is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# - -""" -Some use full methods for security in RDP -""" - -import sha, md5 -from rdpy.network.type import CompositeType, Stream, UInt32Le, String, sizeof - -def saltedHash(inputData, salt, salt1, salt2): - """ - @summary: Generate particular signature from combination of sha1 and md5 - @see: http://msdn.microsoft.com/en-us/library/cc241992.aspx - @param inputData: strange input (see doc) - @param salt: salt for context call - @param salt1: another salt (ex : client random) - @param salt2: another another salt (ex: server random) - @return : MD5(Salt + SHA1(Input + Salt + Salt1 + Salt2)) - """ - sha1Digest = sha.new() - md5Digest = md5.new() - - sha1Digest.update(inputData) - sha1Digest.update(salt[:48]) - sha1Digest.update(salt1) - sha1Digest.update(salt2) - sha1Sig = sha1Digest.digest() - - md5Digest.update(salt[:48]) - md5Digest.update(sha1Sig) - - return md5Digest.digest() - -def finalHash(key, random1, random2): - """ - @summary: MD5(in0[:16] + in1[:32] + in2[:32]) - @param key: in 16 - @param random1: in 32 - @param random2: in 32 - @return MD5(in0[:16] + in1[:32] + in2[:32]) - """ - md5Digest = md5.new() - md5Digest.update(key) - md5Digest.update(random1) - md5Digest.update(random2) - return md5Digest.digest() - -def generateMicrosoftKey(secret, random1, random2): - """ - @summary: Generate master secret - @param secret: secret - @param clientRandom : client random - @param serverRandom : server random - """ - return saltedHash("A", secret, random1, random2) + saltedHash("BB", secret, random1, random2) + saltedHash("CCC", secret, random1, random2) - -def macData(macSaltKey, data): - """ - @see: http://msdn.microsoft.com/en-us/library/cc241995.aspx - """ - sha1Digest = sha.new() - md5Digest = md5.new() - - #encode length - s = Stream() - s.writeType(UInt32Le(len(data))) - - sha1Digest.update(macSaltKey) - sha1Digest.update("\x36" * 40) - sha1Digest.update(s.getvalue()) - sha1Digest.update(data) - - sha1Sig = sha1Digest.digest() - - md5Digest.update(macSaltKey) - md5Digest.update("\x5c" * 48) - md5Digest.update(sha1Sig) - - return md5Digest.digest() - -class ClientSecurityExchangePDU(CompositeType): - """ - @summary: contain client random for basic security - @see: http://msdn.microsoft.com/en-us/library/cc240472.aspx - """ - def __init__(self): - CompositeType.__init__(self) - self.length = UInt32Le(lambda:(sizeof(self) - 4)) - self.encryptedClientRandom = String(readLen = self.length) - -class SecManager(object): - """ - @summary: Basic RDP security manager - """ - def __init__(self): - pass \ No newline at end of file diff --git a/rdpy/protocol/rdp/pdu/rc4.py b/rdpy/protocol/rdp/rc4.py similarity index 100% rename from rdpy/protocol/rdp/pdu/rc4.py rename to rdpy/protocol/rdp/rc4.py diff --git a/rdpy/protocol/rdp/rdp.py b/rdpy/protocol/rdp/rdp.py index f8c09d0..389fdfa 100644 --- a/rdpy/protocol/rdp/rdp.py +++ b/rdpy/protocol/rdp/rdp.py @@ -27,7 +27,7 @@ import pdu.layer import pdu.data import pdu.caps import rdpy.base.log as log -import tpkt, x224, mcs, gcc +import tpkt, x224, mcs, gcc, sec class RDPClientController(pdu.layer.PDUClientListener): """ @@ -38,8 +38,10 @@ class RDPClientController(pdu.layer.PDUClientListener): self._clientObserver = [] #PDU layer self._pduLayer = pdu.layer.Client(self) + #secure layer + self._secLayer = sec.SecLayer(self._pduLayer) #multi channel service - self._mcsLayer = mcs.Client(self._pduLayer) + self._mcsLayer = mcs.Client(self._secLayer) #transport pdu layer self._x224Layer = x224.Client(self._mcsLayer) #transport packet (protocol layer) @@ -129,6 +131,12 @@ class RDPClientController(pdu.layer.PDUClientListener): self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).clientName.value = hostname[:15] + "\x00" * (15 - len(hostname)) self._pduLayer._licenceManager._hostname = hostname + def setRDPBasicSecurity(self): + """ + @summary: Request basic security + """ + self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_RDP + def addClientObserver(self, observer): """ @summary: Add observer to RDP protocol @@ -478,8 +486,9 @@ class RDPServerController(pdu.layer.PDUServerListener): class ClientFactory(layer.RawLayerClientFactory): """ @summary: Factory of Client RDP protocol + @param reason: twisted reason """ - def connectionLost(self, tpktLayer): + def connectionLost(self, tpktLayer, reason): #retrieve controller x224Layer = tpktLayer._presentation mcsLayer = x224Layer._presentation @@ -518,7 +527,10 @@ class ServerFactory(layer.RawLayerServerFactory): self._certificateFileName = certificateFileName self._colorDepth = colorDepth - def connectionLost(self, tpktLayer): + def connectionLost(self, tpktLayer, reason): + """ + @param reason: twisted reason + """ #retrieve controller x224Layer = tpktLayer._presentation mcsLayer = x224Layer._presentation diff --git a/rdpy/protocol/rdp/sec.py b/rdpy/protocol/rdp/sec.py new file mode 100644 index 0000000..9de75ec --- /dev/null +++ b/rdpy/protocol/rdp/sec.py @@ -0,0 +1,216 @@ +# +# Copyright (c) 2014 Sylvain Peyrefitte +# +# This file is part of rdpy. +# +# rdpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +""" +Some use full methods for security in RDP +""" + +import sha, md5, rsa, gcc, rc4 +from rdpy.network.type import CompositeType, Stream, UInt32Le, String, sizeof +from rdpy.network.layer import LayerAutomata, IStreamSender + +class SecurityFlag(object): + """ + @summary: Microsoft security flags + @see: http://msdn.microsoft.com/en-us/library/cc240579.aspx + """ + SEC_EXCHANGE_PKT = 0x0001 + SEC_TRANSPORT_REQ = 0x0002 + RDP_SEC_TRANSPORT_RSP = 0x0004 + SEC_ENCRYPT = 0x0008 + SEC_RESET_SEQNO = 0x0010 + SEC_IGNORE_SEQNO = 0x0020 + SEC_INFO_PKT = 0x0040 + SEC_LICENSE_PKT = 0x0080 + SEC_LICENSE_ENCRYPT_CS = 0x0200 + SEC_LICENSE_ENCRYPT_SC = 0x0200 + SEC_REDIRECTION_PKT = 0x0400 + SEC_SECURE_CHECKSUM = 0x0800 + SEC_AUTODETECT_REQ = 0x1000 + SEC_AUTODETECT_RSP = 0x2000 + SEC_HEARTBEAT = 0x4000 + SEC_FLAGSHI_VALID = 0x8000 + +def saltedHash(inputData, salt, salt1, salt2): + """ + @summary: Generate particular signature from combination of sha1 and md5 + @see: http://msdn.microsoft.com/en-us/library/cc241992.aspx + @param inputData: strange input (see doc) + @param salt: salt for context call + @param salt1: another salt (ex : client random) + @param salt2: another another salt (ex: server random) + @return : MD5(Salt + SHA1(Input + Salt + Salt1 + Salt2)) + """ + sha1Digest = sha.new() + md5Digest = md5.new() + + sha1Digest.update(inputData) + sha1Digest.update(salt[:48]) + sha1Digest.update(salt1) + sha1Digest.update(salt2) + sha1Sig = sha1Digest.digest() + + md5Digest.update(salt[:48]) + md5Digest.update(sha1Sig) + + return md5Digest.digest() + +def finalHash(key, random1, random2): + """ + @summary: MD5(in0[:16] + in1[:32] + in2[:32]) + @param key: in 16 + @param random1: in 32 + @param random2: in 32 + @return MD5(in0[:16] + in1[:32] + in2[:32]) + """ + md5Digest = md5.new() + md5Digest.update(key) + md5Digest.update(random1) + md5Digest.update(random2) + return md5Digest.digest() + +def generateMicrosoftKeyABBCCC(secret, random1, random2): + """ + @summary: Generate master secret + @param secret: secret + @param clientRandom : client random + @param serverRandom : server random + """ + return saltedHash("A", secret, random1, random2) + saltedHash("BB", secret, random1, random2) + saltedHash("CCC", secret, random1, random2) + +def generateMicrosoftKeyXYYZZZ(secret, random1, random2): + """ + @summary: Generate master secret + @param secret: secret + @param clientRandom : client random + @param serverRandom : server random + """ + return saltedHash("X", secret, random1, random2) + saltedHash("YY", secret, random1, random2) + saltedHash("ZZZ", secret, random1, random2) + + +def macData(macSaltKey, data): + """ + @see: http://msdn.microsoft.com/en-us/library/cc241995.aspx + """ + sha1Digest = sha.new() + md5Digest = md5.new() + + #encode length + s = Stream() + s.writeType(UInt32Le(len(data))) + + sha1Digest.update(macSaltKey) + sha1Digest.update("\x36" * 40) + sha1Digest.update(s.getvalue()) + sha1Digest.update(data) + + sha1Sig = sha1Digest.digest() + + md5Digest.update(macSaltKey) + md5Digest.update("\x5c" * 48) + md5Digest.update(sha1Sig) + + return md5Digest.digest() + +def bin2bn(b): + """ + @summary: convert binary string to bignum + @param b: binary string + @return bignum + """ + l = 0L + for ch in b: + l = (l<<8) | ord(ch) + return l + +def bn2bin(b): + s = bytearray() + i = (b.bit_length() + 7) / 8 + while i > 0: + s.append((b >> ((i - 1) * 8)) & 0xff) + i -= 1 + return s + + +class ClientSecurityExchangePDU(CompositeType): + """ + @summary: contain client random for basic security + @see: http://msdn.microsoft.com/en-us/library/cc240472.aspx + """ + def __init__(self): + CompositeType.__init__(self) + self.length = UInt32Le(lambda:(sizeof(self) - 4)) + self.encryptedClientRandom = String(readLen = self.length) + +class SecLayer(LayerAutomata, IStreamSender): + """ + @summary: Basic RDP security manager + This layer is Transparent as possible for upper layer + """ + def __init__(self, presentation): + LayerAutomata.__init__(self, presentation) + self._enableEncryption = False + + def connect(self): + """ + @summary: send client random + """ + self._enableEncryption = (self._transport.getGCCClientSettings.getBlock(gcc.MessageType.CS_CORE).serverSelectedProtocol == 0) + if not self._enableEncryption: + self._presentation.connect() + return + + #generate client random + self._clientRandom = rsa.randnum.read_random_bits(128) + self._serverRandom = self._transport.getGCCServerSettings().getBlock(gcc.MessageType.SC_SECURITY).serverRandom.value + self.generateKeys() + + #send client random encrypted with + certificate = self._transport.getGCCServerSettings().getBlock(gcc.MessageType.SC_SECURITY).serverCertificate.certData + serverPublicKey = rsa.PublicKey(bin2bn(certificate.PublicKeyBlob.modulus.value), certificate.PublicKeyBlob.pubExp.value) + + message = ClientSecurityExchangePDU() + message.encryptedClientRandom.value = rsa.encrypt(self._clientRandom, serverPublicKey) + self._transport.send(message) + self._presentation.connect() + + def generateKeys(self): + """ + @see: http://msdn.microsoft.com/en-us/library/cc240785.aspx + """ + preMasterSecret = self._clientRandom[:24] + self._serverRandom[:24] + masterSecret = generateMicrosoftKeyABBCCC(preMasterSecret, self._clientRandom, self._serverRandom) + self._sessionKey = generateMicrosoftKeyXYYZZZ(masterSecret, self._clientRandom, self._serverRandom) + + self._macKey128 = self._sessionKey[:16] + self._decrypt = finalHash(self._sessionKey[16:32], self._clientRandom, self._serverRandom) + self._encrypt = finalHash(self._sessionKey[32:48], self._clientRandom, self._serverRandom) + + def recv(self, data): + if not self._enableEncryption: + self._presentation.recv(data) + return + + def send(self, data): + if not self._enableEncryption: + self._presentation.recv(data) + return + + def sendInfoPkt(self, data): + self._transport.send() \ No newline at end of file diff --git a/rdpy/protocol/rdp/x224.py b/rdpy/protocol/rdp/x224.py index 6e0becb..b1fc2ba 100644 --- a/rdpy/protocol/rdp/x224.py +++ b/rdpy/protocol/rdp/x224.py @@ -26,8 +26,7 @@ RDP basic security is supported only on client side from rdpy.network.layer import LayerAutomata, IStreamSender from rdpy.network.type import UInt8, UInt16Le, UInt16Be, UInt32Le, CompositeType, sizeof, String -from rdpy.base.error import InvalidExpectedDataException -import rdpy.base.log as log +from rdpy.base.error import InvalidExpectedDataException, RDPSecurityNegoFail class MessageType(object): """ @@ -133,7 +132,7 @@ class X224Layer(LayerAutomata, IStreamSender): LayerAutomata.__init__(self, presentation) #default selectedProtocol is SSl #client requested selectedProtocol - self._requestedProtocol = Protocols.PROTOCOL_RDP + self._requestedProtocol = Protocols.PROTOCOL_RDP | Protocols.PROTOCOL_SSL #server selected selectedProtocol self._selectedProtocol = Protocols.PROTOCOL_SSL @@ -195,6 +194,9 @@ class Client(X224Layer): message = ServerConnectionConfirm() data.readType(message) + if message.protocolNeg.failureCode._is_readed: + raise RDPSecurityNegoFail("negotiation failure code %x"%message.protocolNeg.failureCode.value) + #check presence of negotiation response if message.protocolNeg._is_readed: self._selectedProtocol = message.protocolNeg.selectedProtocol.value @@ -205,9 +207,6 @@ class Client(X224Layer): if self._selectedProtocol in [ Protocols.PROTOCOL_HYBRID, Protocols.PROTOCOL_HYBRID_EX ]: raise InvalidExpectedDataException("RDPY doesn't support NLA security Layer") - if message.protocolNeg.failureCode._is_readed: - log.info("negotiation failure code %x"%message.protocolNeg.failureCode.value) - if self._selectedProtocol == Protocols.PROTOCOL_SSL: #_transport is TPKT and transport is TCP layer of twisted self._transport.transport.startTLS(ClientTLSContext()) diff --git a/rdpy/protocol/rfb/rfb.py b/rdpy/protocol/rfb/rfb.py index e444494..83acdb1 100644 --- a/rdpy/protocol/rfb/rfb.py +++ b/rdpy/protocol/rfb/rfb.py @@ -671,10 +671,11 @@ class ClientFactory(RawLayerClientFactory): self.buildObserver(controller, addr) return controller.getProtocol() - def connectionLost(self, rfblayer): + def connectionLost(self, rfblayer, reason): """ @summary: Override this method to handle connection lost @param rfblayer: rfblayer that cause connectionLost event + @param reason: twisted reason """ #call controller rfblayer._clientListener.onClose()