30 Commits

Author SHA1 Message Date
Sylvain Peyrefitte
cef16a9f64 Merge pull request #108 from cudeso/master
Add logging for rdphoneypot
2020-04-10 21:12:15 +02:00
Koen Van Impe
9aea135fd9 Add logging 2020-04-10 21:05:18 +02:00
Sylvain Peyrefitte
4109b7a6fe Merge pull request #67 from Mutchako/patch-1
Update setup.py
2018-08-23 22:20:14 +02:00
Sylvain Peyrefitte
d3b0ae5e90 Merge pull request #80 from speidy/pointerex
onPointerEvent: handle 4, 5 mouse buttons based on INPUT_EVENT_MOUSEX event
2018-08-23 22:18:20 +02:00
Idan Freiberg
a1f9afa87a onPointerEvent: handle 4, 5 mouse buttons based on INPUT_EVENT_MOUSEX event 2018-08-06 07:52:32 +03:00
Sylvain Peyrefitte
b8ff4136b6 Merge pull request #78 from speidy/mitm
rdpy-rdpmitm: pep8 fixes, argument parsing improvements
2018-08-05 16:23:28 +02:00
Sylvain Peyrefitte
ce04150790 Merge pull request #77 from speidy/pointerex
data: add support for INPUT_EVENT_MOUSEX event
2018-08-05 16:23:03 +02:00
speidy
bcec1aad25 rdpy-rdpmitm: use argparse for argument parsing 2018-08-05 17:07:29 +03:00
speidy
c18a4c4101 rdpy-rdpmitm: apply pep8 fixes 2018-08-05 15:58:07 +03:00
speidy
3e37899ae4 data: add support for INPUT_EVENT_MOUSEX event 2018-08-05 14:28:23 +03:00
Mutchako
cd6e14e7ef Update setup.py
Syntax error on install_requires. Unnecessary comma on last statement.
2018-02-12 09:48:17 -05:00
Sylvain Peyrefitte
629d2160af Merge pull request #33 from ChrisTruncer/bin_
Small change to reference object attribute
2015-06-01 11:40:37 +02:00
Christopher Truncer
e23def3179 Small changeto reference object attribute 2015-05-28 12:08:55 -04:00
speyrefitte
a23ae25a1f fix issue on unhandle upadte 2015-05-21 10:29:32 +02:00
speyrefitte
763ed2e3ee fix major bug on update handle 2015-05-20 17:51:43 +02:00
speyrefitte
11d66a4818 add onSessionReady event -> user session is ready 2015-05-19 17:53:15 +02:00
speyrefitte
9b99365f80 fix bug on lwin key activation 2015-05-19 16:42:22 +02:00
speyrefitte
bd7c708bf3 update version 2015-05-04 11:49:37 +02:00
speyrefitte
1deb2d69ea bug fixing 2015-05-04 11:47:25 +02:00
speyrefitte
0a5a1fd12c add keylogger on rss player and file format 2015-04-29 12:13:24 +02:00
speyrefitte
c97b451ce3 Merge branch 'hotfix' of https://github.com/citronneur/rdpy into dev 2015-04-29 09:32:50 +02:00
speyrefitte
80f989a804 Merge branch 'dev' of https://github.com/citronneur/rdpy into dev 2015-04-29 09:32:44 +02:00
speyrefitte
c6e100f9a6 Merge branch 'master' of https://github.com/citronneur/rdpy into dev 2015-04-29 09:31:05 +02:00
Sylvain Peyrefitte
15df00ec20 Merge pull request #24 from citronneur/master
Update readme with pypi version
2015-03-25 14:48:18 +01:00
Sylvain Peyrefitte
342349cf41 Merge pull request #23 from citronneur/master
Just add pypi package
2015-03-25 14:47:14 +01:00
Sylvain Peyrefitte
d6043106e3 Update README.md 2015-03-25 12:15:57 +01:00
speyrefitte
bd7e73a6e7 change log format 2015-03-20 17:54:48 +01:00
Sylvain Peyrefitte
fc1685e652 Merge pull request #21 from ojosdegris/patch-1
Added OS X install example
2015-03-18 10:47:01 +01:00
vittore
4320824aae Fixed example to follow existing style. 2015-03-13 12:11:41 -04:00
vittore
5a438174b9 Added OS X install example 2015-03-13 12:07:22 -04:00
17 changed files with 552 additions and 257 deletions

View File

@@ -1,4 +1,4 @@
# RDPY [![Build Status](https://travis-ci.org/citronneur/rdpy.svg?branch=dev)](https://travis-ci.org/citronneur/rdpy) # RDPY [![Build Status](https://travis-ci.org/citronneur/rdpy.svg?branch=dev)](https://travis-ci.org/citronneur/rdpy) [![PyPI version](https://badge.fury.io/py/rdpy.png)](http://badge.fury.io/py/rdpy)
Remote Desktop Protocol in twisted python. Remote Desktop Protocol in twisted python.
@@ -33,6 +33,12 @@ Example for Debian based systems :
sudo apt-get install python-qt4 sudo apt-get install python-qt4
``` ```
#### OS X
Example for OS X to install PyQt with homebrew
```
$ brew install qt sip pyqt
```
#### Windows #### Windows
x86 | x86_64 x86 | x86_64
@@ -175,6 +181,11 @@ class MyRDPFactory(rdp.ClientFactory):
@param isCompress: use RLE compression @param isCompress: use RLE compression
@param data: bitmap data @param data: bitmap data
""" """
def onSessionReady(self):
"""
@summary: Windows session is ready
"""
def onClose(self): def onClose(self):
""" """
@@ -225,7 +236,7 @@ class MyRDPFactory(rdp.ServerFactory):
@summary: Event call on mouse event @summary: Event call on mouse event
@param x: x position @param x: x position
@param y: y position @param y: y position
@param button: 1, 2 or 3 button @param button: 1, 2, 3, 4 or 5 button
@param isPressed: True if mouse button is pressed @param isPressed: True if mouse button is pressed
@see: rdp.RDPServerObserver.onPointerEvent @see: rdp.RDPServerObserver.onPointerEvent
""" """

View File

@@ -115,7 +115,11 @@ class RDPClientQtFactory(rdp.ClientFactory):
self._nego = security == "nego" self._nego = security == "nego"
self._recodedPath = recodedPath self._recodedPath = recodedPath
if self._nego: if self._nego:
self._security = rdp.SecurityLevel.RDP_LEVEL_NLA #compute start nego nla need credentials
if username != "" and password != "":
self._security = rdp.SecurityLevel.RDP_LEVEL_NLA
else:
self._security = rdp.SecurityLevel.RDP_LEVEL_SSL
else: else:
self._security = security self._security = security
self._w = None self._w = None
@@ -168,7 +172,7 @@ class RDPClientQtFactory(rdp.ClientFactory):
connector.connect() connector.connect()
return return
QtGui.QMessageBox.warning(self._w, "Warning", "Lost connection : %s"%reason) log.info("Lost connection : %s"%reason)
reactor.stop() reactor.stop()
app.exit() app.exit()
@@ -178,7 +182,7 @@ class RDPClientQtFactory(rdp.ClientFactory):
@param connector: twisted connector use for rdp connection (use reconnect to restart connection) @param connector: twisted connector use for rdp connection (use reconnect to restart connection)
@param reason: str use to advertise reason of lost connection @param reason: str use to advertise reason of lost connection
""" """
QtGui.QMessageBox.warning(self._w, "Warning", "Connection failed : %s"%reason) log.info("Connection failed : %s"%reason)
reactor.stop() reactor.stop()
app.exit() app.exit()
@@ -190,7 +194,7 @@ def autoDetectKeyboardLayout():
if os.name == 'posix': if os.name == 'posix':
from subprocess import check_output from subprocess import check_output
result = check_output(["setxkbmap", "-print"]) result = check_output(["setxkbmap", "-print"])
if "azerty" in result: if 'azerty' in result:
return "fr" return "fr"
elif os.name == 'nt': elif os.name == 'nt':
import win32api, win32con, win32process import win32api, win32con, win32process

View File

@@ -22,7 +22,7 @@
RDP Honey pot use Rss scenario file to simulate RDP server RDP Honey pot use Rss scenario file to simulate RDP server
""" """
import sys, os, getopt, time import sys, os, getopt, time, datetime
from rdpy.core import log, error, rss from rdpy.core import log, error, rss
from rdpy.protocol.rdp import rdp from rdpy.protocol.rdp import rdp
@@ -54,23 +54,18 @@ class HoneyPotServer(rdp.RDPServerObserver):
width, height = self._controller.getScreen() width, height = self._controller.getScreen()
size = width * height size = width * height
rssFilePath = sorted(self._rssFileSizeList, key = lambda x: abs(x[0][0] * x[0][1] - size))[0][1] rssFilePath = sorted(self._rssFileSizeList, key = lambda x: abs(x[0][0] * x[0][1] - size))[0][1]
log.info("select file (%s, %s) -> %s"%(width, height, rssFilePath)) log.info("%s --- select file (%s, %s) -> %s"%(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'),width, height, rssFilePath))
self._rssFile = rss.createReader(rssFilePath) self._rssFile = rss.createReader(rssFilePath)
domain, username, password = self._controller.getCredentials() domain, username, password = self._controller.getCredentials()
hostname = self._controller.getHostname() hostname = self._controller.getHostname()
log.info("""Credentials: log.info("""%s --- Credentials: domain: %s username: %s password: %s hostname: %s"""%(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), domain, username, password, hostname));
\tdomain : %s
\tusername : %s
\tpassword : %s
\thostname : %s
"""%(domain, username, password, hostname));
self.start() self.start()
def onClose(self): def onClose(self):
""" HoneyPot """ """ HoneyPot """
def onKeyEventScancode(self, code, isPressed): def onKeyEventScancode(self, code, isPressed, isExtended):
""" HoneyPot """ """ HoneyPot """
def onKeyEventUnicode(self, code, isPressed): def onKeyEventUnicode(self, code, isPressed):
@@ -125,7 +120,7 @@ class HoneyPotServerFactory(rdp.ServerFactory):
@param addr: destination address @param addr: destination address
@see: rdp.ServerFactory.buildObserver @see: rdp.ServerFactory.buildObserver
""" """
log.info("Connection from %s:%s"%(addr.host, addr.port)) log.info("%s --- Connection from %s:%s"%(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), addr.host, addr.port))
return HoneyPotServer(controller, self._rssFileSizeList) return HoneyPotServer(controller, self._rssFileSizeList)
def readSize(filePath): def readSize(filePath):
@@ -146,10 +141,12 @@ def help():
@summary: Print help in console @summary: Print help in console
""" """
print """ print """
Usage: rdpy-rdphoneypot.py rss_filepath(1..n) Usage: rdpy-rdphoneypot.py
[-L logfile]
[-l listen_port default 3389] [-l listen_port default 3389]
[-k private_key_file_path (mandatory for SSL)] [-k private_key_file_path (mandatory for SSL)]
[-c certificate_file_path (mandatory for SSL)] [-c certificate_file_path (mandatory for SSL)]
rss_filepath(1..n)
""" """
if __name__ == '__main__': if __name__ == '__main__':
@@ -159,13 +156,15 @@ if __name__ == '__main__':
rssFileSizeList = [] rssFileSizeList = []
try: try:
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:") opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:L:")
except getopt.GetoptError: except getopt.GetoptError:
help() help()
for opt, arg in opts: for opt, arg in opts:
if opt == "-h": if opt == "-h":
help() help()
sys.exit() sys.exit()
elif opt == "-L":
log._LOG_FILE = arg
elif opt == "-l": elif opt == "-l":
listen = arg listen = arg
elif opt == "-k": elif opt == "-k":
@@ -174,11 +173,12 @@ if __name__ == '__main__':
certificateFilePath = arg certificateFilePath = arg
#build size map #build size map
log.info("Build size map") log.info("%s --- Start rdphoneypot"%datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
log.info("%s --- Build size map"%datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
for arg in args: for arg in args:
size = readSize(arg) size = readSize(arg)
rssFileSizeList.append((size, arg)) rssFileSizeList.append((size, arg))
log.info("(%s, %s) -> %s"%(size[0], size[1], arg)) log.info("%s --- (%s, %s) -> %s"%(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), size[0], size[1], arg))
reactor.listenTCP(int(listen), HoneyPotServerFactory(rssFileSizeList, privateKeyFilePath, certificateFilePath)) reactor.listenTCP(int(listen), HoneyPotServerFactory(rssFileSizeList, privateKeyFilePath, certificateFilePath))
reactor.run() reactor.run()

View File

@@ -29,7 +29,10 @@ Client RDP -> | ProxyServer | ProxyClient | -> Server RDP
----------------- -----------------
""" """
import sys, os, getopt, time import sys
import os
import argparse
import time
from rdpy.core import log, error, rss from rdpy.core import log, error, rss
from rdpy.protocol.rdp import rdp from rdpy.protocol.rdp import rdp
@@ -37,10 +40,12 @@ from twisted.internet import reactor
log._LOG_LEVEL = log.Level.INFO log._LOG_LEVEL = log.Level.INFO
class ProxyServer(rdp.RDPServerObserver): class ProxyServer(rdp.RDPServerObserver):
""" """
@summary: Server side of proxy @summary: Server side of proxy
""" """
def __init__(self, controller, target, clientSecurityLevel, rssRecorder): def __init__(self, controller, target, clientSecurityLevel, rssRecorder):
""" """
@param controller: {RDPServerController} @param controller: {RDPServerController}
@@ -52,14 +57,14 @@ class ProxyServer(rdp.RDPServerObserver):
self._client = None self._client = None
self._rss = rssRecorder self._rss = rssRecorder
self._clientSecurityLevel = clientSecurityLevel self._clientSecurityLevel = clientSecurityLevel
def setClient(self, client): def setClient(self, client):
""" """
@summary: Event throw by client when it's ready @summary: Event throw by client when it's ready
@param client: {ProxyClient} @param client: {ProxyClient}
""" """
self._client = client self._client = client
def onReady(self): def onReady(self):
""" """
@summary: Event use to inform state of server stack @summary: Event use to inform state of server stack
@@ -69,40 +74,44 @@ class ProxyServer(rdp.RDPServerObserver):
@see: rdp.RDPServerObserver.onReady @see: rdp.RDPServerObserver.onReady
""" """
if self._client is None: if self._client is None:
#try a connection # try a connection
domain, username, password = self._controller.getCredentials() domain, username, password = self._controller.getCredentials()
self._rss.credentials(username, password, domain, self._controller.getHostname()) self._rss.credentials(username, password,
domain, self._controller.getHostname())
width, height = self._controller.getScreen() width, height = self._controller.getScreen()
self._rss.screen(width, height, self._controller.getColorDepth()) self._rss.screen(width, height, self._controller.getColorDepth())
reactor.connectTCP(self._target[0], int(self._target[1]), ProxyClientFactory(self, width, height, reactor.connectTCP(self._target[0], int(self._target[1]), ProxyClientFactory(self, width, height,
domain, username, password,self._clientSecurityLevel)) domain, username, password, self._clientSecurityLevel))
def onClose(self): def onClose(self):
""" """
@summary: Call when human client close connection @summary: Call when human client close connection
@see: rdp.RDPServerObserver.onClose @see: rdp.RDPServerObserver.onClose
""" """
#end scenario # end scenario
self._rss.close() self._rss.close()
#close network stack # close network stack
if self._client is None: if self._client is None:
return return
self._client._controller.close() self._client._controller.close()
def onKeyEventScancode(self, code, isPressed): def onKeyEventScancode(self, code, isPressed, isExtended):
""" """
@summary: 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: {int} scan code of key @param code: {integer} scan code of key
@param isPressed: {bool} True if key is down @param isPressed: {boolean} True if key is down
@param isExtended: {boolean} True if a special key
@see: rdp.RDPServerObserver.onKeyEventScancode @see: rdp.RDPServerObserver.onKeyEventScancode
""" """
if self._client is None: if self._client is None:
return return
self._client._controller.sendKeyEventScancode(code, isPressed) self._client._controller.sendKeyEventScancode(
code, isPressed, isExtended)
self._rss.keyScancode(code, isPressed)
def onKeyEventUnicode(self, code, isPressed): def onKeyEventUnicode(self, code, isPressed):
""" """
@summary: Event call when a keyboard event is catch in unicode format @summary: Event call when a keyboard event is catch in unicode format
@@ -113,24 +122,27 @@ class ProxyServer(rdp.RDPServerObserver):
if self._client is None: if self._client is None:
return return
self._client._controller.sendKeyEventUnicode(code, isPressed) self._client._controller.sendKeyEventUnicode(code, isPressed)
self._rss.keyUnicode(code, isPressed)
def onPointerEvent(self, x, y, button, isPressed): def onPointerEvent(self, x, y, button, isPressed):
""" """
@summary: Event call on mouse event @summary: Event call on mouse event
@param x: {int} x position @param x: {int} x position
@param y: {int} y position @param y: {int} y position
@param button: {int} 1, 2 or 3 button @param button: {int} 1, 2, 3, 4 or 5 button
@param isPressed: {bool} True if mouse button is pressed @param isPressed: {bool} True if mouse button is pressed
@see: rdp.RDPServerObserver.onPointerEvent @see: rdp.RDPServerObserver.onPointerEvent
""" """
if self._client is None: if self._client is None:
return return
self._client._controller.sendPointerEvent(x, y, button, isPressed) self._client._controller.sendPointerEvent(x, y, button, isPressed)
class ProxyServerFactory(rdp.ServerFactory): class ProxyServerFactory(rdp.ServerFactory):
""" """
@summary: Factory on listening events @summary: Factory on listening events
""" """
def __init__(self, target, ouputDir, privateKeyFilePath, certificateFilePath, clientSecurity): def __init__(self, target, ouputDir, privateKeyFilePath, certificateFilePath, clientSecurity):
""" """
@param target: {tuple(ip, prt)} @param target: {tuple(ip, prt)}
@@ -138,13 +150,14 @@ class ProxyServerFactory(rdp.ServerFactory):
@param certificateFilePath: {str} file contain server certificate (if none -> back to standard RDP security) @param certificateFilePath: {str} file contain server certificate (if none -> back to standard RDP security)
@param clientSecurity: {str(ssl|rdp)} security layer use in client connection side @param clientSecurity: {str(ssl|rdp)} security layer use in client connection side
""" """
rdp.ServerFactory.__init__(self, 16, privateKeyFilePath, certificateFilePath) rdp.ServerFactory.__init__(
self, 16, privateKeyFilePath, certificateFilePath)
self._target = target self._target = target
self._ouputDir = ouputDir self._ouputDir = ouputDir
self._clientSecurity = clientSecurity self._clientSecurity = clientSecurity
#use produce unique file by connection # use produce unique file by connection
self._uniqueId = 0 self._uniqueId = 0
def buildObserver(self, controller, addr): def buildObserver(self, controller, addr):
""" """
@param controller: {rdp.RDPServerController} @param controller: {rdp.RDPServerController}
@@ -152,12 +165,14 @@ class ProxyServerFactory(rdp.ServerFactory):
@see: rdp.ServerFactory.buildObserver @see: rdp.ServerFactory.buildObserver
""" """
self._uniqueId += 1 self._uniqueId += 1
return ProxyServer(controller, self._target, self._clientSecurity, rss.createRecorder(os.path.join(self._ouputDir, "%s_%s_%s.rss"%(time.strftime('%Y%m%d%H%M%S'), addr.host, self._uniqueId)))) return ProxyServer(controller, self._target, self._clientSecurity, rss.createRecorder(os.path.join(self._ouputDir, "%s_%s_%s.rss" % (time.strftime('%Y%m%d%H%M%S'), addr.host, self._uniqueId))))
class ProxyClient(rdp.RDPClientObserver): class ProxyClient(rdp.RDPClientObserver):
""" """
@summary: Client side of proxy @summary: Client side of proxy
""" """
def __init__(self, controller, server): def __init__(self, controller, server):
""" """
@param controller: {rdp.RDPClientController} @param controller: {rdp.RDPClientController}
@@ -165,7 +180,7 @@ class ProxyClient(rdp.RDPClientObserver):
""" """
rdp.RDPClientObserver.__init__(self, controller) rdp.RDPClientObserver.__init__(self, controller)
self._server = server self._server = server
def onReady(self): def onReady(self):
""" """
@summary: Event use to signal that RDP stack is ready @summary: Event use to signal that RDP stack is ready
@@ -173,18 +188,26 @@ class ProxyClient(rdp.RDPClientObserver):
@see: rdp.RDPClientObserver.onReady @see: rdp.RDPClientObserver.onReady
""" """
self._server.setClient(self) self._server.setClient(self)
#maybe color depth change # maybe color depth change
self._server._controller.setColorDepth(self._controller.getColorDepth()) self._server._controller.setColorDepth(
self._controller.getColorDepth())
def onSessionReady(self):
"""
@summary: Windows session is ready
@see: rdp.RDPClientObserver.onSessionReady
"""
pass
def onClose(self): def onClose(self):
""" """
@summary: Event inform that stack is close @summary: Event inform that stack is close
@see: rdp.RDPClientObserver.onClose @see: rdp.RDPClientObserver.onClose
""" """
#end scenario # end scenario
self._server._rss.close() self._server._rss.close()
self._server._controller.close() self._server._controller.close()
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
""" """
@summary: Event use to inform bitmap update @summary: Event use to inform bitmap update
@@ -199,13 +222,17 @@ class ProxyClient(rdp.RDPClientObserver):
@param data: {str} bitmap data @param data: {str} bitmap data
@see: rdp.RDPClientObserver.onUpdate @see: rdp.RDPClientObserver.onUpdate
""" """
self._server._rss.update(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, rss.UpdateFormat.BMP if isCompress else rss.UpdateFormat.RAW, data) self._server._rss.update(destLeft, destTop, destRight, destBottom, width, height,
self._server._controller.sendUpdate(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data) bitsPerPixel, rss.UpdateFormat.BMP if isCompress else rss.UpdateFormat.RAW, data)
self._server._controller.sendUpdate(
destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data)
class ProxyClientFactory(rdp.ClientFactory): class ProxyClientFactory(rdp.ClientFactory):
""" """
@summary: Factory for proxy client @summary: Factory for proxy client
""" """
def __init__(self, server, width, height, domain, username, password, security): def __init__(self, server, width, height, domain, username, password, security):
""" """
@param server: {ProxyServer} @param server: {ProxyServer}
@@ -223,7 +250,7 @@ class ProxyClientFactory(rdp.ClientFactory):
self._username = username self._username = username
self._password = password self._password = password
self._security = security self._security = security
def buildObserver(self, controller, addr): def buildObserver(self, controller, addr):
""" """
@summary: Build observer @summary: Build observer
@@ -232,69 +259,65 @@ class ProxyClientFactory(rdp.ClientFactory):
@see: rdp.ClientFactory.buildObserver @see: rdp.ClientFactory.buildObserver
@return: ProxyClient @return: ProxyClient
""" """
#set screen resolution # set screen resolution
controller.setScreen(self._width, self._height) controller.setScreen(self._width, self._height)
#set credential # set credential
controller.setDomain(self._domain) controller.setDomain(self._domain)
controller.setUsername(self._username) controller.setUsername(self._username)
controller.setPassword(self._password) controller.setPassword(self._password)
controller.setSecurityLevel(self._security) controller.setSecurityLevel(self._security)
controller.setPerformanceSession() controller.setPerformanceSession()
return ProxyClient(controller, self._server) return ProxyClient(controller, self._server)
def help():
"""
@summary: Print help in console
"""
print """
Usage: rdpy-rdpmitm.py -o output_directory target
[-l listen_port default 3389]
[-k private_key_file_path (mandatory for SSL)]
[-c certificate_file_path (mandatory for SSL)]
[-o output directory for recoded files]
[-r RDP standard security (XP or server 2003 client or older)]
[-n For NLA Client authentication (need to provide credentials)]
"""
def parseIpPort(interface, defaultPort = "3389"):
def parseIpPort(interface, defaultPort="3389"):
if ':' in interface: if ':' in interface:
return interface.split(':') s = interface.split(':')
return s[0], int(s[1])
else: else:
return interface, defaultPort return interface, int(defaultPort)
def isDirectory(outputDirectory):
if outputDirectory is None or not os.path.dirname(outputDirectory):
log.error("{} is an invalid output directory or directory doesn't exist".format(
outputDirectory))
return outputDirectory
def mapSecurityLayer(layer):
return {
"rdp": rdp.SecurityLevel.RDP_LEVEL_RDP,
"tls": rdp.SecurityLevel.RDP_LEVEL_SSL,
"nla": rdp.SecurityLevel.RDP_LEVEL_NLA
}[layer]
if __name__ == '__main__': if __name__ == '__main__':
listen = "3389" p = argparse.ArgumentParser(
privateKeyFilePath = None formatter_class=argparse.ArgumentDefaultsHelpFormatter)
certificateFilePath = None
ouputDirectory = None p.add_argument('-l', '--listen', type=parseIpPort, default="0.0.0.0:3389",
#for anonymous authentication help="<addr>[:<port>] to bind the server")
clientSecurity = rdp.SecurityLevel.RDP_LEVEL_SSL p.add_argument('-t', '--target', type=parseIpPort, required=True,
help="<addr>[:<port>] of the target you want to connect to via proxy")
try: p.add_argument('-o', '--output', type=isDirectory,
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:o:rn") help="output directory", required=True)
except getopt.GetoptError: p.add_argument('-s', '--sec', choices=["rdp", "tls", "nla"],
help() default="rdp", help="set protocol security layer")
for opt, arg in opts: ssl = p.add_argument_group()
if opt == "-h": ssl.add_argument('-c', '--certificate', help="certificate for TLS connections")
help() ssl.add_argument('-k', '--key', help="private key of the given certificate for TLS connections")
sys.exit()
elif opt == "-l": args = p.parse_args()
listen = arg
elif opt == "-k": if args.certificate and args.key and not args.sec == "nla":
privateKeyFilePath = arg args.sec = "tls"
elif opt == "-c":
certificateFilePath = arg log.info("running server on {addr}, using {sec} security layer, proxying to {target}".format(
elif opt == "-o": addr=args.listen, sec=args.sec.upper(), target=args.target))
ouputDirectory = arg reactor.listenTCP(args.listen[1], ProxyServerFactory(
elif opt == "-r": args.target, args.output, args.key, args.certificate, mapSecurityLayer(args.sec)),
clientSecurity = rdp.SecurityLevel.RDP_LEVEL_RDP interface=args.listen[0])
elif opt == "-n":
clientSecurity = rdp.SecurityLevel.RDP_LEVEL_NLA reactor.run()
if ouputDirectory is None or not os.path.dirname(ouputDirectory):
log.error("%s is an invalid output directory"%ouputDirectory)
help()
sys.exit()
reactor.listenTCP(int(listen), ProxyServerFactory(parseIpPort(args[0]), ouputDirectory, privateKeyFilePath, certificateFilePath, clientSecurity))
reactor.run()

View File

@@ -23,7 +23,9 @@ example of use rdpy
take screenshot of login page take screenshot of login page
""" """
import sys, os, getopt import getopt
import os
import sys
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from rdpy.protocol.rdp import rdp from rdpy.protocol.rdp import rdp
@@ -32,15 +34,17 @@ import rdpy.core.log as log
from rdpy.core.error import RDPSecurityNegoFail from rdpy.core.error import RDPSecurityNegoFail
from twisted.internet import task from twisted.internet import task
#set log level # set log level
log._LOG_LEVEL = log.Level.INFO log._LOG_LEVEL = log.Level.INFO
class RDPScreenShotFactory(rdp.ClientFactory): class RDPScreenShotFactory(rdp.ClientFactory):
""" """
@summary: Factory for screenshot exemple @summary: Factory for screenshot exemple
""" """
__INSTANCE__ = 0 __INSTANCE__ = 0
__STATE__ = [] __STATE__ = []
def __init__(self, reactor, app, width, height, path, timeout): def __init__(self, reactor, app, width, height, path, timeout):
""" """
@param reactor: twisted reactor @param reactor: twisted reactor
@@ -58,7 +62,7 @@ class RDPScreenShotFactory(rdp.ClientFactory):
self._timeout = timeout self._timeout = timeout
#NLA server can't be screenshooting #NLA server can't be screenshooting
self._security = rdp.SecurityLevel.RDP_LEVEL_SSL self._security = rdp.SecurityLevel.RDP_LEVEL_SSL
def clientConnectionLost(self, connector, reason): def clientConnectionLost(self, connector, reason):
""" """
@summary: Connection lost event @summary: Connection lost event
@@ -70,14 +74,14 @@ class RDPScreenShotFactory(rdp.ClientFactory):
self._security = rdp.SecurityLevel.RDP_LEVEL_RDP self._security = rdp.SecurityLevel.RDP_LEVEL_RDP
connector.connect() connector.connect()
return return
log.info("connection lost : %s"%reason) log.info("connection lost : %s" % reason)
RDPScreenShotFactory.__STATE__.append((connector.host, connector.port, reason)) RDPScreenShotFactory.__STATE__.append((connector.host, connector.port, reason))
RDPScreenShotFactory.__INSTANCE__ -= 1 RDPScreenShotFactory.__INSTANCE__ -= 1
if(RDPScreenShotFactory.__INSTANCE__ == 0): if(RDPScreenShotFactory.__INSTANCE__ == 0):
self._reactor.stop() self._reactor.stop()
self._app.exit() self._app.exit()
def clientConnectionFailed(self, connector, reason): def clientConnectionFailed(self, connector, reason):
""" """
@summary: Connection failed event @summary: Connection failed event
@@ -90,8 +94,7 @@ class RDPScreenShotFactory(rdp.ClientFactory):
if(RDPScreenShotFactory.__INSTANCE__ == 0): if(RDPScreenShotFactory.__INSTANCE__ == 0):
self._reactor.stop() self._reactor.stop()
self._app.exit() self._app.exit()
def buildObserver(self, controller, addr): def buildObserver(self, controller, addr):
""" """
@summary: build ScreenShot observer @summary: build ScreenShot observer
@@ -117,39 +120,46 @@ class RDPScreenShotFactory(rdp.ClientFactory):
self._timeout = timeout self._timeout = timeout
self._startTimeout = False self._startTimeout = False
self._reactor = reactor self._reactor = reactor
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
""" """
@summary: callback use when bitmap is received @summary: callback use when bitmap is received
""" """
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data); image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data);
with QtGui.QPainter(self._buffer) as qp: with QtGui.QPainter(self._buffer) as qp:
#draw image # draw image
qp.drawImage(destLeft, destTop, image, 0, 0, destRight - destLeft + 1, destBottom - destTop + 1) qp.drawImage(destLeft, destTop, image, 0, 0, destRight - destLeft + 1, destBottom - destTop + 1)
if not self._startTimeout: if not self._startTimeout:
self._startTimeout = False self._startTimeout = False
self._reactor.callLater(self._timeout, self.checkUpdate) self._reactor.callLater(self._timeout, self.checkUpdate)
def onReady(self): def onReady(self):
""" """
@summary: callback use when RDP stack is connected (just before received bitmap) @summary: callback use when RDP stack is connected (just before received bitmap)
""" """
log.info("connected %s"%addr) log.info("connected %s" % addr)
def onSessionReady(self):
"""
@summary: Windows session is ready
@see: rdp.RDPClientObserver.onSessionReady
"""
pass
def onClose(self): def onClose(self):
""" """
@summary: callback use when RDP stack is closed @summary: callback use when RDP stack is closed
""" """
log.info("save screenshot into %s"%self._path) log.info("save screenshot into %s" % self._path)
self._buffer.save(self._path) self._buffer.save(self._path)
def checkUpdate(self): def checkUpdate(self):
self._controller.close(); self._controller.close();
controller.setScreen(width, height); controller.setScreen(self._width, self._height);
controller.setSecurityLevel(self._security) controller.setSecurityLevel(self._security)
return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout, self._reactor) return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout, self._reactor)
def main(width, height, path, timeout, hosts): def main(width, height, path, timeout, hosts):
""" """
@summary: main algorithm @summary: main algorithm
@@ -161,39 +171,40 @@ def main(width, height, path, timeout, hosts):
""" """
#create application #create application
app = QtGui.QApplication(sys.argv) app = QtGui.QApplication(sys.argv)
#add qt4 reactor #add qt4 reactor
import qt4reactor import qt4reactor
qt4reactor.install() qt4reactor.install()
from twisted.internet import reactor from twisted.internet import reactor
for host in hosts: for host in hosts:
if ':' in host: if ':' in host:
ip, port = host.split(':') ip, port = host.split(':')
else: else:
ip, port = host, "3389" ip, port = host, "3389"
reactor.connectTCP(ip, int(port), RDPScreenShotFactory(reactor, app, width, height, path + "%s.jpg"%ip, timeout)) reactor.connectTCP(ip, int(port), RDPScreenShotFactory(reactor, app, width, height, path + "%s.jpg" % ip, timeout))
reactor.runReturn() reactor.runReturn()
app.exec_() app.exec_()
return RDPScreenShotFactory.__STATE__ return RDPScreenShotFactory.__STATE__
def help(): def help():
print "Usage: rdpy-rdpscreenshot [options] ip[:port]" print "Usage: rdpy-rdpscreenshot [options] ip[:port]"
print "\t-w: width of screen default value is 1024" print "\t-w: width of screen default value is 1024"
print "\t-l: height of screen default value is 800" print "\t-l: height of screen default value is 800"
print "\t-o: file path of screenshot default(/tmp/rdpy-rdpscreenshot.jpg)" print "\t-o: file path of screenshot default(/tmp/rdpy-rdpscreenshot.jpg)"
print "\t-t: timeout of connection without any updating order (default is 2s)" print "\t-t: timeout of connection without any updating order (default is 2s)"
if __name__ == '__main__': if __name__ == '__main__':
#default script argument # default script argument
width = 1024 width = 1024
height = 800 height = 800
path = "/tmp/" path = "/tmp/"
timeout = 5.0 timeout = 5.0
try: try:
opts, args = getopt.getopt(sys.argv[1:], "hw:l:o:t:") opts, args = getopt.getopt(sys.argv[1:], "hw:l:o:t:")
except getopt.GetoptError: except getopt.GetoptError:
@@ -210,5 +221,5 @@ if __name__ == '__main__':
path = arg path = arg
elif opt == "-t": elif opt == "-t":
timeout = float(arg) timeout = float(arg)
main(width, height, path, timeout, args) main(width, height, path, timeout, args)

View File

@@ -27,6 +27,7 @@ from PyQt4 import QtGui, QtCore
from rdpy.core import log, rss from rdpy.core import log, rss
from rdpy.ui.qt4 import QRemoteDesktop, RDPBitmapToQtImage from rdpy.ui.qt4 import QRemoteDesktop, RDPBitmapToQtImage
from rdpy.core.scancode import scancodeToChar
log._LOG_LEVEL = log.Level.INFO log._LOG_LEVEL = log.Level.INFO
class RssPlayerWidget(QRemoteDesktop): class RssPlayerWidget(QRemoteDesktop):
@@ -45,9 +46,28 @@ class RssPlayerWidget(QRemoteDesktop):
""" Not Handle """ """ Not Handle """
QRemoteDesktop.__init__(self, width, height, RssAdaptor()) QRemoteDesktop.__init__(self, width, height, RssAdaptor())
def drawInfos(self, domain, username, password, hostname): class RssPlayerWindow(QtGui.QWidget):
QtGui.QMessageBox.about(self, "Credentials Event", "domain : %s\nusername : %s\npassword : %s\nhostname : %s" % ( """
domain, username, password, hostname)) @summary: main window of rss player
"""
def __init__(self):
super(RssPlayerWindow, self).__init__()
self._viewer = RssPlayerWidget(800, 600)
self._text = QtGui.QTextEdit()
self._text.setReadOnly(True)
self._text.setFixedHeight(150)
scrollViewer = QtGui.QScrollArea()
scrollViewer.setWidget(self._viewer)
layout = QtGui.QVBoxLayout()
layout.addWidget(scrollViewer, 1)
layout.addWidget(self._text, 2)
self.setLayout(layout)
self.setGeometry(0, 0, 800, 600)
def help(): def help():
print "Usage: rdpy-rssplayer [-h] rss_filepath" print "Usage: rdpy-rssplayer [-h] rss_filepath"
@@ -64,16 +84,20 @@ def loop(widget, rssFile, nextEvent):
if nextEvent.type.value == rss.EventType.UPDATE: if nextEvent.type.value == rss.EventType.UPDATE:
image = RDPBitmapToQtImage(nextEvent.event.width.value, nextEvent.event.height.value, nextEvent.event.bpp.value, nextEvent.event.format.value == rss.UpdateFormat.BMP, nextEvent.event.data.value); image = RDPBitmapToQtImage(nextEvent.event.width.value, nextEvent.event.height.value, nextEvent.event.bpp.value, nextEvent.event.format.value == rss.UpdateFormat.BMP, nextEvent.event.data.value);
widget.notifyImage(nextEvent.event.destLeft.value, nextEvent.event.destTop.value, image, nextEvent.event.destRight.value - nextEvent.event.destLeft.value + 1, nextEvent.event.destBottom.value - nextEvent.event.destTop.value + 1) widget._viewer.notifyImage(nextEvent.event.destLeft.value, nextEvent.event.destTop.value, image, nextEvent.event.destRight.value - nextEvent.event.destLeft.value + 1, nextEvent.event.destBottom.value - nextEvent.event.destTop.value + 1)
elif nextEvent.type.value == rss.EventType.SCREEN: elif nextEvent.type.value == rss.EventType.SCREEN:
widget.resize(nextEvent.event.width.value, nextEvent.event.height.value) widget._viewer.resize(nextEvent.event.width.value, nextEvent.event.height.value)
elif nextEvent.type.value == rss.EventType.INFO: elif nextEvent.type.value == rss.EventType.INFO:
widget.drawInfos(nextEvent.event.domain.value, nextEvent.event.username.value, nextEvent.event.password.value, nextEvent.event.hostname.value) widget._text.append("Domain : %s\nUsername : %s\nPassword : %s\nHostname : %s\n" % (
nextEvent.event.domain.value, nextEvent.event.username.value, nextEvent.event.password.value, nextEvent.event.hostname.value))
elif nextEvent.type.value == rss.EventType.KEY_SCANCODE:
if nextEvent.event.isPressed.value == 0:
widget._text.moveCursor(QtGui.QTextCursor.End)
widget._text.insertPlainText(scancodeToChar(nextEvent.event.code.value))
elif nextEvent.type.value == rss.EventType.CLOSE: elif nextEvent.type.value == rss.EventType.CLOSE:
widget.close()
return return
e = rssFile.nextEvent() e = rssFile.nextEvent()
@@ -92,8 +116,10 @@ if __name__ == '__main__':
filepath = args[0] filepath = args[0]
#create application #create application
app = QtGui.QApplication(sys.argv) app = QtGui.QApplication(sys.argv)
widget = RssPlayerWidget(800, 600)
widget.show() mainWindow = RssPlayerWindow()
mainWindow.show()
rssFile = rss.createReader(filepath) rssFile = rss.createReader(filepath)
start(widget, rssFile) start(mainWindow, rssFile)
sys.exit(app.exec_()) sys.exit(app.exec_())

View File

@@ -33,13 +33,18 @@ class Level(object):
NONE = 4 NONE = 4
_LOG_LEVEL = Level.DEBUG _LOG_LEVEL = Level.DEBUG
_LOG_FILE = False
def log(message): def log(message):
""" """
@summary: Main log function @summary: Main log function
@param message: string to print @param message: string to print
""" """
print message if _LOG_FILE:
f = open(_LOG_FILE, "a+")
f.write("%s\n"%message)
f.close()
print "[*] %s"%message
def error(message): def error(message):
""" """
@@ -48,7 +53,7 @@ def error(message):
""" """
if _LOG_LEVEL > Level.ERROR: if _LOG_LEVEL > Level.ERROR:
return return
log("ERROR : %s"%message) log("ERROR:\t%s"%message)
def warning(message): def warning(message):
""" """
@@ -57,7 +62,7 @@ def warning(message):
""" """
if _LOG_LEVEL > Level.WARNING: if _LOG_LEVEL > Level.WARNING:
return return
log("WARNING : %s"%message) log("WARNING:\t%s"%message)
def info(message): def info(message):
""" """
@@ -66,7 +71,7 @@ def info(message):
""" """
if _LOG_LEVEL > Level.INFO: if _LOG_LEVEL > Level.INFO:
return return
log("INFO : %s"%message) log("INFO:\t%s"%message)
def debug(message): def debug(message):
""" """
@@ -75,4 +80,4 @@ def debug(message):
""" """
if _LOG_LEVEL > Level.DEBUG: if _LOG_LEVEL > Level.DEBUG:
return return
log("DEBUG : %s"%message) log("DEBUG:\t%s"%message)

View File

@@ -34,6 +34,8 @@ class EventType(object):
SCREEN = 0x0002 SCREEN = 0x0002
INFO = 0x0003 INFO = 0x0003
CLOSE = 0x0004 CLOSE = 0x0004
KEY_UNICODE = 0x0005
KEY_SCANCODE = 0x0006
class UpdateFormat(object): class UpdateFormat(object):
""" """
@@ -56,7 +58,7 @@ class Event(CompositeType):
""" """
@summary: Closure for event factory @summary: Closure for event factory
""" """
for c in [UpdateEvent, ScreenEvent, InfoEvent, CloseEvent]: for c in [UpdateEvent, ScreenEvent, InfoEvent, CloseEvent, KeyEventScancode, KeyEventUnicode]:
if self.type.value == c._TYPE_: if self.type.value == c._TYPE_:
return c(readLen = self.length) return c(readLen = self.length)
log.debug("unknown event type : %s"%hex(self.type.value)) log.debug("unknown event type : %s"%hex(self.type.value))
@@ -123,6 +125,26 @@ class CloseEvent(CompositeType):
def __init__(self, readLen = None): def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen) CompositeType.__init__(self, readLen = readLen)
class KeyEventUnicode(CompositeType):
"""
@summary: keyboard event (keylogger) as unicode event
"""
_TYPE_ = EventType.KEY_UNICODE
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.code = UInt32Le()
self.isPressed = UInt8()
class KeyEventScancode(CompositeType):
"""
@summary: keyboard event (keylogger)
"""
_TYPE_ = EventType.KEY_SCANCODE
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.code = UInt32Le()
self.isPressed = UInt8()
def timeMs(): def timeMs():
""" """
@return: {int} time stamp in milliseconds @return: {int} time stamp in milliseconds
@@ -211,6 +233,28 @@ class FileRecorder(object):
infoEvent.domain.value = domain infoEvent.domain.value = domain
infoEvent.hostname.value = hostname infoEvent.hostname.value = hostname
self.rec(infoEvent) self.rec(infoEvent)
def keyUnicode(self, code, isPressed):
"""
@summary: record key event as unicode
@param code: unicode code
@param isPressed: True if a key press event
"""
keyEvent = KeyEventUnicode()
keyEvent.code.value = code
keyEvent.isPressed.value = 0 if isPressed else 1
self.rec(keyEvent)
def keyScancode(self, code, isPressed):
"""
@summary: record key event as scancode
@param code: scancode code
@param isPressed: True if a key press event
"""
keyEvent = KeyEventScancode()
keyEvent.code.value = code
keyEvent.isPressed.value = 0 if isPressed else 1
self.rec(keyEvent)
def close(self): def close(self):
""" """

60
rdpy/core/scancode.py Normal file
View File

@@ -0,0 +1,60 @@
#
# Copyright (c) 2014-2015 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/>.
#
"""
Basic virtual scancode mapping
"""
_SCANCODE_QWERTY_ = {
0x10 : "q",
0x11 : "w",
0x12 : "e",
0x13 : "r",
0x14 : "t",
0x15 : "y",
0x16 : "u",
0x17 : "i",
0x18 : "o",
0x19 : "p",
0x1e : "a",
0x1f : "s",
0x20 : "d",
0x21 : "f",
0x22 : "g",
0x23 : "h",
0x24 : "j",
0x25 : "k",
0x26 : "l",
0x2c : "z",
0x2d : "x",
0x2e : "c",
0x2f : "v",
0x30 : "b",
0x31 : "n",
0x32 : "m"
}
def scancodeToChar(code):
"""
@summary: try to convert native code to char code
@return: char
"""
if not _SCANCODE_QWERTY_.has_key(code):
return "<unknown scancode %x>"%code
return _SCANCODE_QWERTY_[code];

View File

@@ -477,7 +477,7 @@ class CompositeType(Type):
raise e raise e
if not self._readLen is None and readLen < self._readLen.value: if not self._readLen is None and readLen < self._readLen.value:
log.debug("Still have correct data in packet %s, read it as padding"%self.__class__) log.debug("Still have correct data in packet %s, read %s bytes as padding"%(self.__class__, self._readLen.value - readLen))
s.read(self._readLen.value - readLen) s.read(self._readLen.value - readLen)
def __write__(self, s): def __write__(self, s):

View File

@@ -234,7 +234,7 @@ class LicPacket(CompositeType):
if self.bMsgtype.value == c._MESSAGE_TYPE_: if self.bMsgtype.value == c._MESSAGE_TYPE_:
return c(readLen = self.wMsgSize - 4) return c(readLen = self.wMsgSize - 4)
log.debug("unknown license message : %s"%self.bMsgtype.value) log.debug("unknown license message : %s"%self.bMsgtype.value)
return String() return String(readLen = self.wMsgSize - 4)
if message is None: if message is None:
message = FactoryType(LicensingMessageFactory) message = FactoryType(LicensingMessageFactory)

View File

@@ -160,7 +160,16 @@ class PointerFlag(object):
PTRFLAGS_BUTTON1 = 0x1000 PTRFLAGS_BUTTON1 = 0x1000
PTRFLAGS_BUTTON2 = 0x2000 PTRFLAGS_BUTTON2 = 0x2000
PTRFLAGS_BUTTON3 = 0x4000 PTRFLAGS_BUTTON3 = 0x4000
class PointerExFlag(object):
"""
@summary: Use in Pointer event
@see: https://msdn.microsoft.com/en-us/library/cc240587.aspx
"""
PTRXFLAGS_DOWN = 0x8000
PTRXFLAGS_BUTTON1 = 0x0001
PTRXFLAGS_BUTTON2 = 0x0002
class KeyboardFlag(object): class KeyboardFlag(object):
""" """
@summary: Use in scan code key event @summary: Use in scan code key event
@@ -202,6 +211,16 @@ class Display(object):
SUPPRESS_DISPLAY_UPDATES = 0x00 SUPPRESS_DISPLAY_UPDATES = 0x00
ALLOW_DISPLAY_UPDATES = 0x01 ALLOW_DISPLAY_UPDATES = 0x01
class ToogleFlag(object):
"""
@summary: Use to known state of keyboard
@see: https://msdn.microsoft.com/en-us/library/cc240588.aspx
"""
TS_SYNC_SCROLL_LOCK = 0x00000001
TS_SYNC_NUM_LOCK = 0x00000002
TS_SYNC_CAPS_LOCK = 0x00000004
TS_SYNC_KANA_LOCK = 0x00000008
class ErrorInfo(object): class ErrorInfo(object):
""" """
@summary: Error code use in Error info PDU @summary: Error code use in Error info PDU
@@ -413,8 +432,6 @@ class ErrorInfo(object):
ERRINFO_VCDATATOOLONG : "The size of a received Virtual Channel PDU (section 2.2.6.1) exceeds the chunking size specified in the Virtual Channel Capability Set (section 2.2.7.1.10).", ERRINFO_VCDATATOOLONG : "The size of a received Virtual Channel PDU (section 2.2.6.1) exceeds the chunking size specified in the Virtual Channel Capability Set (section 2.2.7.1.10).",
} }
class ShareControlHeader(CompositeType): class ShareControlHeader(CompositeType):
""" """
@summary: PDU share control header @summary: PDU share control header
@@ -461,10 +478,10 @@ class PDU(CompositeType):
""" """
for c in [DemandActivePDU, ConfirmActivePDU, DataPDU, DeactiveAllPDU]: for c in [DemandActivePDU, ConfirmActivePDU, DataPDU, DeactiveAllPDU]:
if self.shareControlHeader.pduType.value == c._PDUTYPE_: if self.shareControlHeader.pduType.value == c._PDUTYPE_:
return c() return c(readLen = CallableValue(self.shareControlHeader.totalLength.value - sizeof(self.shareControlHeader)))
log.debug("unknown PDU type : %s"%hex(self.shareControlHeader.pduType.value)) log.debug("unknown PDU type : %s"%hex(self.shareControlHeader.pduType.value))
#read entire packet #read entire packet
return String() return String(readLen = CallableValue(self.shareControlHeader.totalLength.value - sizeof(self.shareControlHeader)))
if pduMessage is None: if pduMessage is None:
pduMessage = FactoryType(PDUMessageFactory) pduMessage = FactoryType(PDUMessageFactory)
@@ -481,8 +498,8 @@ class DemandActivePDU(CompositeType):
#may declare the PDU type #may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_DEMANDACTIVEPDU _PDUTYPE_ = PDUType.PDUTYPE_DEMANDACTIVEPDU
def __init__(self): def __init__(self, readLen = None):
CompositeType.__init__(self) CompositeType.__init__(self, readLen = readLen)
self.shareId = UInt32Le() self.shareId = UInt32Le()
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor)) self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
self.lengthCombinedCapabilities = UInt16Le(lambda:(sizeof(self.numberCapabilities) + sizeof(self.pad2Octets) + sizeof(self.capabilitySets))) self.lengthCombinedCapabilities = UInt16Le(lambda:(sizeof(self.numberCapabilities) + sizeof(self.pad2Octets) + sizeof(self.capabilitySets)))
@@ -500,8 +517,8 @@ class ConfirmActivePDU(CompositeType):
#may declare the PDU type #may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_CONFIRMACTIVEPDU _PDUTYPE_ = PDUType.PDUTYPE_CONFIRMACTIVEPDU
def __init__(self): def __init__(self, readLen = None):
CompositeType.__init__(self) CompositeType.__init__(self, readLen = readLen)
self.shareId = UInt32Le() self.shareId = UInt32Le()
self.originatorId = UInt16Le(0x03EA, constant = True) self.originatorId = UInt16Le(0x03EA, constant = True)
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor)) self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
@@ -519,10 +536,10 @@ class DeactiveAllPDU(CompositeType):
#may declare the PDU type #may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_DEACTIVATEALLPDU _PDUTYPE_ = PDUType.PDUTYPE_DEACTIVATEALLPDU
def __init__(self): def __init__(self, readLen = None):
#in old version this packet is empty i don't know #in old version this packet is empty i don't know
#and not specified #and not specified
CompositeType.__init__(self, optional = True) CompositeType.__init__(self, optional = True, readLen = readLen)
self.shareId = UInt32Le() self.shareId = UInt32Le()
self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor)) self.lengthSourceDescriptor = UInt16Le(lambda:sizeof(self.sourceDescriptor))
self.sourceDescriptor = String("rdpy", readLen = self.lengthSourceDescriptor) self.sourceDescriptor = String("rdpy", readLen = self.lengthSourceDescriptor)
@@ -534,19 +551,19 @@ class DataPDU(CompositeType):
#may declare the PDU type #may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_DATAPDU _PDUTYPE_ = PDUType.PDUTYPE_DATAPDU
def __init__(self, pduData = None, shareId = 0): def __init__(self, pduData = None, shareId = 0, readLen = None):
CompositeType.__init__(self) CompositeType.__init__(self, readLen = readLen)
self.shareDataHeader = ShareDataHeader(lambda:sizeof(self), lambda:self.pduData.__class__._PDUTYPE2_, shareId) self.shareDataHeader = ShareDataHeader(lambda:sizeof(self), lambda:self.pduData.__class__._PDUTYPE2_, shareId)
def PDUDataFactory(): def PDUDataFactory():
""" """
@summary: Create object in accordance self.shareDataHeader.pduType2 value @summary: Create object in accordance self.shareDataHeader.pduType2 value
""" """
for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SupressOutputDataPDU]: for c in [UpdateDataPDU, SynchronizeDataPDU, ControlDataPDU, ErrorInfoDataPDU, FontListDataPDU, FontMapDataPDU, PersistentListPDU, ClientInputEventPDU, ShutdownDeniedPDU, ShutdownRequestPDU, SupressOutputDataPDU, SaveSessionInfoPDU]:
if self.shareDataHeader.pduType2.value == c._PDUTYPE2_: if self.shareDataHeader.pduType2.value == c._PDUTYPE2_:
return c() return c(readLen = CallableValue(readLen.value - sizeof(self.shareDataHeader)))
log.debug("unknown PDU data type : %s"%hex(self.shareDataHeader.pduType2.value)) log.debug("unknown PDU data type : %s"%hex(self.shareDataHeader.pduType2.value))
return String() return String(readLen = CallableValue(readLen.value - sizeof(self.shareDataHeader)))
if pduData is None: if pduData is None:
pduData = FactoryType(PDUDataFactory) pduData = FactoryType(PDUDataFactory)
@@ -655,8 +672,8 @@ class PersistentListPDU(CompositeType):
""" """
_PDUTYPE2_ = PDUType2.PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST _PDUTYPE2_ = PDUType2.PDUTYPE2_BITMAPCACHE_PERSISTENT_LIST
def __init__(self, userId = 0, shareId = 0): def __init__(self, userId = 0, shareId = 0, readLen = None):
CompositeType.__init__(self) CompositeType.__init__(self, readLen = readLen)
self.numEntriesCache0 = UInt16Le() self.numEntriesCache0 = UInt16Le()
self.numEntriesCache1 = UInt16Le() self.numEntriesCache1 = UInt16Le()
self.numEntriesCache2 = UInt16Le() self.numEntriesCache2 = UInt16Le()
@@ -679,8 +696,8 @@ class ClientInputEventPDU(CompositeType):
""" """
_PDUTYPE2_ = PDUType2.PDUTYPE2_INPUT _PDUTYPE2_ = PDUType2.PDUTYPE2_INPUT
def __init__(self): def __init__(self, readLen = None):
CompositeType.__init__(self) CompositeType.__init__(self, readLen = readLen)
self.numEvents = UInt16Le(lambda:len(self.slowPathInputEvents._array)) self.numEvents = UInt16Le(lambda:len(self.slowPathInputEvents._array))
self.pad2Octets = UInt16Le() self.pad2Octets = UInt16Le()
self.slowPathInputEvents = ArrayType(SlowPathInputEvent, readLen = self.numEvents) self.slowPathInputEvents = ArrayType(SlowPathInputEvent, readLen = self.numEvents)
@@ -691,8 +708,8 @@ class ShutdownRequestPDU(CompositeType):
client -> server client -> server
""" """
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_REQUEST _PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_REQUEST
def __init__(self): def __init__(self, readLen = None):
CompositeType.__init__(self) CompositeType.__init__(self, readLen = readLen)
class ShutdownDeniedPDU(CompositeType): class ShutdownDeniedPDU(CompositeType):
""" """
@@ -700,8 +717,8 @@ class ShutdownDeniedPDU(CompositeType):
server -> client server -> client
""" """
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_DENIED _PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_DENIED
def __init__(self): def __init__(self, readLen = None):
CompositeType.__init__(self) CompositeType.__init__(self, readLen = readLen)
class InclusiveRectangle(CompositeType): class InclusiveRectangle(CompositeType):
""" """
@@ -761,9 +778,9 @@ class UpdateDataPDU(CompositeType):
""" """
for c in [BitmapUpdateDataPDU]: for c in [BitmapUpdateDataPDU]:
if self.updateType.value == c._UPDATE_TYPE_: if self.updateType.value == c._UPDATE_TYPE_:
return c() return c(readLen = CallableValue(readLen.value - 2))
log.debug("unknown PDU update data type : %s"%hex(self.updateType.value)) log.debug("unknown PDU update data type : %s"%hex(self.updateType.value))
return String() return String(readLen = CallableValue(readLen.value - 2))
if updateData is None: if updateData is None:
updateData = FactoryType(UpdateDataFactory, conditional = lambda:(self.updateType.value != UpdateType.UPDATETYPE_SYNCHRONIZE)) updateData = FactoryType(UpdateDataFactory, conditional = lambda:(self.updateType.value != UpdateType.UPDATETYPE_SYNCHRONIZE))
@@ -771,7 +788,19 @@ class UpdateDataPDU(CompositeType):
raise InvalidExpectedDataException("Try to send an invalid data update PDU") raise InvalidExpectedDataException("Try to send an invalid data update PDU")
self.updateData = updateData self.updateData = updateData
class SaveSessionInfoPDU(CompositeType):
"""
@see: https://msdn.microsoft.com/en-us/library/cc240636.aspx
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SAVE_SESSION_INFO
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.infoType = UInt32Le()
#TODO parse info data
self.infoData = String()
class FastPathUpdatePDU(CompositeType): class FastPathUpdatePDU(CompositeType):
""" """
@summary: Fast path update PDU packet @summary: Fast path update PDU packet
@@ -789,9 +818,9 @@ class FastPathUpdatePDU(CompositeType):
""" """
for c in [FastPathBitmapUpdateDataPDU]: for c in [FastPathBitmapUpdateDataPDU]:
if (self.updateHeader.value & 0xf) == c._FASTPATH_UPDATE_TYPE_: if (self.updateHeader.value & 0xf) == c._FASTPATH_UPDATE_TYPE_:
return c() return c(readLen = self.size)
log.debug("unknown Fast Path PDU update data type : %s"%hex(self.updateHeader.value & 0xf)) log.debug("unknown Fast Path PDU update data type : %s"%hex(self.updateHeader.value & 0xf))
return String() return String(readLen = self.size)
if updateData is None: if updateData is None:
updateData = FactoryType(UpdateDataFactory) updateData = FactoryType(UpdateDataFactory)
@@ -821,8 +850,8 @@ class OrderUpdateDataPDU(CompositeType):
@see: http://msdn.microsoft.com/en-us/library/cc241571.aspx @see: http://msdn.microsoft.com/en-us/library/cc241571.aspx
@todo: not implemented yet but need it @todo: not implemented yet but need it
""" """
def __init__(self): def __init__(self, readLen = None):
CompositeType.__init__(self) CompositeType.__init__(self, readLen = readLen)
self.pad2OctetsA = UInt16Le() self.pad2OctetsA = UInt16Le()
self.numberOrders = UInt16Le(lambda:len(self.orderData._array)) self.numberOrders = UInt16Le(lambda:len(self.orderData._array))
self.pad2OctetsB = UInt16Le() self.pad2OctetsB = UInt16Le()
@@ -882,8 +911,8 @@ class FastPathBitmapUpdateDataPDU(CompositeType):
""" """
_FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP _FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP
def __init__(self): def __init__(self, readLen = None):
CompositeType.__init__(self) CompositeType.__init__(self, readLen = readLen)
self.header = UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP, constant = True) self.header = UInt16Le(FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP, constant = True)
self.numberRectangles = UInt16Le(lambda:len(self.rectangles._array)) self.numberRectangles = UInt16Le(lambda:len(self.rectangles._array))
self.rectangles = ArrayType(BitmapData, readLen = self.numberRectangles) self.rectangles = ArrayType(BitmapData, readLen = self.numberRectangles)
@@ -899,11 +928,10 @@ class SlowPathInputEvent(CompositeType):
self.messageType = UInt16Le(lambda:self.slowPathInputData.__class__._INPUT_MESSAGE_TYPE_) self.messageType = UInt16Le(lambda:self.slowPathInputData.__class__._INPUT_MESSAGE_TYPE_)
def SlowPathInputDataFactory(): def SlowPathInputDataFactory():
for c in [PointerEvent, ScancodeKeyEvent, UnicodeKeyEvent]: for c in [PointerEvent, PointerExEvent, ScancodeKeyEvent, UnicodeKeyEvent, SynchronizeEvent]:
if self.messageType.value == c._INPUT_MESSAGE_TYPE_: if self.messageType.value == c._INPUT_MESSAGE_TYPE_:
return c() return c()
log.debug("unknown slow path input : %s"%hex(self.messageType.value)) raise InvalidExpectedDataException("unknown slow path input : %s"%hex(self.messageType.value))
return String()
if messageData is None: if messageData is None:
messageData = FactoryType(SlowPathInputDataFactory) messageData = FactoryType(SlowPathInputDataFactory)
@@ -911,7 +939,19 @@ class SlowPathInputEvent(CompositeType):
raise InvalidExpectedDataException("try to send an invalid Slow Path Input Event") raise InvalidExpectedDataException("try to send an invalid Slow Path Input Event")
self.slowPathInputData = messageData self.slowPathInputData = messageData
class SynchronizeEvent(CompositeType):
"""
@summary: Synchronize keyboard
@see: https://msdn.microsoft.com/en-us/library/cc240588.aspx
"""
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_SYNC
def __init__(self):
CompositeType.__init__(self)
self.pad2Octets = UInt16Le()
self.toggleFlags = UInt32Le()
class PointerEvent(CompositeType): class PointerEvent(CompositeType):
""" """
@summary: Event use to communicate mouse position @summary: Event use to communicate mouse position
@@ -925,6 +965,19 @@ class PointerEvent(CompositeType):
self.xPos = UInt16Le() self.xPos = UInt16Le()
self.yPos = UInt16Le() self.yPos = UInt16Le()
class PointerExEvent(CompositeType):
"""
@summary: Event use to communicate mouse position
@see: http://msdn.microsoft.com/en-us/library/cc240587.aspx
"""
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_MOUSEX
def __init__(self):
CompositeType.__init__(self)
self.pointerFlags = UInt16Le()
self.xPos = UInt16Le()
self.yPos = UInt16Le()
class ScancodeKeyEvent(CompositeType): class ScancodeKeyEvent(CompositeType):
""" """
@summary: Event use to communicate keyboard informations @summary: Event use to communicate keyboard informations

View File

@@ -25,6 +25,7 @@ In this layer are managed all mains bitmap update orders end user inputs
from rdpy.core.layer import LayerAutomata from rdpy.core.layer import LayerAutomata
from rdpy.core.error import CallPureVirtualFuntion from rdpy.core.error import CallPureVirtualFuntion
from rdpy.core.type import ArrayType
import rdpy.core.log as log import rdpy.core.log as log
import rdpy.protocol.rdp.tpkt as tpkt import rdpy.protocol.rdp.tpkt as tpkt
import data, caps import data, caps
@@ -39,6 +40,13 @@ class PDUClientListener(object):
""" """
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUClientListener")) raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUClientListener"))
def onSessionReady(self):
"""
@summary: Event call when Windows session is ready
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSessionReady", "PDUClientListener"))
def onUpdate(self, rectangles): def onUpdate(self, rectangles):
""" """
@summary: call when a bitmap data is received from update PDU @summary: call when a bitmap data is received from update PDU
@@ -259,15 +267,16 @@ class Client(PDULayer):
@summary: Main receive function after connection sequence @summary: Main receive function after connection sequence
@param s: Stream from transport layer @param s: Stream from transport layer
""" """
pdu = data.PDU() pdus = ArrayType(data.PDU)
s.readType(pdu) s.readType(pdus)
if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU: for pdu in pdus:
self.readDataPDU(pdu.pduMessage) if pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DATAPDU:
elif pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DEACTIVATEALLPDU: self.readDataPDU(pdu.pduMessage)
#use in deactivation-reactivation sequence elif pdu.shareControlHeader.pduType.value == data.PDUType.PDUTYPE_DEACTIVATEALLPDU:
#next state is either a capabilities re exchange or disconnection #use in deactivation-reactivation sequence
#http://msdn.microsoft.com/en-us/library/cc240454.aspx #next state is either a capabilities re exchange or disconnection
self.setNextState(self.recvDemandActivePDU) #http://msdn.microsoft.com/en-us/library/cc240454.aspx
self.setNextState(self.recvDemandActivePDU)
def recvFastPath(self, secFlag, fastPathS): def recvFastPath(self, secFlag, fastPathS):
""" """
@@ -276,25 +285,32 @@ class Client(PDULayer):
@param fastPathS: {Stream} that contain fast path data @param fastPathS: {Stream} that contain fast path data
@param secFlag: {SecFlags} @param secFlag: {SecFlags}
""" """
fastPathPDU = data.FastPathUpdatePDU() updates = ArrayType(data.FastPathUpdatePDU)
fastPathS.readType(fastPathPDU) fastPathS.readType(updates)
if fastPathPDU.updateHeader.value == data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP: for update in updates:
self._listener.onUpdate(fastPathPDU.updateData.rectangles._array) if update.updateHeader.value == data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP:
self._listener.onUpdate(update.updateData.rectangles._array)
def readDataPDU(self, dataPDU): def readDataPDU(self, dataPDU):
""" """
@summary: read a data PDU object @summary: read a data PDU object
@param dataPDU: DataPDU object @param dataPDU: DataPDU object
""" """
if dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU: if dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU:
#ignore 0 error code because is not an error code
if dataPDU.pduData.errorInfo.value == 0:
return
errorMessage = "Unknown code %s"%hex(dataPDU.pduData.errorInfo.value) errorMessage = "Unknown code %s"%hex(dataPDU.pduData.errorInfo.value)
if data.ErrorInfo._MESSAGES_.has_key(dataPDU.pduData.errorInfo): if data.ErrorInfo._MESSAGES_.has_key(dataPDU.pduData.errorInfo):
errorMessage = data.ErrorInfo._MESSAGES_[dataPDU.pduData.errorInfo] errorMessage = data.ErrorInfo._MESSAGES_[dataPDU.pduData.errorInfo]
log.error("INFO PDU : %s"%errorMessage) log.error("INFO PDU : %s"%errorMessage)
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SHUTDOWN_DENIED: elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SHUTDOWN_DENIED:
#may be an event to ask to user #may be an event to ask to user
self._transport.close() self._transport.close()
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SAVE_SESSION_INFO:
#handle session event
self._listener.onSessionReady()
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_UPDATE: elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_UPDATE:
self.readUpdateDataPDU(dataPDU.pduData) self.readUpdateDataPDU(dataPDU.pduData)

View File

@@ -100,7 +100,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def setUsername(self, username): def setUsername(self, username):
""" """
@summary: Set the username for session @summary: Set the username for session
@param username: username of session @param username: {string} username of session
""" """
#username in PDU info packet #username in PDU info packet
self._secLayer._info.userName.value = username self._secLayer._info.userName.value = username
@@ -109,7 +109,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def setPassword(self, password): def setPassword(self, password):
""" """
@summary: Set password for session @summary: Set password for session
@param password: password of session @param password: {string} password of session
""" """
self.setAutologon() self.setAutologon()
self._secLayer._info.password.value = password self._secLayer._info.password.value = password
@@ -117,7 +117,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
def setDomain(self, domain): def setDomain(self, domain):
""" """
@summary: Set the windows domain of session @summary: Set the windows domain of session
@param domain: domain of session @param domain: {string} domain of session
""" """
self._secLayer._info.domain.value = domain self._secLayer._info.domain.value = domain
@@ -127,6 +127,13 @@ class RDPClientController(pdu.layer.PDUClientListener):
""" """
self._secLayer._info.flag |= sec.InfoFlag.INFO_AUTOLOGON self._secLayer._info.flag |= sec.InfoFlag.INFO_AUTOLOGON
def setAlternateShell(self, appName):
"""
@summary: set application name of app which start at the begining of session
@param appName: {string} application name
"""
self._secLayer._info.alternateShell.value = appName
def setKeyboardLayout(self, layout): def setKeyboardLayout(self, layout):
""" """
@summary: keyboard layout @summary: keyboard layout
@@ -192,6 +199,15 @@ class RDPClientController(pdu.layer.PDUClientListener):
for observer in self._clientObserver: for observer in self._clientObserver:
observer.onReady() observer.onReady()
def onSessionReady(self):
"""
@summary: Call when Windows session is ready (connected)
"""
self._isReady = True
#signal all listener
for observer in self._clientObserver:
observer.onSessionReady()
def onClose(self): def onClose(self):
""" """
@summary: Event call when RDP stack is closed @summary: Event call when RDP stack is closed
@@ -212,24 +228,35 @@ class RDPClientController(pdu.layer.PDUClientListener):
return return
try: try:
event = pdu.data.PointerEvent() if button == 4 or button == 5:
if isPressed: event = pdu.data.PointerExEvent()
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_DOWN if isPressed:
event.pointerFlags.value |= pdu.data.PointerExFlag.PTRXFLAGS_DOWN
if button == 1:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON1 if button == 4:
elif button == 2: event.pointerFlags.value |= pdu.data.PointerExFlag.PTRXFLAGS_BUTTON1
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON2 elif button == 5:
elif button == 3: event.pointerFlags.value |= pdu.data.PointerExFlag.PTRXFLAGS_BUTTON2
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON3
else: else:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_MOVE event = pdu.data.PointerEvent()
if isPressed:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_DOWN
if button == 1:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON1
elif button == 2:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON2
elif button == 3:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON3
else:
event.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_MOVE
#position # position
event.xPos.value = x event.xPos.value = x
event.yPos.value = y event.yPos.value = y
#send proper event # send proper event
self._pduLayer.sendInputEvents([event]) self._pduLayer.sendInputEvents([event])
except InvalidValue: except InvalidValue:
@@ -269,11 +296,12 @@ class RDPClientController(pdu.layer.PDUClientListener):
except InvalidValue: except InvalidValue:
log.info("try send wheel event with incorrect position") log.info("try send wheel event with incorrect position")
def sendKeyEventScancode(self, code, isPressed): def sendKeyEventScancode(self, code, isPressed, extended = False):
""" """
@summary: Send a scan code to RDP stack @summary: Send a scan code to RDP stack
@param code: scan code @param code: scan code
@param isPressed: True if key is pressed and false if it's released @param isPressed: True if key is pressed and false if it's released
@param extended: {boolean} extended scancode like ctr or win button
""" """
if not self._isReady: if not self._isReady:
return return
@@ -281,11 +309,12 @@ class RDPClientController(pdu.layer.PDUClientListener):
try: try:
event = pdu.data.ScancodeKeyEvent() event = pdu.data.ScancodeKeyEvent()
event.keyCode.value = code event.keyCode.value = code
if isPressed: if not isPressed:
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_DOWN
else:
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_RELEASE event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_RELEASE
if extended:
event.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_EXTENDED
#send event #send event
self._pduLayer.sendInputEvents([event]) self._pduLayer.sendInputEvents([event])
@@ -478,11 +507,11 @@ class RDPServerController(pdu.layer.PDUServerListener):
for event in slowPathInputEvents: for event in slowPathInputEvents:
#scan code #scan code
if event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_SCANCODE: if event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_SCANCODE:
observer.onKeyEventScancode(event.slowPathInputData.keyCode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE)) observer.onKeyEventScancode(event.slowPathInputData.keyCode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE), bool(event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_EXTENDED))
#unicode #unicode
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_UNICODE: elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_UNICODE:
observer.onKeyEventUnicode(event.slowPathInputData.unicode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE)) observer.onKeyEventUnicode(event.slowPathInputData.unicode.value, not (event.slowPathInputData.keyboardFlags.value & pdu.data.KeyboardFlag.KBDFLAGS_RELEASE))
#mouse event #mouse events
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_MOUSE: elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_MOUSE:
isPressed = event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_DOWN isPressed = event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_DOWN
button = 0 button = 0
@@ -493,6 +522,15 @@ class RDPServerController(pdu.layer.PDUServerListener):
elif event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_BUTTON3: elif event.slowPathInputData.pointerFlags.value & pdu.data.PointerFlag.PTRFLAGS_BUTTON3:
button = 3 button = 3
observer.onPointerEvent(event.slowPathInputData.xPos.value, event.slowPathInputData.yPos.value, button, isPressed) observer.onPointerEvent(event.slowPathInputData.xPos.value, event.slowPathInputData.yPos.value, button, isPressed)
elif event.messageType.value == pdu.data.InputMessageType.INPUT_EVENT_MOUSEX:
isPressed = event.slowPathInputData.pointerFlags.value & pdu.data.PointerExFlag.PTRXFLAGS_DOWN
button = 0
if event.slowPathInputData.pointerFlags.value & pdu.data.PointerExFlag.PTRXFLAGS_BUTTON1:
button = 4
elif event.slowPathInputData.pointerFlags.value & pdu.data.PointerExFlag.PTRXFLAGS_BUTTON2:
button = 5
observer.onPointerEvent(event.slowPathInputData.xPos.value, event.slowPathInputData.yPos.value, button, isPressed)
def sendUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): def sendUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
""" """
@@ -607,6 +645,12 @@ class RDPClientObserver(object):
""" """
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPClientObserver")) raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "RDPClientObserver"))
def onSessionReady(self):
"""
@summary: Windows session is ready
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSessionReady", "RDPClientObserver"))
def onClose(self): def onClose(self):
""" """
@summary: Stack is closes @summary: Stack is closes
@@ -652,11 +696,12 @@ class RDPServerObserver(object):
""" """
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver")) raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onClose", "RDPClientObserver"))
def onKeyEventScancode(self, code, isPressed): def onKeyEventScancode(self, code, isPressed, isExtended):
""" """
@summary: 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 code: {integer} scan code of key
@param isPressed: True if key is down @param isPressed: {boolean} True if key is down
@param isExtended: {boolean} True if a special key
""" """
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onKeyEventScanCode", "RDPServerObserver")) raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onKeyEventScanCode", "RDPServerObserver"))
@@ -673,7 +718,7 @@ class RDPServerObserver(object):
@summary: Event call on mouse event @summary: Event call on mouse event
@param x: x position @param x: x position
@param y: y position @param y: y position
@param button: 1, 2 or 3 button @param button: 1, 2, 3, 4 or 5 button
@param isPressed: True if mouse button is pressed @param isPressed: True if mouse button is pressed
""" """
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onPointerEvent", "RDPServerObserver")) raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onPointerEvent", "RDPServerObserver"))

View File

@@ -323,7 +323,7 @@ class RDPInfo(CompositeType):
#code page #code page
self.codePage = UInt32Le() self.codePage = UInt32Le()
#support flag #support flag
self.flag = UInt32Le(InfoFlag.INFO_MOUSE | InfoFlag.INFO_UNICODE | InfoFlag.INFO_LOGONNOTIFY | InfoFlag.INFO_LOGONERRORS | InfoFlag.INFO_DISABLECTRLALTDEL) self.flag = UInt32Le(InfoFlag.INFO_MOUSE | InfoFlag.INFO_UNICODE | InfoFlag.INFO_LOGONNOTIFY | InfoFlag.INFO_LOGONERRORS | InfoFlag.INFO_DISABLECTRLALTDEL | InfoFlag.INFO_ENABLEWINDOWSKEY)
self.cbDomain = UInt16Le(lambda:sizeof(self.domain) - 2) self.cbDomain = UInt16Le(lambda:sizeof(self.domain) - 2)
self.cbUserName = UInt16Le(lambda:sizeof(self.userName) - 2) self.cbUserName = UInt16Le(lambda:sizeof(self.userName) - 2)
self.cbPassword = UInt16Le(lambda:sizeof(self.password) - 2) self.cbPassword = UInt16Le(lambda:sizeof(self.password) - 2)

View File

@@ -303,7 +303,7 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
@param isCompress: {bool} use RLE compression @param isCompress: {bool} use RLE compression
@param data: {str} bitmap data @param data: {str} bitmap data
""" """
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data); image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data)
#if image need to be cut #if image need to be cut
#For bit alignement server may send more than image pixel #For bit alignement server may send more than image pixel
self._widget.notifyImage(destLeft, destTop, image, destRight - destLeft + 1, destBottom - destTop + 1) self._widget.notifyImage(destLeft, destTop, image, destRight - destLeft + 1, destBottom - destTop + 1)
@@ -311,12 +311,21 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
def onReady(self): def onReady(self):
""" """
@summary: Call when stack is ready @summary: Call when stack is ready
@see: rdp.RDPClientObserver.onReady
""" """
#do something maybe a loader #do something maybe a loader
def onSessionReady(self):
"""
@summary: Windows session is ready
@see: rdp.RDPClientObserver.onSessionReady
"""
pass
def onClose(self): def onClose(self):
""" """
@summary: Call when stack is close @summary: Call when stack is close
@see: rdp.RDPClientObserver.onClose
""" """
#do something maybe a message #do something maybe a message
@@ -336,12 +345,6 @@ class QRemoteDesktop(QtGui.QWidget):
self._adaptor = adaptor self._adaptor = adaptor
#set correct size #set correct size
self.resize(width, height) self.resize(width, height)
#refresh stack of image
#because we can update image only in paint
#event function. When protocol receive image
#we will stock into refresh list
#and in paint event paint list of all refresh images
self._refresh = []
#bind mouse event #bind mouse event
self.setMouseTracking(True) self.setMouseTracking(True)
#buffer image #buffer image
@@ -354,8 +357,9 @@ class QRemoteDesktop(QtGui.QWidget):
@param y: y position of new image @param y: y position of new image
@param qimage: new QImage @param qimage: new QImage
""" """
#save in refresh list (order is important) #fill buffer image
self._refresh.append((x, y, qimage, width, height)) with QtGui.QPainter(self._buffer) as qp:
qp.drawImage(x, y, qimage, 0, 0, width, height)
#force update #force update
self.update() self.update()
@@ -373,16 +377,9 @@ class QRemoteDesktop(QtGui.QWidget):
@summary: Call when Qt renderer engine estimate that is needed @summary: Call when Qt renderer engine estimate that is needed
@param e: QEvent @param e: QEvent
""" """
#fill buffer image
with QtGui.QPainter(self._buffer) as qp:
#draw image
for (x, y, image, width, height) in self._refresh:
qp.drawImage(x, y, image, 0, 0, width, height)
#draw in widget #draw in widget
with QtGui.QPainter(self) as qp: with QtGui.QPainter(self) as qp:
qp.drawImage(0, 0, self._buffer) qp.drawImage(0, 0, self._buffer)
self._refresh = []
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
""" """

View File

@@ -4,7 +4,7 @@ import setuptools
from distutils.core import setup, Extension from distutils.core import setup, Extension
setup(name='rdpy', setup(name='rdpy',
version='1.3.0', version='1.3.2',
description='Remote Desktop Protocol in Python', description='Remote Desktop Protocol in Python',
long_description=""" long_description="""
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (Client and Server side). RDPY is built over the event driven network engine Twisted. RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (Client and Server side). RDPY is built over the event driven network engine Twisted.
@@ -42,6 +42,6 @@ setup(name='rdpy',
'service_identity', 'service_identity',
'qt4reactor', 'qt4reactor',
'rsa', 'rsa',
'pyasn1', 'pyasn1'
], ],
) )