add basic RDP secure layer...

This commit is contained in:
speyrefitte
2014-12-08 18:15:28 +01:00
parent e7c6e61a25
commit ccf0156150
14 changed files with 391 additions and 215 deletions

View File

@@ -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()

View File

@@ -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)

View File

@@ -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):
"""

View File

@@ -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):
"""

View File

@@ -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)

View File

@@ -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
"""

View File

@@ -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

View File

@@ -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))

View File

@@ -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 <http://www.gnu.org/licenses/>.
#
"""
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

View File

@@ -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

216
rdpy/protocol/rdp/sec.py Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
#
"""
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()

View File

@@ -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())

View File

@@ -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()