36 Commits

Author SHA1 Message Date
Sylvain Peyrefitte
58ad5782c1 Revert "Fix typos in README.md" 2015-01-19 11:07:52 +01:00
Sylvain Peyrefitte
b22a7d5dce Merge pull request #11 from r04r/patch-1
Fix typos in README.md
2015-01-19 11:06:39 +01:00
r04r
8e7e6cdcb4 Fix typos in README.md 2015-01-16 18:08:19 +01:00
speyrefitte
ff65c4b701 fix delivrary bugs 2015-01-15 11:05:41 +01:00
speyrefitte
c5b8588e5f update README 2015-01-15 10:50:21 +01:00
speyrefitte
9dcac862ff finish honeypot tools 2015-01-14 15:49:00 +01:00
speyrefitte
e9fd801237 add honey pot binary 2015-01-13 16:21:13 +01:00
speyrefitte
0686b2b677 update license + rsr reader and writer 2015-01-09 18:07:06 +01:00
speyrefitte
9a4f5f254c add first step of rsr support 2015-01-07 18:32:16 +01:00
Sylvain Peyrefitte
522eda39e8 Update README.md 2015-01-06 17:22:32 +01:00
Sylvain Peyrefitte
1b4fd4a686 Update README.md 2015-01-06 17:21:06 +01:00
speyrefitte
33dbbf45d4 fix build error 2015-01-06 16:18:11 +01:00
speyrefitte
a3839e47c4 add new binaries form 2015-01-06 16:00:44 +01:00
speyrefitte
b378a1d15c ready for 1.2.0 2015-01-06 10:24:06 +01:00
speyrefitte
7ff5c5caa3 fix rdp stander security layer server side bug, fix lisense automata, ready for next release 2015-01-05 18:34:50 +01:00
speyrefitte
f4808d0ae2 finish RDP basic security layer for server side but have bug on faspath + basic security layer... 2014-12-24 17:20:09 +01:00
speyrefitte
28f50b1f0e finish RDP basic security layer on server side except signature and fastpath 2014-12-23 18:24:41 +01:00
speyrefitte
f9b30a668a bug on signing key 2014-12-22 18:36:26 +01:00
speyrefitte
d330564d2b bug fix 2014-12-17 18:19:36 +01:00
citronneur
69b3f6befe server side of basic RDP security layer 2014-12-16 22:11:48 +01:00
speyrefitte
a0ae3d97ec fix build issue 2014-12-16 18:03:12 +01:00
speyrefitte
f2481149d9 finish RDP basic security layer client side, start server side 2014-12-15 18:26:27 +01:00
speyrefitte
f9f92b8215 add support for x509 certificate 2014-12-12 18:14:12 +01:00
citronneur
f36f8222df fix update keys for 128 56 and 40 bits keys, Client RDP basic security layer finish 2014-12-11 22:42:59 +01:00
speyrefitte
d4d98471eb support 40bits and 56bits key on client side, bug on update keys 2014-12-11 18:34:51 +01:00
speyrefitte
f3a3ad8ac3 finish 128 bit basic rdp security layer 2014-12-10 18:32:10 +01:00
citronneur
47a9f75fa6 do not decrypt lic packet 2014-12-09 22:23:27 +01:00
speyrefitte
92c642ffed first packet in RDP basic security layer 2014-12-09 18:01:47 +01:00
citronneur
551af0aa7a move info packet to security layer 2014-12-08 23:05:58 +01:00
speyrefitte
ccf0156150 add basic RDP secure layer... 2014-12-08 18:15:28 +01:00
citronneur
e7c6e61a25 RDP Basic security client message 2014-12-07 19:24:09 +01:00
citronneur
de1347840b Build repair 2014-12-06 14:32:35 +01:00
speyrefitte
873d1fac41 Begin og RDP basic security layer 2014-12-05 18:11:19 +01:00
speyrefitte
fc3efa60ee service_identity in travis 2014-12-03 10:18:51 +01:00
speyrefitte
e4c04a7dd1 rrrr tag to quickly 2014-12-03 09:56:41 +01:00
speyrefitte
cdcfdec379 bad setup.py 2014-12-03 09:51:43 +01:00
49 changed files with 3112 additions and 1566 deletions

View File

@@ -7,7 +7,7 @@ before_install:
- sudo apt-get install python-qt4
- ln -s /usr/lib/python2.7/dist-packages/PyQt4/ $VIRTUAL_ENV/lib/python2.7/site-packages/
- ln -s /usr/lib/python2.7/dist-packages/sip.so $VIRTUAL_ENV/lib/python2.7/site-packages/
- pip install qt4reactor pyopenssl twisted
- pip install qt4reactor pyopenssl twisted service_identity rsa
install:
- python setup.py install

View File

@@ -2,7 +2,16 @@
Remote Desktop Protocol in twisted PYthon.
RDPY is a pure Python implementation of 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 (Client and Server Side). RDPY is built over the event driven network engine Twisted.
RDPY provide RDP and VNC binaries :
* RDP Man In The Middle proxy which record session
* RDP Honeypot
* RDP screen shooter
* RDP client
* VNC client
* VNC screen shooter
* RSS Player
## Build
@@ -10,6 +19,13 @@ RDPY is fully implemented in python, except the bitmap uncompression algorithm w
### Depends
Depends are only needed for pyqt4 binaries :
* rdpy-rdpclient
* rdpy-rdpscreenshot
* rdpy-vncclient
* rdpy-vncscreenshot
* rdpy-rssplayer
#### Linux
Exemple from Debian based system :
@@ -21,14 +37,14 @@ sudo apt-get install python-qt4
x86 | x86_64
----|-------
[PyQt4 x86](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x32.exe) | [PyQt4 x86_64](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x64.exe/download)
[PyWin32 x86](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.7.exe/download) | [PyWin32 x86_64](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py2.7.exe/download)
[PyQt4](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x32.exe) | [PyQt4](http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.11.3/PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x64.exe/download)
[PyWin32](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.7.exe/download) | [PyWin32](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py2.7.exe/download)
### Build
```
$ git clone https://github.com/citronneur/rdpy.git rdpy
$ pip install twisted pyopenssl qt4reactor
$ pip install twisted pyopenssl qt4reactor service_identity rsa
$ python rdpy/setup.py install
```
@@ -45,16 +61,18 @@ $ ln -s /usr/lib/python2.7/dist-packages/sip.so $VIRTUAL_ENV/lib/python2.7/site-
## RDPY Binaries
RDPY comes with some very useful binaries; These binaries are linux and windows compatible.
RDPY comes with some very useful binaries. These binaries are linux and windows compatible.
### rdpy-rdpclient
rdpy-rdpclient is a simple RDP Qt4 client .
```
$ rdpy-rdpclient.py [-u username] [-p password] [-d domain] [...] XXX.XXX.XXX.XXX[:3389]
$ rdpy-rdpclient.py [-u username] [-p password] [-d domain] [-r rss_ouput_file] [...] XXX.XXX.XXX.XXX[:3389]
```
You can use rdpy-rdpclient as Recorder Session Scenario, used in rdpy-rdphoneypot.
### rdpy-vncclient
rdpy-vncclient is a simple VNC Qt4 client .
@@ -79,38 +97,35 @@ rdpy-vncscreenshot save first screen update in file.
$ rdpy-vncscreenshot.py [-p password] [-o output_file_path] XXX.XXX.XXX.XXX[:5900]
```
### rdpy-rdpproxy
### rdpy-rdpmitm
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-rdpmitm is a RDP proxy allows you to do a Man In The Middle attack on RDP protocol.
Record Session Scenario into rss file which can be replay by rdpy-rssplayer.
```
$ rdpy-rdpproxy.py -f credentials_file_path -k private_key_file_path -c certificate_file_path [-i admin_ip[:admin_port]] listen_port
$ rdpy-rdpmitm.py -o output_dir [-l listen_port] [-k private_key_file_path] [-c certificate_file_path] [-r (for XP or server 2003 client)] target_host[:target_port]
```
The credentials file is JSON file that must conform with the following format:
Output directory is use to save rss file with following format (YYYYMMDDHHMMSS_ip_index.rss)
The private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer. The CredSSP security layer is planned for an upcoming release. If one of both parameters are omitted, the server use standard RDP as security layer.
### rdpy-rdphoneypot
rdpy-rdphoneypot is a RDP honey Pot. Use Recorded Session Scenario to replay scenario through RDP Protocol.
```
{
"domain1":
{
"username1":
[
{"ip":"machine1", "port":3389"},
{"ip":"machine2", "port":3389"}
],
"username2":
[
{"ip":"machine1", "port":3389"}
]
}
}
$ rdpy-rdphoneypot.py [-l listen_port] [-k private_key_file_path] [-c certificate_file_path] rss_file_path
```
In this exemple domain1\username1 can access to machine1 and machine2 and domain1\username2 can only access to machine1.
The private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer. The CredSSP security layer is planned for an upcoming release. If one of both parameters are omitted, the server use standard RDP as security layer.
The private key file and the certificate file are classic cryptographic files for SSL connections. The RDP protocol can negotiate its own security layer but RDPY is limited to SSL. The CredSSP security layer is planned for an upcoming release. The basic RDP security layer is not supported (windows wp sp1&2).
### rdpy-rssplayer
The IP and port admin are used in order to spy active sessions thanks to a RDP client (rdpy-rdpclient, remina, mstsc). Common values are 127.0.0.1:3389 to protect from connections by unauthorized user.
rdpy-rssplayer is use to replay Record Session Scenario (rss) files generates by either rdpy-rdpmitm or rdpy-rdpclient binaries.
```
$ rdpy-rssplayer.py rss_file_path
```
## RDPY Qt Widget

View File

@@ -1,6 +1,6 @@
#!/usr/bin/python
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -26,24 +26,83 @@ import sys, os, getopt, socket
from PyQt4 import QtGui, QtCore
from rdpy.ui.qt4 import RDPClientQt
from rdpy.protocol.rdp import rdp
from rdpy.core.error import RDPSecurityNegoFail
from rdpy.core import rss
import rdpy.base.log as log
import rdpy.core.log as log
log._LOG_LEVEL = log.Level.INFO
class RDPClientQtRecorder(RDPClientQt):
"""
@summary: Widget with record session
"""
def __init__(self, controller, width, height, rssRecorder):
"""
@param controller: {RDPClientController} RDP controller
@param width: {int} width of widget
@param height: {int} height of widget
@param rssRecorder: {rss.FileRecorder}
"""
RDPClientQt.__init__(self, controller, width, height)
self._screensize = width, height
self._rssRecorder = rssRecorder
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
@summary: Notify bitmap update
@param destLeft: {int} xmin position
@param destTop: {int} ymin position
@param destRight: {int} xmax position because RDP can send bitmap with padding
@param destBottom: {int} ymax position because RDP can send bitmap with padding
@param width: {int} width of bitmap
@param height: {int} height of bitmap
@param bitsPerPixel: {int} number of bit per pixel
@param isCompress: {bool} use RLE compression
@param data: {str} bitmap data
"""
#record update
self._rssRecorder.update(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, rss.UpdateFormat.BMP if isCompress else rss.UpdateFormat.RAW, data)
RDPClientQt.onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data)
def onReady(self):
"""
@summary: Call when stack is ready
"""
self._rssRecorder.screen(self._screensize[0], self._screensize[1], self._controller.getColorDepth())
RDPClientQt.onReady(self)
def onClose(self):
"""
@summary: Call when stack is close
"""
self._rssRecorder.close()
RDPClientQt.onClose(self)
def closeEvent(self, e):
"""
@summary: Convert Qt close widget event into close stack event
@param e: QCloseEvent
"""
self._rssRecorder.close()
RDPClientQt.closeEvent(self, e)
class RDPClientQtFactory(rdp.ClientFactory):
"""
@summary: Factory create a RDP GUI client
"""
def __init__(self, width, height, username, password, domain, fullscreen, keyboardLayout, optimized):
def __init__(self, width, height, username, password, domain, fullscreen, keyboardLayout, optimized, security, recodedPath):
"""
@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
@param keyboardLayout: keyboard layout
@param optimized: enable optimized session orders
@param width: {integer} width of client
@param heigth: {integer} heigth of client
@param username: {str} username present to the server
@param password: {str} password present to the server
@param domain: {str} microsoft domain
@param fullscreen: {bool} show widget in fullscreen mode
@param keyboardLayout: {str} (fr|en) keyboard layout
@param optimized: {bool} enable optimized session orders
@param security: {str} (ssl | rdp | nego)
@param recodedPath: {str | None} Rss file Path
"""
self._width = width
self._height = height
@@ -53,6 +112,12 @@ class RDPClientQtFactory(rdp.ClientFactory):
self._fullscreen = fullscreen
self._keyboardLayout = keyboardLayout
self._optimized = optimized
self._nego = security == "nego"
self._recodedPath = recodedPath
if self._nego:
self._security = "ssl"
else:
self._security = security
self._w = None
def buildObserver(self, controller, addr):
@@ -64,9 +129,12 @@ class RDPClientQtFactory(rdp.ClientFactory):
@return: RDPClientQt
"""
#create client observer
client = RDPClientQt(controller, self._width, self._height)
if self._recodedPath is None:
self._client = RDPClientQt(controller, self._width, self._height)
else:
self._client = RDPClientQtRecorder(controller, self._width, self._height, rss.createRecorder(self._recodedPath))
#create qt widget
self._w = client.getWidget()
self._w = self._client.getWidget()
self._w.setWindowTitle('rdpy-rdpclient')
if self._fullscreen:
self._w.showFullScreen()
@@ -80,11 +148,9 @@ class RDPClientQtFactory(rdp.ClientFactory):
controller.setHostname(socket.gethostname())
if self._optimized:
controller.setPerformanceSession()
controller.setSecurityLevel(self._security)
return client
def startedConnecting(self, connector):
pass
return self._client
def clientConnectionLost(self, connector, reason):
"""
@@ -92,6 +158,16 @@ class RDPClientQtFactory(rdp.ClientFactory):
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
@param reason: str use to advertise reason of lost connection
"""
#try reconnect with basic RDP security
if reason.type == RDPSecurityNegoFail and self._nego:
#stop nego
log.info("due to security nego error back to standard RDP security layer")
self._nego = False
self._security = "rdp"
self._client._widget.hide()
connector.connect()
return
QtGui.QMessageBox.warning(self._w, "Warning", "Lost connection : %s"%reason)
reactor.stop()
app.exit()
@@ -131,15 +207,18 @@ def autoDetectKeyboardLayout():
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 : 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]"
print """
Usage: rdpy-rdpclient [options] ip[:port]"
\t-u: user name
\t-p: password
\t-d: domain
\t-w: width of screen [default : 1024]
\t-l: height of screen [default : 800]
\t-f: enable full screen mode [default : False]
\t-k: keyboard layout [en|fr] [default : en]
\t-o: optimized session (disable costly effect) [default : False]
\t-r: rss_filepath Recorded Session Scenario [default : None]
"""
if __name__ == '__main__':
@@ -151,10 +230,11 @@ if __name__ == '__main__':
height = 800
fullscreen = False
optimized = False
recodedPath = None
keyboardLayout = autoDetectKeyboardLayout()
try:
opts, args = getopt.getopt(sys.argv[1:], "hfou:p:d:w:l:k:")
opts, args = getopt.getopt(sys.argv[1:], "hfou:p:d:w:l:k:r:")
except getopt.GetoptError:
help()
for opt, arg in opts:
@@ -177,6 +257,8 @@ if __name__ == '__main__':
optimized = True
elif opt == "-k":
keyboardLayout = arg
elif opt == "-r":
recodedPath = arg
if ':' in args[0]:
ip, port = args[0].split(':')
@@ -194,12 +276,9 @@ if __name__ == '__main__':
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, keyboardLayout, optimized))
reactor.connectTCP(ip, int(port), RDPClientQtFactory(width, height, username, password, domain, fullscreen, keyboardLayout, optimized, "nego", recodedPath))
reactor.runReturn()
app.exec_()

153
bin/rdpy-rdphoneypot.py Executable file
View File

@@ -0,0 +1,153 @@
#!/usr/bin/python
#
# 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/>.
#
"""
RDP Honey pot use Rss scenario file to simulate RDP server
"""
import sys, os, getopt, time
from rdpy.core import log, error, rss
from rdpy.protocol.rdp import rdp
from twisted.internet import reactor
log._LOG_LEVEL = log.Level.INFO
class HoneyPotServer(rdp.RDPServerObserver):
def __init__(self, controller, rssFile):
"""
@param controller: {RDPServerController}
"""
rdp.RDPServerObserver.__init__(self, controller)
self._rssFile = rssFile
self._dx, self._dy = 0, 0
def onReady(self):
"""
@summary: Event use to inform state of server stack
First time this event is called is when human client is connected
Second time is after color depth nego, because color depth nego
restart a connection sequence
@see: rdp.RDPServerObserver.onReady
"""
domain, username, password = self._controller.getCredentials()
hostname = self._controller.getHostname()
log.info("""Credentials:
\tdomain : %s
\tusername : %s
\tpassword : %s
\thostname : %s
"""%(domain, username, password, hostname));
self.start()
def onClose(self):
""" HoneyPot """
def onKeyEventScancode(self, code, isPressed):
""" HoneyPot """
def onKeyEventUnicode(self, code, isPressed):
""" HoneyPot """
def onPointerEvent(self, x, y, button, isPressed):
""" HoneyPot """
def start(self):
self.loopScenario(self._rssFile.nextEvent())
def loopScenario(self, nextEvent):
"""
@summary: main loop event
"""
if nextEvent.type.value == rss.EventType.UPDATE:
self._controller.sendUpdate(nextEvent.event.destLeft.value + self._dx, nextEvent.event.destTop.value + self._dy, nextEvent.event.destRight.value + self._dx, nextEvent.event.destBottom.value + self._dy, nextEvent.event.width.value, nextEvent.event.height.value, nextEvent.event.bpp.value, nextEvent.event.format.value == rss.UpdateFormat.BMP, nextEvent.event.data.value)
elif nextEvent.type.value == rss.EventType.CLOSE:
self._controller.close()
return
elif nextEvent.type.value == rss.EventType.SCREEN:
self._controller.setColorDepth(nextEvent.event.colorDepth.value)
#compute centering because we cannot resize client
clientSize = nextEvent.event.width.value, nextEvent.event.height.value
serverSize = self._controller.getScreen()
self._dx, self._dy = (serverSize[0] - clientSize[0]) / 2, (serverSize[1] - clientSize[1]) / 2
#restart connection sequence
return
e = self._rssFile.nextEvent()
reactor.callLater(float(e.timestamp.value) / 1000.0, lambda:self.loopScenario(e))
class HoneyPotServerFactory(rdp.ServerFactory):
"""
@summary: Factory on listening events
"""
def __init__(self, rssFilePath, privateKeyFilePath, certificateFilePath):
"""
@param rssFilePath: Recorded Session Scenario File path
@param privateKeyFilePath: {str} file contain server private key (if none -> back to standard RDP security)
@param certificateFilePath: {str} file contain server certificate (if none -> back to standard RDP security)
"""
rdp.ServerFactory.__init__(self, 16, privateKeyFilePath, certificateFilePath)
self._rssFilePath = rssFilePath
def buildObserver(self, controller, addr):
"""
@param controller: {rdp.RDPServerController}
@param addr: destination address
@see: rdp.ServerFactory.buildObserver
"""
log.info("Connection from %s:%s"%(addr.host, addr.port))
return HoneyPotServer(controller, rss.createReader(self._rssFilePath))
def help():
"""
@summary: Print help in console
"""
print """
Usage: rdpy-rdphoneypot.py rss_filepath
[-l listen_port default 3389]
[-k private_key_file_path (mandatory for SSL)]
[-c certificate_file_path (mandatory for SSL)]
"""
if __name__ == '__main__':
listen = "3389"
privateKeyFilePath = None
certificateFilePath = None
try:
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:")
except getopt.GetoptError:
help()
for opt, arg in opts:
if opt == "-h":
help()
sys.exit()
elif opt == "-l":
listen = arg
elif opt == "-k":
privateKeyFilePath = arg
elif opt == "-c":
certificateFilePath = arg
reactor.listenTCP(int(listen), HoneyPotServerFactory(args[0], privateKeyFilePath, certificateFilePath))
reactor.run()

295
bin/rdpy-rdpmitm.py Executable file
View File

@@ -0,0 +1,295 @@
#!/usr/bin/python
#
# 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/>.
#
"""
RDP proxy with Man in the middle capabilities
Save RDP events in output RSR file format
RSR file format can be read by rdpy-rsrplayer.py
----------------------------
Client RDP -> | ProxyServer | ProxyClient | -> Server RDP
----------------------------
| Record Session |
-----------------
"""
import sys, os, getopt, time
from rdpy.core import log, error, rss
from rdpy.protocol.rdp import rdp
from twisted.internet import reactor
log._LOG_LEVEL = log.Level.INFO
class ProxyServer(rdp.RDPServerObserver):
"""
@summary: Server side of proxy
"""
def __init__(self, controller, target, clientSecurityLevel, rssRecorder):
"""
@param controller: {RDPServerController}
@param target: {tuple(ip, port)}
@param rssRecorder: {rss.FileRecorder} use to record session
"""
rdp.RDPServerObserver.__init__(self, controller)
self._target = target
self._client = None
self._rss = rssRecorder
self._clientSecurityLevel = clientSecurityLevel
def setClient(self, client):
"""
@summary: Event throw by client when it's ready
@param client: {ProxyClient}
"""
self._client = client
def onReady(self):
"""
@summary: Event use to inform state of server stack
First time this event is called is when human client is connected
Second time is after color depth nego, because color depth nego
restart a connection sequence
@see: rdp.RDPServerObserver.onReady
"""
if self._client is None:
#try a connection
domain, username, password = self._controller.getCredentials()
self._rss.credentials(username, password, domain, self._controller.getHostname())
width, height = self._controller.getScreen()
self._rss.screen(width, height, self._controller.getColorDepth())
reactor.connectTCP(self._target[0], int(self._target[1]), ProxyClientFactory(self, width, height,
domain, username, password,self._clientSecurityLevel))
def onClose(self):
"""
@summary: Call when human client close connection
@see: rdp.RDPServerObserver.onClose
"""
#end scenario
self._rss.close()
#close network stack
if self._client is None:
return
self._client._controller.close()
def onKeyEventScancode(self, code, isPressed):
"""
@summary: Event call when a keyboard event is catch in scan code format
@param code: {int} scan code of key
@param isPressed: {bool} True if key is down
@see: rdp.RDPServerObserver.onKeyEventScancode
"""
if self._client is None:
return
self._client._controller.sendKeyEventScancode(code, isPressed)
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
"""
if self._client is None:
return
self._client._controller.sendKeyEventUnicode(code, isPressed)
def onPointerEvent(self, x, y, button, isPressed):
"""
@summary: Event call on mouse event
@param x: {int} x position
@param y: {int} y position
@param button: {int} 1, 2 or 3 button
@param isPressed: {bool} True if mouse button is pressed
@see: rdp.RDPServerObserver.onPointerEvent
"""
if self._client is None:
return
self._client._controller.sendPointerEvent(x, y, button, isPressed)
class ProxyServerFactory(rdp.ServerFactory):
"""
@summary: Factory on listening events
"""
def __init__(self, target, ouputDir, privateKeyFilePath, certificateFilePath, clientSecurity):
"""
@param target: {tuple(ip, prt)}
@param privateKeyFilePath: {str} file contain server private key (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
"""
rdp.ServerFactory.__init__(self, 16, privateKeyFilePath, certificateFilePath)
self._target = target
self._ouputDir = ouputDir
self._clientSecurity = clientSecurity
#use produce unique file by connection
self._uniqueId = 0
def buildObserver(self, controller, addr):
"""
@param controller: {rdp.RDPServerController}
@param addr: destination address
@see: rdp.ServerFactory.buildObserver
"""
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))))
class ProxyClient(rdp.RDPClientObserver):
"""
@summary: Client side of proxy
"""
def __init__(self, controller, server):
"""
@param controller: {rdp.RDPClientController}
@param server: {ProxyServer}
"""
rdp.RDPClientObserver.__init__(self, controller)
self._server = server
def onReady(self):
"""
@summary: Event use to signal that RDP stack is ready
Inform ProxyServer that i'm connected
@see: rdp.RDPClientObserver.onReady
"""
self._server.setClient(self)
#maybe color depth change
self._server._controller.setColorDepth(self._controller.getColorDepth())
def onClose(self):
"""
@summary: Event inform that stack is close
@see: rdp.RDPClientObserver.onClose
"""
#end scenario
self._server._rss.close()
self._server._controller.close()
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
@summary: Event use to inform bitmap update
@param destLeft: {int} xmin position
@param destTop: {int} ymin position
@param destRight: {int} xmax position because RDP can send bitmap with padding
@param destBottom: {int} ymax position because RDP can send bitmap with padding
@param width: {int} width of bitmap
@param height: {int} height of bitmap
@param bitsPerPixel: {int} number of bit per pixel
@param isCompress: {bool} use RLE compression
@param data: {str} bitmap data
@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._controller.sendUpdate(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data)
class ProxyClientFactory(rdp.ClientFactory):
"""
@summary: Factory for proxy client
"""
def __init__(self, server, width, height, domain, username, password, security):
"""
@param server: {ProxyServer}
@param width: {int} screen width
@param height: {int} screen height
@param domain: {str} domain session
@param username: {str} username session
@param password: {str} password session
@param security: {str(ssl|rdp)} security level
"""
self._server = server
self._width = width
self._height = height
self._domain = domain
self._username = username
self._password = password
self._security = security
def buildObserver(self, controller, addr):
"""
@summary: Build observer
@param controller: rdp.RDPClientController
@param addr: destination address
@see: rdp.ClientFactory.buildObserver
@return: ProxyClient
"""
#set screen resolution
controller.setScreen(self._width, self._height)
#set credential
controller.setDomain(self._domain)
controller.setUsername(self._username)
controller.setPassword(self._password)
controller.setSecurityLevel(self._security)
controller.setPerformanceSession()
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)]
[-r RDP standard security (XP or server 2003 client or older)]
"""
def parseIpPort(interface, defaultPort = "3389"):
if ':' in interface:
return interface.split(':')
else:
return interface, defaultPort
if __name__ == '__main__':
listen = "3389"
privateKeyFilePath = None
certificateFilePath = None
ouputDirectory = None
clientSecurity = "ssl"
try:
opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:o:r")
except getopt.GetoptError:
help()
for opt, arg in opts:
if opt == "-h":
help()
sys.exit()
elif opt == "-l":
listen = arg
elif opt == "-k":
privateKeyFilePath = arg
elif opt == "-c":
certificateFilePath = arg
elif opt == "-o":
ouputDirectory = arg
elif opt == "-r":
clientSecurity = "rdp"
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

@@ -1,560 +0,0 @@
#!/usr/bin/python
#
# Copyright (c) 2014 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
RDP proxy with spy capabilities
---------------------------
Client RDP -> | ProxyServer | ProxyClient | -> Server RDP
---------------------------
| ProxyAdmin |
------------
^
Admin ----------------------|
"""
import sys, os, getopt, json
from rdpy.base import log, error
from rdpy.protocol.rdp import rdp
from rdpy.ui import view
from twisted.internet import reactor
from PyQt4 import QtCore, QtGui
#log._LOG_LEVEL = log.Level.INFO
class ProxyServer(rdp.RDPServerObserver):
"""
@summary: Server side of proxy
"""
def __init__(self, controller, credentialProvider):
"""
@param controller: RDPServerController
@param credentialProvider: CredentialProvider
"""
rdp.RDPServerObserver.__init__(self, controller)
self._credentialProvider = credentialProvider
self._client = None
self._window = None
def showSelectView(self, machines):
"""
@summary: Show select sever view to the client
@param machines: [(ip, port)]
"""
self._machines = machines
width, height = self._controller.getScreen()
self._window = view.Window(width, height, QtGui.QColor(8, 24, 66))
self._window.addView(view.Anchor(width / 2 - 250, 100,
view.Label("Please select following server",
500, 50, QtGui.QFont('arial', 18, QtGui.QFont.Bold),
backgroundColor = QtGui.QColor(8, 24, 66))))
self._window.addView(view.Anchor(width / 2 - 250, 150,
view.List(["%s:%s"%(ip, port) for ip, port in machines],
500, 500, self.onSelectMachine,
QtGui.QColor(8, 24, 66))), True)
self._window.update(view.RDPRenderer(self._controller), True)
def onSelectMachine(self, index):
"""
@summary: Callback of view.List in Select server view
@param: index in list
"""
ip, port = self._machines[index]
width, height = self._controller.getScreen()
domain, username, password = self._controller.getCredentials()
reactor.connectTCP(ip, port, ProxyClientFactory(self, width, height, domain, username, password))
def clientConnected(self, client):
"""
@summary: Event throw by client when it's ready
@param client: ProxyClient
"""
self._client = client
#need to reevaluate color depth
self._controller.setColorDepth(self._client._controller.getColorDepth())
def showMessage(self, message):
"""
@summary: Print a message to the client
@param message: string
"""
width, height = self._controller.getScreen()
popup = view.Window(width, height, QtGui.QColor(8, 24, 66))
popup.addView(view.Anchor(width / 2 - 250, height / 2 - 25,
view.Label(message, 500, 50,
QtGui.QFont('arial', 18, QtGui.QFont.Bold),
backgroundColor = QtGui.QColor(8, 24, 66))))
popup.update(view.RDPRenderer(self._controller), True)
def onReady(self):
"""
@summary: Event use to inform state of server stack
First time this event is called is when human client is connected
Second time is after color depth nego, because color depth nego
restart a connection sequence
Use to connect proxy client or show available server
@see: rdp.RDPServerObserver.onReady
"""
if self._client is None:
#try a connection
domain, username, password = self._controller.getCredentials()
machines = self._credentialProvider.getProxyPass(domain, username)
if len(machines) == 0:
self.showMessage("No servers attach to account %s\\%s"%(domain, username))
elif len(machines) == 1:
ip, port = machines[0]
width, height = self._controller.getScreen()
reactor.connectTCP(ip, port, ProxyClientFactory(self, width, height,
domain, username, password))
else:
self.showSelectView(machines)
else:
#refresh client
width, height = self._controller.getScreen()
self._client._controller.sendRefreshOrder(0, 0, width, height)
def onClose(self):
"""
@summary: Call when human client close connection
@see: rdp.RDPServerObserver.onClose
"""
if self._client is None:
return
#close proxy client
self._client._controller.close()
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
"""
#no client connected
if not self._client is None:
self._client._controller.sendKeyEventScancode(code, isPressed)
elif not self._window is None and isPressed:
self._window.keyEvent(code)
self._window.update(view.RDPRenderer(self._controller))
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
"""
#no client connected domain
if self._client is None:
return
self._client._controller.sendKeyEventUnicode(code, isPressed)
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
"""
#no client connected
if self._client is None:
return
self._client._controller.sendPointerEvent(x, y, button, isPressed)
class ProxyServerFactory(rdp.ServerFactory):
"""
@summary: Factory on listening events
"""
def __init__(self, credentialProvider, privateKeyFilePath, certificateFilePath):
"""
@param credentialProvider: CredentialProvider
@param privateKeyFilePath: file contain server private key
@param certificateFilePath: file contain server certificate
"""
rdp.ServerFactory.__init__(self, privateKeyFilePath, certificateFilePath, 16)
self._credentialProvider = credentialProvider
def buildObserver(self, controller, addr):
"""
@param controller: rdp.RDPServerController
@param addr: destination address
@see: rdp.ServerFactory.buildObserver
"""
return ProxyServer(controller, self._credentialProvider)
class ProxyClient(rdp.RDPClientObserver):
"""
@summary: Client side of proxy
"""
_CONNECTED_ = []
def __init__(self, controller, server, name = None):
"""
@param controller: rdp.RDPClientController
@param server: ProxyServer
@param name: name of session None if you don't
want to spy this session
"""
rdp.RDPClientObserver.__init__(self, controller)
self._server = server
self._name = name
self._connected = False
def onReady(self):
"""
@summary: Event use to signal that RDP stack is ready
Inform ProxyServer that i'm connected
@see: rdp.RDPClientObserver.onReady
"""
#prevent multiple on ready event
#because each deactive-reactive sequence
#launch an onReady message
if self._connected:
return
else:
self._connected = True
if not self._name is None:
ProxyClient._CONNECTED_.append(self)
self._server.clientConnected(self)
def onClose(self):
"""
@summary: Event inform that stack is close
@see: rdp.RDPClientObserver.onClose
"""
if not self._name is None:
ProxyClient._CONNECTED_.remove(self)
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
@summary: Event use to inform 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
@see: rdp.RDPClientObserver.onUpdate
"""
self._server._controller.sendUpdate(destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data)
class ProxyClientFactory(rdp.ClientFactory):
"""
@summary: Factory for proxy client
"""
def __init__(self, server, width, height, domain, username, password):
"""
@param server: ProxyServer
@param width: screen width
@param height: screen height
@param domain: domain session
@param username: username session
@param password: password session
"""
self._server = server
self._width = width
self._height = height
self._domain = domain
self._username = username
self._password = password
def buildObserver(self, controller, addr):
"""
@summary: Build observer
@param controller: rdp.RDPClientController
@param addr: destination address
@see: rdp.ClientFactory.buildObserver
@return: ProxyClient
"""
#set screen resolution
controller.setScreen(self._width, self._height)
#set credential
controller.setDomain(self._domain)
controller.setUsername(self._username)
controller.setPassword(self._password)
return ProxyClient(controller, self._server, "%s\\%s on %s"%(self._domain, self._username, addr))
def startedConnecting(self, connector):
pass
def clientConnectionLost(self, connector, reason):
pass
def clientConnectionFailed(self, connector, reason):
pass
class ProxyAdmin(rdp.RDPServerObserver):
"""
@summary: Use to manage admin session
Add GUI to select which session to see
Just escape key is authorized during spy session
To switch from spy state to admin state
"""
class State(object):
GUI = 0 #->list of active session
SPY = 1 #->watch active session
def __init__(self, controller):
"""
@param server: rdp.RDPServerController
"""
rdp.RDPServerObserver.__init__(self, controller)
self._spy = None
self._state = ProxyAdmin.State.GUI
def initView(self):
"""
@summary: Initialize Admin GUI view
"""
self._sessions = list(ProxyClient._CONNECTED_) #copy at t time
width, height = self._controller.getScreen()
self._window = view.Window(width, height, QtGui.QColor(8, 24, 66))
self._window.addView(view.Anchor(width / 2 - 250, 100,
view.Label("Please select following session",
500, 50, QtGui.QFont('arial', 18, QtGui.QFont.Bold),
backgroundColor = QtGui.QColor(8, 24, 66))))
self._window.addView(view.Anchor(width / 2 - 250, 150,
view.List([p._name for p in self._sessions],
500, 500, self.onSelect,
QtGui.QColor(8, 24, 66))), True)
def clientConnected(self, client):
pass
def onReady(self):
"""
@summary: Stack is ready and connected
May be called after an setColorDepth too
@see: rdp.RDPServerObserver.onReady
"""
if self._state == ProxyAdmin.State.GUI:
self.initView()
self._window.update(view.RDPRenderer(self._controller), True)
elif self._state == ProxyAdmin.State.SPY:
#refresh client
width, height = self._controller.getScreen()
self._spy._controller.sendRefreshOrder(0, 0, width, height)
def onClose(self):
"""
@summary: Stack is close
@see: rdp.RDPServerObserver.onClose
"""
pass
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
"""
if self._state == ProxyAdmin.State.GUI:
if not isPressed:
return
self._window.keyEvent(code)
self._window.update(view.RDPRenderer(self._controller))
elif code == 1:
#escape button refresh GUI
self._state = ProxyAdmin.State.GUI
self._spy._controller.removeClientObserver(self._spy)
self.onReady()
def onKeyEventUnicode(self, code, isPressed):
"""
@summary: Event call when a keyboard event is catch in unicode format
Admin GUI add filter for this event
@param code: unicode of key
@param isPressed: True if key is down
@see: rdp.RDPServerObserver.onKeyEventUnicode
"""
pass
def onPointerEvent(self, x, y, button, isPressed):
"""
@summary: Event call on mouse event
Admin GUI add filter for this 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
"""
pass
def onSelect(self, index):
"""
@summary: Callback of list view of active session
Connect to select session
@param index: index in sessions array
"""
self._state = ProxyAdmin.State.SPY
self._spy = ProxyClient(self._sessions[index]._controller, self)
self._controller.setColorDepth(self._spy._controller.getColorDepth())
class ProxyAdminFactory(rdp.ServerFactory):
"""
@summary: Factory for admin session
"""
def __init__(self, privateKeyFilePath, certificateFilePath):
"""
@param privateKeyFilePath: private key for admin session
@param certificateFilePath: certificate for admin session
"""
rdp.ServerFactory.__init__(self, privateKeyFilePath, certificateFilePath, 16)
def buildObserver(self, controller, addr):
"""
@summary: Build ProxyAdmin
@param controller: rdp.RDPServerController
@param addr: destination address
@return: ProxyAdmin
@see: rdp.ServerFactory.buildObserver
"""
return ProxyAdmin(controller)
class CredentialProvider(object):
"""
@summary: Credential provider for proxy
"""
def __init__(self, config):
"""
@param config: rdp proxy config
"""
self._config = config
def getAccount(self, domain, username):
"""
@summary: Find account that match domain::username in config file
@param domain: Windows domain
@param username: username for session
@return: [(unicode(ip), port] or None if not found
"""
if not self._config.has_key(domain) or not self._config[domain].has_key(username):
return None
return self._config[domain][username]
def getProxyPass(self, domain, username):
"""
@summary: Find list of server available for thi account
@param domain: domain to check
@param username: username in domain
@return: [(ip, port)]
"""
account = self.getAccount(domain, username)
if account is None:
return []
return [(str(machine["ip"]), machine["port"]) for machine in account]
def help():
"""
@summary: Print help in console
"""
print "Usage: rdpy-rdpproxy -f credential_file_path -k private_key_file_path -c certificate_file_path [-i admin_ip[:admin_port]] listen_port"
def loadConfig(configFilePath):
"""
@summary: Load and check config file
@param configFilePath: config file path
"""
if not os.path.isfile(configFilePath):
log.error("File %s doesn't exist"%configFilePath)
return None
f = open(configFilePath, 'r')
config = json.load(f)
f.close()
return config
if __name__ == '__main__':
configFilePath = None
privateKeyFilePath = None
certificateFilePath = None
adminInterface = None
try:
opts, args = getopt.getopt(sys.argv[1:], "hf:k:c:i:")
except getopt.GetoptError:
help()
for opt, arg in opts:
if opt == "-h":
help()
sys.exit()
elif opt == "-f":
configFilePath = arg
elif opt == "-k":
privateKeyFilePath = arg
elif opt == "-c":
certificateFilePath = arg
elif opt == "-i":
adminInterface = arg
if configFilePath is None:
print "Config file is mandatory"
help()
sys.exit()
if certificateFilePath is None:
print "Certificate file is mandatory"
help()
sys.exit()
if privateKeyFilePath is None:
print "Private key file is mandatory"
help()
sys.exit()
#load config file
config = loadConfig(configFilePath)
if config is None:
log.error("Bad configuration file")
sys.exit()
#use to init font
app = QtGui.QApplication(sys.argv)
reactor.listenTCP(int(args[0]), ProxyServerFactory(CredentialProvider(config), privateKeyFilePath, certificateFilePath))
if not adminInterface is None:
if ':' in adminInterface:
adminInterface, adminPort = adminInterface.split(':')
else:
adminInterface, adminPort = adminInterface, "3390"
log.info("Admin listen on %s:%s"%(adminInterface, adminPort))
reactor.listenTCP(int(adminPort), ProxyAdminFactory(privateKeyFilePath, certificateFilePath), interface = adminInterface)
reactor.run()

View File

@@ -1,6 +1,6 @@
#!/usr/bin/python
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -28,7 +28,8 @@ import sys, os, getopt
from PyQt4 import QtCore, QtGui
from rdpy.protocol.rdp import rdp
from rdpy.ui.qt4 import RDPBitmapToQtImage
import rdpy.base.log as log
import rdpy.core.log as log
from rdpy.core.error import RDPSecurityNegoFail
from twisted.internet import task
#set log level
@@ -39,18 +40,23 @@ class RDPScreenShotFactory(rdp.ClientFactory):
@summary: Factory for screenshot exemple
"""
__INSTANCE__ = 0
def __init__(self, width, height, path, timeout):
__STATE__ = []
def __init__(self, reactor, app, width, height, path, timeout):
"""
@param width: width of screen
@param height: height of screen
@param path: path of output screenshot
@param timeout: close connection after timeout s without any updating
@param reactor: twisted reactor
@param width: {integer} width of screen
@param height: {integer} height of screen
@param path: {str} path of output screenshot
@param timeout: {float} close connection after timeout s without any updating
"""
RDPScreenShotFactory.__INSTANCE__ += 1
self._reactor = reactor
self._app = app
self._width = width
self._height = height
self._path = path
self._timeout = timeout
self._security = "ssl"
def clientConnectionLost(self, connector, reason):
"""
@@ -58,11 +64,18 @@ class RDPScreenShotFactory(rdp.ClientFactory):
@param connector: twisted connector use for rdp connection (use reconnect to restart connection)
@param reason: str use to advertise reason of lost connection
"""
if reason.type == RDPSecurityNegoFail and self._security != "rdp":
log.info("due to RDPSecurityNegoFail try standard security layer")
self._security = "rdp"
connector.connect()
return
log.info("connection lost : %s"%reason)
RDPScreenShotFactory.__STATE__.append((connector.host, connector.port, reason))
RDPScreenShotFactory.__INSTANCE__ -= 1
if(RDPScreenShotFactory.__INSTANCE__ == 0):
reactor.stop()
app.exit()
self._reactor.stop()
self._app.exit()
def clientConnectionFailed(self, connector, reason):
"""
@@ -71,10 +84,11 @@ class RDPScreenShotFactory(rdp.ClientFactory):
@param reason: str use to advertise reason of lost connection
"""
log.info("connection failed : %s"%reason)
RDPScreenShotFactory.__STATE__.append((connector.host, connector.port, reason))
RDPScreenShotFactory.__INSTANCE__ -= 1
if(RDPScreenShotFactory.__INSTANCE__ == 0):
reactor.stop()
app.exit()
self._reactor.stop()
self._app.exit()
def buildObserver(self, controller, addr):
@@ -87,20 +101,21 @@ class RDPScreenShotFactory(rdp.ClientFactory):
"""
@summary: observer that connect, cache every image received and save at deconnection
"""
def __init__(self, controller, width, height, path, timeout):
def __init__(self, controller, width, height, path, timeout, reactor):
"""
@param controller: RDPClientController
@param width: width of screen
@param height: height of screen
@param path: path of output screenshot
@param timeout: close connection after timeout s without any updating
@param controller: {RDPClientController}
@param width: {integer} width of screen
@param height: {integer} height of screen
@param path: {str} path of output screenshot
@param timeout: {float} close connection after timeout s without any updating
@param reactor: twisted reactor
"""
rdp.RDPClientObserver.__init__(self, controller)
controller.setScreen(width, height);
self._buffer = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
self._path = path
self._timeout = timeout
self._startTimeout = False
self._reactor = reactor
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
"""
@@ -112,7 +127,7 @@ class RDPScreenShotFactory(rdp.ClientFactory):
qp.drawImage(destLeft, destTop, image, 0, 0, destRight - destLeft + 1, destBottom - destTop + 1)
if not self._startTimeout:
self._startTimeout = False
reactor.callLater(self._timeout, self.checkUpdate)
self._reactor.callLater(self._timeout, self.checkUpdate)
def onReady(self):
"""
@@ -129,8 +144,40 @@ class RDPScreenShotFactory(rdp.ClientFactory):
def checkUpdate(self):
self._controller.close();
controller.setScreen(width, height);
controller.setSecurityLevel(self._security)
return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout, self._reactor)
def main(width, height, path, timeout, hosts):
"""
@summary: main algorithm
@param height: {integer} height of screenshot
@param width: {integer} width of screenshot
@param timeout: {float} in sec
@param hosts: {list(str(ip[:port]))}
@return: {list(tuple(ip, port, Failure instance)} list of connection state
"""
#create application
app = QtGui.QApplication(sys.argv)
#add qt4 reactor
import qt4reactor
qt4reactor.install()
from twisted.internet import reactor
return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout)
for host in hosts:
if ':' in host:
ip, port = host.split(':')
else:
ip, port = host, "3389"
reactor.connectTCP(ip, int(port), RDPScreenShotFactory(reactor, app, width, height, path + "%s.jpg"%ip, timeout))
reactor.runReturn()
app.exec_()
return RDPScreenShotFactory.__STATE__
def help():
print "Usage: rdpy-rdpscreenshot [options] ip[:port]"
@@ -163,22 +210,4 @@ if __name__ == '__main__':
elif opt == "-t":
timeout = float(arg)
#create application
app = QtGui.QApplication(sys.argv)
#add qt4 reactor
import qt4reactor
qt4reactor.install()
from twisted.internet import reactor
for arg in args:
if ':' in arg:
ip, port = arg.split(':')
else:
ip, port = arg, "3389"
reactor.connectTCP(ip, int(port), RDPScreenShotFactory(width, height, path + "%s.jpg"%ip, timeout))
reactor.runReturn()
app.exec_()
main(width, height, path, timeout, args)

99
bin/rdpy-rssplayer.py Executable file
View File

@@ -0,0 +1,99 @@
#!/usr/bin/python
#
# 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/>.
#
"""
rss file player
"""
import sys, os, getopt, socket
from PyQt4 import QtGui, QtCore
from rdpy.core import log, rss
from rdpy.ui.qt4 import QRemoteDesktop, RDPBitmapToQtImage
log._LOG_LEVEL = log.Level.INFO
class RssPlayerWidget(QRemoteDesktop):
"""
@summary: special rss player widget
"""
def __init__(self, width, height):
class RssAdaptor(object):
def sendMouseEvent(self, e, isPressed):
""" Not Handle """
def sendKeyEvent(self, e, isPressed):
""" Not Handle """
def sendWheelEvent(self, e):
""" Not Handle """
def closeEvent(self, e):
""" Not Handle """
QRemoteDesktop.__init__(self, width, height, RssAdaptor())
def drawInfos(self, domain, username, password, hostname):
QtGui.QMessageBox.about(self, "Credentials Event", "domain : %s\nusername : %s\npassword : %s\nhostname : %s" % (
domain, username, password, hostname))
def help():
print "Usage: rdpy-rssplayer [-h] rss_filepath"
def start(widget, rssFile):
loop(widget, rssFile, rssFile.nextEvent())
def loop(widget, rssFile, nextEvent):
"""
@summary: timer function
@param widget: {QRemoteDesktop}
@param rssFile: {rss.FileReader}
"""
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);
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)
elif nextEvent.type.value == rss.EventType.SCREEN:
widget.resize(nextEvent.event.width.value, nextEvent.event.height.value)
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)
elif nextEvent.type.value == rss.EventType.CLOSE:
widget.close()
return
e = rssFile.nextEvent()
QtCore.QTimer.singleShot(e.timestamp.value,lambda:loop(widget, rssFile, e))
if __name__ == '__main__':
try:
opts, args = getopt.getopt(sys.argv[1:], "h")
except getopt.GetoptError:
help()
for opt, arg in opts:
if opt == "-h":
help()
sys.exit()
filepath = args[0]
#create application
app = QtGui.QApplication(sys.argv)
widget = RssPlayerWidget(800, 600)
widget.show()
rssFile = rss.createReader(filepath)
start(widget, rssFile)
sys.exit(app.exec_())

View File

@@ -1,6 +1,6 @@
#!/usr/bin/python
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -27,7 +27,7 @@ from PyQt4 import QtGui
from rdpy.ui.qt4 import RFBClientQt
from rdpy.protocol.rfb import rfb
import rdpy.base.log as log
import rdpy.core.log as log
log._LOG_LEVEL = log.Level.INFO
class RFBClientQtFactory(rfb.ClientFactory):

View File

@@ -1,6 +1,6 @@
#!/usr/bin/python
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -26,7 +26,7 @@ take screenshot of login page
import sys, os, getopt
from PyQt4 import QtCore, QtGui
from rdpy.protocol.rfb import rfb
import rdpy.base.log as log
import rdpy.core.log as log
from rdpy.ui.qt4 import qtImageFormatFromRFBPixelFormat
from twisted.internet import task

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -90,3 +90,13 @@ class ErrorReportedFromPeer(Exception):
@param message: message show when exception is raised
"""
Exception.__init__(self, message)
class RDPSecurityNegoFail(Exception):
"""
@summary: Raise when security nego fail
"""
def __init__(self, message = ""):
"""
@param message: message show when exception is raised
"""
Exception.__init__(self, message)

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -23,7 +23,7 @@ Join RDPY design with twisted design
RDPY use Layer Protocol design (like twisted)
"""
from rdpy.base.error import CallPureVirtualFuntion
from rdpy.core.error import CallPureVirtualFuntion
class IStreamListener(object):
"""
@@ -99,7 +99,7 @@ class LayerAutomata(Layer, IStreamListener):
@param callback: a callable object
"""
if callback is None:
callback = self.__class__.recv
callback = lambda x:self.__class__.recv(self, x)
self.recv = callback
@@ -129,10 +129,11 @@ class RawLayerClientFactory(protocol.ClientFactory):
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "buildRawLayer", "RawLayerClientFactory"))
def connectionLost(self, rawlayer):
def connectionLost(self, rawlayer, reason):
"""
@summary: Override this method to handle connection lost
@param rawlayer: rawLayer that cause connectionLost event
@param reason: twisted reason
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "connectionLost", "RawLayerClientFactory"))
@@ -156,10 +157,11 @@ class RawLayerServerFactory(protocol.ClientFactory):
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
def connectionLost(self, rawlayer):
def connectionLost(self, rawlayer, reason):
"""
@summary: Override this method to handle connection lost
@param rawlayer: rawLayer that cause connectionLost event
@param reason: twisted reason
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recv", "IStreamListener"))
@@ -218,7 +220,7 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender):
@summary: Call from twisted engine when protocol is closed
@param reason: str represent reason of close connection
"""
self._factory.connectionLost(self)
self._factory.connectionLost(self, reason)
def close(self):
"""

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -30,6 +30,7 @@ class Level(object):
INFO = 1
WARNING = 2
ERROR = 3
NONE = 4
_LOG_LEVEL = Level.DEBUG

255
rdpy/core/rss.py Normal file
View File

@@ -0,0 +1,255 @@
#
# 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/>.
#
"""
Remote Session Scenario File format
Private protocol format to save events
"""
from rdpy.core.type import CompositeType, FactoryType, UInt8, UInt16Le, UInt32Le, String, sizeof, Stream
from rdpy.core import log, error
import time
class EventType(object):
"""
@summary: event type
"""
UPDATE = 0x0001
SCREEN = 0x0002
INFO = 0x0003
CLOSE = 0x0004
class UpdateFormat(object):
"""
@summary: format of update bitmap
"""
RAW = 0x01
BMP = 0x02
class Event(CompositeType):
"""
@summary: A recorded event
"""
def __init__(self, event = None):
CompositeType.__init__(self)
self.type = UInt16Le(lambda:event.__class__._TYPE_)
self.timestamp = UInt32Le()
self.length = UInt32Le(lambda:(sizeof(self) - 10))
def EventFactory():
"""
@summary: Closure for event factory
"""
for c in [UpdateEvent, ScreenEvent, InfoEvent, CloseEvent]:
if self.type.value == c._TYPE_:
return c(readLen = self.length)
log.debug("unknown event type : %s"%hex(self.type.value))
#read entire packet
return String(readLen = self.length)
if event is None:
event = FactoryType(EventFactory)
elif not "_TYPE_" in event.__class__.__dict__:
raise error.InvalidExpectedDataException("Try to send an invalid event block")
self.event = event
class UpdateEvent(CompositeType):
"""
@summary: Update event
"""
_TYPE_ = EventType.UPDATE
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.destLeft = UInt16Le()
self.destTop = UInt16Le()
self.destRight = UInt16Le()
self.destBottom = UInt16Le()
self.width = UInt16Le()
self.height = UInt16Le()
self.bpp = UInt8()
self.format = UInt8()
self.length = UInt32Le(lambda:sizeof(self.data))
self.data = String(readLen = self.length)
class InfoEvent(CompositeType):
"""
@summary: Info event
"""
_TYPE_ = EventType.INFO
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.lenUsername = UInt16Le(lambda:sizeof(self.username))
self.username = String(readLen = self.lenUsername)
self.lenPassword = UInt16Le(lambda:sizeof(self.password))
self.password = String(readLen = self.lenPassword)
self.lenDomain = UInt16Le(lambda:sizeof(self.domain))
self.domain = String(readLen = self.lenDomain)
self.lenHostname = UInt16Le(lambda:sizeof(self.hostname))
self.hostname = String(readLen = self.lenHostname)
class ScreenEvent(CompositeType):
"""
@summary: screen information event
"""
_TYPE_ = EventType.SCREEN
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.width = UInt16Le()
self.height = UInt16Le()
self.colorDepth = UInt8()
class CloseEvent(CompositeType):
"""
@summary: end of session event
"""
_TYPE_ = EventType.CLOSE
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
def timeMs():
"""
@return: {int} time stamp in milliseconds
"""
return int(time.time() * 1000)
class FileRecorder(object):
"""
@summary: RSR File recorder
"""
def __init__(self, f):
"""
@param f: {file} file pointer use to write
"""
self._file = f
#init timer
self._lastEventTimer = timeMs()
def rec(self, event):
"""
@summary: save event in file
@param event: {UpdateEvent}
"""
now = timeMs()
#wrap around event message
e = Event(event)
#timestamp is time since last event
e.timestamp.value = now - self._lastEventTimer
self._lastEventTimer = now
s = Stream()
s.writeType(e)
self._file.write(s.getvalue())
def update(self, destLeft, destTop, destRight, destBottom, width, height, bpp, upateFormat, data):
"""
@summary: record update event
@param destLeft: {int} xmin position
@param destTop: {int} ymin position
@param destRight: {int} xmax position because RDP can send bitmap with padding
@param destBottom: {int} ymax position because RDP can send bitmap with padding
@param width: {int} width of bitmap
@param height: {int} height of bitmap
@param bpp: {int} number of bit per pixel
@param upateFormat: {UpdateFormat} use RLE compression
@param data: {str} bitmap data
"""
updateEvent = UpdateEvent()
updateEvent.destLeft.value = destLeft
updateEvent.destTop.value = destTop
updateEvent.destRight.value = destRight
updateEvent.destBottom.value = destBottom
updateEvent.width.value = width
updateEvent.height.value = height
updateEvent.bpp.value = bpp
updateEvent.format.value = upateFormat
updateEvent.data.value = data
self.rec(updateEvent)
def screen(self, width, height, colorDepth):
"""
@summary: record resize event of screen (maybe first event)
@param width: {int} width of screen
@param height: {int} height of screen
@param colorDepth: {int} colorDepth
"""
screenEvent = ScreenEvent()
screenEvent.width.value = width
screenEvent.height.value = height
screenEvent.colorDepth.value = colorDepth
self.rec(screenEvent)
def credentials(self, username, password, domain = "", hostname = ""):
"""
@summary: Record informations event
@param username: {str} username of session
@param password: {str} password of session
@param domain: {str} domain of session
@param hostname: {str} hostname of session
"""
infoEvent = InfoEvent()
infoEvent.username.value = username
infoEvent.password.value = password
infoEvent.domain.value = domain
infoEvent.hostname.value = hostname
self.rec(infoEvent)
def close(self):
"""
@summary: end of scenario
"""
self.rec(CloseEvent())
class FileReader(object):
"""
@summary: RSR File reader
"""
def __init__(self, f):
"""
@param f: {file} file pointer use to read
"""
self._s = Stream(f.read())
def nextEvent(self):
"""
@summary: read next event and return it
"""
if self._s.dataLen() == 0:
return None
e = Event()
self._s.readType(e)
return e
def createRecorder(path):
"""
@summary: open file from path and return FileRecorder
@param path: {str} path of output file
@return: {FileRecorder}
"""
return FileRecorder(open(path, "wb"))
def createReader(path):
"""
@summary: open file from path and return FileReader
@param path: {str} path of input file
@return: {FileReader}
"""
return FileReader(open(path, "rb"))

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -27,8 +27,8 @@ We are in python!
import struct
from copy import deepcopy
from StringIO import StringIO
from rdpy.base.error import InvalidExpectedDataException, InvalidSize, CallPureVirtualFuntion, InvalidValue
import rdpy.base.log as log
from rdpy.core.error import InvalidExpectedDataException, InvalidSize, CallPureVirtualFuntion, InvalidValue
import rdpy.core.log as log
def sizeof(element):
"""
@@ -959,6 +959,13 @@ class ArrayType(Type):
@param s: Stream
"""
s.writeType(self._array)
def __getitem__(self, item):
"""
@summary: Magic function to be FactoryType as transparent as possible
@return: index of _value
"""
return self._array.__getitem__(item)
def __sizeof__(self):
"""

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -22,8 +22,8 @@ Basic Encoding Rules use in RDP.
ASN.1 standard
"""
from rdpy.network.type import UInt8, UInt16Be, UInt32Be, String
from rdpy.base.error import InvalidExpectedDataException, InvalidSize
from rdpy.core.type import UInt8, UInt16Be, UInt32Be, String
from rdpy.core.error import InvalidExpectedDataException, InvalidSize
class BerPc(object):
BER_PC_MASK = 0x20
@@ -50,7 +50,7 @@ class Tag(object):
def berPC(pc):
"""
Return BER_CONSTRUCT if true
@summary: Return BER_CONSTRUCT if true
BER_PRIMITIVE if false
@param pc: boolean
@return: BerPc value
@@ -62,7 +62,7 @@ def berPC(pc):
def readLength(s):
"""
Read length of BER structure
@summary: Read length of BER structure
length be on 1 2 or 3 bytes
@param s: stream
@return: int or Python long
@@ -86,7 +86,7 @@ def readLength(s):
def writeLength(size):
"""
Return structure length as expected in BER specification
@summary: Return structure length as expected in BER specification
@param size: int or python long
@return: UInt8 or (UInt8(0x82), UInt16Be)
"""
@@ -97,7 +97,7 @@ def writeLength(size):
def readUniversalTag(s, tag, pc):
"""
Read tag of BER packet
@summary: Read tag of BER packet
@param tag: Tag class attributes
@param pc: boolean
@return: true if tag is correctly read
@@ -108,7 +108,7 @@ def readUniversalTag(s, tag, pc):
def writeUniversalTag(tag, pc):
"""
Return universal tag byte
@summary: Return universal tag byte
@param tag: tag class attributes
@param pc: boolean
@return: UInt8
@@ -117,7 +117,7 @@ def writeUniversalTag(tag, pc):
def readApplicationTag(s, tag):
"""
Read application tag
@summary: Read application tag
@param s: stream
@param tag: tag class attributes
@return: length of application packet
@@ -138,7 +138,7 @@ def readApplicationTag(s, tag):
def writeApplicationTag(tag, size):
"""
Return structure that represent BER application tag
@summary: Return structure that represent BER application tag
@param tag: int python that match an uint8(0xff)
@param size: size to rest of packet
"""
@@ -149,7 +149,7 @@ def writeApplicationTag(tag, size):
def readBoolean(s):
"""
Return boolean
@summary: Return boolean
@param s: stream
@return: boolean
"""
@@ -164,7 +164,7 @@ def readBoolean(s):
def writeBoolean(b):
"""
Return structure that represent boolean in BER specification
@summary: Return structure that represent boolean in BER specification
@param b: boolean
@return: BER boolean block
"""
@@ -175,7 +175,7 @@ def writeBoolean(b):
def readInteger(s):
"""
Read integer structure from stream
@summary: Read integer structure from stream
@param s: stream
@return: int or long python
"""
@@ -207,7 +207,7 @@ def readInteger(s):
def writeInteger(value):
"""
Write integer value
@summary: Write integer value
@param param: INT or Python long
@return: BER integer block
"""
@@ -220,7 +220,7 @@ def writeInteger(value):
def readOctetString(s):
"""
Read BER string structure
@summary: Read BER string structure
@param s: stream
@return: string python
"""
@@ -231,7 +231,7 @@ def readOctetString(s):
def writeOctetstring(value):
"""
Write string in BER representation
@summary: Write string in BER representation
@param value: string
@return: BER octet string block
"""
@@ -239,7 +239,7 @@ def writeOctetstring(value):
def readEnumerated(s):
"""
Read enumerated structure
@summary: Read enumerated structure
@param s: Stream
@return: int or long
"""
@@ -253,7 +253,7 @@ def readEnumerated(s):
def writeEnumerated(enumerated):
"""
Write enumerated structure
@summary: Write enumerated structure
@param s: Stream
@return: BER enumerated block
"""

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -22,10 +22,13 @@ Implement GCC structure use in RDP protocol
http://msdn.microsoft.com/en-us/library/cc240508.aspx
"""
from rdpy.network.type import UInt8, UInt16Le, UInt32Le, CompositeType, String, Stream, sizeof, FactoryType, ArrayType
import md5
from rdpy.core.type import UInt8, UInt16Le, UInt32Le, CompositeType, String, Stream, sizeof, FactoryType, ArrayType
import per, mcs
from rdpy.base.error import InvalidExpectedDataException
import rdpy.base.log as log
from rdpy.core.error import InvalidExpectedDataException
from rdpy.core import log
from rdpy.security import x509
import rdpy.security.rsa_wrapper as rsa
t124_02_98_oid = ( 0, 0, 20, 124, 0, 1 )
@@ -34,7 +37,7 @@ h221_sc_key = "McDn";
class MessageType(object):
"""
Server to Client block
@summary: Server to Client block
GCC conference messages
@see: http://msdn.microsoft.com/en-us/library/cc240509.aspx
"""
@@ -52,7 +55,7 @@ class MessageType(object):
class ColorDepth(object):
"""
Depth color
@summary: Depth color
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
RNS_UD_COLOR_8BPP = 0xCA01
@@ -62,7 +65,7 @@ class ColorDepth(object):
class HighColor(object):
"""
High color of client
@summary: High color of client
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
HIGH_COLOR_4BPP = 0x0004
@@ -73,7 +76,7 @@ class HighColor(object):
class Support(object):
"""
Supported depth flag
@summary: Supported depth flag
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
RNS_UD_24BPP_SUPPORT = 0x0001
@@ -83,7 +86,7 @@ class Support(object):
class CapabilityFlags(object):
"""
For more details on each flags click above
@summary: For more details on each flags click above
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
RNS_UD_CS_SUPPORT_ERRINFO_PDU = 0x0001
@@ -100,7 +103,7 @@ class CapabilityFlags(object):
class ConnectionType(object):
"""
This information is correct if
@summary: This information is correct if
RNS_UD_CS_VALID_CONNECTION_TYPE flag is set on capabilityFlag
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
@@ -114,7 +117,7 @@ class ConnectionType(object):
class Version(object):
"""
Supported version of RDP
@summary: Supported version of RDP
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
RDP_VERSION_4 = 0x00080001
@@ -123,20 +126,30 @@ class Version(object):
class Sequence(object):
RNS_UD_SAS_DEL = 0xAA03
class Encryption(object):
class EncryptionMethod(object):
"""
Encryption methods supported
@deprecated: because rdpy use SSL but need to send to server...
@summary: Encryption methods supported
@see: http://msdn.microsoft.com/en-us/library/cc240511.aspx
"""
ENCRYPTION_FLAG_40BIT = 0x00000001
ENCRYPTION_FLAG_128BIT = 0x00000002
ENCRYPTION_FLAG_56BIT = 0x00000008
FIPS_ENCRYPTION_FLAG = 0x00000010
class EncryptionLevel(object):
"""
@summary: level of 'security'
@see: http://msdn.microsoft.com/en-us/library/cc240518.aspx
"""
ENCRYPTION_LEVEL_NONE = 0x00000000
ENCRYPTION_LEVEL_LOW = 0x00000001
ENCRYPTION_LEVEL_CLIENT_COMPATIBLE = 0x00000002
ENCRYPTION_LEVEL_HIGH = 0x00000003
ENCRYPTION_LEVEL_FIPS = 0x00000004
class ChannelOptions(object):
"""
Channel options
@summary: Channel options
@see: http://msdn.microsoft.com/en-us/library/cc240513.aspx
"""
CHANNEL_OPTION_INITIALIZED = 0x80000000
@@ -153,8 +166,8 @@ class ChannelOptions(object):
class KeyboardType(object):
"""
Keyboard type
IBM_101_102_KEYS is the most common keyboard type
@summary: Keyboard type
@see: IBM_101_102_KEYS is the most common keyboard type
"""
IBM_PC_XT_83_KEY = 0x00000001
OLIVETTI = 0x00000002
@@ -166,7 +179,7 @@ class KeyboardType(object):
class KeyboardLayout(object):
"""
Keyboard layout definition
@summary: Keyboard layout definition
@see: http://technet.microsoft.com/en-us/library/cc766503%28WS.10%29.aspx
"""
ARABIC = 0x00000401
@@ -189,9 +202,16 @@ class KeyboardLayout(object):
DUTCH = 0x00000413
NORWEGIAN = 0x00000414
class CertificateType(object):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240521.aspx
"""
CERT_CHAIN_VERSION_1 = 0x00000001
CERT_CHAIN_VERSION_2 = 0x00000002
class DataBlock(CompositeType):
"""
Block settings
@summary: Block settings
"""
def __init__(self, dataBlock = None):
CompositeType.__init__(self)
@@ -200,7 +220,7 @@ class DataBlock(CompositeType):
def DataBlockFactory():
"""
build settings in accordance of type self.type.value
@summary: build settings in accordance of type self.type.value
"""
for c in [ClientCoreData, ClientSecurityData, ClientNetworkData, ServerCoreData, ServerNetworkData, ServerSecurityData]:
if self.type.value == c._TYPE_:
@@ -218,7 +238,7 @@ class DataBlock(CompositeType):
class ClientCoreData(CompositeType):
"""
Class that represent core setting of client
@summary: Class that represent core setting of client
@see: http://msdn.microsoft.com/en-us/library/cc240510.aspx
"""
_TYPE_ = MessageType.CS_CORE
@@ -250,7 +270,7 @@ class ClientCoreData(CompositeType):
class ServerCoreData(CompositeType):
"""
Server side core settings structure
@summary: Server side core settings structure
@see: http://msdn.microsoft.com/en-us/library/cc240517.aspx
"""
_TYPE_ = MessageType.SC_CORE
@@ -262,23 +282,19 @@ class ServerCoreData(CompositeType):
class ClientSecurityData(CompositeType):
"""
Client security setting
@deprecated: because we use ssl
@summary: Client security setting
@see: http://msdn.microsoft.com/en-us/library/cc240511.aspx
"""
_TYPE_ = MessageType.CS_SECURITY
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.encryptionMethods = UInt32Le()
self.encryptionMethods = UInt32Le(EncryptionMethod.ENCRYPTION_FLAG_40BIT | EncryptionMethod.ENCRYPTION_FLAG_56BIT | EncryptionMethod.ENCRYPTION_FLAG_128BIT)
self.extEncryptionMethods = UInt32Le()
class ServerSecurityData(CompositeType):
"""
Server security settings
May be ignored because rdpy don't use
RDP security level
@deprecated: because we use SSL
@summary: Server security settings
@see: http://msdn.microsoft.com/en-us/library/cc240518.aspx
"""
_TYPE_ = MessageType.SC_SECURITY
@@ -287,6 +303,151 @@ class ServerSecurityData(CompositeType):
CompositeType.__init__(self, readLen = readLen)
self.encryptionMethod = UInt32Le()
self.encryptionLevel = UInt32Le()
self.serverRandomLen = UInt32Le(0x00000020, constant = True, conditional = lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel == 0))
self.serverCertLen = UInt32Le(lambda:sizeof(self.serverCertificate), conditional = lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel == 0))
self.serverRandom = String(readLen = self.serverRandomLen, conditional = lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel == 0))
self.serverCertificate = ServerCertificate(readLen = self.serverCertLen, conditional = lambda:not(self.encryptionMethod.value == 0 and self.encryptionLevel == 0))
class ServerCertificate(CompositeType):
"""
@summary: Server certificate structure
@see: http://msdn.microsoft.com/en-us/library/cc240521.aspx
"""
def __init__(self, certData = None, readLen = None, conditional = lambda:True):
CompositeType.__init__(self, readLen = readLen, conditional = conditional)
self.dwVersion = UInt32Le(lambda:(self.certData.__class__._TYPE_))
def CertificateFactory():
"""
Closure for capability factory
"""
for c in [ProprietaryServerCertificate, X509CertificateChain]:
if self.dwVersion.value & 0x7fffffff == c._TYPE_:
return c()
raise InvalidExpectedDataException("unknown certificate type : %s "%hex(self.dwVersion.value))
if certData is None:
certData = FactoryType(CertificateFactory)
elif not "_TYPE_" in certData.__class__.__dict__:
raise InvalidExpectedDataException("Try to send an invalid Certificate")
self.certData = certData
class ProprietaryServerCertificate(CompositeType):
"""
@summary: microsoft proprietary certificate
@see: http://msdn.microsoft.com/en-us/library/cc240519.aspx
"""
_TYPE_ = CertificateType.CERT_CHAIN_VERSION_1
#http://msdn.microsoft.com/en-us/library/cc240776.aspx
_TERMINAL_SERVICES_MODULUS_ = "\x3d\x3a\x5e\xbd\x72\x43\x3e\xc9\x4d\xbb\xc1\x1e\x4a\xba\x5f\xcb\x3e\x88\x20\x87\xef\xf5\xc1\xe2\xd7\xb7\x6b\x9a\xf2\x52\x45\x95\xce\x63\x65\x6b\x58\x3a\xfe\xef\x7c\xe7\xbf\xfe\x3d\xf6\x5c\x7d\x6c\x5e\x06\x09\x1a\xf5\x61\xbb\x20\x93\x09\x5f\x05\x6d\xea\x87"
_TERMINAL_SERVICES_PRIVATE_EXPONENT_ = "\x87\xa7\x19\x32\xda\x11\x87\x55\x58\x00\x16\x16\x25\x65\x68\xf8\x24\x3e\xe6\xfa\xe9\x67\x49\x94\xcf\x92\xcc\x33\x99\xe8\x08\x60\x17\x9a\x12\x9f\x24\xdd\xb1\x24\x99\xc7\x3a\xb8\x0a\x7b\x0d\xdd\x35\x07\x79\x17\x0b\x51\x9b\xb3\xc7\x10\x01\x13\xe7\x3f\xf3\x5f"
_TERMINAL_SERVICES_PUBLIC_EXPONENT_ = "\x5b\x7b\x88\xc0"
def __init__(self):
CompositeType.__init__(self)
self.dwSigAlgId = UInt32Le(0x00000001, constant = True)
self.dwKeyAlgId = UInt32Le(0x00000001, constant = True)
self.wPublicKeyBlobType = UInt16Le(0x0006, constant = True)
self.wPublicKeyBlobLen = UInt16Le(lambda:sizeof(self.PublicKeyBlob))
self.PublicKeyBlob = RSAPublicKey(readLen = self.wPublicKeyBlobLen)
self.wSignatureBlobType = UInt16Le(0x0008, constant = True)
self.wSignatureBlobLen = UInt16Le(lambda:(sizeof(self.SignatureBlob) - 8))
self.SignatureBlob = String(readLen = self.wSignatureBlobLen)
self.padding = String(b"\x00" * 8, readLen = UInt8(8))
def getPublicKey(self):
"""
@return: {Tuple} (modulus, publicExponent)
"""
log.debug("read RSA public key from proprietary certificate")
#reverse because bignum in little endian
return rsa.PublicKey(self.PublicKeyBlob.pubExp.value, self.PublicKeyBlob.modulus.value[::-1])
def computeSignatureHash(self):
"""
@summary: compute hash
"""
s = Stream()
s.writeType(UInt32Le(self.__class__._TYPE_))
s.writeType(self.dwSigAlgId)
s.writeType(self.dwKeyAlgId)
s.writeType(self.wPublicKeyBlobType)
s.writeType(self.wPublicKeyBlobLen)
s.writeType(self.PublicKeyBlob)
md5Digest = md5.new()
md5Digest.update(s.getvalue())
return md5Digest.digest() + "\x00" + "\xff" * 46 + "\x01"
def sign(self):
"""
@summary: sign proprietary certificate
@see: http://msdn.microsoft.com/en-us/library/cc240778.aspx
"""
self.SignatureBlob.value = rsa.sign(self.computeSignatureHash()[::-1], rsa.PrivateKey(d = ProprietaryServerCertificate._TERMINAL_SERVICES_PRIVATE_EXPONENT_[::-1], n = ProprietaryServerCertificate._TERMINAL_SERVICES_MODULUS_[::-1]))[::-1]
def verify(self):
"""
@summary: verify certificate signature
"""
return rsa.verify(self.SignatureBlob.value[::-1], rsa.PublicKey(e = ProprietaryServerCertificate._TERMINAL_SERVICES_PUBLIC_EXPONENT_[::-1], n = ProprietaryServerCertificate._TERMINAL_SERVICES_MODULUS_[::-1]))[::-1] == self.computeSignatureHash()
class CertBlob(CompositeType):
"""
@summary: certificate blob, contain x509 data
@see: http://msdn.microsoft.com/en-us/library/cc241911.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.cbCert = UInt32Le(lambda:sizeof(self.abCert))
self.abCert = String(readLen = self.cbCert)
class X509CertificateChain(CompositeType):
"""
@summary: X509 certificate chain
@see: http://msdn.microsoft.com/en-us/library/cc241910.aspx
"""
_TYPE_ = CertificateType.CERT_CHAIN_VERSION_2
def __init__(self):
CompositeType.__init__(self)
self.NumCertBlobs = UInt32Le()
self.CertBlobArray = ArrayType(CertBlob, readLen = self.NumCertBlobs)
self.padding = String(readLen = UInt8(lambda:(8 + 4 * self.NumCertBlobs.value)))
def getPublicKey(self):
"""
@return: {Tuple} (modulus, publicExponent)
"""
log.debug("read RSA public key from x509 certificate")
#last certifcate contain public key
n, e = x509.extractRSAKey(x509.load(self.CertBlobArray[-1].abCert.value))
return rsa.PublicKey(e, n)
def verify(self):
"""
@todo: verify x509 signature
"""
return True
class RSAPublicKey(CompositeType):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240520.aspx
"""
def __init__(self, readLen):
CompositeType.__init__(self, readLen = readLen)
#magic is RSA1(0x31415352)
self.magic = UInt32Le(0x31415352, constant = True)
self.keylen = UInt32Le(lambda:(sizeof(self.modulus) + sizeof(self.padding)))
self.bitlen = UInt32Le(lambda:((self.keylen.value - 8) * 8))
self.datalen = UInt32Le(lambda:((self.bitlen.value / 8) - 1))
self.pubExp = UInt32Le()
self.modulus = String(readLen = UInt16Le(lambda:(self.keylen.value - 8)))
self.padding = String("\x00" * 8, readLen = UInt8(8))
class ChannelDef(CompositeType):
"""
@@ -345,6 +506,15 @@ class Settings(CompositeType):
if i.type.value == messageType:
return i.dataBlock
return None
def __getattr__(self, name):
"""
@summary: Magic function for better access
@return: _value parameter
"""
if not name in MessageType.__dict__:
return None
return self.getBlock(MessageType.__dict__[name])
def clientSettings():
"""

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -22,12 +22,12 @@
@see: http://msdn.microsoft.com/en-us/library/cc241880.aspx
"""
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
from rdpy.core.type import CompositeType, UInt8, UInt16Le, UInt32Le, String, sizeof, FactoryType, ArrayType, Stream
from rdpy.core.error import InvalidExpectedDataException
import rdpy.core.log as log
import sec, gcc
from rdpy.security import rc4
from rdpy.security import rsa_wrapper as rsa
class MessageType(object):
"""
@@ -126,10 +126,10 @@ 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, unicode = True)
self.pbCompanyName = String("Microsoft Corporation", 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, unicode = True)
self.pbProductId = String("A02", readLen = self.cbProductId, unicode = True)
class Scope(CompositeType):
@@ -161,7 +161,7 @@ class ServerLicenseRequest(CompositeType):
def __init__(self):
CompositeType.__init__(self)
self.serverRandom = String(readLen = UInt8(32))
self.serverRandom = String("\x00" * 32, readLen = UInt8(32))
self.productInfo = ProductInformation()
self.keyExchangeList = LicenseBinaryBlob(BinaryBlobType.BB_KEY_EXCHG_ALG_BLOB)
self.serverCertificate = LicenseBinaryBlob(BinaryBlobType.BB_CERTIFICATE_BLOB)
@@ -182,7 +182,7 @@ class ClientNewLicenseRequest(CompositeType):
#pure microsoft client ;-)
#http://msdn.microsoft.com/en-us/library/1040af38-c733-4fb3-acd1-8db8cc979eda#id10
self.platformId = UInt32Le(0x04000000 | 0x00010000)
self.clientRandom = String("\x00" * 32)
self.clientRandom = String("\x00" * 32, readLen = UInt8(32))
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)
@@ -254,35 +254,22 @@ def createValidClientLicensingErrorMessage():
class LicenseManager(object):
"""
@summary: handle license automata
@summary: handle license automata (client side)
@see: http://msdn.microsoft.com/en-us/library/cc241890.aspx
"""
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 = ""
self._hostname = ""
def generateKeys(self):
"""
@summary: generate key for license session
"""
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):
"""
@summary: receive license packet from PDU layer
@return true when license automata is finish
"""
"""
licPacket = LicPacket()
s.readType(licPacket)
@@ -291,13 +278,12 @@ class LicenseManager(object):
return True
elif licPacket.bMsgtype.value == MessageType.LICENSE_REQUEST:
self._serverRandom = licPacket.licensingMessage.serverRandom.value
self.generateKeys()
self.sendClientNewLicenseRequest()
self.sendClientNewLicenseRequest(licPacket.licensingMessage)
return False
elif licPacket.bMsgtype.value == MessageType.PLATFORM_CHALLENGE:
self._serverEncryptedChallenge = licPacket.licensingMessage.encryptedPlatformChallenge.blobData.value
self.sendClientChallengeResponse()
self.sendClientChallengeResponse(licPacket.licensingMessage)
return False
#yes get a new license
elif licPacket.bMsgtype.value == MessageType.NEW_LICENSE:
@@ -307,35 +293,55 @@ class LicenseManager(object):
raise InvalidExpectedDataException("Not a valid license packet")
def sendClientNewLicenseRequest(self):
def sendClientNewLicenseRequest(self, licenseRequest):
"""
@summary: Create new license request in response to server license request
@param licenseRequest: {ServerLicenseRequest}
@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(self._preMasterSecret + "\x00" * 8)
message.ClientMachineName.blobData = String(self._hostname + "\x00")
message.ClientUserName.blobData = String(self._username + "\x00")
self._transport.sendLicensePacket(LicPacket(message))
#get server information
serverRandom = licenseRequest.serverRandom.value
s = Stream(licenseRequest.serverCertificate.blobData.value)
serverCertificate = gcc.ServerCertificate()
s.readType(serverCertificate)
def sendClientChallengeResponse(self):
#generate crypto values
clientRandom = rsa.random(256)
preMasterSecret = rsa.random(384)
masterSecret = sec.masterSecret(preMasterSecret, clientRandom, serverRandom)
sessionKeyBlob = sec.masterSecret(masterSecret, serverRandom, clientRandom)
self._macSalt = sessionKeyBlob[:16]
self._licenseKey = sec.finalHash(sessionKeyBlob[16:32], clientRandom, serverRandom)
#format message
message = ClientNewLicenseRequest()
message.clientRandom.value = clientRandom
message.encryptedPreMasterSecret.blobData.value = rsa.encrypt(preMasterSecret[::-1], serverCertificate.certData.getPublicKey())[::-1] + "\x00" * 8
message.ClientMachineName.blobData.value = self._hostname + "\x00"
message.ClientUserName.blobData.value = self._username + "\x00"
self._transport.sendFlagged(sec.SecurityFlag.SEC_LICENSE_PKT, LicPacket(message))
def sendClientChallengeResponse(self, platformChallenge):
"""
@summary: generate valid challenge response
@param platformChallenge: {ServerPlatformChallenge}
"""
serverEncryptedChallenge = platformChallenge.encryptedPlatformChallenge.blobData.value
#decrypt server challenge
#it should be TEST word in unicode format
serverChallenge = rc4.crypt(self._licenseKey, self._serverEncryptedChallenge)
serverChallenge = rc4.crypt(rc4.RC4Key(self._licenseKey), serverEncryptedChallenge)
if serverChallenge != "T\x00E\x00S\x00T\x00\x00\x00":
raise InvalidExpectedDataException("bad license server challenge")
#generate hwid
s = Stream()
s.writeType((UInt32Le(2), String(self._hostname + "\x00" * 16)))
s.writeType((UInt32Le(2), String(self._hostname + self._username + "\x00" * 16)))
hwid = s.getvalue()[:20]
message = ClientPLatformChallengeResponse()
message.encryptedPlatformChallengeResponse.blobData.value = self._serverEncryptedChallenge
message.encryptedHWID.blobData.value = rc4.crypt(self._licenseKey, hwid)
message.encryptedPlatformChallengeResponse.blobData.value = serverEncryptedChallenge
message.encryptedHWID.blobData.value = rc4.crypt(rc4.RC4Key(self._licenseKey), hwid)
message.MACData.value = sec.macData(self._macSalt, serverChallenge + hwid)
self._transport.sendLicensePacket(LicPacket(message))
self._transport.sendFlagged(sec.SecurityFlag.SEC_LICENSE_PKT, LicPacket(message))

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -24,24 +24,25 @@ Each channel have a particular role.
The main channel is the graphical channel.
It exist channel for file system order, audio channel, clipboard etc...
"""
from rdpy.network.layer import LayerAutomata, IStreamSender, Layer
from rdpy.network.type import sizeof, Stream, UInt8, UInt16Le, String
from rdpy.base.error import InvalidExpectedDataException, InvalidValue, InvalidSize
from rdpy.core.layer import LayerAutomata, IStreamSender, Layer
from rdpy.core.type import sizeof, Stream, UInt8, UInt16Le, String
from rdpy.core.error import InvalidExpectedDataException, InvalidValue, InvalidSize, CallPureVirtualFuntion
from rdpy.protocol.rdp.ber import writeLength
import rdpy.base.log as log
import rdpy.core.log as log
import ber, gcc, per
import rdpy.security.rsa_wrapper as rsa
class Message(object):
"""
Message type
@summary: Message type
"""
MCS_TYPE_CONNECT_INITIAL = 0x65
MCS_TYPE_CONNECT_RESPONSE = 0x66
class DomainMCSPDU:
"""
Domain MCS PDU header
@summary: Domain MCS PDU header
"""
ERECT_DOMAIN_REQUEST = 1
DISCONNECT_PROVIDER_ULTIMATUM = 8
@@ -54,74 +55,113 @@ class DomainMCSPDU:
class Channel:
"""
Channel id of main channels use in RDP
@summary: Channel id of main channels use in RDP
"""
MCS_GLOBAL_CHANNEL = 1003
MCS_USERCHANNEL_BASE = 1001
class IGCCConfig(object):
"""
@summary: Channel information
"""
def getUserId(self):
"""
@return: {integer} mcs user id
@see: mcs.IGCCConfig
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getUserId", "IGCCConfig"))
def getChannelId(self):
"""
@return: {integer} return channel id of proxy
@see: mcs.IGCCConfig
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getChannelId", "IGCCConfig"))
def getGCCClientSettings(self):
"""
@return: {gcc.Settings} mcs layer gcc client settings
@see: mcs.IGCCConfig
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getGCCClientSettings", "IGCCConfig"))
def getGCCServerSettings(self):
"""
@return: {gcc.Settings} mcs layer gcc server settings
@see: mcs.IGCCConfig
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getGCCServerSettings", "IGCCConfig"))
class MCSLayer(LayerAutomata):
"""
Multiple Channel Service layer
@summary: Multiple Channel Service layer
the main layer of RDP protocol
is why he can do everything and more!
"""
class MCSProxySender(Layer, IStreamSender):
class MCSProxySender(Layer, IStreamSender, IGCCConfig):
"""
Proxy use to set as transport layer for upper channel
@summary: Proxy use to set as transport layer for upper channel
use to abstract channel id for presentation layer
"""
def __init__(self, mcs, channelId):
def __init__(self, presentation, mcs, channelId):
"""
@param mcs: MCS layer use as proxy
@param channelId: channel id for presentation layer
@param presentation: {Layer} presentation layer
@param mcs: {MCSLayer} MCS layer use as proxy
@param channelId: {integer} channel id for presentation layer
"""
Layer.__init__(self, presentation)
self._mcs = mcs
self._channelId = channelId
def send(self, data):
"""
A send proxy function, use channel id and specific
@summary: A send proxy function, use channel id and specific
send function of MCS layer
@param data: {type.Type | Tuple}
"""
self._mcs.send(self._channelId, data)
def close(self):
"""
Close wrapped layer
@summary: Close wrapped layer
"""
self._mcs.close()
def getUserId(self):
"""
@return: mcs user id
@return: {integer} mcs user id
@see: mcs.IGCCConfig
"""
return self._mcs._userId
def getChannelId(self):
"""
@return: return channel id of proxy
@return: {integer} return channel id of proxy
@see: mcs.IGCCConfig
"""
return self._channelId
def getGCCClientSettings(self):
"""
@return: mcs layer gcc client settings
@return: {gcc.Settings} mcs layer gcc client settings
@see: mcs.IGCCConfig
"""
return self._mcs._clientSettings
def getGCCServerSettings(self):
"""
@return: mcs layer gcc server settings
@return: {gcc.Settings} mcs layer gcc server settings
@see: mcs.IGCCConfig
"""
return self._mcs._serverSettings
def __init__(self, presentation, receiveOpcode, sendOpcode, virtualChannels = []):
"""
@param presentation: presentation layer
@param virtualChannels: list additional channels like rdpsnd... [tuple(mcs.ChannelDef, layer)]
@param receiveOpcode: opcode check when receive data
@param sendOpcode: opcode use when send data
@param presentation: {Layer} presentation layer
@param virtualChannels: {Array(Layer]} list additional channels like rdpsnd... [tuple(mcs.ChannelDef, layer)]
@param receiveOpcode: {integer} opcode check when receive data
@param sendOpcode: {integer} opcode use when send data
"""
LayerAutomata.__init__(self, presentation)
self._clientSettings = gcc.clientSettings()
@@ -139,7 +179,7 @@ class MCSLayer(LayerAutomata):
def close(self):
"""
Send disconnect provider ultimatum
@summary: Send disconnect provider ultimatum
"""
self._transport.send((UInt8(self.writeMCSPDUHeader(DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM, 1)),
per.writeEnumerates(0x80), String("\x00" * 6)))
@@ -147,7 +187,7 @@ class MCSLayer(LayerAutomata):
def allChannelConnected(self):
"""
All channels are connected to MCS layer
@summary: All channels are connected to MCS layer
Send connect to upper channel
And prepare MCS layer to receive data
"""
@@ -156,14 +196,13 @@ class MCSLayer(LayerAutomata):
#try connection on all requested channel
for (channelId, layer) in self._channels.iteritems():
#use proxy for each channel
layer._transport = MCSLayer.MCSProxySender(self, channelId)
layer.connect()
MCSLayer.MCSProxySender(layer, self, channelId).connect()
def send(self, channelId, data):
"""
Specific send function for channelId
@param channelId: Channel use to send
@param data: message to send
@summary: Specific send function for channelId
@param channelId: {integer} Channel use to send
@param data: {type.type | tuple} message to send
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(self._sendOpcode)),
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
@@ -173,8 +212,8 @@ class MCSLayer(LayerAutomata):
def recvData(self, data):
"""
Main receive method
@param data: Stream
@summary: Main receive method
@param data: {Stream}
"""
opcode = UInt8()
data.readType(opcode)
@@ -205,13 +244,13 @@ class MCSLayer(LayerAutomata):
def writeDomainParams(self, maxChannels, maxUsers, maxTokens, maxPduSize):
"""
Write a special domain parameter structure
@summary: Write a special domain parameter structure
use in connection sequence
@param maxChannels: number of MCS channel use
@param maxUsers: number of MCS user used (1)
@param maxTokens: unknown
@param maxPduSize: unknown
@return: domain parameter structure
@param maxChannels: {integer} number of MCS channel use
@param maxUsers: {integer} number of MCS user used (1)
@param maxTokens: {integer} unknown
@param maxPduSize: {integer} unknown
@return: {Tuple(type)} domain parameter structure
"""
domainParam = (ber.writeInteger(maxChannels), ber.writeInteger(maxUsers), ber.writeInteger(maxTokens),
ber.writeInteger(1), ber.writeInteger(0), ber.writeInteger(1),
@@ -220,26 +259,27 @@ class MCSLayer(LayerAutomata):
def writeMCSPDUHeader(self, mcsPdu, options = 0):
"""
Write MCS PDU header
@param mcsPdu: PDU code
@param options: option contains in header
@return: UInt8
@summary: Write MCS PDU header
@param mcsPdu: {integer} PDU code
@param options: {integer} option contains in header
@return: {integer}
"""
return (mcsPdu << 2) | options
def readMCSPDUHeader(self, opcode, mcsPdu):
"""
Read mcsPdu header and return options parameter
@param opcode: opcode
@param mcsPdu: mcsPdu will be checked
@return: true if opcode is correct
@summary: Read mcsPdu header and return options parameter
@param opcode: {integer} opcode
@param mcsPdu: {integer} mcsPdu will be checked
@return: {boolean} true if opcode is correct
"""
return (opcode >> 2) == mcsPdu
def readDomainParams(self, s):
"""
Read domain parameters structure
@return: (max_channels, max_users, max_tokens, max_pdu_size)
@summary: Read domain parameters structure
@param s: {Stream}
@return: {Tuple} (max_channels, max_users, max_tokens, max_pdu_size)
"""
if not ber.readUniversalTag(s, ber.Tag.BER_TAG_SEQUENCE, True):
raise InvalidValue("bad BER tags")
@@ -256,12 +296,12 @@ class MCSLayer(LayerAutomata):
class Client(MCSLayer):
"""
Client automata of multiple channel service layer
@summary: Client automata of multiple channel service layer
"""
def __init__(self, presentation, virtualChannels = []):
"""
@param presentation: presentation layer
@param virtualChannels: list additional channels like rdpsnd... [tuple(mcs.ChannelDef, layer)]
@param presentation: {Layer} presentation layer
@param virtualChannels: {Array(Layer)} list additional channels like rdpsnd... [tuple(mcs.ChannelDef, layer)]
"""
MCSLayer.__init__(self, presentation, DomainMCSPDU.SEND_DATA_INDICATION, DomainMCSPDU.SEND_DATA_REQUEST, virtualChannels)
#use to know state of static channel
@@ -272,13 +312,13 @@ class Client(MCSLayer):
def connect(self):
"""
Connect message in client automata case
@summary: Connect message in client automata case
Send ConnectInitial
Wait ConnectResponse
"""
self._clientSettings.getBlock(gcc.MessageType.CS_CORE).serverSelectedProtocol.value = self._transport._selectedProtocol
self._clientSettings.CS_CORE.serverSelectedProtocol.value = self._transport._selectedProtocol
#ask for virtual channel
self._clientSettings.getBlock(gcc.MessageType.CS_NET).channelDefArray._array = [x for (x, _) in self._virtualChannels]
self._clientSettings.CS_NET.channelDefArray._array = [x for (x, _) in self._virtualChannels]
#send connect initial
self.sendConnectInitial()
#next wait response
@@ -286,7 +326,7 @@ class Client(MCSLayer):
def connectNextChannel(self):
"""
Send sendChannelJoinRequest message on next disconnect channel
@summary: Send sendChannelJoinRequest message on next disconnect channel
Send channel request or connect upper layer if all channels are connected
Wait channel confirm
"""
@@ -305,7 +345,7 @@ class Client(MCSLayer):
#static virtual channel
if self._nbChannelRequested < self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelCount.value:
channelId = self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelIdArray._array[self._nbChannelRequested]
channelId = self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelIdArray[self._nbChannelRequested]
self._nbChannelRequested += 1
self.sendChannelJoinRequest(channelId)
return
@@ -314,11 +354,11 @@ class Client(MCSLayer):
def recvConnectResponse(self, data):
"""
Receive MCS connect response from server
@summary: Receive MCS connect response from server
Send Erect domain Request
Send Attach User Request
Wait Attach User Confirm
@param data: Stream
@param data: {Stream}
"""
ber.readApplicationTag(data, UInt8(Message.MCS_TYPE_CONNECT_RESPONSE))
ber.readEnumerated(data)
@@ -340,9 +380,9 @@ class Client(MCSLayer):
def recvAttachUserConfirm(self, data):
"""
Receive an attach user confirm
@summary: Receive an attach user confirm
Send Connect Channel
@param data: Stream
@param data: {Stream}
"""
opcode = UInt8()
data.readType(opcode)
@@ -359,9 +399,9 @@ class Client(MCSLayer):
def recvChannelJoinConfirm(self, data):
"""
Receive a channel join confirm from server
@summary: Receive a channel join confirm from server
client automata function
@param data: Stream
@param data: {Stream}
"""
opcode = UInt8()
data.readType(opcode)
@@ -383,14 +423,14 @@ class Client(MCSLayer):
if confirm == 0:
serverNet = self._serverSettings.getBlock(gcc.MessageType.SC_NET)
for i in range(0, serverNet.channelCount.value):
if channelId == serverNet.channelIdArray._array[i].value:
if channelId == serverNet.channelIdArray[i].value:
self._channels[channelId] = self._virtualChannels[i][1]
self.connectNextChannel()
def sendConnectInitial(self):
"""
Send connect initial packet
@summary: Send connect initial packet
client automata function
"""
ccReq = gcc.writeConferenceCreateRequest(self._clientSettings)
@@ -406,7 +446,7 @@ class Client(MCSLayer):
def sendErectDomainRequest(self):
"""
Send a formated erect domain request for RDP connection
@summary: Send a formated erect domain request for RDP connection
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ERECT_DOMAIN_REQUEST)),
per.writeInteger(0),
@@ -414,15 +454,15 @@ class Client(MCSLayer):
def sendAttachUserRequest(self):
"""
Send a formated attach user request for RDP connection
@summary: Send a formated attach user request for RDP connection
"""
self._transport.send(self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ATTACH_USER_REQUEST)))
def sendChannelJoinRequest(self, channelId):
"""
Send a formated Channel join request from client to server
@summary: Send a formated Channel join request from client to server
client automata function
@param channelId: id of channel requested
@param channelId: {integer} id of channel requested
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.CHANNEL_JOIN_REQUEST)),
per.writeInteger16(self._userId, Channel.MCS_USERCHANNEL_BASE),
@@ -430,12 +470,12 @@ class Client(MCSLayer):
class Server(MCSLayer):
"""
Server automata of multiple channel service layer
@summary: Server automata of multiple channel service layer
"""
def __init__(self, presentation, virtualChannels = []):
"""
@param presentation: presentation layer
@param virtualChannels: list additional channels like rdpsnd... [tuple(mcs.ChannelDef, layer)]
@param presentation: {Layer} presentation layer
@param virtualChannels: {List(Layer)} list additional channels like rdpsnd... [tuple(mcs.ChannelDef, layer)]
"""
MCSLayer.__init__(self, presentation, DomainMCSPDU.SEND_DATA_REQUEST, DomainMCSPDU.SEND_DATA_INDICATION, virtualChannels)
#nb channel requested
@@ -443,18 +483,26 @@ class Server(MCSLayer):
def connect(self):
"""
Connect message for server automata
@summary: Connect message for server automata
Wait Connect Initial
"""
self._serverSettings.getBlock(gcc.MessageType.SC_CORE).clientRequestedProtocol.value = self._transport._requestedProtocol
#basic rdp security layer
if self._transport._selectedProtocol == 0:
self._serverSettings.SC_SECURITY.encryptionMethod.value = gcc.EncryptionMethod.ENCRYPTION_FLAG_128BIT
self._serverSettings.SC_SECURITY.encryptionLevel.value = gcc.EncryptionLevel.ENCRYPTION_LEVEL_HIGH
self._serverSettings.SC_SECURITY.serverRandom.value = rsa.random(256)
self._serverSettings.SC_SECURITY.serverCertificate = self._presentation.getCertificate()
self._serverSettings.SC_CORE.clientRequestedProtocol.value = self._transport._requestedProtocol
self.setNextState(self.recvConnectInitial)
def recvConnectInitial(self, data):
"""
Receive MCS connect initial from client
@summary: Receive MCS connect initial from client
Send Connect Response
Wait Erect Domain Request
@param data: Stream
@param data: {Stream}
"""
ber.readApplicationTag(data, UInt8(Message.MCS_TYPE_CONNECT_INITIAL))
ber.readOctetString(data)
@@ -482,9 +530,9 @@ class Server(MCSLayer):
def recvErectDomainRequest(self, data):
"""
Receive erect domain request
@summary: Receive erect domain request
Wait Attach User Request
@param data: Stream
@param data: {Stream}
"""
opcode = UInt8()
data.readType(opcode)
@@ -499,10 +547,10 @@ class Server(MCSLayer):
def recvAttachUserRequest(self, data):
"""
Receive Attach user request
@summary: Receive Attach user request
Send Attach User Confirm
Wait Channel Join Request
@param data: Stream
@param data: {Stream}
"""
opcode = UInt8()
data.readType(opcode)
@@ -515,9 +563,9 @@ class Server(MCSLayer):
def recvChannelJoinRequest(self, data):
"""
Receive for each client channel a request
@summary: Receive for each client channel a request
Send Channel Join Confirm or Connect upper layer when all channel are joined
@param data: Stream
@param data: {Stream}
"""
opcode = UInt8()
@@ -540,7 +588,7 @@ class Server(MCSLayer):
def sendConnectResponse(self):
"""
Send connect response
@summary: Send connect response
"""
ccReq = gcc.writeConferenceCreateResponse(self._serverSettings)
ccReqStream = Stream()
@@ -552,7 +600,7 @@ class Server(MCSLayer):
def sendAttachUserConfirm(self):
"""
Send attach user confirm
@summary: Send attach user confirm
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.ATTACH_USER_CONFIRM), 2),
per.writeEnumerates(0),
@@ -560,9 +608,9 @@ class Server(MCSLayer):
def sendChannelJoinConfirm(self, channelId, confirm):
"""
Send a confirm channel (or not) to client
@param channelId: id of channel
@param confirm: connection state
@summary: Send a confirm channel (or not) to client
@param channelId: {integer} id of channel
@param confirm: {boolean} connection state
"""
self._transport.send((self.writeMCSPDUHeader(UInt8(DomainMCSPDU.CHANNEL_JOIN_CONFIRM), 2),
per.writeEnumerates(int(confirm)),

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -16,19 +16,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from rdpy.base.error import InvalidExpectedDataException
import rdpy.base.log as log
from rdpy.core.error import InvalidExpectedDataException
import rdpy.core.log as log
"""
Definition of structure use for capabilities nego
Use in PDU layer
"""
from rdpy.network.type import CompositeType, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
from rdpy.core.type import CompositeType, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
class CapsType(object):
"""
Different type of capabilities
@summary: Different type of capabilities
@see: http://msdn.microsoft.com/en-us/library/cc240486.aspx
"""
CAPSTYPE_GENERAL = 0x0001
@@ -62,7 +62,7 @@ class CapsType(object):
class MajorType(object):
"""
Use in general capability
@summary: Use in general capability
@see: http://msdn.microsoft.com/en-us/library/cc240549.aspx
"""
OSMAJORTYPE_UNSPECIFIED = 0x0000
@@ -76,7 +76,7 @@ class MajorType(object):
class MinorType(object):
"""
Use in general capability
@summary: Use in general capability
@see: http://msdn.microsoft.com/en-us/library/cc240549.aspx
"""
OSMINORTYPE_UNSPECIFIED = 0x0000
@@ -92,7 +92,7 @@ class MinorType(object):
class GeneralExtraFlag(object):
"""
Use in general capability
@summary: Use in general capability
@see: http://msdn.microsoft.com/en-us/library/cc240549.aspx
"""
FASTPATH_OUTPUT_SUPPORTED = 0x0001
@@ -107,7 +107,7 @@ class Boolean(object):
class OrderFlag(object):
"""
Use in order capability
@summary: Use in order capability
@see: http://msdn.microsoft.com/en-us/library/cc240556.aspx
"""
NEGOTIATEORDERSUPPORT = 0x0002
@@ -118,7 +118,7 @@ class OrderFlag(object):
class Order(object):
"""
Drawing orders supported
@summary: Drawing orders supported
Use in order capability
@see: http://msdn.microsoft.com/en-us/library/cc240556.aspx
"""
@@ -146,7 +146,7 @@ class Order(object):
class OrderEx(object):
"""
Extension orders
@summary: Extension orders
Use in order capability
"""
ORDERFLAGS_EX_CACHE_BITMAP_REV3_SUPPORT = 0x0002
@@ -154,7 +154,7 @@ class OrderEx(object):
class InputFlags(object):
"""
Input flag use in input capability
@summary: Input flag use in input capability
@see: http://msdn.microsoft.com/en-us/library/cc240563.aspx
"""
INPUT_FLAG_SCANCODES = 0x0001
@@ -168,7 +168,7 @@ class InputFlags(object):
class BrushSupport(object):
"""
Brush support of client
@summary: Brush support of client
@see: http://msdn.microsoft.com/en-us/library/cc240564.aspx
"""
BRUSH_DEFAULT = 0x00000000
@@ -177,7 +177,7 @@ class BrushSupport(object):
class GlyphSupport(object):
"""
Use by glyph order
@summary: Use by glyph order
@see: http://msdn.microsoft.com/en-us/library/cc240565.aspx
"""
GLYPH_SUPPORT_NONE = 0x0000
@@ -187,7 +187,7 @@ class GlyphSupport(object):
class OffscreenSupportLevel(object):
"""
Use to determine offscreen cache level supported
@summary: Use to determine offscreen cache level supported
@see: http://msdn.microsoft.com/en-us/library/cc240550.aspx
"""
FALSE = 0x00000000
@@ -195,7 +195,7 @@ class OffscreenSupportLevel(object):
class VirtualChannelCompressionFlag(object):
"""
Use to determine virtual channel compression
@summary: Use to determine virtual channel compression
@see: http://msdn.microsoft.com/en-us/library/cc240551.aspx
"""
VCCAPS_NO_COMPR = 0x00000000
@@ -204,7 +204,7 @@ class VirtualChannelCompressionFlag(object):
class SoundFlag(object):
"""
Use in sound capability to inform it
@summary: Use in sound capability to inform it
@see: http://msdn.microsoft.com/en-us/library/cc240552.aspx
"""
NONE = 0x0000
@@ -212,7 +212,7 @@ class SoundFlag(object):
class CacheEntry(CompositeType):
"""
Use in capability cache exchange
@summary: Use in capability cache exchange
@see: http://msdn.microsoft.com/en-us/library/cc240566.aspx
"""
def __init__(self):
@@ -223,7 +223,7 @@ class CacheEntry(CompositeType):
class Capability(CompositeType):
"""
A capability
@summary: A capability
@see: http://msdn.microsoft.com/en-us/library/cc240486.aspx
"""
def __init__(self, capability = None):
@@ -235,8 +235,8 @@ class Capability(CompositeType):
"""
Closure for capability factory
"""
for c in [GeneralCapability, BitmapCapability, OrderCapability, BitmapCacheCapability, PointerCapability, InputCapability, BrushCapability, GlyphCapability, OffscreenBitmapCacheCapability, VirtualChannelCapability, SoundCapability, ControlCapability, WindowActivationCapability, FontCapability, ColorCacheCapability, ShareCapability]:
if self.capabilitySetType.value == c._TYPE_:
for c in [GeneralCapability, BitmapCapability, OrderCapability, BitmapCacheCapability, PointerCapability, InputCapability, BrushCapability, GlyphCapability, OffscreenBitmapCacheCapability, VirtualChannelCapability, SoundCapability, ControlCapability, WindowActivationCapability, FontCapability, ColorCacheCapability, ShareCapability, MultiFragmentUpdate]:
if self.capabilitySetType.value == c._TYPE_ and (self.lengthCapability.value - 4) > 0:
return c(readLen = self.lengthCapability - 4)
log.debug("unknown Capability type : %s"%hex(self.capabilitySetType.value))
#read entire packet
@@ -251,7 +251,7 @@ class Capability(CompositeType):
class GeneralCapability(CompositeType):
"""
General capability (protocol version and compression mode)
@summary: General capability (protocol version and compression mode)
client -> server
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240549.aspx
@@ -274,7 +274,7 @@ class GeneralCapability(CompositeType):
class BitmapCapability(CompositeType):
"""
Bitmap format Capability
@summary: Bitmap format Capability
client -> server
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240554.aspx
@@ -299,7 +299,7 @@ class BitmapCapability(CompositeType):
class OrderCapability(CompositeType):
"""
Order capability list all drawing order supported
@summary: Order capability list all drawing order supported
client -> server
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240556.aspx
@@ -328,7 +328,7 @@ class OrderCapability(CompositeType):
class BitmapCacheCapability(CompositeType):
"""
Order use to cache bitmap very useful
@summary: Order use to cache bitmap very useful
client -> server
@see: http://msdn.microsoft.com/en-us/library/cc240559.aspx
"""
@@ -351,7 +351,7 @@ class BitmapCacheCapability(CompositeType):
class PointerCapability(CompositeType):
"""
Use to indicate pointer handle of client
@summary: Use to indicate pointer handle of client
Paint by server or per client
client -> server
server -> client
@@ -359,15 +359,16 @@ class PointerCapability(CompositeType):
"""
_TYPE_ = CapsType.CAPSTYPE_POINTER
def __init__(self, readLen = None):
def __init__(self, isServer = False, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.colorPointerFlag = UInt16Le()
self.colorPointerCacheSize = UInt16Le()
self.pointerCacheSize = UInt16Le()
self.colorPointerCacheSize = UInt16Le(20)
#old version of rdp doesn't support ...
self.pointerCacheSize = UInt16Le(conditional = lambda:isServer)
class InputCapability(CompositeType):
"""
Use to indicate input capabilities
@summary: Use to indicate input capabilities
client -> server
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240563.aspx
@@ -391,7 +392,7 @@ class InputCapability(CompositeType):
class BrushCapability(CompositeType):
"""
Use to indicate brush capability
@summary: Use to indicate brush capability
client -> server
@see: http://msdn.microsoft.com/en-us/library/cc240564.aspx
"""
@@ -403,7 +404,7 @@ class BrushCapability(CompositeType):
class GlyphCapability(CompositeType):
"""
Use in font order
@summary: Use in font order
client -> server
@see: http://msdn.microsoft.com/en-us/library/cc240565.aspx
"""
@@ -419,7 +420,7 @@ class GlyphCapability(CompositeType):
class OffscreenBitmapCacheCapability(CompositeType):
"""
use to cached bitmap in offscreen area
@summary: use to cached bitmap in offscreen area
client -> server
@see: http://msdn.microsoft.com/en-us/library/cc240550.aspx
"""
@@ -433,7 +434,7 @@ class OffscreenBitmapCacheCapability(CompositeType):
class VirtualChannelCapability(CompositeType):
"""
use to determine virtual channel compression
@summary: use to determine virtual channel compression
client -> server
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240551.aspx
@@ -447,7 +448,7 @@ class VirtualChannelCapability(CompositeType):
class SoundCapability(CompositeType):
"""
Use to exchange sound capability
@summary: Use to exchange sound capability
client -> server
@see: http://msdn.microsoft.com/en-us/library/cc240552.aspx
"""
@@ -460,7 +461,7 @@ class SoundCapability(CompositeType):
class ControlCapability(CompositeType):
"""
client -> server but server ignore contents! Thanks krosoft for brandwidth
@summary: client -> server but server ignore contents! Thanks krosoft for brandwidth
@see: http://msdn.microsoft.com/en-us/library/cc240568.aspx
"""
_TYPE_ = CapsType.CAPSTYPE_CONTROL
@@ -474,7 +475,7 @@ class ControlCapability(CompositeType):
class WindowActivationCapability(CompositeType):
"""
client -> server but server ignore contents! Thanks krosoft for brandwidth
@summary: client -> server but server ignore contents! Thanks krosoft for brandwidth
@see: http://msdn.microsoft.com/en-us/library/cc240569.aspx
"""
_TYPE_ = CapsType.CAPSTYPE_ACTIVATION
@@ -488,7 +489,7 @@ class WindowActivationCapability(CompositeType):
class FontCapability(CompositeType):
"""
Use to indicate font support
@summary: Use to indicate font support
client -> server
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240571.aspx
@@ -515,7 +516,7 @@ class ColorCacheCapability(CompositeType):
class ShareCapability(CompositeType):
"""
Use to advertise channel id of server
@summary: Use to advertise channel id of server
client -> server
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240570.aspx
@@ -525,4 +526,17 @@ class ShareCapability(CompositeType):
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.nodeId = UInt16Le()
self.pad2octets = UInt16Le()
self.pad2octets = UInt16Le()
class MultiFragmentUpdate(CompositeType):
"""
@summary: Use to advertise fast path max buffer to use
client -> server
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240649.aspx
"""
_TYPE_ = CapsType.CAPSETTYPE_MULTIFRAGMENTUPDATE
def __init__(self, readLen = None):
CompositeType.__init__(self, readLen = readLen)
self.MaxRequestSize = UInt32Le(0)

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -22,80 +22,14 @@ Implement the main graphic layer
In this layer are managed all mains bitmap update orders end user inputs
"""
from rdpy.network.type import CompositeType, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
from rdpy.base.error import InvalidExpectedDataException
import rdpy.base.log as log
from rdpy.core.type import CompositeType, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType
from rdpy.core.error import InvalidExpectedDataException
import rdpy.core.log as log
import caps, order
class SecurityFlag(object):
"""
Microsoft security flags
@see: http://msdn.microsoft.com/en-us/library/cc240579.aspx
"""
SEC_EXCHANGE_PKT = 0x0001
SEC_TRANSPORT_REQ = 0x0002
RDP_SEC_TRANSPORT_RSP = 0x0004
SEC_ENCRYPT = 0x0008
SEC_RESET_SEQNO = 0x0010
SEC_IGNORE_SEQNO = 0x0020
SEC_INFO_PKT = 0x0040
SEC_LICENSE_PKT = 0x0080
SEC_LICENSE_ENCRYPT_CS = 0x0200
SEC_LICENSE_ENCRYPT_SC = 0x0200
SEC_REDIRECTION_PKT = 0x0400
SEC_SECURE_CHECKSUM = 0x0800
SEC_AUTODETECT_REQ = 0x1000
SEC_AUTODETECT_RSP = 0x2000
SEC_HEARTBEAT = 0x4000
SEC_FLAGSHI_VALID = 0x8000
class InfoFlag(object):
"""
Client capabilities informations
"""
INFO_MOUSE = 0x00000001
INFO_DISABLECTRLALTDEL = 0x00000002
INFO_AUTOLOGON = 0x00000008
INFO_UNICODE = 0x00000010
INFO_MAXIMIZESHELL = 0x00000020
INFO_LOGONNOTIFY = 0x00000040
INFO_COMPRESSION = 0x00000080
INFO_ENABLEWINDOWSKEY = 0x00000100
INFO_REMOTECONSOLEAUDIO = 0x00002000
INFO_FORCE_ENCRYPTED_CS_PDU = 0x00004000
INFO_RAIL = 0x00008000
INFO_LOGONERRORS = 0x00010000
INFO_MOUSE_HAS_WHEEL = 0x00020000
INFO_PASSWORD_IS_SC_PIN = 0x00040000
INFO_NOAUDIOPLAYBACK = 0x00080000
INFO_USING_SAVED_CREDS = 0x00100000
INFO_AUDIOCAPTURE = 0x00200000
INFO_VIDEO_DISABLE = 0x00400000
INFO_CompressionTypeMask = 0x00001E00
class PerfFlag(object):
"""
Network performances flag
"""
PERF_DISABLE_WALLPAPER = 0x00000001
PERF_DISABLE_FULLWINDOWDRAG = 0x00000002
PERF_DISABLE_MENUANIMATIONS = 0x00000004
PERF_DISABLE_THEMING = 0x00000008
PERF_DISABLE_CURSOR_SHADOW = 0x00000020
PERF_DISABLE_CURSORSETTINGS = 0x00000040
PERF_ENABLE_FONT_SMOOTHING = 0x00000080
PERF_ENABLE_DESKTOP_COMPOSITION = 0x00000100
class AfInet(object):
"""
IPv4 or IPv6 adress style
"""
AF_INET = 0x00002
AF_INET6 = 0x0017
class PDUType(object):
"""
Data PDU type primary index
@summary: Data PDU type primary index
@see: http://msdn.microsoft.com/en-us/library/cc240576.aspx
"""
PDUTYPE_DEMANDACTIVEPDU = 0x11
@@ -106,7 +40,7 @@ class PDUType(object):
class PDUType2(object):
"""
Data PDU type secondary index
@summary: Data PDU type secondary index
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
"""
PDUTYPE2_UPDATE = 0x02
@@ -136,7 +70,7 @@ class PDUType2(object):
class StreamId(object):
"""
Stream priority
@summary: Stream priority
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
"""
STREAM_UNDEFINED = 0x00
@@ -146,7 +80,7 @@ class StreamId(object):
class CompressionOrder(object):
"""
PDU compression order
@summary: PDU compression order
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
"""
CompressionTypeMask = 0x0F
@@ -156,7 +90,7 @@ class CompressionOrder(object):
class CompressionType(object):
"""
PDU compression type
@summary: PDU compression type
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
"""
PACKET_COMPR_TYPE_8K = 0x0
@@ -166,7 +100,7 @@ class CompressionType(object):
class Action(object):
"""
Action flag use in Control PDU packet
@summary: Action flag use in Control PDU packet
@see: http://msdn.microsoft.com/en-us/library/cc240492.aspx
"""
CTRLACTION_REQUEST_CONTROL = 0x0001
@@ -176,7 +110,7 @@ class Action(object):
class PersistentKeyListFlag(object):
"""
Use to determine the number of persistent key packet
@summary: Use to determine the number of persistent key packet
@see: http://msdn.microsoft.com/en-us/library/cc240495.aspx
"""
PERSIST_FIRST_PDU = 0x01
@@ -184,7 +118,7 @@ class PersistentKeyListFlag(object):
class BitmapFlag(object):
"""
Use in bitmap update PDU
@summary: Use in bitmap update PDU
@see: http://msdn.microsoft.com/en-us/library/cc240612.aspx
"""
BITMAP_COMPRESSION = 0x0001
@@ -192,7 +126,7 @@ class BitmapFlag(object):
class UpdateType(object):
"""
Use in update PDU to determine which type of update
@summary: Use in update PDU to determine which type of update
@see: http://msdn.microsoft.com/en-us/library/cc240608.aspx
"""
UPDATETYPE_ORDERS = 0x0000
@@ -202,7 +136,7 @@ class UpdateType(object):
class InputMessageType(object):
"""
Use in slow-path input PDU
@summary: Use in slow-path input PDU
@see: http://msdn.microsoft.com/en-us/library/cc240583.aspx
"""
INPUT_EVENT_SYNC = 0x0000
@@ -214,7 +148,7 @@ class InputMessageType(object):
class PointerFlag(object):
"""
Use in Pointer event
@summary: Use in Pointer event
@see: http://msdn.microsoft.com/en-us/library/cc240586.aspx
"""
PTRFLAGS_HWHEEL = 0x0400
@@ -229,7 +163,7 @@ class PointerFlag(object):
class KeyboardFlag(object):
"""
Use in scan code key event
@summary: Use in scan code key event
@see: http://msdn.microsoft.com/en-us/library/cc240584.aspx
"""
KBDFLAGS_EXTENDED = 0x0100
@@ -238,7 +172,7 @@ class KeyboardFlag(object):
class FastPathUpdateType(object):
"""
Use in Fast Path update packet
@summary: Use in Fast Path update packet
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
"""
FASTPATH_UPDATETYPE_ORDERS = 0x0
@@ -255,14 +189,14 @@ class FastPathUpdateType(object):
class FastPathOutputCompression(object):
"""
Flag for compression
@summary: Flag for compression
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
"""
FASTPATH_OUTPUT_COMPRESSION_USED = 0x2
class Display(object):
"""
Use in supress output PDU
@summary: Use in supress output PDU
@see: http://msdn.microsoft.com/en-us/library/cc240648.aspx
"""
SUPPRESS_DISPLAY_UPDATES = 0x00
@@ -270,7 +204,7 @@ class Display(object):
class ErrorInfo(object):
"""
Error code use in Error info PDU
@summary: Error code use in Error info PDU
@see: http://msdn.microsoft.com/en-us/library/cc240544.aspx
"""
ERRINFO_RPC_INITIATED_DISCONNECT = 0x00000001
@@ -479,57 +413,16 @@ 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).",
}
class RDPInfo(CompositeType):
"""
Client informations
Contains credentials (very important packet)
@see: http://msdn.microsoft.com/en-us/library/cc240475.aspx
"""
def __init__(self, extendedInfoConditional):
CompositeType.__init__(self)
#code page
self.codePage = UInt32Le()
#support flag
self.flag = UInt32Le(InfoFlag.INFO_MOUSE | InfoFlag.INFO_UNICODE | InfoFlag.INFO_LOGONNOTIFY | InfoFlag.INFO_LOGONERRORS)
self.cbDomain = UInt16Le(lambda:sizeof(self.domain) - 2)
self.cbUserName = UInt16Le(lambda:sizeof(self.userName) - 2)
self.cbPassword = UInt16Le(lambda:sizeof(self.password) - 2)
self.cbAlternateShell = UInt16Le(lambda:sizeof(self.alternateShell) - 2)
self.cbWorkingDir = UInt16Le(lambda:sizeof(self.workingDir) - 2)
#microsoft domain
self.domain = String(readLen = UInt16Le(lambda:self.cbDomain.value + 2), unicode = True)
self.userName = String(readLen = UInt16Le(lambda:self.cbUserName.value + 2), unicode = True)
self.password = String(readLen = UInt16Le(lambda:self.cbPassword.value + 2), unicode = True)
#shell execute at start of session
self.alternateShell = String(readLen = UInt16Le(lambda:self.cbAlternateShell.value + 2), unicode = True)
#working directory for session
self.workingDir = String(readLen = UInt16Le(lambda:self.cbWorkingDir.value + 2), unicode = True)
self.extendedInfo = RDPExtendedInfo(conditional = extendedInfoConditional)
class RDPExtendedInfo(CompositeType):
"""
Add more client informations
"""
def __init__(self, conditional):
CompositeType.__init__(self, conditional = conditional)
self.clientAddressFamily = UInt16Le(AfInet.AF_INET)
self.cbClientAddress = UInt16Le(lambda:sizeof(self.clientAddress))
self.clientAddress = String(readLen = self.cbClientAddress, unicode = True)
self.cbClientDir = UInt16Le(lambda:sizeof(self.clientDir))
self.clientDir = String(readLen = self.cbClientDir, unicode = True)
#TODO make tiomezone
self.clientTimeZone = String("\x00" * 172)
self.clientSessionId = UInt32Le()
self.performanceFlags = UInt32Le()
class ShareControlHeader(CompositeType):
"""
PDU share control header
@summary: PDU share control header
@see: http://msdn.microsoft.com/en-us/library/cc240576.aspx
"""
def __init__(self, totalLength, pduType, userId):
"""
Set pduType as constant
@summary: Set pduType as constant
@param totalLength: total length of PDU packet
"""
CompositeType.__init__(self)
@@ -540,7 +433,7 @@ class ShareControlHeader(CompositeType):
class ShareDataHeader(CompositeType):
"""
PDU share data header
@summary: PDU share data header
@see: http://msdn.microsoft.com/en-us/library/cc240577.aspx
"""
def __init__(self, size, pduType2 = 0, shareId = 0):
@@ -555,7 +448,7 @@ class ShareDataHeader(CompositeType):
class PDU(CompositeType):
"""
Main PDU message
@summary: Main PDU message
"""
def __init__(self, userId = 0, pduMessage = None):
CompositeType.__init__(self)
@@ -563,7 +456,7 @@ class PDU(CompositeType):
def PDUMessageFactory():
"""
build message in accordance of type self.shareControlHeader.pduType.value
@summary: build message in accordance of type self.shareControlHeader.pduType.value
"""
for c in [DemandActivePDU, ConfirmActivePDU, DataPDU, DeactiveAllPDU]:
if self.shareControlHeader.pduType.value == c._PDUTYPE_:
@@ -582,7 +475,7 @@ class PDU(CompositeType):
class DemandActivePDU(CompositeType):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240485.aspx
Main use for capabilities exchange server -> client
@summary: Main use for capabilities exchange server -> client
"""
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_DEMANDACTIVEPDU
@@ -601,7 +494,7 @@ class DemandActivePDU(CompositeType):
class ConfirmActivePDU(CompositeType):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240488.aspx
Main use for capabilities confirm client -> sever
@summary: Main use for capabilities confirm client -> sever
"""
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_CONFIRMACTIVEPDU
@@ -619,7 +512,7 @@ class ConfirmActivePDU(CompositeType):
class DeactiveAllPDU(CompositeType):
"""
Use to signal already connected session
@summary: Use to signal already connected session
@see: http://msdn.microsoft.com/en-us/library/cc240536.aspx
"""
#may declare the PDU type
@@ -633,7 +526,7 @@ class DeactiveAllPDU(CompositeType):
class DataPDU(CompositeType):
"""
Generic PDU packet use after connection sequence
@summary: Generic PDU packet use after connection sequence
"""
#may declare the PDU type
_PDUTYPE_ = PDUType.PDUTYPE_DATAPDU
@@ -644,7 +537,7 @@ class DataPDU(CompositeType):
def PDUDataFactory():
"""
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]:
if self.shareDataHeader.pduType2.value == c._PDUTYPE2_:
@@ -691,7 +584,7 @@ class ControlDataPDU(CompositeType):
class ErrorInfoDataPDU(CompositeType):
"""
Use to inform error in PDU layer
@summary: Use to inform error in PDU layer
@see: http://msdn.microsoft.com/en-us/library/cc240544.aspx
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU
@@ -707,7 +600,7 @@ class ErrorInfoDataPDU(CompositeType):
class FontListDataPDU(CompositeType):
"""
Use to indicate list of font. Deprecated packet
@summary: Use to indicate list of font. Deprecated packet
client -> server
@see: http://msdn.microsoft.com/en-us/library/cc240498.aspx
"""
@@ -725,7 +618,7 @@ class FontListDataPDU(CompositeType):
class FontMapDataPDU(CompositeType):
"""
Use to indicate map of font. Deprecated packet (maybe the same as FontListDataPDU)
@summary: Use to indicate map of font. Deprecated packet (maybe the same as FontListDataPDU)
server -> client
@see: http://msdn.microsoft.com/en-us/library/cc240498.aspx
"""
@@ -743,7 +636,7 @@ class FontMapDataPDU(CompositeType):
class PersistentListEntry(CompositeType):
"""
Use to record persistent key in PersistentListPDU
@summary: Use to record persistent key in PersistentListPDU
@see: http://msdn.microsoft.com/en-us/library/cc240496.aspx
"""
def __init__(self):
@@ -753,7 +646,7 @@ class PersistentListEntry(CompositeType):
class PersistentListPDU(CompositeType):
"""
Use to indicate that bitmap cache was already
@summary: Use to indicate that bitmap cache was already
Fill with some keys from previous session
@see: http://msdn.microsoft.com/en-us/library/cc240495.aspx
"""
@@ -778,7 +671,7 @@ class PersistentListPDU(CompositeType):
class ClientInputEventPDU(CompositeType):
"""
PDU use to send client inputs in slow path mode
@summary: PDU use to send client inputs in slow path mode
@see: http://msdn.microsoft.com/en-us/library/cc746160.aspx
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_INPUT
@@ -791,7 +684,7 @@ class ClientInputEventPDU(CompositeType):
class ShutdownRequestPDU(CompositeType):
"""
PDU use to signal that the session will be closed
@summary: PDU use to signal that the session will be closed
client -> server
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_REQUEST
@@ -800,7 +693,7 @@ class ShutdownRequestPDU(CompositeType):
class ShutdownDeniedPDU(CompositeType):
"""
PDU use to signal which the session will be closed is connected
@summary: PDU use to signal which the session will be closed is connected
server -> client
"""
_PDUTYPE2_ = PDUType2.PDUTYPE2_SHUTDOWN_DENIED
@@ -844,7 +737,7 @@ class RefreshRectPDU(CompositeType):
class UpdateDataPDU(CompositeType):
"""
Update data PDU use by server to inform update image or palet
@summary: Update data PDU use by server to inform update image or palet
for example
@see: http://msdn.microsoft.com/en-us/library/cc240608.aspx
"""
@@ -861,7 +754,7 @@ class UpdateDataPDU(CompositeType):
def UpdateDataFactory():
"""
Create object in accordance self.updateType value
@summary: Create object in accordance self.updateType value
"""
for c in [BitmapUpdateDataPDU]:
if self.updateType.value == c._UPDATE_TYPE_:
@@ -878,7 +771,7 @@ class UpdateDataPDU(CompositeType):
class FastPathUpdatePDU(CompositeType):
"""
Fast path update PDU packet
@summary: Fast path update PDU packet
@see: http://msdn.microsoft.com/en-us/library/cc240622.aspx
"""
def __init__(self, updateData = None):
@@ -889,7 +782,7 @@ class FastPathUpdatePDU(CompositeType):
def UpdateDataFactory():
"""
Create correct object in accordance to self.updateHeader field
@summary: Create correct object in accordance to self.updateHeader field
"""
for c in [FastPathBitmapUpdateDataPDU]:
if (self.updateHeader.value & 0xf) == c._FASTPATH_UPDATE_TYPE_:
@@ -906,7 +799,7 @@ class FastPathUpdatePDU(CompositeType):
class BitmapUpdateDataPDU(CompositeType):
"""
PDU use to send raw bitmap compressed or not
@summary: PDU use to send raw bitmap compressed or not
@see: http://msdn.microsoft.com/en-us/library/dd306368.aspx
"""
_UPDATE_TYPE_ = UpdateType.UPDATETYPE_BITMAP
@@ -921,7 +814,7 @@ class BitmapUpdateDataPDU(CompositeType):
class OrderUpdateDataPDU(CompositeType):
"""
PDU type use to communicate Accelerated order (GDI)
@summary: PDU type use to communicate Accelerated order (GDI)
@see: http://msdn.microsoft.com/en-us/library/cc241571.aspx
@todo: not implemented yet but need it
"""
@@ -934,7 +827,7 @@ class OrderUpdateDataPDU(CompositeType):
class BitmapCompressedDataHeader(CompositeType):
"""
Compressed header of bitmap
@summary: Compressed header of bitmap
@see: http://msdn.microsoft.com/en-us/library/cc240644.aspx
"""
def __init__(self, bodySize = 0, scanWidth = 0, uncompressedSize = 0, conditional = lambda:True):
@@ -953,7 +846,7 @@ class BitmapCompressedDataHeader(CompositeType):
class BitmapData(CompositeType):
"""
Bitmap data here the screen capture
@summary: Bitmap data here the screen capture
"""
def __init__(self, destLeft = 0, destTop = 0, destRight = 0, destBottom = 0, width = 0, height = 0, bitsPerPixel = 0, bitmapDataStream = ""):
"""
@@ -981,7 +874,7 @@ class BitmapData(CompositeType):
class FastPathBitmapUpdateDataPDU(CompositeType):
"""
Fast path version of bitmap update PDU
@summary: Fast path version of bitmap update PDU
@see: http://msdn.microsoft.com/en-us/library/dd306368.aspx
"""
_FASTPATH_UPDATE_TYPE_ = FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP
@@ -994,7 +887,7 @@ class FastPathBitmapUpdateDataPDU(CompositeType):
class SlowPathInputEvent(CompositeType):
"""
PDU use in slow-path sending client inputs
@summary: PDU use in slow-path sending client inputs
@see: http://msdn.microsoft.com/en-us/library/cc240583.aspx
"""
def __init__(self, messageData = None):
@@ -1018,7 +911,7 @@ class SlowPathInputEvent(CompositeType):
class PointerEvent(CompositeType):
"""
Event use to communicate mouse position
@summary: Event use to communicate mouse position
@see: http://msdn.microsoft.com/en-us/library/cc240586.aspx
"""
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_MOUSE
@@ -1031,7 +924,7 @@ class PointerEvent(CompositeType):
class ScancodeKeyEvent(CompositeType):
"""
Event use to communicate keyboard informations
@summary: Event use to communicate keyboard informations
@see: http://msdn.microsoft.com/en-us/library/cc240584.aspx
"""
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_SCANCODE
@@ -1044,7 +937,7 @@ class ScancodeKeyEvent(CompositeType):
class UnicodeKeyEvent(CompositeType):
"""
Event use to communicate keyboard informations
@summary: Event use to communicate keyboard informations
@see: http://msdn.microsoft.com/en-us/library/cc240585.aspx
"""
_INPUT_MESSAGE_TYPE_ = InputMessageType.INPUT_EVENT_UNICODE

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -23,27 +23,25 @@ Implement the main graphic layer
In this layer are managed all mains bitmap update orders end user inputs
"""
from rdpy.network.layer import LayerAutomata
from rdpy.base.error import InvalidExpectedDataException, CallPureVirtualFuntion
from rdpy.network.type import UInt16Le
import rdpy.base.log as log
import rdpy.protocol.rdp.gcc as gcc
from rdpy.core.layer import LayerAutomata
from rdpy.core.error import CallPureVirtualFuntion
import rdpy.core.log as log
import rdpy.protocol.rdp.tpkt as tpkt
import lic, data, caps
import data, caps
class PDUClientListener(object):
"""
Interface for PDU client automata listener
@summary: Interface for PDU client automata listener
"""
def onReady(self):
"""
Event call when PDU layer is ready to send events
@summary: Event call when PDU layer is ready to send events
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUClientListener"))
def onUpdate(self, rectangles):
"""
call when a bitmap data is received from update PDU
@summary: call when a bitmap data is received from update PDU
@param rectangles: [pdu.BitmapData] struct
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onUpdate", "PDUClientListener"))
@@ -56,37 +54,34 @@ class PDUClientListener(object):
class PDUServerListener(object):
"""
Interface for PDU server automata listener
@summary: Interface for PDU server automata listener
"""
def onReady(self):
"""
Event call when PDU layer is ready to send update
@summary: Event call when PDU layer is ready to send update
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onReady", "PDUServerListener"))
def onSlowPathInput(self, slowPathInputEvents):
"""
Event call when slow path input are available
@summary: Event call when slow path input are available
@param slowPathInputEvents: [data.SlowPathInputEvent]
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onSlowPathInput", "PDUServerListener"))
class PDULayer(LayerAutomata):
class PDULayer(LayerAutomata, tpkt.IFastPathListener):
"""
Global channel for MCS that handle session
@summary: Global channel for MCS that handle session
identification user, licensing management, and capabilities exchange
"""
def __init__(self):
LayerAutomata.__init__(self, None)
#logon info send from client to server
self._info = data.RDPInfo(extendedInfoConditional = lambda:(self._transport.getGCCServerSettings().getBlock(gcc.MessageType.SC_CORE).rdpVersion.value == gcc.Version.RDP_VERSION_5_PLUS))
#server capabilities
self._serverCapabilities = {
caps.CapsType.CAPSTYPE_GENERAL : caps.Capability(caps.GeneralCapability()),
caps.CapsType.CAPSTYPE_BITMAP : caps.Capability(caps.BitmapCapability()),
caps.CapsType.CAPSTYPE_ORDER : caps.Capability(caps.OrderCapability()),
caps.CapsType.CAPSTYPE_POINTER : caps.Capability(caps.PointerCapability()),
caps.CapsType.CAPSTYPE_POINTER : caps.Capability(caps.PointerCapability(isServer = True)),
caps.CapsType.CAPSTYPE_INPUT : caps.Capability(caps.InputCapability()),
caps.CapsType.CAPSTYPE_VIRTUALCHANNEL : caps.Capability(caps.VirtualChannelCapability()),
caps.CapsType.CAPSTYPE_FONT : caps.Capability(caps.FontCapability()),
@@ -105,28 +100,38 @@ class PDULayer(LayerAutomata):
caps.CapsType.CAPSTYPE_GLYPHCACHE : caps.Capability(caps.GlyphCapability()),
caps.CapsType.CAPSTYPE_OFFSCREENCACHE : caps.Capability(caps.OffscreenBitmapCacheCapability()),
caps.CapsType.CAPSTYPE_VIRTUALCHANNEL : caps.Capability(caps.VirtualChannelCapability()),
caps.CapsType.CAPSTYPE_SOUND : caps.Capability(caps.SoundCapability())
caps.CapsType.CAPSTYPE_SOUND : caps.Capability(caps.SoundCapability()),
caps.CapsType.CAPSETTYPE_MULTIFRAGMENTUPDATE : caps.Capability(caps.MultiFragmentUpdate())
}
#share id between client and server
self._shareId = 0x103EA
#enable or not fast path
self._fastPathSender = None
def setFastPathSender(self, fastPathSender):
"""
@param fastPathSender: {tpkt.FastPathSender}
@note: implement tpkt.IFastPathListener
"""
self._fastPathSender = fastPathSender
def sendPDU(self, pduMessage):
"""
Send a PDU data to transport layer
@summary: Send a PDU data to transport layer
@param pduMessage: PDU message
"""
self._transport.send(data.PDU(self._transport.getUserId(), pduMessage))
def sendDataPDU(self, pduData):
"""
Send an PDUData to transport layer
@summary: Send an PDUData to transport layer
@param pduData: PDU data message
"""
self.sendPDU(data.DataPDU(pduData, self._shareId))
class Client(PDULayer, tpkt.IFastPathListener):
class Client(PDULayer):
"""
Client automata of PDU layer
@summary: Client automata of PDU layer
"""
def __init__(self, listener):
"""
@@ -134,56 +139,26 @@ class Client(PDULayer, tpkt.IFastPathListener):
"""
PDULayer.__init__(self)
self._listener = listener
#enable or not fast path
self._fastPathSender = None
self._licenceManager = lic.LicenseManager(self)
def connect(self):
"""
Connect message in client automata
Send INfo packet (credentials)
Wait License info
@summary: Connect message in client automata
"""
self.sendInfoPkt()
#next state is license info PDU
self.setNextState(self.recvLicenceInfo)
self._gccCore = self._transport.getGCCClientSettings().CS_CORE
self.setNextState(self.recvDemandActivePDU)
#check if client support fast path message
self._clientFastPathSupported = False
def close(self):
"""
Send PDU close packet and call close method on transport method
@summary: Send PDU close packet and call close method on transport method
"""
self._transport.close()
#self.sendDataPDU(data.ShutdownRequestPDU())
def setFastPathSender(self, fastPathSender):
"""
@param fastPathSender: tpkt.FastPathSender
@note: implement tpkt.IFastPathListener
"""
self._fastPathSender = fastPathSender
def recvLicenceInfo(self, s):
"""
Read license info packet and check if is a valid client info
Wait Demand Active PDU
@param s: Stream
"""
#packet preambule
securityFlag = UInt16Le()
securityFlagHi = UInt16Le()
s.readType((securityFlag, securityFlagHi))
if not (securityFlag.value & data.SecurityFlag.SEC_LICENSE_PKT):
raise InvalidExpectedDataException("Waiting license packet")
if self._licenceManager.recv(s):
self.setNextState(self.recvDemandActivePDU)
def recvDemandActivePDU(self, s):
"""
Receive demand active PDU which contains
@summary: Receive demand active PDU which contains
Server capabilities. In this version of RDPY only
Restricted group of capabilities are used.
Send Confirm Active PDU
@@ -212,7 +187,7 @@ class Client(PDULayer, tpkt.IFastPathListener):
def recvServerSynchronizePDU(self, s):
"""
Receive from server
@summary: Receive from server
Wait Control Cooperate PDU
@param s: Stream from transport layer
"""
@@ -228,7 +203,7 @@ class Client(PDULayer, tpkt.IFastPathListener):
def recvServerControlCooperatePDU(self, s):
"""
Receive control cooperate PDU from server
@summary: Receive control cooperate PDU from server
Wait Control Granted PDU
@param s: Stream from transport layer
"""
@@ -244,7 +219,7 @@ class Client(PDULayer, tpkt.IFastPathListener):
def recvServerControlGrantedPDU(self, s):
"""
Receive last control PDU the granted control PDU
@summary: Receive last control PDU the granted control PDU
Wait Font map PDU
@param s: Stream from transport layer
"""
@@ -260,7 +235,7 @@ class Client(PDULayer, tpkt.IFastPathListener):
def recvServerFontMapPDU(self, s):
"""
Last useless connection packet from server to client
@summary: Last useless connection packet from server to client
Wait any PDU
@param s: Stream from transport layer
"""
@@ -278,7 +253,7 @@ class Client(PDULayer, tpkt.IFastPathListener):
def recvPDU(self, s):
"""
Main receive function after connection sequence
@summary: Main receive function after connection sequence
@param s: Stream from transport layer
"""
pdu = data.PDU()
@@ -291,11 +266,12 @@ class Client(PDULayer, tpkt.IFastPathListener):
#http://msdn.microsoft.com/en-us/library/cc240454.aspx
self.setNextState(self.recvDemandActivePDU)
def recvFastPath(self, fastPathS):
def recvFastPath(self, secFlag, fastPathS):
"""
Implement IFastPathListener interface
@summary: Implement IFastPathListener interface
Fast path is needed by RDP 8.0
@param fastPathS: Stream that contain fast path data
@param fastPathS: {Stream} that contain fast path data
@param secFlag: {SecFlags}
"""
fastPathPDU = data.FastPathUpdatePDU()
fastPathS.readType(fastPathPDU)
@@ -304,7 +280,7 @@ class Client(PDULayer, tpkt.IFastPathListener):
def readDataPDU(self, dataPDU):
"""
read a data PDU object
@summary: read a data PDU object
@param dataPDU: DataPDU object
"""
if dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU:
@@ -321,30 +297,16 @@ class Client(PDULayer, tpkt.IFastPathListener):
def readUpdateDataPDU(self, updateDataPDU):
"""
Read an update data PDU data
@summary: Read an update data PDU data
dispatch update data
@param: UpdateDataPDU object
@param: {UpdateDataPDU} object
"""
if updateDataPDU.updateType.value == data.UpdateType.UPDATETYPE_BITMAP:
self._listener.onUpdate(updateDataPDU.updateData.rectangles._array)
def sendInfoPkt(self):
"""
Send a logon info packet
client automata data
"""
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
@summary: Send all client capabilities
"""
#init general capability
generalCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability
@@ -356,9 +318,9 @@ class Client(PDULayer, tpkt.IFastPathListener):
#init bitmap capability
bitmapCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_BITMAP].capability
bitmapCapability.preferredBitsPerPixel = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).highColorDepth
bitmapCapability.desktopWidth = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).desktopWidth
bitmapCapability.desktopHeight = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).desktopHeight
bitmapCapability.preferredBitsPerPixel = self._gccCore.highColorDepth
bitmapCapability.desktopWidth = self._gccCore.desktopWidth
bitmapCapability.desktopHeight = self._gccCore.desktopHeight
#init order capability
orderCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_ORDER].capability
@@ -367,11 +329,11 @@ class Client(PDULayer, tpkt.IFastPathListener):
#init input capability
inputCapability = self._clientCapabilities[caps.CapsType.CAPSTYPE_INPUT].capability
inputCapability.inputFlags.value = caps.InputFlags.INPUT_FLAG_SCANCODES | caps.InputFlags.INPUT_FLAG_MOUSEX | caps.InputFlags.INPUT_FLAG_UNICODE
inputCapability.keyboardLayout = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).kbdLayout
inputCapability.keyboardType = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).keyboardType
inputCapability.keyboardSubType = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).keyboardSubType
inputCapability.keyboardrFunctionKey = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).keyboardFnKeys
inputCapability.imeFileName = self._transport.getGCCClientSettings().getBlock(gcc.MessageType.CS_CORE).imeFileName
inputCapability.keyboardLayout = self._gccCore.kbdLayout
inputCapability.keyboardType = self._gccCore.keyboardType
inputCapability.keyboardSubType = self._gccCore.keyboardSubType
inputCapability.keyboardrFunctionKey = self._gccCore.keyboardFnKeys
inputCapability.imeFileName = self._gccCore.imeFileName
#make active PDU packet
confirmActivePDU = data.ConfirmActivePDU()
@@ -381,7 +343,7 @@ class Client(PDULayer, tpkt.IFastPathListener):
def sendClientFinalizeSynchronizePDU(self):
"""
send a synchronize PDU from client to server
@summary: send a synchronize PDU from client to server
"""
synchronizePDU = data.SynchronizeDataPDU(self._transport.getChannelId())
self.sendDataPDU(synchronizePDU)
@@ -402,16 +364,16 @@ class Client(PDULayer, tpkt.IFastPathListener):
def sendInputEvents(self, pointerEvents):
"""
send client input events
@summary: send client input events
@param pointerEvents: list of pointer events
"""
pdu = data.ClientInputEventPDU()
pdu.slowPathInputEvents._array = [data.SlowPathInputEvent(x) for x in pointerEvents]
self.sendDataPDU(pdu)
class Server(PDULayer, tpkt.IFastPathListener):
class Server(PDULayer):
"""
Server Automata of PDU layer
@summary: Server Automata of PDU layer
"""
def __init__(self, listener):
"""
@@ -424,43 +386,14 @@ class Server(PDULayer, tpkt.IFastPathListener):
def connect(self):
"""
Connect message for server automata
Wait Info Packet
@summary: Connect message for server automata
"""
self.setNextState(self.recvInfoPkt)
def setFastPathSender(self, fastPathSender):
"""
@param fastPathSender: tpkt.FastPathSender
@note: implement tpkt.IFastPathListener
"""
self._fastPathSender = fastPathSender
def recvInfoPkt(self, s):
"""
Receive info packet from client
Client credentials
Send License valid error message
Send Demand Active PDU
Wait Confirm Active PDU
@param s: Stream
"""
securityFlag = UInt16Le()
securityFlagHi = UInt16Le()
s.readType((securityFlag, securityFlagHi))
if not (securityFlag.value & data.SecurityFlag.SEC_INFO_PKT):
raise InvalidExpectedDataException("Waiting info packet")
s.readType(self._info)
#next state send error license
self.sendLicensingErrorMessage()
self.sendDemandActivePDU()
self.setNextState(self.recvConfirmActivePDU)
self.setNextState(self.recvConfirmActivePDU)
def recvConfirmActivePDU(self, s):
"""
Receive confirm active PDU from client
@summary: Receive confirm active PDU from client
Capabilities exchange
Wait Client Synchronize PDU
@param s: Stream
@@ -478,13 +411,13 @@ class Server(PDULayer, tpkt.IFastPathListener):
self._clientCapabilities[cap.capabilitySetType] = cap
#find use full flag
self._clientFastPathSupported = self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags.value & caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED
self._clientFastPathSupported = bool(self._clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability.extraFlags.value & caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED)
self.setNextState(self.recvClientSynchronizePDU)
def recvClientSynchronizePDU(self, s):
"""
Receive from client
@summary: Receive from client
Wait Control Cooperate PDU
@param s: Stream from transport layer
"""
@@ -499,7 +432,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
def recvClientControlCooperatePDU(self, s):
"""
Receive control cooperate PDU from client
@summary: Receive control cooperate PDU from client
Wait Control Request PDU
@param s: Stream from transport layer
"""
@@ -514,7 +447,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
def recvClientControlRequestPDU(self, s):
"""
Receive last control PDU the request control PDU from client
@summary: Receive last control PDU the request control PDU from client
Wait Font List PDU
@param s: Stream from transport layer
"""
@@ -529,7 +462,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
def recvClientFontListPDU(self, s):
"""
Last synchronize packet from client to server
@summary: Last synchronize packet from client to server
Send Server Finalize PDUs
Wait any PDU
@param s: Stream from transport layer
@@ -550,7 +483,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
def recvPDU(self, s):
"""
Main receive function after connection sequence
@summary: Main receive function after connection sequence
@param s: Stream from transport layer
"""
pdu = data.PDU()
@@ -560,35 +493,30 @@ class Server(PDULayer, tpkt.IFastPathListener):
def readDataPDU(self, dataPDU):
"""
read a data PDU object
@summary: read a data PDU object
@param dataPDU: DataPDU object
"""
if dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU:
errorMessage = "Unknown code %s"%hex(dataPDU.pduData.errorInfo.value)
if data.ErrorInfo._MESSAGES_.has_key(dataPDU.pduData.errorInfo):
errorMessage = data.ErrorInfo._MESSAGES_[dataPDU.pduData.errorInfo]
log.error("INFO PDU : %s"%errorMessage)
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_INPUT:
self._listener.onSlowPathInput(dataPDU.pduData.slowPathInputEvents._array)
elif dataPDU.shareDataHeader.pduType2.value == data.PDUType2.PDUTYPE2_SHUTDOWN_REQUEST:
log.debug("Receive Shutdown Request")
self._transport.close()
def recvFastPath(self, fastPathS):
"""
Implement IFastPathListener interface
@summary: Implement IFastPathListener interface
Fast path is needed by RDP 8.0
@param fastPathS: Stream that contain fast path data
"""
pass
def sendLicensingErrorMessage(self):
"""
Send a licensing error data
"""
self._transport.send((UInt16Le(data.SecurityFlag.SEC_LICENSE_PKT), UInt16Le(), lic.createValidClientLicensingErrorMessage()))
def sendDemandActivePDU(self):
"""
@summary: Send server capabilities server automata PDU
@@ -597,7 +525,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
generalCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_GENERAL].capability
generalCapability.osMajorType.value = caps.MajorType.OSMAJORTYPE_WINDOWS
generalCapability.osMinorType.value = caps.MinorType.OSMINORTYPE_WINDOWS_NT
generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR
generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED | caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR | caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED
inputCapability = self._serverCapabilities[caps.CapsType.CAPSTYPE_INPUT].capability
inputCapability.inputFlags.value = caps.InputFlags.INPUT_FLAG_SCANCODES | caps.InputFlags.INPUT_FLAG_MOUSEX
@@ -609,7 +537,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
def sendServerFinalizeSynchronizePDU(self):
"""
Send last synchronize packet from server to client
@summary: Send last synchronize packet from server to client
"""
synchronizePDU = data.SynchronizeDataPDU(self._transport.getChannelId())
self.sendDataPDU(synchronizePDU)
@@ -630,7 +558,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
def sendPDU(self, pduMessage):
"""
Send a PDU data to transport layer
@summary: Send a PDU data to transport layer
@param pduMessage: PDU message
"""
PDULayer.sendPDU(self, pduMessage)
@@ -641,7 +569,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
def sendBitmapUpdatePDU(self, bitmapDatas):
"""
Send bitmap update data
@summary: Send bitmap update data
@param bitmapDatas: List of data.BitmapData
"""
#check bitmap header for client that want it (very old client)
@@ -654,7 +582,7 @@ class Server(PDULayer, tpkt.IFastPathListener):
#fast path case
fastPathUpdateDataPDU = data.FastPathBitmapUpdateDataPDU()
fastPathUpdateDataPDU.rectangles._array = bitmapDatas
self._fastPathSender.sendFastPath(data.FastPathUpdatePDU(fastPathUpdateDataPDU))
self._fastPathSender.sendFastPath(0, data.FastPathUpdatePDU(fastPathUpdateDataPDU))
else:
#slow path case
updateDataPDU = data.BitmapUpdateDataPDU()

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -21,9 +21,9 @@
GDI order structure
"""
from rdpy.base import log
from rdpy.base.error import InvalidExpectedDataException
from rdpy.network.type import CompositeType, UInt8, String, FactoryType, SInt8, SInt16Le
from rdpy.core import log
from rdpy.core.error import InvalidExpectedDataException
from rdpy.core.type import CompositeType, UInt8, String, FactoryType, SInt8, SInt16Le
class ControlFlag(object):
"""

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -21,8 +21,8 @@
Per encoded function
"""
from rdpy.network.type import UInt8, UInt16Be, UInt32Be, String
from rdpy.base.error import InvalidValue, InvalidExpectedDataException
from rdpy.core.type import UInt8, UInt16Be, UInt32Be, String
from rdpy.core.error import InvalidValue, InvalidExpectedDataException
def readLength(s):
"""

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -21,13 +21,13 @@
Use to manage RDP stack in twisted
"""
from rdpy.network import layer
from rdpy.base.error import CallPureVirtualFuntion, InvalidValue
from rdpy.core import layer
from rdpy.core.error import CallPureVirtualFuntion, InvalidValue
import pdu.layer
import pdu.data
import pdu.caps
import rdpy.base.log as log
import tpkt, x224, mcs, gcc
import rdpy.core.log as log
import tpkt, x224, mcs, gcc, sec
class RDPClientController(pdu.layer.PDUClientListener):
"""
@@ -38,12 +38,17 @@ class RDPClientController(pdu.layer.PDUClientListener):
self._clientObserver = []
#PDU layer
self._pduLayer = pdu.layer.Client(self)
#secure layer
self._secLayer = sec.Client(self._pduLayer)
#multi channel service
self._mcsLayer = mcs.Client(self._pduLayer)
self._mcsLayer = mcs.Client(self._secLayer)
#transport pdu layer
self._x224Layer = x224.Client(self._mcsLayer)
#transport packet (protocol layer)
self._tpktLayer = tpkt.TPKT(self._x224Layer, self._pduLayer)
self._tpktLayer = tpkt.TPKT(self._x224Layer)
#fastpath stack
self._pduLayer.initFastPath(self._secLayer)
self._secLayer.initFastPath(self._tpktLayer)
#is pdu layer is ready to send
self._isReady = False
@@ -70,7 +75,7 @@ class RDPClientController(pdu.layer.PDUClientListener):
"""
@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
self._secLayer._info.extendedInfo.performanceFlags.value = sec.PerfFlag.PERF_DISABLE_WALLPAPER | sec.PerfFlag.PERF_DISABLE_MENUANIMATIONS | sec.PerfFlag.PERF_DISABLE_CURSOR_SHADOW | sec.PerfFlag.PERF_DISABLE_THEMING | sec.PerfFlag.PERF_DISABLE_FULLWINDOWDRAG
def setScreen(self, width, height):
"""
@@ -88,8 +93,8 @@ class RDPClientController(pdu.layer.PDUClientListener):
@param username: username of session
"""
#username in PDU info packet
self._pduLayer._info.userName.value = username
self._pduLayer._licenceManager._username = username
self._secLayer._info.userName.value = username
self._secLayer._licenceManager._username = username
def setPassword(self, password):
"""
@@ -97,20 +102,20 @@ class RDPClientController(pdu.layer.PDUClientListener):
@param password: password of session
"""
self.setAutologon()
self._pduLayer._info.password.value = password
self._secLayer._info.password.value = password
def setDomain(self, domain):
"""
@summary: Set the windows domain of session
@param domain: domain of session
"""
self._pduLayer._info.domain.value = domain
self._secLayer._info.domain.value = domain
def setAutologon(self):
"""
@summary: enable autologon
"""
self._pduLayer._info.flag |= pdu.data.InfoFlag.INFO_AUTOLOGON
self._secLayer._info.flag |= sec.InfoFlag.INFO_AUTOLOGON
def setKeyboardLayout(self, layout):
"""
@@ -118,16 +123,26 @@ class RDPClientController(pdu.layer.PDUClientListener):
@param layout: us | fr
"""
if layout == "fr":
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).kbdLayout.value = gcc.KeyboardLayout.FRENCH
self._mcsLayer._clientSettings.CS_CORE.kbdLayout.value = gcc.KeyboardLayout.FRENCH
elif layout == "us":
self._mcsLayer._clientSettings.getBlock(gcc.MessageType.CS_CORE).kbdLayout.value = gcc.KeyboardLayout.US
self._mcsLayer._clientSettings.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
self._mcsLayer._clientSettings.CS_CORE.clientName.value = hostname[:15] + "\x00" * (15 - len(hostname))
self._secLayer._licenceManager._hostname = hostname
def setSecurityLevel(self, level):
"""
@summary: Request basic security
@param level: {str} (ssl | rdp)
"""
if level == "rdp":
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_RDP
elif level == "ssl":
self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_SSL
def addClientObserver(self, observer):
"""
@@ -313,7 +328,7 @@ class RDPServerController(pdu.layer.PDUServerListener):
"""
@summary: Controller use in server side mode
"""
def __init__(self, privateKeyFileName, certificateFileName, colorDepth):
def __init__(self, colorDepth, privateKeyFileName = None, certificateFileName = None):
"""
@param privateKeyFileName: file contain server private key
@param certficiateFileName: file that contain public key
@@ -324,12 +339,17 @@ class RDPServerController(pdu.layer.PDUServerListener):
self._serverObserver = []
#build RDP protocol stack
self._pduLayer = pdu.layer.Server(self)
#secure layer
self._secLayer = sec.Server(self._pduLayer)
#multi channel service
self._mcsLayer = mcs.Server(self._pduLayer)
self._mcsLayer = mcs.Server(self._secLayer)
#transport pdu layer
self._x224Layer = x224.Server(self._mcsLayer, privateKeyFileName, certificateFileName)
self._x224Layer = x224.Server(self._mcsLayer, privateKeyFileName, certificateFileName, False)
#transport packet (protocol layer)
self._tpktLayer = tpkt.TPKT(self._x224Layer, self._pduLayer)
self._tpktLayer = tpkt.TPKT(self._x224Layer)
#fastpath stack
self._pduLayer.initFastPath(self._secLayer)
self._secLayer.initFastPath(self._tpktLayer)
#set color depth of session
self.setColorDepth(colorDepth)
@@ -346,26 +366,32 @@ class RDPServerController(pdu.layer.PDUServerListener):
"""
return self._tpktLayer
def getHostname(self):
"""
@return: name of client (information done by RDP)
"""
return self._mcsLayer._clientSettings.CS_CORE.clientName.value.strip('\x00')
def getUsername(self):
"""
@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
return self._secLayer._info.userName.value
def getPassword(self):
"""
@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
return self._secLayer._info.password.value
def getDomain(self):
"""
@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
return self._secLayer._info.domain.value
def getCredentials(self):
"""
@@ -398,7 +424,8 @@ class RDPServerController(pdu.layer.PDUServerListener):
"""
@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)
and an onReady message is re send when client is ready
@param colorDepth: {integer} depth of session (15, 16, 24)
"""
self._colorDepth = colorDepth
self._pduLayer._serverCapabilities[pdu.caps.CapsType.CAPSTYPE_BITMAP].capability.preferredBitsPerPixel.value = colorDepth
@@ -478,12 +505,14 @@ class RDPServerController(pdu.layer.PDUServerListener):
class ClientFactory(layer.RawLayerClientFactory):
"""
@summary: Factory of Client RDP protocol
@param reason: twisted reason
"""
def connectionLost(self, tpktLayer):
def connectionLost(self, tpktLayer, reason):
#retrieve controller
x224Layer = tpktLayer._presentation
mcsLayer = x224Layer._presentation
pduLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
secLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
pduLayer = secLayer._presentation
controller = pduLayer._listener
controller.onClose()
@@ -508,21 +537,25 @@ class ServerFactory(layer.RawLayerServerFactory):
"""
@summary: Factory of Server RDP protocol
"""
def __init__(self, privateKeyFileName, certificateFileName, colorDepth):
def __init__(self, colorDepth, privateKeyFileName = None, certificateFileName = None):
"""
@param privateKeyFileName: file contain server private key
@param certficiateFileName: file that contain public key
@param colorDepth: color depth of session
@param privateKeyFileName: file contain server private key (if none -> back to standard RDP security)
@param certficiateFileName: file that contain public key (if none -> back to standard RDP security)
"""
self._colorDepth = colorDepth
self._privateKeyFileName = privateKeyFileName
self._certificateFileName = certificateFileName
self._colorDepth = colorDepth
def connectionLost(self, tpktLayer):
def connectionLost(self, tpktLayer, reason):
"""
@param reason: twisted reason
"""
#retrieve controller
x224Layer = tpktLayer._presentation
mcsLayer = x224Layer._presentation
pduLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
secLayer = mcsLayer._channels[mcs.Channel.MCS_GLOBAL_CHANNEL]
pduLayer = secLayer._presentation
controller = pduLayer._listener
controller.onClose()
@@ -531,7 +564,7 @@ class ServerFactory(layer.RawLayerServerFactory):
@summary: Function call from twisted and build rdp protocol stack
@param addr: destination address
"""
controller = RDPServerController(self._privateKeyFileName, self._certificateFileName, self._colorDepth)
controller = RDPServerController(self._colorDepth, self._privateKeyFileName, self._certificateFileName)
self.buildObserver(controller, addr)
return controller.getProtocol()

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -18,12 +18,84 @@
#
"""
Some use full methods for security in RDP
RDP Standard security layer
"""
import sha, md5
from rdpy.network.type import Stream, UInt32Le
import gcc, lic, tpkt, mcs
from rdpy.core.type import CompositeType, Stream, UInt32Le, UInt16Le, String, sizeof, UInt8
from rdpy.core.layer import LayerAutomata, IStreamSender
from rdpy.core.error import InvalidExpectedDataException
from rdpy.core import log
from rdpy.security import rc4
import rdpy.security.rsa_wrapper as rsa
class SecurityFlag(object):
"""
@summary: Microsoft security flags
@see: http://msdn.microsoft.com/en-us/library/cc240579.aspx
"""
SEC_EXCHANGE_PKT = 0x0001
SEC_TRANSPORT_REQ = 0x0002
RDP_SEC_TRANSPORT_RSP = 0x0004
SEC_ENCRYPT = 0x0008
SEC_RESET_SEQNO = 0x0010
SEC_IGNORE_SEQNO = 0x0020
SEC_INFO_PKT = 0x0040
SEC_LICENSE_PKT = 0x0080
SEC_LICENSE_ENCRYPT_CS = 0x0200
SEC_LICENSE_ENCRYPT_SC = 0x0200
SEC_REDIRECTION_PKT = 0x0400
SEC_SECURE_CHECKSUM = 0x0800
SEC_AUTODETECT_REQ = 0x1000
SEC_AUTODETECT_RSP = 0x2000
SEC_HEARTBEAT = 0x4000
SEC_FLAGSHI_VALID = 0x8000
class InfoFlag(object):
"""
Client capabilities informations
"""
INFO_MOUSE = 0x00000001
INFO_DISABLECTRLALTDEL = 0x00000002
INFO_AUTOLOGON = 0x00000008
INFO_UNICODE = 0x00000010
INFO_MAXIMIZESHELL = 0x00000020
INFO_LOGONNOTIFY = 0x00000040
INFO_COMPRESSION = 0x00000080
INFO_ENABLEWINDOWSKEY = 0x00000100
INFO_REMOTECONSOLEAUDIO = 0x00002000
INFO_FORCE_ENCRYPTED_CS_PDU = 0x00004000
INFO_RAIL = 0x00008000
INFO_LOGONERRORS = 0x00010000
INFO_MOUSE_HAS_WHEEL = 0x00020000
INFO_PASSWORD_IS_SC_PIN = 0x00040000
INFO_NOAUDIOPLAYBACK = 0x00080000
INFO_USING_SAVED_CREDS = 0x00100000
INFO_AUDIOCAPTURE = 0x00200000
INFO_VIDEO_DISABLE = 0x00400000
INFO_CompressionTypeMask = 0x00001E00
class PerfFlag(object):
"""
Network performances flag
"""
PERF_DISABLE_WALLPAPER = 0x00000001
PERF_DISABLE_FULLWINDOWDRAG = 0x00000002
PERF_DISABLE_MENUANIMATIONS = 0x00000004
PERF_DISABLE_THEMING = 0x00000008
PERF_DISABLE_CURSOR_SHADOW = 0x00000020
PERF_DISABLE_CURSORSETTINGS = 0x00000040
PERF_ENABLE_FONT_SMOOTHING = 0x00000080
PERF_ENABLE_DESKTOP_COMPOSITION = 0x00000100
class AfInet(object):
"""
IPv4 or IPv6 address style
"""
AF_INET = 0x00002
AF_INET6 = 0x0017
def saltedHash(inputData, salt, salt1, salt2):
"""
@summary: Generate particular signature from combination of sha1 and md5
@@ -62,18 +134,31 @@ def finalHash(key, random1, random2):
md5Digest.update(random2)
return md5Digest.digest()
def generateMicrosoftKey(secret, random1, random2):
def masterSecret(secret, random1, random2):
"""
@summary: Generate master secret
@param secret: {str} secret
@param clientRandom : {str} client random
@param serverRandom : {str} server random
@see: http://msdn.microsoft.com/en-us/library/cc241992.aspx
"""
return saltedHash("A", secret, random1, random2) + saltedHash("BB", secret, random1, random2) + saltedHash("CCC", secret, random1, random2)
def sessionKeyBlob(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)
return saltedHash("X", secret, random1, random2) + saltedHash("YY", secret, random1, random2) + saltedHash("ZZZ", secret, random1, random2)
def macData(macSaltKey, data):
"""
@see: http://msdn.microsoft.com/en-us/library/cc241995.aspx
@param macSaltKey: {str} mac key
@param data: {str} data to sign
@return: {str} signature
"""
sha1Digest = sha.new()
md5Digest = md5.new()
@@ -93,4 +178,500 @@ def macData(macSaltKey, data):
md5Digest.update("\x5c" * 48)
md5Digest.update(sha1Sig)
return md5Digest.digest()
return md5Digest.digest()
def tempKey(initialKey, currentKey):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240792.aspx
@param initialKey: {str} key computed first time
@param currentKey: {str} key actually used
@return: {str} temp key
"""
sha1Digest = sha.new()
md5Digest = md5.new()
sha1Digest.update(initialKey)
sha1Digest.update("\x36" * 40)
sha1Digest.update(currentKey)
sha1Sig = sha1Digest.digest()
md5Digest.update(initialKey)
md5Digest.update("\x5c" * 48)
md5Digest.update(sha1Sig)
return md5Digest.digest()
def gen40bits(data):
"""
@summary: generate 40 bits data from 128 bits data
@param data: {str} 128 bits data
@return: {str} 40 bits data
@see: http://msdn.microsoft.com/en-us/library/cc240785.aspx
"""
return "\xd1\x26\x9e" + data[:8][-5:]
def gen56bits(data):
"""
@summary: generate 56 bits data from 128 bits data
@param data: {str} 128 bits data
@return: {str} 56 bits data
@see: http://msdn.microsoft.com/en-us/library/cc240785.aspx
"""
return "\xd1" + data[:8][-7:]
def generateKeys(clientRandom, serverRandom, method):
"""
@param method: {gcc.Encryption}
@param clientRandom: {str[32]} client random
@param serverRandom: {str[32]} server random
@see: http://msdn.microsoft.com/en-us/library/cc240785.aspx
@return: MACKey, initialFirstKey128(ClientdecryptKey, serverEncryptKey), initialSecondKey128(ServerDecryptKey, ClientEncryptKey)
"""
preMasterHash = clientRandom[:24] + serverRandom[:24]
masterHash = masterSecret(preMasterHash, clientRandom, serverRandom)
sessionKey = sessionKeyBlob(masterHash, clientRandom, serverRandom)
macKey128 = sessionKey[:16]
initialFirstKey128 = finalHash(sessionKey[16:32], clientRandom, serverRandom)
initialSecondKey128 = finalHash(sessionKey[32:48], clientRandom, serverRandom)
#generate valid key
if method == gcc.EncryptionMethod.ENCRYPTION_FLAG_40BIT:
return gen40bits(macKey128), gen40bits(initialFirstKey128), gen40bits(initialSecondKey128)
elif method == gcc.EncryptionMethod.ENCRYPTION_FLAG_56BIT:
return gen56bits(macKey128), gen56bits(initialFirstKey128), gen56bits(initialSecondKey128)
elif method == gcc.EncryptionMethod.ENCRYPTION_FLAG_128BIT:
return macKey128, initialFirstKey128, initialSecondKey128
raise InvalidExpectedDataException("Bad encryption method")
def updateKey(initialKey, currentKey, method):
"""
@summary: update session key
@param initialKey: {str} Initial key
@param currentKey: {str} Current key
@return newKey: {str} key to use
@see: http://msdn.microsoft.com/en-us/library/cc240792.aspx
"""
#generate valid key
if method == gcc.EncryptionMethod.ENCRYPTION_FLAG_40BIT:
tempKey128 = tempKey(initialKey[:8], currentKey[:8])
return gen40bits(rc4.crypt(rc4.RC4Key(tempKey128[:8]), tempKey128[:8]))
elif method == gcc.EncryptionMethod.ENCRYPTION_FLAG_56BIT:
tempKey128 = tempKey(initialKey[:8], currentKey[:8])
return gen56bits(rc4.crypt(rc4.RC4Key(tempKey128[:8]), tempKey128[:8]))
elif method == gcc.EncryptionMethod.ENCRYPTION_FLAG_128BIT:
tempKey128 = tempKey(initialKey, currentKey)
return rc4.crypt(rc4.RC4Key(tempKey128), tempKey128)
class ClientSecurityExchangePDU(CompositeType):
"""
@summary: contain client random for basic security
@see: http://msdn.microsoft.com/en-us/library/cc240472.aspx
"""
def __init__(self):
CompositeType.__init__(self)
self.length = UInt32Le(lambda:(sizeof(self) - 4))
self.encryptedClientRandom = String(readLen = UInt8(lambda:(self.length.value - 8)))
self.padding = String("\x00" * 8, readLen = UInt8(8))
class RDPInfo(CompositeType):
"""
@summary: Client informations
Contains credentials (very important packet)
@see: http://msdn.microsoft.com/en-us/library/cc240475.aspx
"""
def __init__(self, extendedInfoConditional):
CompositeType.__init__(self)
#code page
self.codePage = UInt32Le()
#support flag
self.flag = UInt32Le(InfoFlag.INFO_MOUSE | InfoFlag.INFO_UNICODE | InfoFlag.INFO_LOGONNOTIFY | InfoFlag.INFO_LOGONERRORS | InfoFlag.INFO_DISABLECTRLALTDEL)
self.cbDomain = UInt16Le(lambda:sizeof(self.domain) - 2)
self.cbUserName = UInt16Le(lambda:sizeof(self.userName) - 2)
self.cbPassword = UInt16Le(lambda:sizeof(self.password) - 2)
self.cbAlternateShell = UInt16Le(lambda:sizeof(self.alternateShell) - 2)
self.cbWorkingDir = UInt16Le(lambda:sizeof(self.workingDir) - 2)
#microsoft domain
self.domain = String(readLen = UInt16Le(lambda:self.cbDomain.value + 2), unicode = True)
self.userName = String(readLen = UInt16Le(lambda:self.cbUserName.value + 2), unicode = True)
self.password = String(readLen = UInt16Le(lambda:self.cbPassword.value + 2), unicode = True)
#shell execute at start of session
self.alternateShell = String(readLen = UInt16Le(lambda:self.cbAlternateShell.value + 2), unicode = True)
#working directory for session
self.workingDir = String(readLen = UInt16Le(lambda:self.cbWorkingDir.value + 2), unicode = True)
self.extendedInfo = RDPExtendedInfo(conditional = extendedInfoConditional)
class RDPExtendedInfo(CompositeType):
"""
@summary: Add more client informations
"""
def __init__(self, conditional):
CompositeType.__init__(self, conditional = conditional)
self.clientAddressFamily = UInt16Le(AfInet.AF_INET)
self.cbClientAddress = UInt16Le(lambda:sizeof(self.clientAddress))
self.clientAddress = String(readLen = self.cbClientAddress, unicode = True)
self.cbClientDir = UInt16Le(lambda:sizeof(self.clientDir))
self.clientDir = String(readLen = self.cbClientDir, unicode = True)
#TODO make tiomezone
self.clientTimeZone = String("\x00" * 172)
self.clientSessionId = UInt32Le()
self.performanceFlags = UInt32Le()
class SecLayer(LayerAutomata, IStreamSender, tpkt.IFastPathListener, tpkt.IFastPathSender, mcs.IGCCConfig):
"""
@summary: Standard RDP security layer
This layer is Transparent as possible for upper layer
"""
def __init__(self, presentation):
"""
@param presentation: Layer (generally pdu layer)
"""
LayerAutomata.__init__(self, presentation)
#thios layer is like a fastpath proxy
self._fastPathTransport = None
self._fastPathPresentation = None
#credentials
self._info = RDPInfo(extendedInfoConditional = lambda:(self.getGCCServerSettings().SC_CORE.rdpVersion.value == gcc.Version.RDP_VERSION_5_PLUS))
#True if classic encryption is enable
self._enableEncryption = False
#initialise decrypt and encrypt keys
self._macKey = None
self._initialDecrytKey = None
self._initialEncryptKey = None
self._currentDecrytKey = None
self._currentEncryptKey = None
#counter before update
self._nbEncryptedPacket = 0
self._nbDecryptedPacket = 0
#current rc4 tab
self._decryptRc4 = None
self._encryptRc4 = None
def readEncryptedPayload(self, s):
"""
@summary: decrypt basic RDP security payload
@param s: {Stream} encrypted stream
@return: {Stream} decrypted
"""
#if update is needed
if self._nbDecryptedPacket == 4096:
log.debug("update decrypt key")
self._currentDecrytKey = updateKey( self._initialDecrytKey, self._currentDecrytKey,
self.getGCCServerSettings().SC_SECURITY.encryptionMethod.value)
self._decryptRc4 = rc4.RC4Key(self._currentDecrytKey)
self._nbDecryptedPacket = 0
signature = String(readLen = UInt8(8))
encryptedPayload = String()
s.readType((signature, encryptedPayload))
decrypted = rc4.crypt(self._decryptRc4, encryptedPayload.value)
#ckeck signature
if macData(self._macKey, decrypted)[:8] != signature.value:
raise InvalidExpectedDataException("Bad packet signature")
#count
self._nbDecryptedPacket += 1
return Stream(decrypted)
def writeEncryptedPayload(self, data):
"""
@summary: sign and crypt data
@param s: {Stream} raw stream
@return: {Tuple} (signature, encryptedData)
"""
if self._nbEncryptedPacket == 4096:
log.debug("update encrypt key")
self._currentEncryptKey = updateKey( self._initialEncryptKey, self._currentEncryptKey,
self.getGCCServerSettings().SC_SECURITY.encryptionMethod.value)
self._encryptRc4 = rc4.RC4Key(self._currentEncryptKey)
self._nbEncryptedPacket = 0
self._nbEncryptedPacket += 1
s = Stream()
s.writeType(data)
return (String(macData(self._macKey, s.getvalue())[:8]), String(rc4.crypt(self._encryptRc4, s.getvalue())))
def recv(self, data):
"""
@summary: if basic RDP security layer is activate decrypt
else pass to upper layer
@param data : {Stream} input Stream
"""
if not self._enableEncryption:
self._presentation.recv(data)
return
securityFlag = UInt16Le()
securityFlagHi = UInt16Le()
data.readType((securityFlag, securityFlagHi))
if securityFlag.value & SecurityFlag.SEC_ENCRYPT:
data = self.readEncryptedPayload(data)
self._presentation.recv(data)
def send(self, data):
"""
@summary: if basic RDP security layer is activate encrypt
else pass to upper layer
@param data: {Type | Tuple}
"""
if not self._enableEncryption:
self._transport.send(data)
return
self.sendFlagged(SecurityFlag.SEC_ENCRYPT, data)
def sendFlagged(self, flag, data):
"""
@summary: explicit send flag method for particular packet
(info packet or license packet)
If encryption is enable apply it
@param flag: {integer} security flag
@param data: {Type | Tuple}
"""
if flag & SecurityFlag.SEC_ENCRYPT:
data = self.writeEncryptedPayload(data)
self._transport.send((UInt16Le(flag), UInt16Le(), data))
def recvFastPath(self, secFlag, fastPathS):
"""
@summary: Call when fast path packet is received
@param secFlag: {SecFlags}
@param fastPathS: {Stream}
"""
if self._enableEncryption and secFlag & tpkt.SecFlags.FASTPATH_OUTPUT_ENCRYPTED:
fastPathS = self.readEncryptedPayload(fastPathS)
self._fastPathPresentation.recvFastPath(secFlag, fastPathS)
def setFastPathListener(self, fastPathListener):
"""
@param fastPathListener : {IFastPathListener}
"""
self._fastPathPresentation = fastPathListener
def sendFastPath(self, secFlag, fastPathS):
"""
@summary: Send fastPathS Type as fast path packet
@param secFlag: {SecFlags}
@param fastPathS: {Stream} type transform to stream and send as fastpath
"""
if self._enableEncryption:
secFlag |= tpkt.SecFlags.FASTPATH_OUTPUT_ENCRYPTED
fastPathS = self.writeEncryptedPayload(fastPathS)
self._fastPathTransport.sendFastPath(secFlag, fastPathS)
def setFastPathSender(self, fastPathSender):
"""
@param fastPathSender: {tpkt.FastPathSender}
"""
self._fastPathTransport = fastPathSender
def getUserId(self):
"""
@return: {integer} mcs user id
@see: mcs.IGCCConfig
"""
return self._transport.getUserId()
def getChannelId(self):
"""
@return: {integer} return channel id of proxy
@see: mcs.IGCCConfig
"""
return self._transport.getChannelId()
def getGCCClientSettings(self):
"""
@return: {gcc.Settings} mcs layer gcc client settings
@see: mcs.IGCCConfig
"""
return self._transport.getGCCClientSettings()
def getGCCServerSettings(self):
"""
@return: {gcc.Settings} mcs layer gcc server settings
@see: mcs.IGCCConfig
"""
return self._transport.getGCCServerSettings()
class Client(SecLayer):
"""
@summary: Client side of security layer
"""
def __init__(self, presentation):
SecLayer.__init__(self, presentation)
self._licenceManager = lic.LicenseManager(self)
def connect(self):
"""
@summary: send client random if needed and send info packet
"""
self._enableEncryption = self.getGCCClientSettings().CS_CORE.serverSelectedProtocol == 0
if self._enableEncryption:
self.sendClientRandom()
self.sendInfoPkt()
def sendInfoPkt(self):
"""
@summary: send information packet (with credentials)
next state -> recvLicenceInfo
"""
secFlag = SecurityFlag.SEC_INFO_PKT
if self._enableEncryption:
secFlag |= SecurityFlag.SEC_ENCRYPT
self.sendFlagged(secFlag, self._info)
self.setNextState(self.recvLicenceInfo)
def sendClientRandom(self):
"""
@summary: generate and send client random and init session keys
"""
#generate client random
clientRandom = rsa.random(256)
self._macKey, self._initialDecrytKey, self._initialEncryptKey = generateKeys( clientRandom,
self.getGCCServerSettings().SC_SECURITY.serverRandom.value,
self.getGCCServerSettings().SC_SECURITY.encryptionMethod.value)
#initialize keys
self._currentDecrytKey = self._initialDecrytKey
self._currentEncryptKey = self._initialEncryptKey
self._decryptRc4 = rc4.RC4Key(self._currentDecrytKey)
self._encryptRc4 = rc4.RC4Key(self._currentEncryptKey)
#verify certificate
if not self.getGCCServerSettings().SC_SECURITY.serverCertificate.certData.verify():
log.warning("cannot verify server identity")
#send client random encrypted with
serverPublicKey = self.getGCCServerSettings().SC_SECURITY.serverCertificate.certData.getPublicKey()
message = ClientSecurityExchangePDU()
#reverse because bignum in little endian
message.encryptedClientRandom.value = rsa.encrypt(clientRandom[::-1], serverPublicKey)[::-1]
self.sendFlagged(SecurityFlag.SEC_EXCHANGE_PKT, message)
def recvLicenceInfo(self, s):
"""
@summary: Read license info packet and check if is a valid client info
Wait Demand Active PDU
@param s: Stream
"""
#packet preambule
securityFlag = UInt16Le()
securityFlagHi = UInt16Le()
s.readType((securityFlag, securityFlagHi))
if not (securityFlag.value & SecurityFlag.SEC_LICENSE_PKT):
raise InvalidExpectedDataException("waiting license packet")
if self._licenceManager.recv(s):
self.setNextState()
#end of connection step of
self._presentation.connect()
class Server(SecLayer):
"""
@summary: Client side of security layer
"""
def __init__(self, presentation):
"""
@param presentation: {Layer}
"""
SecLayer.__init__(self, presentation)
self._rsaPublicKey, self._rsaPrivateKey = rsa.newkeys(512)
def connect(self):
"""
@summary: init automata to wait info packet
"""
self._enableEncryption = self.getGCCClientSettings().CS_CORE.serverSelectedProtocol == 0
if self._enableEncryption:
self.setNextState(self.recvClientRandom)
else:
self.setNextState(self.recvInfoPkt)
def getCertificate(self):
"""
@summary: generate proprietary certificate from rsa public key
"""
certificate = gcc.ProprietaryServerCertificate()
certificate.PublicKeyBlob.modulus.value = rsa.int2bytes(self._rsaPublicKey.n)[::-1]
certificate.PublicKeyBlob.pubExp.value = self._rsaPublicKey.e
certificate.sign()
return gcc.ServerCertificate(certificate)
def recvClientRandom(self, s):
"""
@summary: receive client random and generate session keys
@param s: {Stream}
"""
#packet preambule
securityFlag = UInt16Le()
securityFlagHi = UInt16Le()
s.readType((securityFlag, securityFlagHi))
if not (securityFlag.value & SecurityFlag.SEC_EXCHANGE_PKT):
raise InvalidExpectedDataException("waiting client random")
message = ClientSecurityExchangePDU()
s.readType(message)
clientRandom = rsa.decrypt(message.encryptedClientRandom.value[::-1], self._rsaPrivateKey)[::-1]
self._macKey, self._initialEncryptKey, self._initialDecrytKey = generateKeys( clientRandom,
self.getGCCServerSettings().SC_SECURITY.serverRandom.value,
self.getGCCServerSettings().SC_SECURITY.encryptionMethod.value)
#initialize keys
self._currentDecrytKey = self._initialDecrytKey
self._currentEncryptKey = self._initialEncryptKey
self._decryptRc4 = rc4.RC4Key(self._currentDecrytKey)
self._encryptRc4 = rc4.RC4Key(self._currentEncryptKey)
self.setNextState(self.recvInfoPkt)
def recvInfoPkt(self, s):
"""
@summary: receive info packet from client
Client credentials
Send License valid error message
Send Demand Active PDU
Wait Confirm Active PDU
@param s: {Stream}
"""
securityFlag = UInt16Le()
securityFlagHi = UInt16Le()
s.readType((securityFlag, securityFlagHi))
if not (securityFlag.value & SecurityFlag.SEC_INFO_PKT):
raise InvalidExpectedDataException("Waiting info packet")
if securityFlag.value & SecurityFlag.SEC_ENCRYPT:
s = self.readEncryptedPayload(s)
s.readType(self._info)
#next state send error license
self.sendLicensingErrorMessage()
#reinit state
self.setNextState()
self._presentation.connect()
def sendLicensingErrorMessage(self):
"""
@summary: Send a licensing error data
"""
self.sendFlagged(SecurityFlag.SEC_LICENSE_PKT, lic.createValidClientLicensingErrorMessage())

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -22,9 +22,9 @@ Transport packet layer implementation
Use to build correct size packet and handle slow path and fast path mode
"""
from rdpy.network.layer import RawLayer
from rdpy.network.type import UInt8, UInt16Be, sizeof
from rdpy.base.error import CallPureVirtualFuntion
from rdpy.core.layer import RawLayer
from rdpy.core.type import UInt8, UInt16Be, sizeof
from rdpy.core.error import CallPureVirtualFuntion
class Action(object):
"""
@@ -33,36 +33,67 @@ class Action(object):
"""
FASTPATH_ACTION_FASTPATH = 0x0
FASTPATH_ACTION_X224 = 0x3
class SecFlags(object):
"""
@see: http://msdn.microsoft.com/en-us/library/cc240621.aspx
"""
#hihi 'secure' checksum but private key is public !!!
FASTPATH_OUTPUT_SECURE_CHECKSUM = 0x1
FASTPATH_OUTPUT_ENCRYPTED = 0x2
class IFastPathListener(object):
"""
@summary: Fast path packet listener
Usually X224 layer
"""
def recvFastPath(self, fastPathS):
def recvFastPath(self, secFlag, fastPathS):
"""
@summary: Call when fast path packet is received
@param fastPathS: Stream
@param secFlag: {SecFlags}
@param fastPathS: {Stream}
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recvFastPath", "recvFastPath"))
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recvFastPath", "IFastPathListener"))
def initFastPath(self, fastPathSender):
"""
@summary: initialize stack
@param fastPathSender: {IFastPathSender}
"""
self.setFastPathSender(fastPathSender)
fastPathSender.setFastPathListener(self)
def setFastPathSender(self, fastPathSender):
"""
@summary: Call to set a fast path sender to listener
@param fastPathSender: IFastPathSender
@param fastPathSender : {IFastPathSender}
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "setFastPathSender", "recvFastPath"))
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "setFastPathSender", "IFastPathListener"))
class IFastPathSender(object):
"""
@summary: Fast path send capability
"""
def sendFastPath(self, fastPathS):
def sendFastPath(self, secFlag, fastPathS):
"""
@summary: Send fastPathS Type as fast path packet
@param fastPathS: type transform to stream and send as fastpath
@param secFlag: {integer} Security flag for fastpath packet
@param fastPathS: {Type | Tuple} type transform to stream and send as fastpath
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendFastPath", "IFastPathSender"))
def initFastPath(self, fastPathListener):
"""
@summary: initialize stack
@param fastPathListener: {IFastPathListener}
"""
self.setFastPathListener(fastPathListener)
fastPathListener.setFastPathSender(self)
def setFastPathListener(self, fastPathListener):
"""
@param fastPathListener: {IFastPathListener}
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "setFastPathListener", "IFastPathSender"))
class TPKT(RawLayer, IFastPathSender):
"""
@@ -70,21 +101,25 @@ class TPKT(RawLayer, IFastPathSender):
represent the Raw Layer in stack (first layer)
This layer only handle size of packet and determine if is a fast path packet
"""
def __init__(self, presentation, fastPathListener = None):
def __init__(self, presentation):
"""
@param presentation: presentation layer, in RDP case is x224 layer
@param fastPathListener: IFastPathListener
@param presentation: {Layer} presentation layer, in RDP case is x224 layer
"""
RawLayer.__init__(self, presentation)
#length may be coded on more than 1 bytes
self._lastShortLength = UInt8()
#fast path listener
self._fastPathListener = None
#last secure flag
self._secFlag = 0
def setFastPathListener(self, fastPathListener):
"""
@param fastPathListener : {IFastPathListener}
@note: implement IFastPathSender
"""
self._fastPathListener = fastPathListener
if not fastPathListener is None:
#set me as fast path sender
fastPathListener.setFastPathSender(self)
def connect(self):
"""
@summary: Call when transport layer connection
@@ -98,8 +133,8 @@ class TPKT(RawLayer, IFastPathSender):
def readHeader(self, data):
"""
Read header of TPKT packet
@param data: Stream received from twisted layer
@summary: Read header of TPKT packet
@param data: {Stream} received from twisted layer
"""
#first read packet version
version = UInt8()
@@ -112,6 +147,7 @@ class TPKT(RawLayer, IFastPathSender):
self.expect(2, self.readExtendedHeader)
else:
#is fast path packet
self._secFlag = ((version.value >> 6) & 0x3)
data.readType(self._lastShortLength)
if self._lastShortLength.value & 0x80:
#size is 1 byte more
@@ -122,8 +158,8 @@ class TPKT(RawLayer, IFastPathSender):
def readExtendedHeader(self, data):
"""
Header may be on 4 bytes
@param data: Stream from twisted layer
@summary: Header may be on 4 bytes
@param data: {Stream} from twisted layer
"""
#next state is read data
size = UInt16Be()
@@ -132,8 +168,8 @@ class TPKT(RawLayer, IFastPathSender):
def readExtendedFastPathHeader(self, data):
"""
Fast path header may be on 1 byte more
@param data: Stream from twisted layer
@summary: Fast path header may be on 1 byte more
@param data: {Stream} from twisted layer
"""
leftPart = UInt8()
data.readType(leftPart)
@@ -144,16 +180,16 @@ class TPKT(RawLayer, IFastPathSender):
def readFastPath(self, data):
"""
Fast path data
@param data: Stream from twisted layer
@summary: Fast path data
@param data: {Stream} from twisted layer
"""
self._fastPathListener.recvFastPath(data)
self._fastPathListener.recvFastPath(self._secFlag, data)
self.expect(2, self.readHeader)
def readData(self, data):
"""
Read classic TPKT packet, last state in tpkt automata
@param data: Stream with correct size
@summary: Read classic TPKT packet, last state in tpkt automata
@param data: {Stream} with correct size
"""
#next state is pass to
self._presentation.recv(data)
@@ -161,13 +197,14 @@ class TPKT(RawLayer, IFastPathSender):
def send(self, message):
"""
Send encompassed data
@param message: network.Type message to send
@summary: Send encompassed data
@param message: {network.Type} message to send
"""
RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_X224), UInt8(0), UInt16Be(sizeof(message) + 4), message))
def sendFastPath(self, fastPathS):
def sendFastPath(self, secFlag, fastPathS):
"""
@param fastPathS: type transform to stream and send as fastpath
@param secFlag: {integer} Security flag for fastpath packet
"""
RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_FASTPATH), UInt16Be((sizeof(fastPathS) + 3) | 0x8000), fastPathS))
RawLayer.send(self, (UInt8(Action.FASTPATH_ACTION_FASTPATH | ((secFlag & 0x3) << 6)), UInt16Be((sizeof(fastPathS) + 3) | 0x8000), fastPathS))

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -21,12 +21,13 @@
Implement transport PDU layer
This layer have main goal to negociate SSL transport
RDP basic security is not supported by RDPY (because is not a true security layer...)
RDP basic security is supported only on client side
"""
from rdpy.core import log
from rdpy.network.layer import LayerAutomata, IStreamSender
from rdpy.network.type import UInt8, UInt16Le, UInt16Be, UInt32Le, CompositeType, sizeof, String
from rdpy.base.error import InvalidExpectedDataException
from rdpy.core.layer import LayerAutomata, IStreamSender
from rdpy.core.type import UInt8, UInt16Le, UInt16Be, UInt32Le, CompositeType, sizeof, String
from rdpy.core.error import InvalidExpectedDataException, RDPSecurityNegoFail
class MessageType(object):
"""
@@ -130,8 +131,6 @@ class X224Layer(LayerAutomata, IStreamSender):
@param presentation: upper layer, MCS layer in RDP case
"""
LayerAutomata.__init__(self, presentation)
#default selectedProtocol is SSl because is the only supported
#in this version of RDPY
#client requested selectedProtocol
self._requestedProtocol = Protocols.PROTOCOL_SSL
#server selected selectedProtocol
@@ -195,20 +194,23 @@ class Client(X224Layer):
message = ServerConnectionConfirm()
data.readType(message)
#check presence of negotiation response
if not message.protocolNeg._is_readed:
raise InvalidExpectedDataException("server must support negotiation protocol to use SSL")
if message.protocolNeg.failureCode._is_readed:
raise InvalidExpectedDataException("negotiation failure code %x"%message.protocolNeg.failureCode.value)
raise RDPSecurityNegoFail("negotiation failure code %x"%message.protocolNeg.failureCode.value)
self._selectedProtocol = message.protocolNeg.selectedProtocol.value
#check presence of negotiation response
if message.protocolNeg._is_readed:
self._selectedProtocol = message.protocolNeg.selectedProtocol.value
else:
self._selectedProtocol = Protocols.PROTOCOL_RDP
if self._selectedProtocol != Protocols.PROTOCOL_SSL:
raise InvalidExpectedDataException("only SSL protocol is supported in RDPY version")
#NLA protocol doesn't support in actual version of RDPY
if self._selectedProtocol in [ Protocols.PROTOCOL_HYBRID, Protocols.PROTOCOL_HYBRID_EX ]:
raise InvalidExpectedDataException("RDPY doesn't support NLA security Layer")
#_transport is TPKT and transport is TCP layer of twisted
self._transport.transport.startTLS(ClientTLSContext())
if self._selectedProtocol == Protocols.PROTOCOL_SSL:
log.debug("*" * 10 + " select SSL layer " + "*" * 10)
#_transport is TPKT and transport is TCP layer of twisted
self._transport.transport.startTLS(ClientTLSContext())
#now i'm ready to receive data
self.setNextState(self.recvData)
@@ -220,16 +222,18 @@ class Server(X224Layer):
"""
@summary: Server automata of X224 layer
"""
def __init__(self, presentation, privateKeyFileName, certificateFileName):
def __init__(self, presentation, privateKeyFileName = None, certificateFileName = None, forceSSL = False):
"""
@param presentation: upper layer, MCS layer in RDP case
@param privateKeyFileName: file contain server private key
@param certficiateFileName: file that contain public key
@param presentation: {layer} upper layer, MCS layer in RDP case
@param privateKeyFileName: {str} file contain server private key
@param certficiateFileName: {str} file that contain public key
@param forceSSL: {boolean} reject old client that doerasn't support SSL
"""
X224Layer.__init__(self, presentation)
#Server mode informations for TLS connection
self._serverPrivateKeyFileName = privateKeyFileName
self._serverCertificateFileName = certificateFileName
self._forceSSL = forceSSL and not self._serverPrivateKeyFileName is None and not self._serverCertificateFileName is None
def connect(self):
"""
@@ -241,26 +245,34 @@ class Server(X224Layer):
"""
@summary: Read connection confirm packet
Next state is send connection confirm
@param data: Stream
@param data: {Stream}
@see : http://msdn.microsoft.com/en-us/library/cc240470.aspx
"""
message = ClientConnectionRequestPDU()
data.readType(message)
if not message.protocolNeg._is_readed or message.protocolNeg.failureCode._is_readed:
raise InvalidExpectedDataException("Too older RDP client")
if not message.protocolNeg._is_readed:
self._requestedProtocol = Protocols.PROTOCOL_RDP
else:
self._requestedProtocol = message.protocolNeg.selectedProtocol.value
self._requestedProtocol = message.protocolNeg.selectedProtocol.value
#match best security layer available
if not self._serverPrivateKeyFileName is None and not self._serverCertificateFileName is None:
self._selectedProtocol = self._requestedProtocol & Protocols.PROTOCOL_SSL
else:
self._selectedProtocol = self._requestedProtocol & Protocols.PROTOCOL_RDP
if not self._requestedProtocol & Protocols.PROTOCOL_SSL:
#if force ssl is enable
if not self._selectedProtocol & Protocols.PROTOCOL_SSL and self._forceSSL:
log.warning("server reject client because doesn't support SSL")
#send error message and quit
message = ServerConnectionConfirm()
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_FAILURE
message.protocolNeg.failureCode.value = NegotiationFailureCode.SSL_REQUIRED_BY_SERVER
self._transport.send(message)
raise InvalidExpectedDataException("rdpy needs ssl client compliant")
self.close()
return
self._selectedProtocol = Protocols.PROTOCOL_SSL
self.sendConnectionConfirm()
def sendConnectionConfirm(self):
@@ -274,8 +286,11 @@ class Server(X224Layer):
message.protocolNeg.code.value = NegociationType.TYPE_RDP_NEG_RSP
message.protocolNeg.selectedProtocol.value = self._selectedProtocol
self._transport.send(message)
#_transport is TPKT and transport is TCP layer of twisted
self._transport.transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName))
if self._selectedProtocol == Protocols.PROTOCOL_SSL:
log.debug("*" * 10 + " select SSL layer " + "*" * 10)
#_transport is TPKT and transport is TCP layer of twisted
self._transport.transport.startTLS(ServerTLSContext(self._serverPrivateKeyFileName, self._serverCertificateFileName))
#connection is done send to presentation
self.setNextState(self.recvData)
self._presentation.connect()

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -25,11 +25,11 @@ Implement Remote FrameBuffer protocol use in VNC client and server
@todo: more encoding rectangle
"""
from rdpy.network.layer import RawLayer, RawLayerClientFactory
from rdpy.network.type import UInt8, UInt16Be, UInt32Be, SInt32Be, String, CompositeType
from rdpy.base.error import InvalidValue, CallPureVirtualFuntion
from rdpy.protocol.rfb.pyDes import des
import rdpy.base.log as log
from rdpy.core.layer import RawLayer, RawLayerClientFactory
from rdpy.core.type import UInt8, UInt16Be, UInt32Be, SInt32Be, String, CompositeType
from rdpy.core.error import InvalidValue, CallPureVirtualFuntion
from rdpy.security.pyDes import des
import rdpy.core.log as log
class ProtocolVersion(object):
"""
@@ -671,10 +671,11 @@ class ClientFactory(RawLayerClientFactory):
self.buildObserver(controller, addr)
return controller.getProtocol()
def connectionLost(self, rfblayer):
def connectionLost(self, rfblayer, reason):
"""
@summary: Override this method to handle connection lost
@param rfblayer: rfblayer that cause connectionLost event
@param reason: twisted reason
"""
#call controller
rfblayer._clientListener.onClose()

View File

@@ -50,7 +50,8 @@ def RC4(key):
S = KSA(key)
return PRGA(S)
def crypt(key, plaintext):
keystream = RC4([ord(c) for c in key])
def RC4Key(key):
return RC4([ord(c) for c in key])
def crypt(keystream, plaintext):
return "".join([chr(ord(c) ^ keystream.next()) for c in plaintext])

View File

@@ -0,0 +1,99 @@
#
# 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/>.
#
"""
Wrapper around RSA library
"""
import rsa
def newkeys(size):
"""
@summary: wrapper around rsa.newkeys function
@param size: {integer} size of key
"""
return rsa.newkeys(size)
def PublicKey(e, n):
"""
@param e: {long | str}public exponent
@param n: {long | str}modulus
"""
if isinstance(e, str):
e = rsa.transform.bytes2int(e)
if isinstance(n, str):
n = rsa.transform.bytes2int(n)
return { 'e' : e, 'n' : n }
def PrivateKey(d, n):
"""
@param d: {long | str}private exponent
@param n: {long | str}modulus
"""
if isinstance(d, str):
d = rsa.transform.bytes2int(d)
if isinstance(n, str):
n = rsa.transform.bytes2int(n)
return { 'd' : d, 'n' : n }
def int2bytes(i, fill_size=None):
"""
@summary: wrapper of rsa.transform.int2bytes
"""
return rsa.transform.int2bytes(i,fill_size)
def random(size):
"""
@summary: wrapper around rsa.randnum.read_random_bits function
@param size: {integer] size in bits
@return: {str} random bytes array
"""
return rsa.randnum.read_random_bits(size)
def encrypt(message, publicKey):
"""
@summary: wrapper around rsa.core.encrypt_int function
@param message: {str} source message
@param publicKey: {rsa.PublicKey}
"""
return rsa.transform.int2bytes(rsa.core.encrypt_int(rsa.transform.bytes2int(message), publicKey['e'], publicKey['n']), rsa.common.byte_size(publicKey['n']))
def decrypt(message, privateKey):
"""
@summary: wrapper around rsa.core.decrypt_int function
@param message: {str} source message
@param publicKey: {rsa.PrivateKey}
"""
return rsa.transform.int2bytes(rsa.core.decrypt_int(rsa.transform.bytes2int(message), privateKey['d'], privateKey['n']))
def sign(message, privateKey):
"""
@summary: sign message with private key
@param message: {str} message to sign
@param privateKey : {rsa.privateKey} key use to sugn
"""
return rsa.transform.int2bytes(rsa.core.encrypt_int(rsa.transform.bytes2int(message), privateKey['d'], privateKey['n']), rsa.common.byte_size(privateKey['n']))
def verify(message, publicKey):
"""
@summary: return hash
@param message: {str} message to verify
@param publicKey : {rsa.publicKey} key use to sugn
"""
return rsa.transform.int2bytes(rsa.core.decrypt_int(rsa.transform.bytes2int(message), publicKey['e'], publicKey['n']))

160
rdpy/security/x509.py Normal file
View File

@@ -0,0 +1,160 @@
#
# 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/>.
#
"""
@see: https://github.com/filippog/pyasn1/blob/master/examples/x509.py
"""
from pyasn1.type import tag, namedtype, namedval, univ, constraint, char, useful
from pyasn1.codec.ber import decoder
MAX = 64
class DirectoryString(univ.Choice):
componentType = namedtype.NamedTypes(
namedtype.NamedType('teletexString', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
namedtype.NamedType('printableString', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
namedtype.NamedType('universalString', char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
namedtype.NamedType('ia5String', char.IA5String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))) # hm, this should not be here!? XXX
)
class AttributeValue(DirectoryString): pass
class AttributeType(univ.ObjectIdentifier): pass
class AttributeTypeAndValue(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('type', AttributeType()),
namedtype.NamedType('value', AttributeValue())
)
class RelativeDistinguishedName(univ.SetOf):
componentType = AttributeTypeAndValue()
class RDNSequence(univ.SequenceOf):
componentType = RelativeDistinguishedName()
class Name(univ.Choice):
componentType = namedtype.NamedTypes(
namedtype.NamedType('', RDNSequence())
)
class AlgorithmIdentifier(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('algorithm', univ.ObjectIdentifier()),
namedtype.OptionalNamedType('parameters', univ.Null())
# XXX syntax screwed?
# namedtype.OptionalNamedType('parameters', univ.ObjectIdentifier())
)
class Extension(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('extnID', univ.ObjectIdentifier()),
namedtype.DefaultedNamedType('critical', univ.Boolean('False')),
namedtype.NamedType('extnValue', univ.OctetString())
)
class Extensions(univ.SequenceOf):
componentType = Extension()
sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX)
class SubjectPublicKeyInfo(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('algorithm', AlgorithmIdentifier()),
namedtype.NamedType('subjectPublicKey', univ.BitString())
)
class UniqueIdentifier(univ.BitString): pass
class Time(univ.Choice):
componentType = namedtype.NamedTypes(
namedtype.NamedType('utcTime', useful.UTCTime()),
namedtype.NamedType('generalTime', useful.GeneralizedTime())
)
class Validity(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('notBefore', Time()),
namedtype.NamedType('notAfter', Time())
)
class CertificateSerialNumber(univ.Integer): pass
class Version(univ.Integer):
namedValues = namedval.NamedValues(
('v1', 0), ('v2', 1), ('v3', 2)
)
class TBSCertificate(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.DefaultedNamedType('version', Version('v1', tagSet=Version.tagSet.tagExplicitly(tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))),
namedtype.NamedType('serialNumber', CertificateSerialNumber()),
namedtype.NamedType('signature', AlgorithmIdentifier()),
namedtype.NamedType('issuer', Name()),
namedtype.NamedType('validity', Validity()),
namedtype.NamedType('subject', Name()),
namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo()),
namedtype.OptionalNamedType('issuerUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
namedtype.OptionalNamedType('subjectUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))),
namedtype.OptionalNamedType('extensions', Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)))
)
class X509Certificate(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('tbsCertificate', TBSCertificate()),
namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()),
namedtype.NamedType('signatureValue', univ.BitString())
)
class RSAPublicKey(univ.Sequence):
"""
@summary: asn1 public rsa key
@see: https://tools.ietf.org/html/rfc3447
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType('modulus', univ.Integer()),
namedtype.NamedType('publicExponent', univ.Integer()),
)
def load(stream):
"""
@summary: load X509 certificate
@param stream: {str}
"""
return decoder.decode(stream, asn1Spec=X509Certificate())[0]
def extractRSAKey(certificate):
"""
@summary: try to extract rsa key
@return: (modulus, public exponent)
"""
#http://www.alvestrand.no/objectid/1.2.840.113549.1.1.1.html
#extract binary data
l = 0L
for b in certificate.getComponentByName('tbsCertificate').getComponentByName('subjectPublicKeyInfo').getComponentByName('subjectPublicKey'):
l = (l << 1) | b
rsaKey = decoder.decode(hex(l)[2:-1].decode('hex'), asn1Spec=RSAPublicKey())[0]
return rsaKey.getComponentByName('modulus')._value , rsaKey.getComponentByName('publicExponent')._value

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -26,10 +26,10 @@ QRemoteDesktop is a widget use for render in rdpy
from PyQt4 import QtGui, QtCore
from rdpy.protocol.rfb.rfb import RFBClientObserver
from rdpy.protocol.rdp.rdp import RDPClientObserver
from rdpy.base.error import CallPureVirtualFuntion
from rdpy.core.error import CallPureVirtualFuntion
import sys
import rdpy.base.log as log
import rdpy.core.log as log
import rle
class QAdaptor(object):
@@ -60,12 +60,6 @@ class QAdaptor(object):
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendWheelEvent", "QAdaptor"))
def getWidget(self):
"""
@return: widget use for render
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getWidget", "QAdaptor"))
def closeEvent(self, e):
"""
@summary: Call when you want to close connection
@@ -94,7 +88,7 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
@param height: height of widget
"""
RFBClientObserver.__init__(self, controller)
self._widget = QRemoteDesktop(self, 1024, 800)
self._widget = QRemoteDesktop(1024, 800, self)
def getWidget(self):
"""
@@ -185,7 +179,7 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
#do something maybe a message
pass
def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data):
def RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data):
"""
@summary: Bitmap transformation to Qt object
@param width: width of bitmap
@@ -239,12 +233,12 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
"""
def __init__(self, controller, width, height):
"""
@param controller: RDP controller
@param width: width of widget
@param height: height of widget
@param controller: {RDPClientController} RDP controller
@param width: {int} width of widget
@param height: {int} height of widget
"""
RDPClientObserver.__init__(self, controller)
self._widget = QRemoteDesktop(self, width, height)
self._widget = QRemoteDesktop(width, height, self)
#set widget screen to RDP stack
controller.setScreen(width, height)
@@ -299,17 +293,17 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
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
@param destLeft: {int} xmin position
@param destTop: {int} ymin position
@param destRight: {int} xmax position because RDP can send bitmap with padding
@param destBottom: {int} ymax position because RDP can send bitmap with padding
@param width: {int} width of bitmap
@param height: {int} height of bitmap
@param bitsPerPixel: {int} number of bit per pixel
@param isCompress: {bool} use RLE compression
@param data: {str} bitmap data
"""
image = RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data);
image = RDPBitmapToQtImage(width, height, bitsPerPixel, isCompress, data);
#if image need to be cut
#For bit alignement server may send more than image pixel
self._widget.notifyImage(destLeft, destTop, image, destRight - destLeft + 1, destBottom - destTop + 1)
@@ -319,23 +313,23 @@ class RDPClientQt(RDPClientObserver, QAdaptor):
@summary: Call when stack is ready
"""
#do something maybe a loader
pass
def onClose(self):
"""
@summary: Call when stack is close
"""
#do something maybe a message
pass
class QRemoteDesktop(QtGui.QWidget):
"""
@summary: Qt display widget
"""
def __init__(self, adaptor, width, height):
def __init__(self, width, height, adaptor):
"""
@param adaptor: QAdaptor
@param adaptor: {QAdaptor}
@param width: {int} width of widget
@param height: {int} height of widget
"""
super(QRemoteDesktop, self).__init__()
#adaptor use to send
@@ -365,6 +359,15 @@ class QRemoteDesktop(QtGui.QWidget):
#force update
self.update()
def resize(self, width, height):
"""
@summary: override resize function
@param width: {int} width of widget
@param height: {int} height of widget
"""
self._buffer = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
QtGui.QWidget.resize(self, width, height)
def paintEvent(self, e):
"""
@summary: Call when Qt renderer engine estimate that is needed

View File

@@ -1,5 +1,5 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
# Copyright (c) 2014-2015 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
@@ -20,7 +20,7 @@
"""
Fake widget
"""
from rdpy.base.error import CallPureVirtualFuntion
from rdpy.core.error import CallPureVirtualFuntion
from PyQt4 import QtGui, QtCore

View File

@@ -4,19 +4,28 @@ import setuptools
from distutils.core import setup, Extension
setup(name='rdpy',
version='1.1.1',
version='1.2.1',
description='Remote Desktop Protocol in Python',
long_description="""
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol.
RDPY is a pure Python implementation of the Microsoft RDP (Remote Desktop Protocol) protocol (Client and Server side).
RDPY is built over the event driven network engine Twisted.
RDPY provide RDP and VNC binaries :
\t-RDP Man In The Middle proxy which record session
\t-RDP Honeypot
\t-RDP screenshoter
\t-RDP client
\t-VNC client
\t-VNC screenshoter
\t-RSS Player
""",
author='Sylvain Peyrefitte',
author_email='citronneur@gmail.com',
url='https://github.com/citronneur/rdpy',
packages=[
'rdpy',
'rdpy.base',
'rdpy.network',
'rdpy.core',
'rdpy.security',
'rdpy.protocol',
'rdpy.protocol.rdp',
'rdpy.protocol.rdp.pdu',
@@ -25,16 +34,20 @@ setup(name='rdpy',
],
ext_modules=[Extension('rle', ['ext/rle.c'])],
scripts = [
'bin/rdpy-rdpclient.py',
'bin/rdpy-rdpproxy.py',
'bin/rdpy-rdpclient.py',
'bin/rdpy-rdphoneypot.py',
'bin/rdpy-rdpmitm.py',
'bin/rdpy-rdpscreenshot.py',
'bin/rdpy-rssplayer.py',
'bin/rdpy-vncclient.py',
'bin/rdpy-vncscreenshot.py'
],
install_requires=[
'twisted',
'pyopenssl',
'service_identity'
'service_identity',
'qt4reactor',
'rsa',
'pyasn1',
],
)

View File

@@ -18,7 +18,7 @@
#
"""
unit test for rdpy.base.const module
unit test for rdpy.core.const module
"""
import os, sys
@@ -26,10 +26,10 @@ import os, sys
sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
import rdpy.base.const
import rdpy.network.type
import rdpy.core.const
import rdpy.core.type
class ConstCase(unittest.TestCase):
class ConstTest(unittest.TestCase):
'''
represent test case for all classes and function
present in rdpy.base.const
@@ -38,19 +38,19 @@ class ConstCase(unittest.TestCase):
'''
test if type attributes decorator works
'''
@rdpy.base.const.TypeAttributes(rdpy.network.type.UInt16Le)
@rdpy.core.const.TypeAttributes(rdpy.core.type.UInt16Le)
class Test:
MEMBER_1 = 1
MEMBER_2 = 2
self.assertIsInstance(Test.MEMBER_1, rdpy.network.type.UInt16Le, "MEMBER_1 is not in correct type")
self.assertIsInstance(Test.MEMBER_2, rdpy.network.type.UInt16Le, "MEMBER_2 is not in correct type")
self.assertIsInstance(Test.MEMBER_1, rdpy.core.type.UInt16Le, "MEMBER_1 is not in correct type")
self.assertIsInstance(Test.MEMBER_2, rdpy.core.type.UInt16Le, "MEMBER_2 is not in correct type")
def test_const(self):
'''
test if get on const class member generate new object each
'''
@rdpy.base.const.ConstAttributes
@rdpy.core.const.ConstAttributes
class Test:
MEMBER_1 = 1
MEMBER_2 = 2

View File

@@ -26,12 +26,12 @@ import os, sys
sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
import rdpy.network.layer
import rdpy.core.layer
class LayerCase(unittest.TestCase):
class LayerTest(unittest.TestCase):
"""
@summary: represent test case for all classes and function
present in rdpy.network.layer
present in rdpy.core.layer
"""
class LayerCaseException(Exception):
@@ -44,33 +44,33 @@ class LayerCase(unittest.TestCase):
"""
@summary: test if connect event is send from transport to presentation
"""
class TestConnect(rdpy.network.layer.Layer):
class TestConnect(rdpy.core.layer.Layer):
def connect(self):
raise LayerCase.LayerCaseException()
raise LayerTest.LayerCaseException()
self.assertRaises(LayerCase.LayerCaseException, rdpy.network.layer.Layer(presentation = TestConnect()).connect)
self.assertRaises(LayerTest.LayerCaseException, rdpy.core.layer.Layer(presentation = TestConnect()).connect)
def test_layer_automata_more_than_expected(self):
"""
@summary: test layer automata mechanism if data received is more than expected
"""
class TestAutomata(rdpy.network.layer.RawLayer):
class TestAutomata(rdpy.core.layer.RawLayer):
def expectedCallBack(self, data):
if data.dataLen() == 4:
raise LayerCase.LayerCaseException()
raise LayerTest.LayerCaseException()
t = TestAutomata()
t.expect(4, t.expectedCallBack)
self.assertRaises(LayerCase.LayerCaseException, t.dataReceived, "\x00\x00\x00\x00\x00")
self.assertRaises(LayerTest.LayerCaseException, t.dataReceived, "\x00\x00\x00\x00\x00")
def test_layer_automata_less_than_expected(self):
"""
@summary: test layer automata mechanism
"""
class TestAutomata(rdpy.network.layer.RawLayer):
class TestAutomata(rdpy.core.layer.RawLayer):
def expectedCallBack(self, data):
if data.dataLen() == 4:
raise LayerCase.LayerCaseException()
raise LayerTest.LayerCaseException()
t = TestAutomata()
t.expect(4, t.expectedCallBack)

View File

@@ -26,10 +26,10 @@ import os, sys
sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
import rdpy.network.type
from rdpy.base.error import InvalidSize
import rdpy.core.type
from rdpy.core.error import InvalidSize
class TypeCase(unittest.TestCase):
class TypeTest(unittest.TestCase):
"""
@summary: represent test case for all classes and function
present in rdpy.network.type
@@ -38,24 +38,24 @@ class TypeCase(unittest.TestCase):
"""
@summary: test if callable value with const ctor doesn't change value
"""
c = rdpy.network.type.CallableValue(5)
c = rdpy.core.type.CallableValue(5)
self.assertEqual(c.value, 5, "invalid callable const")
def test_callable_value_lambda(self):
"""
@summary: test if callable value with lambda ctor return dynamic value
"""
c = rdpy.network.type.CallableValue(lambda:5)
c = rdpy.core.type.CallableValue(lambda:5)
self.assertEqual(c.value, 5, "invalid callable lambda")
def test_type_write_conditional_true(self):
"""
@summary: test when write is obligatory call write function
"""
class TestType(rdpy.network.type.Type):
class TestType(rdpy.core.type.Type):
def __write__(self, s):
raise Exception()
s = rdpy.network.type.Stream()
s = rdpy.core.type.Stream()
self.assertRaises(Exception, s.writeType, TestType(conditional = lambda:True))
@unittest.expectedFailure
@@ -63,20 +63,20 @@ class TypeCase(unittest.TestCase):
"""
@summary: test when write doesn't needed, doesn't call write function
"""
class TestType(rdpy.network.type.Type):
class TestType(rdpy.core.type.Type):
def __write__(self, s):
raise Exception()
s = rdpy.network.type.Stream()
s = rdpy.core.type.Stream()
self.assertRaises(Exception, s.writeType, TestType(conditional = lambda:False))
def test_type_read_conditional_true(self):
"""
@summary: test when read is obligatory call write function
"""
class TestType(rdpy.network.type.Type):
class TestType(rdpy.core.type.Type):
def __read__(self, s):
raise Exception()
s = rdpy.network.type.Stream()
s = rdpy.core.type.Stream()
self.assertRaises(Exception, s.readType, TestType(conditional = lambda:True))
@unittest.expectedFailure
@@ -84,10 +84,10 @@ class TypeCase(unittest.TestCase):
"""
@summary: test when read doesn't needed, doesn't call read function
"""
class TestType(rdpy.network.type.Type):
class TestType(rdpy.core.type.Type):
def __read__(self, s):
raise Exception()
s = rdpy.network.type.Stream()
s = rdpy.core.type.Stream()
self.assertRaises(Exception, s.readType, TestType(conditional = lambda:False))
@@ -95,106 +95,106 @@ class TypeCase(unittest.TestCase):
"""
@summary: test if sizeof of simple type is init value(4) when type is conditional true
"""
v = rdpy.network.type.SimpleType("I", 4, False, 0, conditional = lambda:True)
self.assertEqual(rdpy.network.type.sizeof(v), 4, "invalid sizeof")
v = rdpy.core.type.SimpleType("I", 4, False, 0, conditional = lambda:True)
self.assertEqual(rdpy.core.type.sizeof(v), 4, "invalid sizeof")
def test_sizeof_conditional_false(self):
"""
@summary: test if sizeof of simple type is 0 when type is conditional false
"""
v = rdpy.network.type.SimpleType("I", 4, False, 0, conditional = lambda:False)
self.assertEqual(rdpy.network.type.sizeof(v), 0, "invalid sizeof")
v = rdpy.core.type.SimpleType("I", 4, False, 0, conditional = lambda:False)
self.assertEqual(rdpy.core.type.sizeof(v), 0, "invalid sizeof")
def test_sizeof_list(self):
"""
@summary: test call sizeof on list of type
"""
v = [rdpy.network.type.UInt8(), rdpy.network.type.UInt16Le(), rdpy.network.type.UInt32Le()]
self.assertEqual(rdpy.network.type.sizeof(v), 7, "invalid sizeof")
v = [rdpy.core.type.UInt8(), rdpy.core.type.UInt16Le(), rdpy.core.type.UInt32Le()]
self.assertEqual(rdpy.core.type.sizeof(v), 7, "invalid sizeof")
def test_sizeof_list_conditional(self):
"""
@summary: test call sizeof on list of type with one type hidden
"""
v = [rdpy.network.type.UInt8(), rdpy.network.type.UInt16Le(conditional = lambda:False), rdpy.network.type.UInt32Le()]
self.assertEqual(rdpy.network.type.sizeof(v), 5, "invalid sizeof")
v = [rdpy.core.type.UInt8(), rdpy.core.type.UInt16Le(conditional = lambda:False), rdpy.core.type.UInt32Le()]
self.assertEqual(rdpy.core.type.sizeof(v), 5, "invalid sizeof")
def test_sizeof_tuple(self):
"""
@summary: test call sizeof on tuple of type
"""
v = [rdpy.network.type.UInt8(), rdpy.network.type.UInt16Le(), rdpy.network.type.UInt32Le()]
self.assertEqual(rdpy.network.type.sizeof(v), 7, "invalid sizeof")
v = [rdpy.core.type.UInt8(), rdpy.core.type.UInt16Le(), rdpy.core.type.UInt32Le()]
self.assertEqual(rdpy.core.type.sizeof(v), 7, "invalid sizeof")
def test_sizeof_tuple_conditional(self):
"""
@summary: test call sizeof on tuple of type with one type hidden
"""
v = (rdpy.network.type.UInt8(), rdpy.network.type.UInt16Le(), rdpy.network.type.UInt32Le(conditional = lambda:False))
self.assertEqual(rdpy.network.type.sizeof(v), 3, "invalid sizeof")
v = (rdpy.core.type.UInt8(), rdpy.core.type.UInt16Le(), rdpy.core.type.UInt32Le(conditional = lambda:False))
self.assertEqual(rdpy.core.type.sizeof(v), 3, "invalid sizeof")
def test_stream_write_uint8_type(self):
"""
@summary: test write uint8 in stream
"""
s = rdpy.network.type.Stream()
s.writeType(rdpy.network.type.UInt8(1))
s = rdpy.core.type.Stream()
s.writeType(rdpy.core.type.UInt8(1))
self.assertEqual(''.join(s.buflist), '\x01', "invalid stream write")
def test_stream_write_uint16Le_type(self):
"""
@summary: test write UInt16Le in stream
"""
s = rdpy.network.type.Stream()
s.writeType(rdpy.network.type.UInt16Le(1))
s = rdpy.core.type.Stream()
s.writeType(rdpy.core.type.UInt16Le(1))
self.assertEqual(''.join(s.buflist), '\x01\x00', "invalid stream write")
def test_stream_write_uint16Be_type(self):
"""
@summary: test write UInt16Be in stream
"""
s = rdpy.network.type.Stream()
s.writeType(rdpy.network.type.UInt16Be(1))
s = rdpy.core.type.Stream()
s.writeType(rdpy.core.type.UInt16Be(1))
self.assertEqual(''.join(s.buflist), '\x00\x01', "invalid stream write")
def test_stream_write_uint24Le_type(self):
"""
@summary: test write UInt24Le in stream
"""
s = rdpy.network.type.Stream()
s.writeType(rdpy.network.type.UInt24Le(1))
s = rdpy.core.type.Stream()
s.writeType(rdpy.core.type.UInt24Le(1))
self.assertEqual(''.join(s.buflist), '\x01\x00\x00', "invalid stream write")
def test_stream_write_uint24Be_type(self):
"""
@summary: test write uint24Be in stream
"""
s = rdpy.network.type.Stream()
s.writeType(rdpy.network.type.UInt24Be(1))
s = rdpy.core.type.Stream()
s.writeType(rdpy.core.type.UInt24Be(1))
self.assertEqual(''.join(s.buflist), '\x00\x00\x01', "invalid stream write")
def test_stream_write_uint32Le_type(self):
"""
@summary: test write UInt32Le in stream
"""
s = rdpy.network.type.Stream()
s.writeType(rdpy.network.type.UInt32Le(1))
s = rdpy.core.type.Stream()
s.writeType(rdpy.core.type.UInt32Le(1))
self.assertEqual(''.join(s.buflist), '\x01\x00\x00\x00', "invalid stream write")
def test_stream_write_uint32Be_type(self):
"""
@summary: test write UInt32Be in stream
"""
s = rdpy.network.type.Stream()
s.writeType(rdpy.network.type.UInt32Be(1))
s = rdpy.core.type.Stream()
s.writeType(rdpy.core.type.UInt32Be(1))
self.assertEqual(''.join(s.buflist), '\x00\x00\x00\x01', "invalid stream write")
def test_stream_read_uint8_type(self):
"""
@summary: test read UInt8 type from stream
"""
s = rdpy.network.type.Stream('\x01')
t = rdpy.network.type.UInt8()
s = rdpy.core.type.Stream('\x01')
t = rdpy.core.type.UInt8()
s.readType(t)
self.assertEqual(t.value, 1, "invalid stream read value")
self.assertEqual(s.dataLen(), 0, "not read all stream")
@@ -203,8 +203,8 @@ class TypeCase(unittest.TestCase):
"""
@summary: test read UInt16Le type from stream
"""
s = rdpy.network.type.Stream('\x01\x00')
t = rdpy.network.type.UInt16Le()
s = rdpy.core.type.Stream('\x01\x00')
t = rdpy.core.type.UInt16Le()
s.readType(t)
self.assertEqual(t.value, 1, "invalid stream read value")
self.assertEqual(s.dataLen(), 0, "not read all stream")
@@ -213,8 +213,8 @@ class TypeCase(unittest.TestCase):
"""
@summary: test read UInt16Be type from stream
"""
s = rdpy.network.type.Stream('\x00\x01')
t = rdpy.network.type.UInt16Be()
s = rdpy.core.type.Stream('\x00\x01')
t = rdpy.core.type.UInt16Be()
s.readType(t)
self.assertEqual(t.value, 1, "invalid stream read value")
self.assertEqual(s.dataLen(), 0, "not read all stream")
@@ -223,8 +223,8 @@ class TypeCase(unittest.TestCase):
"""
@summary: test read UInt24Le type from stream
"""
s = rdpy.network.type.Stream('\x01\x00\x00')
t = rdpy.network.type.UInt24Le()
s = rdpy.core.type.Stream('\x01\x00\x00')
t = rdpy.core.type.UInt24Le()
s.readType(t)
self.assertEqual(t.value, 1, "invalid stream read value")
self.assertEqual(s.dataLen(), 0, "not read all stream")
@@ -233,8 +233,8 @@ class TypeCase(unittest.TestCase):
"""
@summary: test read UInt24Be type from stream
"""
s = rdpy.network.type.Stream('\x00\x00\x01')
t = rdpy.network.type.UInt24Be()
s = rdpy.core.type.Stream('\x00\x00\x01')
t = rdpy.core.type.UInt24Be()
s.readType(t)
self.assertEqual(t.value, 1, "invalid stream read")
self.assertEqual(s.dataLen(), 0, "not read all stream")
@@ -243,8 +243,8 @@ class TypeCase(unittest.TestCase):
"""
@summary: test read UInt32Le type from stream
"""
s = rdpy.network.type.Stream('\x01\x00\x00\x00')
t = rdpy.network.type.UInt32Le()
s = rdpy.core.type.Stream('\x01\x00\x00\x00')
t = rdpy.core.type.UInt32Le()
s.readType(t)
self.assertEqual(t.value, 1, "invalid stream read value")
self.assertEqual(s.dataLen(), 0, "not read all stream")
@@ -253,8 +253,8 @@ class TypeCase(unittest.TestCase):
"""
@summary: test read UInt32Be type from stream
"""
s = rdpy.network.type.Stream('\x00\x00\x00\x01')
t = rdpy.network.type.UInt32Be()
s = rdpy.core.type.Stream('\x00\x00\x00\x01')
t = rdpy.core.type.UInt32Be()
s.readType(t)
self.assertEqual(t.value, 1, "invalid stream read")
self.assertEqual(s.dataLen(), 0, "not read all stream")
@@ -264,9 +264,9 @@ class TypeCase(unittest.TestCase):
@summary: test optional option in case of simple type reading
"""
#unsigned int case
t = rdpy.network.type.SimpleType("I", 4, False, 0, optional = True)
t = rdpy.core.type.SimpleType("I", 4, False, 0, optional = True)
#empty stream
s1 = rdpy.network.type.Stream()
s1 = rdpy.core.type.Stream()
s1.readType(t)
self.assertEqual(t.value, 0, "invalid stream read optional value")
@@ -275,8 +275,8 @@ class TypeCase(unittest.TestCase):
@summary: test conditional option in case of simple type reading and when condition is false (not read)
"""
#unsigned int case
t = rdpy.network.type.SimpleType("I", 4, False, 0, conditional = lambda:False)
s1 = rdpy.network.type.Stream("\x01\x00\x00\x00")
t = rdpy.core.type.SimpleType("I", 4, False, 0, conditional = lambda:False)
s1 = rdpy.core.type.Stream("\x01\x00\x00\x00")
s1.readType(t)
self.assertEqual(t.value, 0, "invalid stream read conditional value")
@@ -285,8 +285,8 @@ class TypeCase(unittest.TestCase):
@summary: test conditional option in case of simple type reading and when condition is true (must be read)
"""
#unsigned int case
t = rdpy.network.type.SimpleType("I", 4, False, 0, conditional = lambda:True)
s1 = rdpy.network.type.Stream("\x01\x00\x00\x00")
t = rdpy.core.type.SimpleType("I", 4, False, 0, conditional = lambda:True)
s1 = rdpy.core.type.Stream("\x01\x00\x00\x00")
s1.readType(t)
self.assertEqual(t.value, 1, "invalid stream read conditional value")
@@ -294,13 +294,13 @@ class TypeCase(unittest.TestCase):
"""
@summary: test if constant constraint fail, the reading stream is correctly rollback
"""
class TestComposite(rdpy.network.type.CompositeType):
class TestComposite(rdpy.core.type.CompositeType):
def __init__(self):
rdpy.network.type.CompositeType.__init__(self)
self.padding = rdpy.network.type.UInt32Le(0)
self.constraint = rdpy.network.type.UInt32Le(1, constant = True)
rdpy.core.type.CompositeType.__init__(self)
self.padding = rdpy.core.type.UInt32Le(0)
self.constraint = rdpy.core.type.UInt32Le(1, constant = True)
s = rdpy.network.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00")
s = rdpy.core.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00")
try:
s.readType(TestComposite())
except Exception:
@@ -313,19 +313,19 @@ class TypeCase(unittest.TestCase):
@summary: test if constant constraint fail even in recurcive composite type,
the reading stream is correctly rollback
"""
class TestSubComposite(rdpy.network.type.CompositeType):
class TestSubComposite(rdpy.core.type.CompositeType):
def __init__(self):
rdpy.network.type.CompositeType.__init__(self)
self.padding = rdpy.network.type.UInt32Le(0)
self.constraint = rdpy.network.type.UInt32Le(1, constant = True)
rdpy.core.type.CompositeType.__init__(self)
self.padding = rdpy.core.type.UInt32Le(0)
self.constraint = rdpy.core.type.UInt32Le(1, constant = True)
class TestComposite(rdpy.network.type.CompositeType):
class TestComposite(rdpy.core.type.CompositeType):
def __init__(self):
rdpy.network.type.CompositeType.__init__(self)
self.padding = rdpy.network.type.UInt32Le(0)
rdpy.core.type.CompositeType.__init__(self)
self.padding = rdpy.core.type.UInt32Le(0)
self.recurcive = TestSubComposite()
s = rdpy.network.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
s = rdpy.core.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
try:
s.readType(TestComposite())
except Exception:
@@ -338,19 +338,19 @@ class TypeCase(unittest.TestCase):
@summary: test if constant constraint fail even in recurcive composite type,
the reading stream is correctly rollback
"""
class TestSubComposite(rdpy.network.type.CompositeType):
class TestSubComposite(rdpy.core.type.CompositeType):
def __init__(self):
rdpy.network.type.CompositeType.__init__(self)
self.padding = rdpy.network.type.UInt32Le(0)
self.constraint = rdpy.network.type.UInt32Le(1)
rdpy.core.type.CompositeType.__init__(self)
self.padding = rdpy.core.type.UInt32Le(0)
self.constraint = rdpy.core.type.UInt32Le(1)
class TestComposite(rdpy.network.type.CompositeType):
class TestComposite(rdpy.core.type.CompositeType):
def __init__(self):
rdpy.network.type.CompositeType.__init__(self)
self.padding = rdpy.network.type.UInt32Le(0)
rdpy.core.type.CompositeType.__init__(self)
self.padding = rdpy.core.type.UInt32Le(0)
self.recurcive = TestSubComposite()
s = rdpy.network.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
s = rdpy.core.type.Stream("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
try:
s.readType(TestComposite())
except Exception:
@@ -364,12 +364,12 @@ class TypeCase(unittest.TestCase):
if total stream read length < to forced read length
the trash must be read as padding
"""
class TestReadLength(rdpy.network.type.CompositeType):
class TestReadLength(rdpy.core.type.CompositeType):
def __init__(self, readLen):
rdpy.network.type.CompositeType.__init__(self, readLen = readLen)
self.padding = rdpy.network.type.UInt32Le(0)
s = rdpy.network.type.Stream("\x00" * 10)
s.readType(TestReadLength(rdpy.network.type.UInt8(10)))
rdpy.core.type.CompositeType.__init__(self, readLen = readLen)
self.padding = rdpy.core.type.UInt32Le(0)
s = rdpy.core.type.Stream("\x00" * 10)
s.readType(TestReadLength(rdpy.core.type.UInt8(10)))
self.assertEqual(s.dataLen(), 0, "invalid stream read trash data as padding")
def test_stream_read_with_static_length_inferior(self):
@@ -378,12 +378,12 @@ class TypeCase(unittest.TestCase):
if total stream read length > to forced read length
an InvalidSize exception is throw
"""
class TestReadLength(rdpy.network.type.CompositeType):
class TestReadLength(rdpy.core.type.CompositeType):
def __init__(self, readLen):
rdpy.network.type.CompositeType.__init__(self, readLen = readLen)
self.padding = rdpy.network.type.UInt32Le(0)
s = rdpy.network.type.Stream("\x00" * 10)
self.assertRaises(InvalidSize, s.readType, TestReadLength(rdpy.network.type.UInt8(2)))
rdpy.core.type.CompositeType.__init__(self, readLen = readLen)
self.padding = rdpy.core.type.UInt32Le(0)
s = rdpy.core.type.Stream("\x00" * 10)
self.assertRaises(InvalidSize, s.readType, TestReadLength(rdpy.core.type.UInt8(2)))
def test_stream_read_string(self):
"""

View File

@@ -27,10 +27,10 @@ sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
import rdpy.protocol.rdp.ber as ber
import rdpy.network.type as type
import rdpy.base.error as error
import rdpy.core.type as type
import rdpy.core.error as error
class BERCase(unittest.TestCase):
class BERTest(unittest.TestCase):
"""
@summary: test case for ber layer (RDP)
"""

View File

@@ -0,0 +1,118 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
unit test for rdpy.protocol.rdp.lic automata
"""
import os, sys
# Change path so we find rdpy
sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
from rdpy.protocol.rdp import lic, sec
import rdpy.core.type as type
#dump of server request
SERVERREQUEST = """
AQNfCBkr6c1CVLRPx7PPYVgzW5uMQ1pSvtzs9XlTt74jwjslAAAGACwAAABNAGkAYwByAG8AcwBv
AGYAdAAgAEMAbwByAHAAbwByAGEAdABpAG8AbgAAAAgAAABBADAAMgAAAA0ABAABAAAAAwDZBwIA
AAACAAAAWAMAADCCA1QwggJAoAMCAQICCAGemF/kFo3QMAkGBSsOAwIdBQAwODE2MBUGA1UEBx4O
AFMASQBSAEEARABFAEwwHQYDVQQDHhYAVgBTAEkALQBTAFIAVgAtADAAMAA0MB4XDTcwMTAyMTA4
MDMxN1oXDTQ5MTAyMTA4MDMxN1owODE2MBUGA1UEBx4OAFMASQBSAEEARABFAEwwHQYDVQQDHhYA
VgBTAEkALQBTAFIAVgAtADAAMAA0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0mh+
rgsNk7WCR8Ck46jTV6OgnMgsFmdt0RqBJqtFwk+uqH8cBKFohtD9CeHTeQYkk+8m1C5GDpjQ/5BK
jaBfZHW+g5lZQITKbTi2n8TZfDg76wHvfl71JzwB84AG736KOZSOkBg8mJk3xf8LX5hbYHPQCRc5
y+06CWOyPiBYCEElXPOqsZYwum72N/NIsrKe98QHLn+X6MSbZ9Qzw6jnoBFgjk65bzpcChaOhnlW
875D6+8KZXgFmHT6MSJX7eabKw1zbKXszYpPvPfMDybp+6z125AJ+GxJ847AQ10xOV2X13ZVfSvS
Np62ogDD23duWHvcwLmHJ+45Jdgt7y4C0wIDAQABo2owaDAPBgNVHRMECDAGAQH/AgEAMFUGCSsG
AQQBgjcSCARIQwBDAFIATQBWAEoANgBWAEMAVABUAFcAUgBXAEcATQA4AEgASwBIAFkAMwBKAEsA
OABXADMAVgBNAEIARABXAFQAUQBGAAAAMAkGBSsOAwIdBQADggEBAD2NQPpBREuD+CZI9T4bUF9s
2yyQ8HidxJ+7NMcDyW4Ozid/SrAT9JP42RQrBosgq8S8teWNubrsNMsJjGaeueeQisLP1OigXQH1
8DcnLUc5TZKIsKjBqxJ40tssR5KaOfjGHXX5VoADsthnp3wO4bRuaXyhpLQzN1bUNKQk0Fok6Fzt
zo/rNEiAQv+M/Y1vmCoTuulXwxymZ+UdbxHPr/IvmxD0Gcmz8Qm45Lpypt2t0lGwNiuV2/3dm13C
spjIGxJFf/26QiRuZi5cps2YsOaQfW3xByklE5EbZWYIlsTCQpht+owKqnoPy7aD2On8z0IyGgY3
YdsOzHDvRZ+S0hxhBAAAMIIEXTCCA0mgAwIBAgIFAQAAABgwCQYFKw4DAh0FADA4MTYwFQYDVQQH
Hg4AUwBJAFIAQQBEAEUATDAdBgNVBAMeFgBWAFMASQAtAFMAUgBWAC0AMAAwADQwHhcNMTMxMTI3
MTQyNDIyWhcNMzgwMTE5MDMxNDA3WjCBqDGBpTAnBgNVBAMeIABuAGMAYQBjAG4AXwBuAHAAOgAx
ADkAMgAuADEANgA4MDUGA1UEBx4uAG4AYwBhAGMAbgBfAG4AcAA6ADEAOQAyAC4AMQA2ADgALgAx
ADMANQAuADMAMDBDBgNVBAUePAAxAEIAYwBLAGUAYQBiADcAWABPAGoAbgBXAEUATQBuAHgAMQBw
AFMAaQA2AEoAUgBBAHUAMAA9AA0ACjBYMAkGBSsOAwIPBQADSwAwSAJBANGDkkicUIVwIX2apm1b
U9WXVMnVT+S081PVP87vGp6VtzYpT9cMNKv70Qi2U3MQoQ4MuKS1XN2uc9SrNC7RWoUCAwEAAaOC
Ac8wggHLMBQGCSsGAQQBgjcSBAEB/wQEAQAFADA8BgkrBgEEAYI3EgIBAf8ELE0AaQBjAHIAbwBz
AG8AZgB0ACAAQwBvAHIAcABvAHIAYQB0AGkAbwBuAAAAMIHNBgkrBgEEAYI3EgUBAf8EgbwAMAAA
AQAAAAIAAAAJBAAAHABKAGYASgCwAAEAMwBkADIANgA3ADkANQA0AC0AZQBlAGIANwAtADEAMQBk
ADEALQBiADkANABlAC0AMAAwAGMAMAA0AGYAYQAzADAAOAAwAGQAAAAzAGQAMgA2ADcAOQA1ADQA
LQBlAGUAYgA3AC0AMQAxAGQAMQAtAGIAOQA0AGUALQAwADAAYwAwADQAZgBhADMAMAA4ADAAZAAA
AAAAABAAgOQAAAAAADB0BgkrBgEEAYI3EgYBAf8EZAAwAAAAABgASABWAFMASQAtAFMAUgBWAC0A
MAAwADQAAAA1ADUAMAA0ADEALQAwADAAOAAtADIAOQAxADMAMwA5ADEALQA4ADQANgAwADkAAABT
AEkAUgBBAEQARQBMAAAAAAAwLwYDVR0jAQH/BCUwI6EapBhWAFMASQAtAFMAUgBWAC0AMAAwADQA
AACCBQEAAAAYMAkGBSsOAwIdBQADggEBAGFI8teU2iypPG2BbpNLa+zZeb87MNjtzHgQlC0RfoK0
dY91t3OXA5pdyD5IRRkvGUVKBf/5G/OVZZvKTnyh3daopD6InPy1X6Wlx5QveCL8ydd/H+ezm0zl
KlMg0pImG0V2NX50q4b9R4I5xiaA7mUeww6rz6iAh1iB2CYHOc/uGAS1/PPtHqnfss5bY+wZR+tW
dvyMFvn6DbvzAN9wT2KIcv1LtMGgxv28v+dnqGhcxrLRE4nRjMIV/AKpBJXhgH7DaUEc1NJNDPUt
5Pdz4qy8WM2d8JO0yNXQVqykJahgt5TJxCoP46SPFUU5XBbNTZuKv7CtPkSxgtrdrzwFTtcAAAAA
AAAAAAAAAAAAAAAAAQAAAA4ADgBtaWNyb3NvZnQuY29tAA==
"""
class TestLic(unittest.TestCase):
"""
@summary: Test case for MCS automata
"""
class LIC_PASS(Exception):
"""
@summary: for OK tests
"""
pass
class LIC_FAIL(Exception):
"""
@summary: for KO tests
"""
pass
def test_valid_client_licensing_error_message(self):
l = lic.LicenseManager(None)
s = type.Stream()
s.writeType(lic.createValidClientLicensingErrorMessage())
#reinit position
s.pos = 0
self.assertTrue(l.recv(s), "Manager can retrieve valid case")
def test_new_license(self):
class Transport(object):
def __init__(self):
self._state = False
def sendFlagged(self, flag, message):
if flag != sec.SecurityFlag.SEC_LICENSE_PKT:
return
s = type.Stream()
s.writeType(message)
s.pos = 0
s.readType(lic.LicPacket(lic.ClientNewLicenseRequest()))
self._state = True
t = Transport()
l = lic.LicenseManager(t)
s = type.Stream(SERVERREQUEST.decode("base64"))
self.assertFalse(l.recv(s) and t._state, "Bad message after license request")

View File

@@ -0,0 +1,36 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
unit test for rdpy.protocol.rdp.mcs automata
"""
import os, sys
# Change path so we find rdpy
sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
class MCSTest(unittest.TestCase):
"""
@summary: test case for per layer (RDP)
"""
def test_per_readLength(self):
pass

View File

@@ -27,10 +27,10 @@ sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
import rdpy.protocol.rdp.per as per
import rdpy.network.type as type
import rdpy.base.error as error
import rdpy.core.type as type
import rdpy.core.error as error
class PERCase(unittest.TestCase):
class PERTest(unittest.TestCase):
"""
@summary: test case for per layer (RDP)
"""

View File

@@ -0,0 +1,48 @@
#
# Copyright (c) 2014 Sylvain Peyrefitte
#
# This file is part of rdpy.
#
# rdpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
unit test for rdpy.protocol.rdp.rc4 module
"""
import os, sys
# Change path so we find rdpy
sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
import rdpy.security.rc4 as rc4
class RC4Test(unittest.TestCase):
"""
@summary: unit tests for rc4
@see: http://fr.wikipedia.org/wiki/RC4
"""
def test_rc4_key_plaintext(self):
self.assertEqual("\xBB\xF3\x16\xE8\xD9\x40\xAF\x0A\xD3", rc4.crypt(rc4.RC4Key("Key"), "Plaintext"), "RC4 bad crypt")
self.assertEqual("Plaintext", rc4.crypt(rc4.RC4Key("Key"), "\xBB\xF3\x16\xE8\xD9\x40\xAF\x0A\xD3"), "RC4 bad crypt")
def test_rc4_wiki_pedia(self):
self.assertEqual("\x10\x21\xBF\x04\x20", rc4.crypt(rc4.RC4Key("Wiki"), "pedia"), "RC4 bad crypt")
self.assertEqual("pedia", rc4.crypt(rc4.RC4Key("Wiki"), "\x10\x21\xBF\x04\x20"), "RC4 bad crypt")
def test_rc4_secret_attack_at_down(self):
self.assertEqual("\x45\xA0\x1F\x64\x5F\xC3\x5B\x38\x35\x52\x54\x4B\x9B\xF5", rc4.crypt(rc4.RC4Key("Secret"), "Attack at dawn"), "RC4 bad crypt")
self.assertEqual("Attack at dawn", rc4.crypt(rc4.RC4Key("Secret"), "\x45\xA0\x1F\x64\x5F\xC3\x5B\x38\x35\x52\x54\x4B\x9B\xF5"), "RC4 bad crypt")

View File

@@ -27,10 +27,10 @@ sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
import rdpy.protocol.rdp.tpkt as tpkt
import rdpy.network.type as type
import rdpy.base.error as error
import rdpy.core.type as type
import rdpy.core.error as error
class TPKTCase(unittest.TestCase):
class TPKTTest(unittest.TestCase):
"""
@summary: test case for tpkt layer (RDP)
"""
@@ -47,10 +47,10 @@ class TPKTCase(unittest.TestCase):
"""
class Presentation(object):
def connect(self):
raise TPKTCase.TPKT_PASS()
raise TPKTTest.TPKT_PASS()
layer = tpkt.TPKT(Presentation(), None)
self.assertRaises(TPKTCase.TPKT_PASS, layer.connect)
layer = tpkt.TPKT(Presentation())
self.assertRaises(TPKTTest.TPKT_PASS, layer.connect)
def test_tpkt_layer_recv(self):
"""
@@ -61,16 +61,16 @@ class TPKTCase(unittest.TestCase):
pass
def recv(self, data):
data.readType(type.String("test_tpkt_layer_recv", constant = True))
raise TPKTCase.TPKT_PASS()
raise TPKTTest.TPKT_PASS()
message = type.String("test_tpkt_layer_recv")
s = type.Stream()
s.writeType((type.UInt8(tpkt.Action.FASTPATH_ACTION_X224), type.UInt8(), type.UInt16Be(type.sizeof(message) + 4), message))
layer = tpkt.TPKT(Presentation(), None)
layer = tpkt.TPKT(Presentation())
layer.connect()
self.assertRaises(TPKTCase.TPKT_PASS, layer.dataReceived, s.getvalue())
self.assertRaises(TPKTTest.TPKT_PASS, layer.dataReceived, s.getvalue())
def test_tpkt_layer_recv_fastpath(self):
"""
@@ -79,18 +79,19 @@ class TPKTCase(unittest.TestCase):
class FastPathLayer(tpkt.IFastPathListener):
def setFastPathSender(self, fastPathSender):
pass
def recvFastPath(self, fastPathS):
def recvFastPath(self, secFlag, fastPathS):
fastPathS.readType(type.String("test_tpkt_layer_recv_fastpath", constant = True))
raise TPKTCase.TPKT_PASS()
raise TPKTTest.TPKT_PASS()
message = type.String("test_tpkt_layer_recv_fastpath")
s = type.Stream()
s.writeType((type.UInt8(tpkt.Action.FASTPATH_ACTION_FASTPATH), type.UInt8(type.sizeof(message) + 2), message))
layer = tpkt.TPKT(None, FastPathLayer())
layer = tpkt.TPKT(None)
layer.initFastPath(FastPathLayer())
layer.connect()
self.assertRaises(TPKTCase.TPKT_PASS, layer.dataReceived, s.getvalue())
self.assertRaises(TPKTTest.TPKT_PASS, layer.dataReceived, s.getvalue())
def test_tpkt_layer_recv_fastpath_ext_length(self):
"""
@@ -99,15 +100,16 @@ class TPKTCase(unittest.TestCase):
class FastPathLayer(tpkt.IFastPathListener):
def setFastPathSender(self, fastPathSender):
pass
def recvFastPath(self, fastPathS):
def recvFastPath(self, secflag, fastPathS):
fastPathS.readType(type.String("test_tpkt_layer_recv_fastpath_ext_length", constant = True))
raise TPKTCase.TPKT_PASS()
raise TPKTTest.TPKT_PASS()
message = type.String("test_tpkt_layer_recv_fastpath_ext_length")
s = type.Stream()
s.writeType((type.UInt8(tpkt.Action.FASTPATH_ACTION_FASTPATH), type.UInt16Be((type.sizeof(message) + 3) | 0x8000), message))
layer = tpkt.TPKT(None, FastPathLayer())
layer = tpkt.TPKT(None)
layer.initFastPath(FastPathLayer())
layer.connect()
self.assertRaises(TPKTCase.TPKT_PASS, layer.dataReceived, s.getvalue())
self.assertRaises(TPKTTest.TPKT_PASS, layer.dataReceived, s.getvalue())

View File

@@ -27,10 +27,10 @@ sys.path.insert(1, os.path.join(sys.path[0], '..'))
import unittest
import rdpy.protocol.rdp.x224 as x224
import rdpy.network.type as type
import rdpy.base.error as error
import rdpy.core.type as type
import rdpy.core.error as error
class X224Case(unittest.TestCase):
class X224Test(unittest.TestCase):
"""
@summary: test case for x224 layer (RDP)
"""
@@ -54,7 +54,7 @@ class X224Case(unittest.TestCase):
class Presentation(object):
def recv(self, data):
data.readType(type.String('test_x224_layer_recvData', constant = True))
raise X224Case.X224_PASS()
raise X224Test.X224_PASS()
layer = x224.X224Layer(Presentation())
s = type.Stream()
@@ -62,7 +62,7 @@ class X224Case(unittest.TestCase):
#reinit position
s.pos = 0
self.assertRaises(X224Case.X224_PASS, layer.recvData, s)
self.assertRaises(X224Test.X224_PASS, layer.recvData, s)
def test_x224_layer_send(self):
"""
@@ -75,12 +75,12 @@ class X224Case(unittest.TestCase):
s.pos = 0
s.readType(x224.X224DataHeader())
s.readType(type.String('test_x224_layer_send', constant = True))
raise X224Case.X224_PASS()
raise X224Test.X224_PASS()
layer = x224.X224Layer(None)
layer._transport = Transport()
self.assertRaises(X224Case.X224_PASS, layer.send, type.String('test_x224_layer_send'))
self.assertRaises(X224Test.X224_PASS, layer.send, type.String('test_x224_layer_send'))
def test_x224_client_connect(self):
"""
@@ -95,37 +95,34 @@ class X224Case(unittest.TestCase):
s.readType(t)
if t.protocolNeg.code != x224.NegociationType.TYPE_RDP_NEG_REQ:
raise X224Case.X224_FAIL()
if t.protocolNeg.selectedProtocol.value != x224.Protocols.PROTOCOL_SSL:
raise X224Case.X224_FAIL()
raise X224Test.X224_FAIL()
def nextAutomata(data):
raise X224Case.X224_PASS()
raise X224Test.X224_PASS()
layer = x224.Client(None)
layer._transport = Transport()
layer.recvConnectionConfirm = nextAutomata
layer.connect()
self.assertRaises(X224Case.X224_PASS, layer.recv, type.String('\x01\x02'))
def test_x224_client_recvConnectionConfirm_negotiation_old(self):
self.assertRaises(X224Test.X224_PASS, layer.recv, type.String('\x01\x02'))
def test_x224_client_recvConnectionConfirm_negotiation_bad_protocol(self):
"""
@summary: unit test for X224Client.recvConnectionConfirm and sendConnectionRequest function
whithout protocol negotiation (doesn't support)
Server ask another protocol than SSL or RDP
"""
message = x224.ServerConnectionConfirm()
del message._typeName[message._typeName.index("protocolNeg")]
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_HYBRID
s = type.Stream()
s.writeType(message)
s.pos = 0
layer = x224.Client(None)
self.assertRaises(error.InvalidExpectedDataException, layer.recvConnectionConfirm, s)
def test_x224_client_recvConnectionConfirm_negotiation_failure(self):
"""
@summary: unit test for X224Client.recvConnectionConfirm and sendConnectionRequest function
@summary: unit test for X224Client.recvConnectionConfirm and sendConnectionRequest function
check negotiation failure
"""
message = x224.ServerConnectionConfirm()
@@ -134,20 +131,7 @@ class X224Case(unittest.TestCase):
s.writeType(message)
s.pos = 0
layer = x224.Client(None)
self.assertRaises(error.InvalidExpectedDataException, layer.recvConnectionConfirm, s)
def test_x224_client_recvConnectionConfirm_negotiation_bad_protocol(self):
"""
@summary: unit test for X224Client.recvConnectionConfirm and sendConnectionRequest function
Server ask another protocol than SSL
"""
message = x224.ServerConnectionConfirm()
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_RDP
s = type.Stream()
s.writeType(message)
s.pos = 0
layer = x224.Client(None)
self.assertRaises(error.InvalidExpectedDataException, layer.recvConnectionConfirm, s)
self.assertRaises(error.RDPSecurityNegoFail, layer.recvConnectionConfirm, s)
def test_x224_client_recvConnectionConfirm_ok(self):
"""
@@ -170,7 +154,7 @@ class X224Case(unittest.TestCase):
presentation_connect = True
def recvData(data):
raise X224Case.X224_PASS()
raise X224Test.X224_PASS()
message = x224.ServerConnectionConfirm()
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_SSL
@@ -186,38 +170,7 @@ class X224Case(unittest.TestCase):
self.assertTrue(tls_begin, "TLS is not started")
self.assertTrue(presentation_connect, "connect event is not forwarded")
self.assertRaises(X224Case.X224_PASS, layer.recv, type.String('\x01\x02'))
def test_x224_server_recvConnectionRequest_invalid_old_client(self):
"""
@summary: unit test for X224Server.recvConnectionRequest function
old client with non protocol neg
"""
message = x224.ClientConnectionRequestPDU()
del message._typeName[message._typeName.index("protocolNeg")]
s = type.Stream()
s.writeType(message)
s.pos = 0
layer = x224.Server(None, "key", "cert")
layer.connect()
self.assertRaises(error.InvalidExpectedDataException, layer.recv, s)
def test_x224_server_recvConnectionRequest_invalid_protocol_neg_failure(self):
"""
@summary: unit test for X224Server.recvConnectionRequest function
"""
message = x224.ClientConnectionRequestPDU()
message.protocolNeg.code.value = x224.NegociationType.TYPE_RDP_NEG_FAILURE
s = type.Stream()
s.writeType(message)
s.pos = 0
layer = x224.Server(None, "key", "cert")
layer.connect()
self.assertRaises(error.InvalidExpectedDataException, layer.recv, s)
self.assertRaises(X224Test.X224_PASS, layer.recv, type.String('\x01\x02'))
def test_x224_server_recvConnectionRequest_client_accept_ssl(self):
"""
@@ -228,9 +181,11 @@ class X224Case(unittest.TestCase):
class Transport(object):
def send(self, data):
if not isinstance(data, x224.ServerConnectionConfirm):
raise X224Case.X224_FAIL()
raise X224Test.X224_FAIL()
if data.protocolNeg.code.value != x224.NegociationType.TYPE_RDP_NEG_FAILURE or data.protocolNeg.failureCode.value != x224.NegotiationFailureCode.SSL_REQUIRED_BY_SERVER:
raise X224Case.X224_FAIL()
raise X224Test.X224_FAIL()
def close(self):
raise X224Test.X224_PASS()
message = x224.ClientConnectionRequestPDU()
message.protocolNeg.selectedProtocol.value = x224.Protocols.PROTOCOL_HYBRID
@@ -238,11 +193,11 @@ class X224Case(unittest.TestCase):
s.writeType(message)
s.pos = 0
layer = x224.Server(None, "key", "cert")
layer = x224.Server(None, "key", "cert", True)
layer._transport = Transport()
layer.connect()
self.assertRaises(error.InvalidExpectedDataException, layer.recv, s)
self.assertRaises(X224Test.X224_PASS, layer.recv, s)
def test_x224_server_recvConnectionRequest_valid(self):
"""
@@ -268,9 +223,9 @@ class X224Case(unittest.TestCase):
def send(self, data):
if not isinstance(data, x224.ServerConnectionConfirm):
raise X224Case.X224_FAIL()
raise X224Test.X224_FAIL()
if data.protocolNeg.code.value != x224.NegociationType.TYPE_RDP_NEG_RSP or data.protocolNeg.selectedProtocol.value != x224.Protocols.PROTOCOL_SSL:
raise X224Case.X224_FAIL()
raise X224Test.X224_FAIL()
class Presentation(object):
def connect(self):