From 7e98bc373a8034e318a2473f50d7eb4c5c307f64 Mon Sep 17 00:00:00 2001 From: speyrefitte Date: Mon, 17 Nov 2014 18:37:48 +0100 Subject: [PATCH 1/6] =?UTF-8?q?=C2=A0license=20automata=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rdpy/protocol/rdp/pdu/lic.py | 5 ++-- rdpy/protocol/rdp/sec.py | 52 ++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 rdpy/protocol/rdp/sec.py diff --git a/rdpy/protocol/rdp/pdu/lic.py b/rdpy/protocol/rdp/pdu/lic.py index adf6094..24bc4c3 100644 --- a/rdpy/protocol/rdp/pdu/lic.py +++ b/rdpy/protocol/rdp/pdu/lic.py @@ -226,6 +226,7 @@ def createNewLicenseRequest(serverLicenseRequest): Create new license request in response to server license request @see: http://msdn.microsoft.com/en-us/library/cc241989.aspx @see: http://msdn.microsoft.com/en-us/library/cc241918.aspx - @todo: need RDP license server """ - return LicPacket(message = ClientNewLicenseRequest()) \ No newline at end of file + message = ClientNewLicenseRequest() + + return LicPacket(message) \ No newline at end of file diff --git a/rdpy/protocol/rdp/sec.py b/rdpy/protocol/rdp/sec.py new file mode 100644 index 0000000..60e06c2 --- /dev/null +++ b/rdpy/protocol/rdp/sec.py @@ -0,0 +1,52 @@ +# +# 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 + +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 masterSecret(preMasterSecret, clientRandom, serverRandom): + """ + """ \ No newline at end of file From 4e7ea069063935e756e1445f7d81fb268b64ec6a Mon Sep 17 00:00:00 2001 From: citronneur Date: Thu, 20 Nov 2014 22:14:16 +0100 Subject: [PATCH 2/6] code refactoring + gitignore updtae --- .gitignore | 5 +++-- rdpy/protocol/rdp/tpkt.py | 7 +++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index b8f27d5..56750d3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,8 @@ .project .pydevproject README.md~ -rdpy/core/tmp/* *.so *.os -.sconsign.dblite +dist/* +build/* +rdpy.egg-info/* diff --git a/rdpy/protocol/rdp/tpkt.py b/rdpy/protocol/rdp/tpkt.py index fb64096..fda5791 100644 --- a/rdpy/protocol/rdp/tpkt.py +++ b/rdpy/protocol/rdp/tpkt.py @@ -76,8 +76,6 @@ class TPKT(RawLayer, IFastPathSender): @param fastPathListener: IFastPathListener """ RawLayer.__init__(self, presentation) - #last packet version read from header - self._lastPacketVersion = UInt8() #length may be coded on more than 1 bytes self._lastShortLength = UInt8() #fast path listener @@ -104,9 +102,10 @@ class TPKT(RawLayer, IFastPathSender): @param data: Stream received from twisted layer """ #first read packet version - data.readType(self._lastPacketVersion) + version = UInt8() + data.readType(version) #classic packet - if self._lastPacketVersion.value == Action.FASTPATH_ACTION_X224: + if version.value == Action.FASTPATH_ACTION_X224: #padding data.readType(UInt8()) #read end header From 5af9f0708abd82078f88a14e78cee582906e0f61 Mon Sep 17 00:00:00 2001 From: speyrefitte Date: Fri, 28 Nov 2014 17:54:49 +0100 Subject: [PATCH 3/6] bug fix on gui + add license neg (almost) --- rdpy/protocol/rdp/pdu/data.py | 2 +- rdpy/protocol/rdp/pdu/layer.py | 22 ++--- rdpy/protocol/rdp/pdu/lic.py | 175 ++++++++++++++++++++++++++------- rdpy/protocol/rdp/rc4.py | 56 +++++++++++ rdpy/protocol/rdp/rdp.py | 118 ++++++++++++++-------- rdpy/protocol/rdp/sec.py | 45 ++++++++- rdpy/ui/qt4.py | 98 +++++++++++------- 7 files changed, 389 insertions(+), 127 deletions(-) create mode 100644 rdpy/protocol/rdp/rc4.py diff --git a/rdpy/protocol/rdp/pdu/data.py b/rdpy/protocol/rdp/pdu/data.py index 8435fc6..d88e28f 100644 --- a/rdpy/protocol/rdp/pdu/data.py +++ b/rdpy/protocol/rdp/pdu/data.py @@ -930,7 +930,7 @@ class OrderUpdateDataPDU(CompositeType): self.pad2OctetsA = UInt16Le() self.numberOrders = UInt16Le(lambda:len(self.orderData._array)) self.pad2OctetsB = UInt16Le() - self.orderData = ArrayType(order.DrawingOrder, readLen = self.numberOrders) + self.orderData = ArrayType(order.PrimaryDrawingOrder, readLen = self.numberOrders) class BitmapCompressedDataHeader(CompositeType): """ diff --git a/rdpy/protocol/rdp/pdu/layer.py b/rdpy/protocol/rdp/pdu/layer.py index bcafdbc..c340109 100644 --- a/rdpy/protocol/rdp/pdu/layer.py +++ b/rdpy/protocol/rdp/pdu/layer.py @@ -136,6 +136,8 @@ class Client(PDULayer, tpkt.IFastPathListener): self._listener = listener #enable or not fast path self._fastPathSender = None + #todo generate hostname + self._licenceManager = lic.LicenseManager(self, self._info.userName.value, "wav-glw-009") def connect(self): """ @@ -177,18 +179,9 @@ class Client(PDULayer, tpkt.IFastPathListener): if not (securityFlag.value & data.SecurityFlag.SEC_LICENSE_PKT): raise InvalidExpectedDataException("Waiting license packet") - validClientPdu = lic.LicPacket() - s.readType(validClientPdu) - - if validClientPdu.bMsgtype.value == lic.MessageType.ERROR_ALERT and validClientPdu.licensingMessage.dwErrorCode.value == lic.ErrorCode.STATUS_VALID_CLIENT and validClientPdu.licensingMessage.dwStateTransition.value == lic.StateTransition.ST_NO_TRANSITION: + if self._licenceManager.recv(s): self.setNextState(self.recvDemandActivePDU) - #not tested because i can't buy RDP license server - elif validClientPdu.bMsgtype.value == lic.MessageType.LICENSE_REQUEST: - newLicenseReq = lic.createNewLicenseRequest(validClientPdu.licensingMessage) - self._transport.send((UInt16Le(data.SecurityFlag.SEC_LICENSE_PKT), UInt16Le(), newLicenseReq)) - else: - raise InvalidExpectedDataException("Not a valid license packet") - + def recvDemandActivePDU(self, s): """ Receive demand active PDU which contains @@ -343,6 +336,13 @@ class Client(PDULayer, tpkt.IFastPathListener): """ self._transport.send((UInt16Le(data.SecurityFlag.SEC_INFO_PKT), UInt16Le(), self._info)) + def sendLicensePacket(self, licPkt): + """ + @summary: send license packet + @param licPktr: license packet + """ + self._transport.send((UInt16Le(data.SecurityFlag.SEC_LICENSE_PKT), UInt16Le(), licPkt)) + def sendConfirmActivePDU(self): """ Send all client capabilities diff --git a/rdpy/protocol/rdp/pdu/lic.py b/rdpy/protocol/rdp/pdu/lic.py index 24bc4c3..2a81bef 100644 --- a/rdpy/protocol/rdp/pdu/lic.py +++ b/rdpy/protocol/rdp/pdu/lic.py @@ -18,17 +18,20 @@ # """ -RDP extended license +@summary: RDP extended license @see: http://msdn.microsoft.com/en-us/library/cc241880.aspx """ -from rdpy.network.type import CompositeType, UInt8, UInt16Le, UInt32Le, String, sizeof, FactoryType, ArrayType +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 rdpy.protocol.rdp.sec as sec +import rdpy.protocol.rdp.rc4 as rc4 class MessageType(object): """ - License packet message type + @summary: License packet message type """ LICENSE_REQUEST = 0x01 PLATFORM_CHALLENGE = 0x02 @@ -42,7 +45,7 @@ class MessageType(object): class ErrorCode(object): """ - License error message code + @summary: License error message code @see: http://msdn.microsoft.com/en-us/library/cc240482.aspx """ ERR_INVALID_SERVER_CERTIFICATE = 0x00000001 @@ -57,7 +60,7 @@ class ErrorCode(object): class StateTransition(object): """ - Automata state transition + @summary: Automata state transition @see: http://msdn.microsoft.com/en-us/library/cc240482.aspx """ ST_TOTAL_ABORT = 0x00000001 @@ -67,9 +70,10 @@ class StateTransition(object): class BinaryBlobType(object): """ - Binary blob data type + @summary: Binary blob data type @see: http://msdn.microsoft.com/en-us/library/cc240481.aspx """ + BB_ANY_BLOB = 0x0000 BB_DATA_BLOB = 0x0001 BB_RANDOM_BLOB = 0x0002 BB_CERTIFICATE_BLOB = 0x0003 @@ -82,25 +86,25 @@ class BinaryBlobType(object): class Preambule(object): """ - Preambule version + @summary: Preambule version """ PREAMBLE_VERSION_2_0 = 0x2 PREAMBLE_VERSION_3_0 = 0x3 class LicenseBinaryBlob(CompositeType): """ - Blob use by license manager to exchange security data + @summary: Blob use by license manager to exchange security data @see: http://msdn.microsoft.com/en-us/library/cc240481.aspx """ - def __init__(self, blobType = 0): + def __init__(self, blobType = BinaryBlobType.BB_ANY_BLOB): CompositeType.__init__(self) - self.wBlobType = UInt16Le(blobType, constant = True) + 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) class LicensingErrorMessage(CompositeType): """ - License error message + @summary: License error message @see: http://msdn.microsoft.com/en-us/library/cc240482.aspx """ _MESSAGE_TYPE_ = MessageType.ERROR_ALERT @@ -113,7 +117,7 @@ class LicensingErrorMessage(CompositeType): class ProductInformation(CompositeType): """ - License server product information + @summary: License server product information @see: http://msdn.microsoft.com/en-us/library/cc241915.aspx """ def __init__(self): @@ -121,15 +125,15 @@ class ProductInformation(CompositeType): self.dwVersion = UInt32Le() self.cbCompanyName = UInt32Le(lambda:sizeof(self.pbCompanyName)) #may contain "Microsoft Corporation" from server microsoft - self.pbCompanyName = String(readLen = self.cbCompanyName) + self.pbCompanyName = String(readLen = self.cbCompanyName, unicode = True) self.cbProductId = UInt32Le(lambda:sizeof(self.pbProductId)) #may contain "A02" from microsoft license server - self.pbProductId = String(readLen = self.cbProductId) + self.pbProductId = String(readLen = self.cbProductId, unicode = True) class Scope(CompositeType): """ - Use in license nego + @summary: Use in license nego @see: http://msdn.microsoft.com/en-us/library/cc241917.aspx """ def __init__(self): @@ -138,7 +142,7 @@ class Scope(CompositeType): class ScopeList(CompositeType): """ - Use in license nego + @summary: Use in license nego @see: http://msdn.microsoft.com/en-us/library/cc241916.aspx """ def __init__(self): @@ -148,8 +152,8 @@ class ScopeList(CompositeType): class ServerLicenseRequest(CompositeType): """ - Send by server to signal license request - server -> client + @summary: Send by server to signal license request + server -> client @see: http://msdn.microsoft.com/en-us/library/cc241914.aspx """ _MESSAGE_TYPE_ = MessageType.LICENSE_REQUEST @@ -164,8 +168,8 @@ class ServerLicenseRequest(CompositeType): class ClientNewLicenseRequest(CompositeType): """ - Send by client to ask new license for client. - RDPY doesn'support license reuse, need it in futur version + @summary: Send by client to ask new license for client. + RDPY doesn'support license reuse, need it in futur version @see: http://msdn.microsoft.com/en-us/library/cc241918.aspx """ _MESSAGE_TYPE_ = MessageType.NEW_LICENSE_REQUEST @@ -181,6 +185,32 @@ class ClientNewLicenseRequest(CompositeType): self.encryptedPreMasterSecret = LicenseBinaryBlob(BinaryBlobType.BB_RANDOM_BLOB) self.ClientUserName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_USER_NAME_BLOB) self.ClientMachineName = LicenseBinaryBlob(BinaryBlobType.BB_CLIENT_MACHINE_NAME_BLOB) + +class ServerPlatformChallenge(CompositeType): + """ + @summary: challenge send from server to client + @see: http://msdn.microsoft.com/en-us/library/cc241921.aspx + """ + _MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE + + def __init__(self): + CompositeType.__init__(self) + self.connectFlags = UInt32Le() + self.encryptedPlatformChallenge = LicenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB) + self.MACData = String(readLen = UInt8(16)) + +class ClientPLatformChallengeResponse(CompositeType): + """ + @summary: client challenge response + @see: http://msdn.microsoft.com/en-us/library/cc241922.aspx + """ + _MESSAGE_TYPE_ = MessageType.PLATFORM_CHALLENGE_RESPONSE + + def __init__(self): + CompositeType.__init__(self) + self.encryptedPlatformChallengeResponse = LicenseBinaryBlob(BinaryBlobType.BB_ENCRYPTED_DATA_BLOB) + self.encryptedHWID = LicenseBinaryBlob(BinaryBlobType.BB_ENCRYPTED_DATA_BLOB) + self.MACData = String(readLen = UInt8(16)) class LicPacket(CompositeType): """ @@ -198,7 +228,7 @@ class LicPacket(CompositeType): factory for message nego Use in read mode """ - for c in [LicensingErrorMessage, ServerLicenseRequest, ClientNewLicenseRequest]: + for c in [LicensingErrorMessage, ServerLicenseRequest, ClientNewLicenseRequest, ServerPlatformChallenge, ClientPLatformChallengeResponse]: if self.bMsgtype.value == c._MESSAGE_TYPE_: return c() log.debug("unknown license message : %s"%self.bMsgtype.value) @@ -210,23 +240,94 @@ class LicPacket(CompositeType): raise InvalidExpectedDataException("Try to send an invalid license message") self.licensingMessage = message - + def createValidClientLicensingErrorMessage(): + """ + @summary: Create a licensing error message that accept client + server automata message + """ + message = LicensingErrorMessage() + message.dwErrorCode.value = ErrorCode.STATUS_VALID_CLIENT + message.dwStateTransition.value = StateTransition.ST_NO_TRANSITION + return LicPacket(message = message) + +class LicenseManager(object): """ - Create a licensing error message that accept client - server automata message + @summary: handle license automata + @see: http://msdn.microsoft.com/en-us/library/cc241890.aspx """ - message = LicensingErrorMessage() - message.dwErrorCode.value = ErrorCode.STATUS_VALID_CLIENT - message.dwStateTransition.value = StateTransition.ST_NO_TRANSITION - return LicPacket(message = message) - -def createNewLicenseRequest(serverLicenseRequest): - """ - Create new license request in response to server license request - @see: http://msdn.microsoft.com/en-us/library/cc241989.aspx - @see: http://msdn.microsoft.com/en-us/library/cc241918.aspx - """ - message = ClientNewLicenseRequest() + def __init__(self, transport, username, hostname): + """ + @param transport: layer use to send packet + """ + self._clientRandom = "\x00" * 32 + self._serverRandom = None + self._serverEncryptedChallenge = None + self._transport = transport + self._username = username + self._hostname = hostname + + def generateKeys(self): + """ + @summary: generate key for license session + """ + self._masterSecret = sec.generateMicrosoftKey("\x00" * 64, self._clientRandom, self._serverRandom) + self._sessionKeyBlob = sec.generateMicrosoftKey(self._masterSecret, self._serverRandom, self._clientRandom) + self._macSalt = self._sessionKeyBlob[:16] + self._licenseKey = sec.md5_16_32_32(self._sessionKeyBlob[16:], self._clientRandom, self._serverRandom) + + def recv(self, s): + """ + @summary: receive license packet from PDU layer + @return true when license automata is finish + """ + licPacket = LicPacket() + s.readType(licPacket) + + #end of automata + if licPacket.bMsgtype.value == MessageType.ERROR_ALERT and licPacket.licensingMessage.dwErrorCode.value == ErrorCode.STATUS_VALID_CLIENT and licPacket.licensingMessage.dwStateTransition.value == StateTransition.ST_NO_TRANSITION: + return True + + elif licPacket.bMsgtype.value == MessageType.LICENSE_REQUEST: + self._serverRandom = licPacket.licensingMessage.serverRandom.value + self.generateKeys() + self.sendClientNewLicenseRequest() + + elif licPacket.bMsgtype.value == MessageType.PLATFORM_CHALLENGE: + self._serverEncryptedChallenge = licPacket.licensingMessage.encryptedPlatformChallenge.blobData.value + self.sendClientChallengeResponse() + + else: + raise InvalidExpectedDataException("Not a valid license packet") + - return LicPacket(message) \ No newline at end of file + def sendClientNewLicenseRequest(self): + """ + @summary: Create new license request in response to server license request + @see: http://msdn.microsoft.com/en-us/library/cc241989.aspx + @see: http://msdn.microsoft.com/en-us/library/cc241918.aspx + """ + message = ClientNewLicenseRequest() + message.clientRandom.value = self._clientRandom + message.encryptedPreMasterSecret.blobData = String("\x00" * (64 + 8)) + message.ClientMachineName.blobData = String(self._hostname + "\x00") + message.ClientUserName.blobData = String(self._username + "\x00") + self._transport.sendLicensePacket(LicPacket(message)) + + def sendClientChallengeResponse(self): + #it should be TEST in unicode format + serverChallenge = rc4.crypt(self._licenseKey, self._serverEncryptedChallenge) + + #generate hwid + s = Stream() + s.writeType((UInt32Le(2), String(self._username + self._hostname + "\x00" * 20))) + hwid = s.getvalue()[:20] + + signature = sec.macData(self._macSalt, serverChallenge + hwid) + + message = ClientPLatformChallengeResponse() + message.encryptedPlatformChallengeResponse.blobData.value = self._serverEncryptedChallenge + message.encryptedHWID.blobData.value = rc4.crypt(self._licenseKey, hwid) + message.MACData.value = signature + + self._transport.sendLicensePacket(LicPacket(message)) \ No newline at end of file diff --git a/rdpy/protocol/rdp/rc4.py b/rdpy/protocol/rdp/rc4.py new file mode 100644 index 0000000..476bfa3 --- /dev/null +++ b/rdpy/protocol/rdp/rc4.py @@ -0,0 +1,56 @@ +""" + Copyright (C) 2012 Bo Zhu http://about.bozhu.me + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +""" + + +def KSA(key): + keylength = len(key) + + S = range(256) + + j = 0 + for i in range(256): + j = (j + S[i] + key[i % keylength]) % 256 + S[i], S[j] = S[j], S[i] # swap + + return S + + +def PRGA(S): + i = 0 + j = 0 + while True: + i = (i + 1) % 256 + j = (j + S[i]) % 256 + S[i], S[j] = S[j], S[i] # swap + + K = S[(S[i] + S[j]) % 256] + yield K + + +def RC4(key): + S = KSA(key) + return PRGA(S) + +def crypt(key, plaintext): + keystream = RC4([ord(c) for c in key]) + + return "".join([chr(ord(c) ^ keystream.next()) for c in plaintext]) \ No newline at end of file diff --git a/rdpy/protocol/rdp/rdp.py b/rdpy/protocol/rdp/rdp.py index 657e68e..2576011 100644 --- a/rdpy/protocol/rdp/rdp.py +++ b/rdpy/protocol/rdp/rdp.py @@ -68,13 +68,13 @@ class RDPClientController(pdu.layer.PDUClientListener): def setPerformanceSession(self): """ - Set particular flag in RDP stack to avoid wall-paper, theme, menu animation etc... + @summary: Set particular flag in RDP stack to avoid wall-paper, theme, menu animation etc... """ self._pduLayer._info.extendedInfo.performanceFlags.value = pdu.data.PerfFlag.PERF_DISABLE_WALLPAPER | pdu.data.PerfFlag.PERF_DISABLE_MENUANIMATIONS | pdu.data.PerfFlag.PERF_DISABLE_CURSOR_SHADOW | pdu.data.PerfFlag.PERF_DISABLE_THEMING | pdu.data.PerfFlag.PERF_DISABLE_FULLWINDOWDRAG def setScreen(self, width, height): """ - Set screen dim of session + @summary: Set screen dim of session @param width: width in pixel of screen @param height: height in pixel of screen """ @@ -84,7 +84,7 @@ class RDPClientController(pdu.layer.PDUClientListener): def setUsername(self, username): """ - Set the username for session + @summary: Set the username for session @param username: username of session """ #username in PDU info packet @@ -92,7 +92,7 @@ class RDPClientController(pdu.layer.PDUClientListener): def setPassword(self, password): """ - Set password for session + @summary: Set password for session @param password: password of session """ self.setAutologon() @@ -100,7 +100,7 @@ class RDPClientController(pdu.layer.PDUClientListener): def setDomain(self, domain): """ - Set the windows domain of session + @summary: Set the windows domain of session @param domain: domain of session """ self._pduLayer._info.domain.value = domain @@ -113,14 +113,14 @@ class RDPClientController(pdu.layer.PDUClientListener): def addClientObserver(self, observer): """ - Add observer to RDP protocol + @summary: Add observer to RDP protocol @param observer: new observer to add """ self._clientObserver.append(observer) def removeClientObserver(self, observer): """ - Remove observer to RDP protocol stack + @summary: Remove observer to RDP protocol stack @param observer: observer to remove """ for i in range(0, len(self._clientObserver)): @@ -130,7 +130,7 @@ class RDPClientController(pdu.layer.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 """ for observer in self._clientObserver: @@ -140,7 +140,7 @@ class RDPClientController(pdu.layer.PDUClientListener): def onReady(self): """ - Call when PDU layer is connected + @summary: Call when PDU layer is connected """ self._isReady = True #signal all listener @@ -149,7 +149,7 @@ class RDPClientController(pdu.layer.PDUClientListener): def onClose(self): """ - Event call when RDP stack is closed + @summary: Event call when RDP stack is closed """ self._isReady = False for observer in self._clientObserver: @@ -157,7 +157,7 @@ class RDPClientController(pdu.layer.PDUClientListener): def sendPointerEvent(self, x, y, button, isPressed): """ - send pointer events + @summary: send pointer events @param x: x position of pointer @param y: y position of pointer @param button: 1 or 2 or 3 @@ -189,10 +189,44 @@ class RDPClientController(pdu.layer.PDUClientListener): except InvalidValue: log.info("try send pointer event with incorrect position") + + def sendWheelEvent(self, x, y, step, isNegative = False, isHorizontal = False): + """ + @summary: Send a mouse wheel event + @param x: x position of pointer + @param y: y position of pointer + @param step: number of step rolled + @param isHorizontal: horizontal wheel (default is vertical) + @param isNegative: is upper (default down) + """ + if not self._isReady: + return + + try: + event = pdu.data.PointerEvent() + if isHorizontal: + event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_HWHEEL + else: + event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_WHEEL + + if isNegative: + event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_WHEEL_NEGATIVE + + event.pointerFlags.value |= (step & pdu.data.PointerFlag.WheelRotationMask) + + #position + event.xPos.value = x + event.yPos.value = y + + #send proper event + self._pduLayer.sendInputEvents([event]) + + except InvalidValue: + log.info("try send wheel event with incorrect position") def sendKeyEventScancode(self, code, isPressed): """ - Send a scan code to RDP stack + @summary: Send a scan code to RDP stack @param code: scan code @param isPressed: True if key is pressed and false if it's released """ @@ -215,7 +249,7 @@ class RDPClientController(pdu.layer.PDUClientListener): def sendKeyEventUnicode(self, code, isPressed): """ - Send a scan code to RDP stack + @summary: Send a scan code to RDP stack @param code: unicode @param isPressed: True if key is pressed and false if it's released """ @@ -236,7 +270,7 @@ class RDPClientController(pdu.layer.PDUClientListener): def sendRefreshOrder(self, left, top, right, bottom): """ - Force server to resend a particular zone + @summary: Force server to resend a particular zone @param left: left coordinate @param top: top coordinate @param right: right coordinate @@ -253,13 +287,13 @@ class RDPClientController(pdu.layer.PDUClientListener): def close(self): """ - Close protocol stack + @summary: Close protocol stack """ self._pduLayer.close() class RDPServerController(pdu.layer.PDUServerListener): """ - Controller use in server side mode + @summary: Controller use in server side mode """ def __init__(self, privateKeyFileName, certificateFileName, colorDepth): """ @@ -283,7 +317,7 @@ class RDPServerController(pdu.layer.PDUServerListener): def close(self): """ - Close protocol stack + @summary: Close protocol stack """ self._pduLayer.close() @@ -296,28 +330,28 @@ class RDPServerController(pdu.layer.PDUServerListener): def getUsername(self): """ - Must be call after on ready event else always empty string + @summary: Must be call after on ready event else always empty string @return: username send by client may be an empty string """ return self._pduLayer._info.userName.value def getPassword(self): """ - Must be call after on ready event else always empty string + @summary: Must be call after on ready event else always empty string @return: password send by client may be an empty string """ return self._pduLayer._info.password.value def getDomain(self): """ - Must be call after on ready event else always empty string + @summary: Must be call after on ready event else always empty string @return: domain send by client may be an empty string """ return self._pduLayer._info.domain.value def getCredentials(self): """ - Must be call after on ready event else always empty string + @summary: Must be call after on ready event else always empty string @return: tuple(domain, username, password) """ return (self.getDomain(), self.getUsername(), self.getPassword()) @@ -337,15 +371,15 @@ class RDPServerController(pdu.layer.PDUServerListener): def addServerObserver(self, observer): """ - Add observer to RDP protocol + @summary: Add observer to RDP protocol @param observer: new observer to add """ self._serverObserver.append(observer) def setColorDepth(self, colorDepth): """ - Set color depth of session - if PDU stack is already connected send a deactive-reactive sequence + @summary: Set color depth of session + if PDU stack is already connected send a deactive-reactive sequence @param colorDepth: depth of session (15, 16, 24) """ self._colorDepth = colorDepth @@ -357,13 +391,13 @@ class RDPServerController(pdu.layer.PDUServerListener): def setKeyEventUnicodeSupport(self): """ - Enable key event in unicode format + @summary: Enable key event in unicode format """ self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_INPUT].capability.inputFlags.value |= pdu.caps.InputFlags.INPUT_FLAG_UNICODE def onReady(self): """ - RDP stack is now ready + @summary: RDP stack is now ready """ self._isReady = True for observer in self._serverObserver: @@ -371,7 +405,7 @@ class RDPServerController(pdu.layer.PDUServerListener): def onClose(self): """ - Event call when RDP stack is closed + @summary: Event call when RDP stack is closed """ self._isReady = False for observer in self._serverObserver: @@ -379,7 +413,7 @@ class RDPServerController(pdu.layer.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] """ for observer in self._serverObserver: @@ -404,7 +438,7 @@ class RDPServerController(pdu.layer.PDUServerListener): def sendUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): """ - send bitmap update + @summary: send bitmap update @param destLeft: xmin position @param destTop: ymin position @param destRight: xmax position because RDP can send bitmap with padding @@ -454,7 +488,7 @@ class ClientFactory(layer.RawLayerClientFactory): class ServerFactory(layer.RawLayerServerFactory): """ - Factory of Server RDP protocol + @summary: Factory of Server RDP protocol """ def __init__(self, privateKeyFileName, certificateFileName, colorDepth): """ @@ -476,7 +510,7 @@ class ServerFactory(layer.RawLayerServerFactory): def buildRawLayer(self, addr): """ - Function call from twisted and build rdp protocol stack + @summary: Function call from twisted and build rdp protocol stack @param addr: destination address """ controller = RDPServerController(self._privateKeyFileName, self._certificateFileName, self._colorDepth) @@ -485,7 +519,7 @@ class ServerFactory(layer.RawLayerServerFactory): def buildObserver(self, controller, addr): """ - Build observer use for connection + @summary: Build observer use for connection @param controller: RDP stack controller @param addr: destination address """ @@ -493,7 +527,7 @@ class ServerFactory(layer.RawLayerServerFactory): class RDPClientObserver(object): """ - Class use to inform all RDP event handle by RDPY + @summary: Class use to inform all RDP event handle by RDPY """ def __init__(self, controller): """ @@ -504,19 +538,19 @@ class RDPClientObserver(object): def onReady(self): """ - Stack is ready and connected + @summary: Stack is ready and connected """ raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPClientObserver")) def onClose(self): """ - Stack is closes + @summary: Stack is closes """ raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver")) def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): """ - Notify bitmap update + @summary: Notify bitmap update @param destLeft: xmin position @param destTop: ymin position @param destRight: xmax position because RDP can send bitmap with padding @@ -531,7 +565,7 @@ class RDPClientObserver(object): class RDPServerObserver(object): """ - Class use to inform all RDP event handle by RDPY + @summary: Class use to inform all RDP event handle by RDPY """ def __init__(self, controller): """ @@ -542,20 +576,20 @@ class RDPServerObserver(object): def onReady(self): """ - Stack is ready and connected + @summary: Stack is ready and connected May be called after an setColorDepth too """ raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPServerObserver")) def onClose(self): """ - Stack is closes + @summary: Stack is closes """ raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver")) def onKeyEventScancode(self, code, isPressed): """ - Event call when a keyboard event is catch in scan code format + @summary: Event call when a keyboard event is catch in scan code format @param code: scan code of key @param isPressed: True if key is down """ @@ -563,7 +597,7 @@ class RDPServerObserver(object): def onKeyEventUnicode(self, code, isPressed): """ - Event call when a keyboard event is catch in unicode format + @summary: Event call when a keyboard event is catch in unicode format @param code: unicode of key @param isPressed: True if key is down """ @@ -571,7 +605,7 @@ class RDPServerObserver(object): def onPointerEvent(self, x, y, button, isPressed): """ - Event call on mouse event + @summary: Event call on mouse event @param x: x position @param y: y position @param button: 1, 2 or 3 button diff --git a/rdpy/protocol/rdp/sec.py b/rdpy/protocol/rdp/sec.py index 60e06c2..a601901 100644 --- a/rdpy/protocol/rdp/sec.py +++ b/rdpy/protocol/rdp/sec.py @@ -22,6 +22,7 @@ Some use full methods for security in RDP """ import sha, md5 +from rdpy.network.type import Stream, UInt32Le def saltedHash(inputData, salt, salt1, salt2): """ @@ -47,6 +48,46 @@ def saltedHash(inputData, salt, salt1, salt2): return md5Digest.digest() -def masterSecret(preMasterSecret, clientRandom, serverRandom): +def md5_16_32_32(in0, in1, in2): """ - """ \ No newline at end of file + @summary: MD5(in0[:16] + in1[:32] + in2[:32]) + @param in0: in 16 + @param in1: in 32 + @param in2: in 32 + @return MD5(in0[:16] + in1[:32] + in2[:32]) + """ + md5Digest = md5.new() + md5Digest.update(in0[:16]) + md5Digest.update(in1[:32]) + md5Digest.update(in2[:32]) + 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): + 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() \ No newline at end of file diff --git a/rdpy/ui/qt4.py b/rdpy/ui/qt4.py index 041e9e2..9f357fb 100644 --- a/rdpy/ui/qt4.py +++ b/rdpy/ui/qt4.py @@ -34,12 +34,12 @@ import rle class QAdaptor(object): """ - Adaptor model with link between protocol - And Qt widget + @summary: Adaptor model with link between protocol + And Qt widget """ def sendMouseEvent(self, e, isPressed): """ - Interface to send mouse event to protocol stack + @summary: Interface to send mouse event to protocol stack @param e: QMouseEvent @param isPressed: event come from press or release action """ @@ -47,11 +47,18 @@ class QAdaptor(object): def sendKeyEvent(self, e, isPressed): """ - Interface to send key event to protocol stack + @summary: Interface to send key event to protocol stack @param e: QEvent @param isPressed: event come from press or release action """ - raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendKeyEvent", "QAdaptor")) + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendKeyEvent", "QAdaptor")) + + def sendWheelEvent(self, e): + """ + @summary: Interface to send wheel event to protocol stack + @param e: QWheelEvent + """ + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendWheelEvent", "QAdaptor")) def getWidget(self): """ @@ -61,7 +68,7 @@ class QAdaptor(object): def closeEvent(self, e): """ - Call when you want to close connection + @summary: Call when you want to close connection @param: QCloseEvent """ raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "closeEvent", "QAdaptor")) @@ -77,7 +84,7 @@ def qtImageFormatFromRFBPixelFormat(pixelFormat): class RFBClientQt(RFBClientObserver, QAdaptor): """ - QAdaptor for specific RFB protocol stack + @summary: QAdaptor for specific RFB protocol stack is to an RFB observer """ def __init__(self, controller): @@ -97,7 +104,7 @@ class RFBClientQt(RFBClientObserver, QAdaptor): def onUpdate(self, width, height, x, y, pixelFormat, encoding, data): """ - Implement RFBClientObserver interface + @summary: Implement RFBClientObserver interface @param width: width of new image @param height: height of new image @param x: x position of new image @@ -136,7 +143,7 @@ class RFBClientQt(RFBClientObserver, QAdaptor): def sendMouseEvent(self, e, isPressed): """ - Convert Qt mouse event to RFB mouse event + @summary: Convert Qt mouse event to RFB mouse event @param e: qMouseEvent @param isPressed: event come from press or release action """ @@ -152,29 +159,37 @@ class RFBClientQt(RFBClientObserver, QAdaptor): def sendKeyEvent(self, e, isPressed): """ - Convert Qt key press event to RFB press event + @summary: Convert Qt key press event to RFB press event @param e: qKeyEvent @param isPressed: event come from press or release action """ self.keyEvent(isPressed, e.nativeVirtualKey()) + def sendWheelEvent(self, e): + """ + @summary: Convert Qt wheel event to RFB Wheel event + @param e: QKeyEvent + @param isPressed: event come from press or release action + """ + pass + def closeEvent(self, e): """ - Call when you want to close connection + @summary: Call when you want to close connection @param: QCloseEvent """ self._controller.close() def onClose(self): """ - Call when stack is close + @summary: Call when stack is close """ #do something maybe a message pass def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data): """ - Bitmap transformation to Qt object + @summary: Bitmap transformation to Qt object @param width: width of bitmap @param height: height of bitmap @param bitsPerPixel: number of bit per pixel @@ -204,15 +219,15 @@ def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data): if isCompress: buf = bytearray(width * height * 3) rle.bitmap_decompress(buf, width, height, data, 3) - image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB24) + image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB888) else: - image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB24).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) + image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB888).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) elif bitsPerPixel == 32: if isCompress: buf = bytearray(width * height * 4) rle.bitmap_decompress(buf, width, height, data, 4) - image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB24) + image = QtGui.QImage(buf, width, height, QtGui.QImage.Format_RGB32) else: image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB32).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) else: @@ -222,7 +237,7 @@ def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data): class RDPClientQt(RDPClientObserver, QAdaptor): """ - Adaptor for RDP client + @summary: Adaptor for RDP client """ def __init__(self, controller, width, height): """ @@ -243,7 +258,7 @@ class RDPClientQt(RDPClientObserver, QAdaptor): def sendMouseEvent(self, e, isPressed): """ - Convert Qt mouse event to RDP mouse event + @summary: Convert Qt mouse event to RDP mouse event @param e: qMouseEvent @param isPressed: event come from press(true) or release(false) action """ @@ -251,15 +266,15 @@ class RDPClientQt(RDPClientObserver, QAdaptor): buttonNumber = 0 if button == QtCore.Qt.LeftButton: buttonNumber = 1 - elif button == QtCore.Qt.MidButton: - buttonNumber = 2 elif button == QtCore.Qt.RightButton: + buttonNumber = 2 + elif button == QtCore.Qt.MidButton: buttonNumber = 3 self._controller.sendPointerEvent(e.pos().x(), e.pos().y(), buttonNumber, isPressed) def sendKeyEvent(self, e, isPressed): """ - Convert Qt key press event to RFB press event + @summary: Convert Qt key press event to RDP press event @param e: QKeyEvent @param isPressed: event come from press or release action """ @@ -267,17 +282,25 @@ class RDPClientQt(RDPClientObserver, QAdaptor): if sys.platform == "linux2": code -= 8 self._controller.sendKeyEventScancode(code, isPressed) + + def sendWheelEvent(self, e): + """ + @summary: Convert Qt wheel event to RDP Wheel event + @param e: QKeyEvent + @param isPressed: event come from press or release action + """ + self._controller.sendWheelEvent(e.pos().x(), e.pos().y(), (abs(e.delta()) / 8) / 15, e.delta() < 0, e.orientation() == QtCore.Qt.Horizontal) def closeEvent(self, e): """ - Convert Qt close widget event into close stack event + @summary: Convert Qt close widget event into close stack event @param e: QCloseEvent """ self._controller.close() def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): """ - Notify bitmap update + @summary: Notify bitmap update @param destLeft: xmin position @param destTop: ymin position @param destRight: xmax position because RDP can send bitmap with padding @@ -295,14 +318,14 @@ class RDPClientQt(RDPClientObserver, QAdaptor): def onReady(self): """ - Call when stack is ready + @summary: Call when stack is ready """ #do something maybe a loader pass def onClose(self): """ - Call when stack is close + @summary: Call when stack is close """ #do something maybe a message pass @@ -310,7 +333,7 @@ class RDPClientQt(RDPClientObserver, QAdaptor): class QRemoteDesktop(QtGui.QWidget): """ - Qt display widget + @summary: Qt display widget """ def __init__(self, adaptor, width, height): """ @@ -334,7 +357,7 @@ class QRemoteDesktop(QtGui.QWidget): def notifyImage(self, x, y, qimage, width, height): """ - Function call from QAdaptor + @summary: Function call from QAdaptor @param x: x position of new image @param y: y position of new image @param qimage: new QImage @@ -346,7 +369,7 @@ class QRemoteDesktop(QtGui.QWidget): def paintEvent(self, e): """ - Call when Qt renderer engine estimate that is needed + @summary: Call when Qt renderer engine estimate that is needed @param e: QEvent """ #fill buffer image @@ -362,42 +385,49 @@ class QRemoteDesktop(QtGui.QWidget): def mouseMoveEvent(self, event): """ - Call when mouse move + @summary: Call when mouse move @param event: QMouseEvent """ self._adaptor.sendMouseEvent(event, False) def mousePressEvent(self, event): """ - Call when button mouse is pressed + @summary: Call when button mouse is pressed @param event: QMouseEvent """ self._adaptor.sendMouseEvent(event, True) def mouseReleaseEvent(self, event): """ - Call when button mouse is released + @summary: Call when button mouse is released @param event: QMouseEvent """ self._adaptor.sendMouseEvent(event, False) def keyPressEvent(self, event): """ - Call when button key is pressed + @summary: Call when button key is pressed @param event: QKeyEvent """ self._adaptor.sendKeyEvent(event, True) def keyReleaseEvent(self, event): """ - Call when button key is released + @summary: Call when button key is released @param event: QKeyEvent """ self._adaptor.sendKeyEvent(event, False) + def wheelEvent(self, event): + """ + @summary: Call on wheel event + @param event: QWheelEvent + """ + self._adaptor.sendWheelEvent(event) + def closeEvent(self, event): """ - Call when widget is closed + @summary: Call when widget is closed @param event: QCloseEvent """ self._adaptor.closeEvent(event) \ No newline at end of file From 3c3d7423a5666a5b5d93fd02a3e6f42178c4d6a8 Mon Sep 17 00:00:00 2001 From: citronneur Date: Sat, 29 Nov 2014 19:00:14 +0100 Subject: [PATCH 4/6] add fullscreen mode for client --- README.md | 25 +++++++------- bin/{rdpy-rdpclient => rdpy-rdpclient.py} | 33 ++++++++++++------- bin/{rdpy-rdpproxy => rdpy-rdpproxy.py} | 0 ...py-rdpscreenshot => rdpy-rdpscreenshot.py} | 0 bin/{rdpy-vncclient => rdpy-vncclient.py} | 0 ...py-vncscreenshot => rdpy-vncscreenshot.py} | 0 setup.py | 16 +++++---- 7 files changed, 44 insertions(+), 30 deletions(-) rename bin/{rdpy-rdpclient => rdpy-rdpclient.py} (90%) rename bin/{rdpy-rdpproxy => rdpy-rdpproxy.py} (100%) rename bin/{rdpy-rdpscreenshot => rdpy-rdpscreenshot.py} (100%) rename bin/{rdpy-vncclient => rdpy-vncclient.py} (100%) rename bin/{rdpy-vncscreenshot => rdpy-vncscreenshot.py} (100%) diff --git a/README.md b/README.md index bfdf510..d65a924 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ -# RDPY [![Build Status](https://travis-ci.org/citronneur/rdpy.svg?branch=master)](https://travis-ci.org/citronneur/rdpy) +# RDPY [![Build Status](https://travis-ci.org/citronneur/rdpy.svg?branch=dev)](https://travis-ci.org/citronneur/rdpy) Remote Desktop Protocol in twisted PYthon. -RDPY is still under development. - -RDPY is a pure Python implementation ot the Microsoft RDP (Remote Desktop Protocol) protocol. RDPY is built over the event driven network engine Twisted. +RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol. RDPY is built over the event driven network engine Twisted. ## Build @@ -22,9 +20,10 @@ sudo apt-get install python-qt4 #### Windows [PyQt4](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x32.exe) + [PyWin32](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.7.exe/download) -### Make +### Build ``` $ git clone https://github.com/citronneur/rdpy.git rdpy @@ -52,7 +51,7 @@ RDPY comes with some very useful binaries; These binaries are linux and windows rdpy-rdpclient is a simple RDP Qt4 client . ``` -$ rdpy/bin/rdpy-rdpclient [-u username] [-p password] [-d domain] [...] XXX.XXX.XXX.XXX[:3389] +$ rdpy-rdpclient.py [-u username] [-p password] [-d domain] [...] XXX.XXX.XXX.XXX[:3389] ``` ### rdpy-vncclient @@ -60,7 +59,7 @@ $ rdpy/bin/rdpy-rdpclient [-u username] [-p password] [-d domain] [...] XXX.XXX. rdpy-vncclient is a simple VNC Qt4 client . ``` -$ rdpy/bin/rdpy-vncclient [-p password] XXX.XXX.XXX.XXX[:5900] +$ rdpy-vncclient.py [-p password] XXX.XXX.XXX.XXX[:5900] ``` ### rdpy-rdpscreenshot @@ -68,7 +67,7 @@ $ rdpy/bin/rdpy-vncclient [-p password] XXX.XXX.XXX.XXX[:5900] rdpy-rdpscreenshot save login screen in file. ``` -$ rdpy/bin/rdpy-rdpscreenshot [-w width] [-l height] [-o output_file_path] XXX.XXX.XXX.XXX[:3389] +$ rdpy-rdpscreenshot.py [-w width] [-l height] [-o output_file_path] XXX.XXX.XXX.XXX[:3389] ``` ### rdpy-vncscreenshot @@ -76,7 +75,7 @@ $ rdpy/bin/rdpy-rdpscreenshot [-w width] [-l height] [-o output_file_path] XXX.X rdpy-vncscreenshot save first screen update in file. ``` -$ rdpy/bin/rdpy-vncscreenshot [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:5900] +$ rdpy-vncscreenshot.py [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:5900] ``` ### rdpy-rdpproxy @@ -84,7 +83,7 @@ $ rdpy/bin/rdpy-vncscreenshot [-p password] [-o output_file_path] XXX.XXX.XXX.XX rdpy-rdpproxy is a RDP proxy. It is used to manage and control access to the RDP servers as well as watch live sessions through any RDP client. It can be compared to a HTTP reverse proxy with added spy features. ``` -$ rdpy/bin/rdpy-rdpproxy -f credentials_file_path -k private_key_file_path -c certificate_file_path [-i admin_ip[:admin_port]] listen_port +$ rdpy-rdpproxy.py -f credentials_file_path -k private_key_file_path -c certificate_file_path [-i admin_ip[:admin_port]] listen_port ``` The credentials file is JSON file that must conform with the following format: @@ -120,9 +119,11 @@ RDPY can also be used as Qt widget throw rdpy.ui.qt4.QRemoteDesktop class. It ca In a nutshell the RDPY can be used as a protocol library with a twisted engine. +### Client library + The RDP client code looks like this: -``` +```python from rdpy.protocol.rdp import rdp class MyRDPFactory(rdp.ClientFactory): @@ -157,7 +158,7 @@ reactor.run() ``` The VNC client code looks like this: -``` +```python from rdpy.protocol.rfb import rdp class MyRDPFactory(rfb.ClientFactory): diff --git a/bin/rdpy-rdpclient b/bin/rdpy-rdpclient.py similarity index 90% rename from bin/rdpy-rdpclient rename to bin/rdpy-rdpclient.py index 1424e81..f40209c 100755 --- a/bin/rdpy-rdpclient +++ b/bin/rdpy-rdpclient.py @@ -35,19 +35,21 @@ class RDPClientQtFactory(rdp.ClientFactory): """ @summary: Factory create a RDP GUI client """ - def __init__(self, width, height, username, password, domain): + def __init__(self, width, height, username, password, domain, fullscreen): """ @param width: width of client @param heigth: heigth of client @param username: username present to the server @param password: password present to the server @param domain: microsoft domain + @param fullscreen: show widget in fullscreen mode """ self._width = width self._height = height self._username = username self._passwod = password self._domain = domain + self._fullscreen = fullscreen self._w = None def buildObserver(self, controller, addr): @@ -63,7 +65,10 @@ class RDPClientQtFactory(rdp.ClientFactory): #create qt widget self._w = client.getWidget() self._w.setWindowTitle('rdpy-rdpclient') - self._w.show() + if self._fullscreen: + self._w.showFullScreen() + else: + self._w.show() controller.setUsername(self._username) controller.setPassword(self._passwod) @@ -104,12 +109,21 @@ def help(): print "\t-l: height of screen default value is 800" if __name__ == '__main__': + + #create application + app = QtGui.QApplication(sys.argv) + + #add qt4 reactor + import qt4reactor + qt4reactor.install() + #default script argument username = "" password = "" domain = "" - width = 1024 - height = 800 + width = QtGui.QDesktopWidget().screenGeometry().width() + height = QtGui.QDesktopWidget().screenGeometry().height() + fullscreen = True try: opts, args = getopt.getopt(sys.argv[1:], "hu:p:d:w:l:") @@ -127,22 +141,17 @@ if __name__ == '__main__': domain = arg elif opt == "-w": width = int(arg) + fullscreen = False elif opt == "-l": height = int(arg) + fullscreen = False if ':' in args[0]: ip, port = args[0].split(':') else: ip, port = args[0], "3389" - - #create application - app = QtGui.QApplication(sys.argv) - - #add qt4 reactor - import qt4reactor - qt4reactor.install() from twisted.internet import reactor - reactor.connectTCP(ip, int(port), RDPClientQtFactory(width, height, username, password, domain)) + reactor.connectTCP(ip, int(port), RDPClientQtFactory(width, height, username, password, domain, fullscreen)) reactor.runReturn() app.exec_() \ No newline at end of file diff --git a/bin/rdpy-rdpproxy b/bin/rdpy-rdpproxy.py similarity index 100% rename from bin/rdpy-rdpproxy rename to bin/rdpy-rdpproxy.py diff --git a/bin/rdpy-rdpscreenshot b/bin/rdpy-rdpscreenshot.py similarity index 100% rename from bin/rdpy-rdpscreenshot rename to bin/rdpy-rdpscreenshot.py diff --git a/bin/rdpy-vncclient b/bin/rdpy-vncclient.py similarity index 100% rename from bin/rdpy-vncclient rename to bin/rdpy-vncclient.py diff --git a/bin/rdpy-vncscreenshot b/bin/rdpy-vncscreenshot.py similarity index 100% rename from bin/rdpy-vncscreenshot rename to bin/rdpy-vncscreenshot.py diff --git a/setup.py b/setup.py index 127c4c9..a818be0 100644 --- a/setup.py +++ b/setup.py @@ -4,8 +4,12 @@ import setuptools from distutils.core import setup, Extension setup(name='rdpy', - version='1.0.1', + version='1.1.0', description='Remote Desktop Protocol in Python', + long_description=""" + RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol. + RDPY is built over the event driven network engine Twisted. + """, author='Sylvain Peyrefitte', author_email='citronneur@gmail.com', url='https://github.com/citronneur/rdpy', @@ -21,11 +25,11 @@ setup(name='rdpy', ], ext_modules=[Extension('rle', ['ext/rle.c'])], scripts = [ - 'bin/rdpy-rdpclient', - 'bin/rdpy-rdpproxy', - 'bin/rdpy-rdpscreenshot', - 'bin/rdpy-vncclient', - 'bin/rdpy-vncscreenshot' + 'bin/rdpy-rdpclient.py', + 'bin/rdpy-rdpproxy.py', + 'bin/rdpy-rdpscreenshot.py', + 'bin/rdpy-vncclient.py', + 'bin/rdpy-vncscreenshot.py' ], install_requires=[ 'twisted', From 99198321a43a7f8b9e72146cb20dfbc4fc10154e Mon Sep 17 00:00:00 2001 From: citronneur Date: Sun, 30 Nov 2014 11:22:59 +0100 Subject: [PATCH 5/6] add keyboard layout detection or force --- README.md | 124 ++++++++++++++++++++++++++++++++++----- bin/rdpy-rdpclient.py | 86 ++++++++++++++++++++------- rdpy/protocol/rdp/gcc.py | 2 +- rdpy/protocol/rdp/rdp.py | 10 ++++ rdpy/ui/qt4.py | 2 - 5 files changed, 185 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index d65a924..6717182 100644 --- a/README.md +++ b/README.md @@ -119,9 +119,7 @@ RDPY can also be used as Qt widget throw rdpy.ui.qt4.QRemoteDesktop class. It ca In a nutshell the RDPY can be used as a protocol library with a twisted engine. -### Client library - -The RDP client code looks like this: +### Simple RDP Client ```python from rdpy.protocol.rdp import rdp @@ -135,20 +133,36 @@ class MyRDPFactory(rdp.ClientFactory): reactor.stop() def buildObserver(self, controller, addr): + class MyObserver(rdp.RDPClientObserver) - def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): - #here code handle bitmap - pass - - def onReady(self): + def onReady(self): + """ + @summary: Call when stack is ready + """ #send 'r' key self._controller.sendKeyEventUnicode(ord(unicode("r".toUtf8(), encoding="UTF-8")), True) #mouse move and click at pixel 200x200 self._controller.sendPointerEvent(200, 200, 1, true) + def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): + """ + @summary: Notify bitmap update + @param destLeft: xmin position + @param destTop: ymin position + @param destRight: xmax position because RDP can send bitmap with padding + @param destBottom: ymax position because RDP can send bitmap with padding + @param width: width of bitmap + @param height: height of bitmap + @param bitsPerPixel: number of bit per pixel + @param isCompress: use RLE compression + @param data: bitmap data + """ + def onClose(self): - pass + """ + @summary: Call when stack is close + """ return MyObserver(controller) @@ -157,11 +171,66 @@ reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRDPFactory()) reactor.run() ``` -The VNC client code looks like this: +### Simple RDP Server +```python +from rdpy.protocol.rdp import rdp + +class MyRDPFactory(rdp.ServerFactory): + + def buildObserver(self, controller, addr): + + class MyObserver(rdp.RDPServerObserver) + + def onReady(self): + """ + @summary: Call when server is ready + to send and receive messages + """ + + def onKeyEventScancode(self, code, isPressed): + """ + @summary: Event call when a keyboard event is catch in scan code format + @param code: scan code of key + @param isPressed: True if key is down + @see: rdp.RDPServerObserver.onKeyEventScancode + """ + + def onKeyEventUnicode(self, code, isPressed): + """ + @summary: Event call when a keyboard event is catch in unicode format + @param code: unicode of key + @param isPressed: True if key is down + @see: rdp.RDPServerObserver.onKeyEventUnicode + """ + + def onPointerEvent(self, x, y, button, isPressed): + """ + @summary: Event call on mouse event + @param x: x position + @param y: y position + @param button: 1, 2 or 3 button + @param isPressed: True if mouse button is pressed + @see: rdp.RDPServerObserver.onPointerEvent + """ + + def onClose(self): + """ + @summary: Call when human client close connection + @see: rdp.RDPServerObserver.onClose + """ + + return MyObserver(controller) + +from twisted.internet import reactor +reactor.listenTCP(3389, MyRDPFactory()) +reactor.run() +``` + +### Simple VNC Client ```python from rdpy.protocol.rfb import rdp -class MyRDPFactory(rfb.ClientFactory): +class MyRFBFactory(rfb.ClientFactory): def clientConnectionLost(self, connector, reason): reactor.stop() @@ -173,18 +242,41 @@ class MyRDPFactory(rfb.ClientFactory): class MyObserver(rfb.RFBClientObserver) def onReady(self): - pass + """ + @summary: Event when network stack is ready to receive or send event + """ def onUpdate(self, width, height, x, y, pixelFormat, encoding, data): - #here code handle bitmap - pass + """ + @summary: Implement RFBClientObserver interface + @param width: width of new image + @param height: height of new image + @param x: x position of new image + @param y: y position of new image + @param pixelFormat: pixefFormat structure in rfb.message.PixelFormat + @param encoding: encoding type rfb.message.Encoding + @param data: image data in accordance with pixel format and encoding + """ + + def onCutText(self, text): + """ + @summary: event when server send cut text event + @param text: text received + """ + + def onBell(self): + """ + @summary: event when server send biiip + """ def onClose(self): - pass + """ + @summary: Call when stack is close + """ return MyObserver(controller) from twisted.internet import reactor -reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRDPFactory()) +reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRFBFactory()) reactor.run() ``` diff --git a/bin/rdpy-rdpclient.py b/bin/rdpy-rdpclient.py index f40209c..f0a6127 100755 --- a/bin/rdpy-rdpclient.py +++ b/bin/rdpy-rdpclient.py @@ -17,14 +17,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # - """ example of use rdpy as rdp client """ import sys, os, getopt -from PyQt4 import QtGui +from PyQt4 import QtGui, QtCore from rdpy.ui.qt4 import RDPClientQt from rdpy.protocol.rdp import rdp @@ -35,7 +34,7 @@ class RDPClientQtFactory(rdp.ClientFactory): """ @summary: Factory create a RDP GUI client """ - def __init__(self, width, height, username, password, domain, fullscreen): + def __init__(self, width, height, username, password, domain, fullscreen, keyboardLayout, optimized): """ @param width: width of client @param heigth: heigth of client @@ -43,6 +42,8 @@ class RDPClientQtFactory(rdp.ClientFactory): @param password: password present to the server @param domain: microsoft domain @param fullscreen: show widget in fullscreen mode + @param keyboardLayout: keyboard layout + @param optimized: enable optimized session orders """ self._width = width self._height = height @@ -50,6 +51,8 @@ class RDPClientQtFactory(rdp.ClientFactory): self._passwod = password self._domain = domain self._fullscreen = fullscreen + self._keyboardLayout = keyboardLayout + self._optimized = optimized self._w = None def buildObserver(self, controller, addr): @@ -73,7 +76,9 @@ class RDPClientQtFactory(rdp.ClientFactory): controller.setUsername(self._username) controller.setPassword(self._passwod) controller.setDomain(self._domain) - controller.setPerformanceSession() + controller.setKeyboardLayout(self._keyboardLayout) + if self._optimized: + controller.setPerformanceSession() return client @@ -100,33 +105,54 @@ class RDPClientQtFactory(rdp.ClientFactory): reactor.stop() app.exit() +def autoDetectKeyboardLayout(): + """ + @summary: try to auto detect keyboard layout + """ + try: + if os.name == 'posix': + from subprocess import check_output + result = check_output(["setxkbmap", "-print"]) + if "azerty" in result: + return "fr" + elif os.name == 'nt': + import win32api, win32con, win32process + import ctypes.windll.user32 as user32 + w = user32.GetForegroundWindow() + tid = user32.GetWindowThreadProcessId(w, 0) + result = user32.GetKeyboardLayout(tid) + if result == 0x409409: + return "fr" + except: + log.info("failed to auto detect keyboard layout") + pass + return "en" + def help(): print "Usage: rdpy-rdpclient [options] ip[:port]" print "\t-u: user name" print "\t-p: password" print "\t-d: domain" - print "\t-w: width of screen default value is 1024" - print "\t-l: height of screen default value is 800" + print "\t-w: width of screen [default : 1024]" + print "\t-l: height of screen [default : 800]" + print "\t-f: enable full screen mode [default : False]" + print "\t-k: keyboard layout [en|fr] [default : en]" + print "\t-o: optimized session (disable costly effect) [default : False]" if __name__ == '__main__': - #create application - app = QtGui.QApplication(sys.argv) - - #add qt4 reactor - import qt4reactor - qt4reactor.install() - #default script argument username = "" password = "" domain = "" - width = QtGui.QDesktopWidget().screenGeometry().width() - height = QtGui.QDesktopWidget().screenGeometry().height() - fullscreen = True + width = 1024 + height = 800 + fullscreen = False + optimized = False + keyboardLayout = autoDetectKeyboardLayout() try: - opts, args = getopt.getopt(sys.argv[1:], "hu:p:d:w:l:") + opts, args = getopt.getopt(sys.argv[1:], "hfou:p:d:w:l:k:") except getopt.GetoptError: help() for opt, arg in opts: @@ -141,17 +167,37 @@ if __name__ == '__main__': domain = arg elif opt == "-w": width = int(arg) - fullscreen = False elif opt == "-l": height = int(arg) - fullscreen = False + elif opt == "-f": + fullscreen = True + elif opt == "-o": + optimized = True + elif opt == "-k": + keyboardLayout = arg if ':' in args[0]: ip, port = args[0].split(':') else: ip, port = args[0], "3389" + #create application + app = QtGui.QApplication(sys.argv) + + #add qt4 reactor + import qt4reactor + qt4reactor.install() + + if fullscreen: + width = QtGui.QDesktopWidget().screenGeometry().width() + height = QtGui.QDesktopWidget().screenGeometry().height() + + + + + log.info("keyboard layout set to %s"%keyboardLayout) + from twisted.internet import reactor - reactor.connectTCP(ip, int(port), RDPClientQtFactory(width, height, username, password, domain, fullscreen)) + reactor.connectTCP(ip, int(port), RDPClientQtFactory(width, height, username, password, domain, fullscreen, keyboardLayout, optimized)) reactor.runReturn() app.exec_() \ No newline at end of file diff --git a/rdpy/protocol/rdp/gcc.py b/rdpy/protocol/rdp/gcc.py index a934e71..c419cab 100644 --- a/rdpy/protocol/rdp/gcc.py +++ b/rdpy/protocol/rdp/gcc.py @@ -230,7 +230,7 @@ class ClientCoreData(CompositeType): self.desktopHeight = UInt16Le(800) self.colorDepth = UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP) self.sasSequence = UInt16Le(Sequence.RNS_UD_SAS_DEL) - self.kbdLayout = UInt32Le(KeyboardLayout.FRENCH) + self.kbdLayout = UInt32Le(KeyboardLayout.US) self.clientBuild = UInt32Le(3790) self.clientName = String("rdpy" + "\x00"*11, readLen = UInt8(32), unicode = True) self.keyboardType = UInt32Le(KeyboardType.IBM_101_102_KEYS) diff --git a/rdpy/protocol/rdp/rdp.py b/rdpy/protocol/rdp/rdp.py index 2576011..1b72263 100644 --- a/rdpy/protocol/rdp/rdp.py +++ b/rdpy/protocol/rdp/rdp.py @@ -111,6 +111,16 @@ class RDPClientController(pdu.layer.PDUClientListener): """ self._pduLayer._info.flag |= pdu.data.InfoFlag.INFO_AUTOLOGON + def setKeyboardLayout(self, layout): + """ + @summary: keyboard layout + @param layout: us | fr + """ + if layout == "fr": + self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).kbdLayout.value = gcc.KeyboardLayout.FRENCH + elif layout == "us": + self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).kbdLayout.value = gcc.KeyboardLayout.US + def addClientObserver(self, observer): """ @summary: Add observer to RDP protocol diff --git a/rdpy/ui/qt4.py b/rdpy/ui/qt4.py index 9f357fb..735fc34 100644 --- a/rdpy/ui/qt4.py +++ b/rdpy/ui/qt4.py @@ -126,13 +126,11 @@ class RFBClientQt(RFBClientObserver, QAdaptor): @summary: event when server send cut text event @param text: text received """ - pass def onBell(self): """ @summary: event when server send biiip """ - pass def onReady(self): """ From fa25a40721d28b841661e326a5b3948f701976d6 Mon Sep 17 00:00:00 2001 From: speyrefitte Date: Mon, 1 Dec 2014 18:06:08 +0100 Subject: [PATCH 6/6] add hostname support + finish license automata --- bin/rdpy-rdpclient.py | 3 ++- bin/rdpy-rdpproxy.py | 2 +- rdpy/protocol/rdp/pdu/layer.py | 6 ++--- rdpy/protocol/rdp/pdu/lic.py | 46 ++++++++++++++++++++-------------- rdpy/protocol/rdp/rdp.py | 8 ++++++ rdpy/protocol/rdp/sec.py | 17 +++++++------ 6 files changed, 50 insertions(+), 32 deletions(-) diff --git a/bin/rdpy-rdpclient.py b/bin/rdpy-rdpclient.py index f0a6127..cfbc907 100755 --- a/bin/rdpy-rdpclient.py +++ b/bin/rdpy-rdpclient.py @@ -21,7 +21,7 @@ example of use rdpy as rdp client """ -import sys, os, getopt +import sys, os, getopt, socket from PyQt4 import QtGui, QtCore from rdpy.ui.qt4 import RDPClientQt @@ -77,6 +77,7 @@ class RDPClientQtFactory(rdp.ClientFactory): controller.setPassword(self._passwod) controller.setDomain(self._domain) controller.setKeyboardLayout(self._keyboardLayout) + controller.setHostname(socket.gethostname()) if self._optimized: controller.setPerformanceSession() diff --git a/bin/rdpy-rdpproxy.py b/bin/rdpy-rdpproxy.py index 97950e1..4bde05f 100755 --- a/bin/rdpy-rdpproxy.py +++ b/bin/rdpy-rdpproxy.py @@ -37,7 +37,7 @@ from rdpy.ui import view from twisted.internet import reactor from PyQt4 import QtCore, QtGui -log._LOG_LEVEL = log.Level.INFO +#log._LOG_LEVEL = log.Level.INFO class ProxyServer(rdp.RDPServerObserver): """ diff --git a/rdpy/protocol/rdp/pdu/layer.py b/rdpy/protocol/rdp/pdu/layer.py index c340109..7aa0db1 100644 --- a/rdpy/protocol/rdp/pdu/layer.py +++ b/rdpy/protocol/rdp/pdu/layer.py @@ -136,8 +136,7 @@ class Client(PDULayer, tpkt.IFastPathListener): self._listener = listener #enable or not fast path self._fastPathSender = None - #todo generate hostname - self._licenceManager = lic.LicenseManager(self, self._info.userName.value, "wav-glw-009") + self._licenceManager = lic.LicenseManager(self) def connect(self): """ @@ -592,8 +591,7 @@ class Server(PDULayer, tpkt.IFastPathListener): def sendDemandActivePDU(self): """ - Send server capabilities - server automata PDU + @summary: Send server capabilities server automata PDU """ #init general capability generalCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability diff --git a/rdpy/protocol/rdp/pdu/lic.py b/rdpy/protocol/rdp/pdu/lic.py index 2a81bef..557c1d9 100644 --- a/rdpy/protocol/rdp/pdu/lic.py +++ b/rdpy/protocol/rdp/pdu/lic.py @@ -90,6 +90,7 @@ class Preambule(object): """ PREAMBLE_VERSION_2_0 = 0x2 PREAMBLE_VERSION_3_0 = 0x3 + EXTENDED_ERROR_MSG_SUPPORTED = 0x80 class LicenseBinaryBlob(CompositeType): """ @@ -208,13 +209,13 @@ class ClientPLatformChallengeResponse(CompositeType): def __init__(self): CompositeType.__init__(self) - self.encryptedPlatformChallengeResponse = LicenseBinaryBlob(BinaryBlobType.BB_ENCRYPTED_DATA_BLOB) - self.encryptedHWID = LicenseBinaryBlob(BinaryBlobType.BB_ENCRYPTED_DATA_BLOB) + self.encryptedPlatformChallengeResponse = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB) + self.encryptedHWID = LicenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB) self.MACData = String(readLen = UInt8(16)) class LicPacket(CompositeType): """ - A license packet + @summary: A license packet """ def __init__(self, message = None): CompositeType.__init__(self) @@ -225,7 +226,7 @@ class LicPacket(CompositeType): def LicensingMessageFactory(): """ - factory for message nego + @summary: factory for message nego Use in read mode """ for c in [LicensingErrorMessage, ServerLicenseRequest, ClientNewLicenseRequest, ServerPlatformChallenge, ClientPLatformChallengeResponse]: @@ -249,32 +250,33 @@ def createValidClientLicensingErrorMessage(): message = LicensingErrorMessage() message.dwErrorCode.value = ErrorCode.STATUS_VALID_CLIENT message.dwStateTransition.value = StateTransition.ST_NO_TRANSITION - return LicPacket(message = message) + return LicPacket(message) class LicenseManager(object): """ @summary: handle license automata @see: http://msdn.microsoft.com/en-us/library/cc241890.aspx """ - def __init__(self, transport, username, hostname): + def __init__(self, transport): """ @param transport: layer use to send packet """ + self._preMasterSecret = "\x00" * 64 self._clientRandom = "\x00" * 32 self._serverRandom = None self._serverEncryptedChallenge = None self._transport = transport - self._username = username - self._hostname = hostname + self._username = "" + self._hostname = "" def generateKeys(self): """ @summary: generate key for license session """ - self._masterSecret = sec.generateMicrosoftKey("\x00" * 64, self._clientRandom, self._serverRandom) - self._sessionKeyBlob = sec.generateMicrosoftKey(self._masterSecret, self._serverRandom, self._clientRandom) - self._macSalt = self._sessionKeyBlob[:16] - self._licenseKey = sec.md5_16_32_32(self._sessionKeyBlob[16:], self._clientRandom, self._serverRandom) + masterSecret = sec.generateMicrosoftKey(self._preMasterSecret, self._clientRandom, self._serverRandom) + sessionKeyBlob = sec.generateMicrosoftKey(masterSecret, self._serverRandom, self._clientRandom) + self._macSalt = sessionKeyBlob[:16] + self._licenseKey = sec.finalHash(sessionKeyBlob[16:32], self._clientRandom, self._serverRandom) def recv(self, s): """ @@ -296,7 +298,11 @@ class LicenseManager(object): elif licPacket.bMsgtype.value == MessageType.PLATFORM_CHALLENGE: self._serverEncryptedChallenge = licPacket.licensingMessage.encryptedPlatformChallenge.blobData.value self.sendClientChallengeResponse() - + + #yes get a new license + elif licPacket.bMsgtype.value == MessageType.NEW_LICENSE: + return True + else: raise InvalidExpectedDataException("Not a valid license packet") @@ -309,25 +315,27 @@ class LicenseManager(object): """ message = ClientNewLicenseRequest() message.clientRandom.value = self._clientRandom - message.encryptedPreMasterSecret.blobData = String("\x00" * (64 + 8)) + message.encryptedPreMasterSecret.blobData = String(self._preMasterSecret + "\x00" * 8) message.ClientMachineName.blobData = String(self._hostname + "\x00") message.ClientUserName.blobData = String(self._username + "\x00") self._transport.sendLicensePacket(LicPacket(message)) def sendClientChallengeResponse(self): - #it should be TEST in unicode format + """ + @summary: generate valid challenge response + """ + #decrypt server challenge + #it should be TEST word in unicode format serverChallenge = rc4.crypt(self._licenseKey, self._serverEncryptedChallenge) #generate hwid s = Stream() - s.writeType((UInt32Le(2), String(self._username + self._hostname + "\x00" * 20))) + s.writeType((UInt32Le(2), String(self._hostname + "\x00" * 16))) hwid = s.getvalue()[:20] - signature = sec.macData(self._macSalt, serverChallenge + hwid) - message = ClientPLatformChallengeResponse() message.encryptedPlatformChallengeResponse.blobData.value = self._serverEncryptedChallenge message.encryptedHWID.blobData.value = rc4.crypt(self._licenseKey, hwid) - message.MACData.value = signature + message.MACData.value = sec.macData(self._macSalt, serverChallenge + hwid) self._transport.sendLicensePacket(LicPacket(message)) \ No newline at end of file diff --git a/rdpy/protocol/rdp/rdp.py b/rdpy/protocol/rdp/rdp.py index 1b72263..f8c09d0 100644 --- a/rdpy/protocol/rdp/rdp.py +++ b/rdpy/protocol/rdp/rdp.py @@ -89,6 +89,7 @@ class RDPClientController(pdu.layer.PDUClientListener): """ #username in PDU info packet self._pduLayer._info.userName.value = username + self._pduLayer._licenceManager._username = username def setPassword(self, password): """ @@ -120,6 +121,13 @@ class RDPClientController(pdu.layer.PDUClientListener): self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).kbdLayout.value = gcc.KeyboardLayout.FRENCH elif layout == "us": self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).kbdLayout.value = gcc.KeyboardLayout.US + + def setHostname(self, hostname): + """ + @summary: set hostname of machine + """ + self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).clientName.value = hostname[:15] + "\x00" * (15 - len(hostname)) + self._pduLayer._licenceManager._hostname = hostname def addClientObserver(self, observer): """ diff --git a/rdpy/protocol/rdp/sec.py b/rdpy/protocol/rdp/sec.py index a601901..d5b94e5 100644 --- a/rdpy/protocol/rdp/sec.py +++ b/rdpy/protocol/rdp/sec.py @@ -48,18 +48,18 @@ def saltedHash(inputData, salt, salt1, salt2): return md5Digest.digest() -def md5_16_32_32(in0, in1, in2): +def finalHash(key, random1, random2): """ @summary: MD5(in0[:16] + in1[:32] + in2[:32]) - @param in0: in 16 - @param in1: in 32 - @param in2: in 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(in0[:16]) - md5Digest.update(in1[:32]) - md5Digest.update(in2[:32]) + md5Digest.update(key) + md5Digest.update(random1) + md5Digest.update(random2) return md5Digest.digest() def generateMicrosoftKey(secret, random1, random2): @@ -72,6 +72,9 @@ def generateMicrosoftKey(secret, random1, random2): 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()